aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/config5
-rw-r--r--.github/bors.toml5
-rw-r--r--.github/workflows/build.yml784
-rw-r--r--.github/workflows/properties/build.properties.json6
-rw-r--r--.travis.yml11
-rw-r--r--CHANGELOG.md158
-rw-r--r--CNAME1
-rw-r--r--CONTRIBUTING.md16
-rw-r--r--Cargo.toml97
-rw-r--r--LICENSE-MIT2
-rw-r--r--README.md33
-rw-r--r--book/en/book.toml6
-rw-r--r--book/en/src/SUMMARY.md17
-rw-r--r--book/en/src/by-example.md8
-rw-r--r--book/en/src/by-example/app.md129
-rw-r--r--book/en/src/by-example/new.md16
-rw-r--r--book/en/src/by-example/resources.md162
-rw-r--r--book/en/src/by-example/singletons.md26
-rw-r--r--book/en/src/by-example/tasks.md105
-rw-r--r--book/en/src/by-example/timer-queue.md84
-rw-r--r--book/en/src/by-example/tips.md141
-rw-r--r--book/en/src/by-example/types-send-sync.md42
-rw-r--r--book/en/src/internals.md7
-rw-r--r--book/en/src/internals/access.md158
-rw-r--r--book/en/src/internals/ceilings.md83
-rw-r--r--book/en/src/internals/critical-sections.md523
-rw-r--r--book/en/src/internals/interrupt-configuration.md73
-rw-r--r--book/en/src/internals/late-resources.md116
-rw-r--r--book/en/src/internals/non-reentrancy.md80
-rw-r--r--book/en/src/internals/tasks.md398
-rw-r--r--book/en/src/internals/timer-queue.md367
-rw-r--r--book/en/src/migration.md4
-rw-r--r--book/en/src/migration/migration_rtic.md54
-rw-r--r--book/en/src/migration/migration_v4.md232
-rw-r--r--book/en/src/migration/migration_v5.md96
-rw-r--r--book/en/src/preface.md17
-rw-r--r--book/ru/book.toml2
-rw-r--r--book/ru/src/README_RU.md10
-rw-r--r--book/ru/src/SUMMARY.md2
-rw-r--r--book/ru/src/by-example.md6
-rw-r--r--book/ru/src/by-example/app.md26
-rw-r--r--book/ru/src/by-example/new.md12
-rw-r--r--book/ru/src/by-example/resources.md10
-rw-r--r--book/ru/src/by-example/tasks.md6
-rw-r--r--book/ru/src/by-example/timer-queue.md8
-rw-r--r--book/ru/src/by-example/tips.md6
-rw-r--r--book/ru/src/by-example/types-send-sync.md4
-rw-r--r--book/ru/src/preface.md6
-rw-r--r--build.rs10
-rw-r--r--ci/after-success.sh49
-rw-r--r--ci/expected/cfg.run2
-rw-r--r--ci/expected/generics.run6
-rw-r--r--ci/expected/hardware.run (renamed from ci/expected/interrupt.run)0
-rw-r--r--ci/expected/lock.run4
-rw-r--r--ci/expected/only-shared-access.run2
-rw-r--r--ci/expected/peripherals-taken.run0
-rw-r--r--ci/expected/pool.run2
-rw-r--r--ci/expected/preempt.run5
-rw-r--r--ci/expected/ramfunc.grep.bar4
-rw-r--r--ci/expected/ramfunc.grep.foo4
-rw-r--r--ci/expected/resource.run4
-rw-r--r--ci/expected/shared-with-init.run0
-rw-r--r--ci/expected/static.run2
-rw-r--r--ci/expected/task.run4
-rw-r--r--ci/install.sh33
-rw-r--r--ci/script.sh210
-rw-r--r--examples/baseline.rs39
-rw-r--r--examples/binds.rs28
-rw-r--r--examples/capacity.rs40
-rw-r--r--examples/cfg.rs58
-rw-r--r--examples/destructure.rs50
-rw-r--r--examples/double_schedule.rs39
-rw-r--r--examples/generics.rs54
-rw-r--r--examples/hardware.rs (renamed from examples/interrupt.rs)30
-rw-r--r--examples/idle.rs20
-rw-r--r--examples/init.rs22
-rw-r--r--examples/late.rs49
-rw-r--r--examples/lock.rs50
-rw-r--r--examples/message.rs33
-rw-r--r--examples/not-send.rs50
-rw-r--r--examples/not-sync.rs40
-rw-r--r--examples/only-shared-access.rs39
-rw-r--r--examples/periodic.rs31
-rw-r--r--examples/peripherals-taken.rs18
-rw-r--r--examples/pool.rs76
-rw-r--r--examples/preempt.rs39
-rw-r--r--examples/ramfunc.rs22
-rw-r--r--examples/resource-user-struct.rs63
-rw-r--r--examples/resource.rs60
-rw-r--r--examples/schedule.rs39
-rw-r--r--examples/shared-with-init.rs45
-rw-r--r--examples/singleton.rs61
-rw-r--r--examples/smallest.rs13
-rw-r--r--examples/static.rs37
-rw-r--r--examples/t-binds.rs34
-rw-r--r--examples/t-cfg-resources.rs36
-rw-r--r--examples/t-cfg.rs58
-rw-r--r--examples/t-htask-main.rs22
-rw-r--r--examples/t-idle-main.rs23
-rw-r--r--examples/t-init-main.rs17
-rw-r--r--examples/t-late-not-send.rs41
-rw-r--r--examples/t-resource.rs92
-rw-r--r--examples/t-schedule.rs66
-rw-r--r--examples/t-spawn.rs65
-rw-r--r--examples/t-stask-main.rs29
-rw-r--r--examples/task.rs40
-rw-r--r--examples/types.rs75
-rw-r--r--macros/Cargo.toml32
-rw-r--r--macros/src/analyze.rs285
-rw-r--r--macros/src/check.rs471
-rw-r--r--macros/src/codegen.rs2327
-rw-r--r--macros/src/codegen/assertions.rs19
-rw-r--r--macros/src/codegen/dispatchers.rs155
-rw-r--r--macros/src/codegen/hardware_tasks.rs134
-rw-r--r--macros/src/codegen/idle.rs104
-rw-r--r--macros/src/codegen/init.rs125
-rw-r--r--macros/src/codegen/locals.rs94
-rw-r--r--macros/src/codegen/module.rs330
-rw-r--r--macros/src/codegen/post_init.rs31
-rw-r--r--macros/src/codegen/pre_init.rs109
-rw-r--r--macros/src/codegen/resources.rs122
-rw-r--r--macros/src/codegen/resources_struct.rs177
-rw-r--r--macros/src/codegen/schedule.rs90
-rw-r--r--macros/src/codegen/schedule_body.rs59
-rw-r--r--macros/src/codegen/software_tasks.rs169
-rw-r--r--macros/src/codegen/spawn.rs121
-rw-r--r--macros/src/codegen/spawn_body.rs76
-rw-r--r--macros/src/codegen/timer_queue.rs137
-rw-r--r--macros/src/codegen/util.rs247
-rw-r--r--macros/src/lib.rs288
-rw-r--r--macros/src/syntax.rs1394
-rw-r--r--macros/src/tests.rs4
-rw-r--r--macros/src/tests/single.rs34
-rw-r--r--redirect.html6
-rw-r--r--src/cyccnt.rs221
-rw-r--r--src/export.rs178
-rw-r--r--src/lib.rs382
-rw-r--r--src/tq.rs164
-rw-r--r--tests/cfail/cfg-resources.rs64
-rw-r--r--tests/cfail/cfg-static.rs57
-rw-r--r--tests/cfail/duplicate-args-2.rs24
-rw-r--r--tests/cfail/duplicate-args.rs24
-rw-r--r--tests/cfail/early-return-2.rs29
-rw-r--r--tests/cfail/early-return.rs32
-rw-r--r--tests/cfail/exception-divergent.rs20
-rw-r--r--tests/cfail/exception-input.rs19
-rw-r--r--tests/cfail/exception-invalid.rs19
-rw-r--r--tests/cfail/exception-output.rs20
-rw-r--r--tests/cfail/exception-sys-tick.rs19
-rw-r--r--tests/cfail/idle-input.rs19
-rw-r--r--tests/cfail/idle-not-divergent.rs19
-rw-r--r--tests/cfail/init-divergent.rs17
-rw-r--r--tests/cfail/init-input.rs16
-rw-r--r--tests/cfail/init-not-send.rs30
-rw-r--r--tests/cfail/init-output.rs17
-rw-r--r--tests/cfail/insufficient-free-interrupts.rs17
-rw-r--r--tests/cfail/interrupt-divergent.rs20
-rw-r--r--tests/cfail/interrupt-input.rs19
-rw-r--r--tests/cfail/interrupt-output.rs20
-rw-r--r--tests/cfail/late-assigned-to-init.rs16
-rw-r--r--tests/cfail/late-not-send.rs33
-rw-r--r--tests/cfail/late-uninit.rs18
-rw-r--r--tests/cfail/needs-send.rs30
-rw-r--r--tests/cfail/needs-sync.rs36
-rw-r--r--tests/cfail/priority-too-high.rs22
-rw-r--r--tests/cfail/priority-too-low.rs22
-rw-r--r--tests/cfail/resource-not-declared.rs14
-rw-r--r--tests/cfail/resource-pub.rs17
-rw-r--r--tests/cfail/task-divergent.rs24
-rw-r--r--tests/cfail/task-idle.rs23
-rw-r--r--tests/cfail/task-not-declared.rs14
-rw-r--r--tests/cfail/used-free-interrupt-2.rs21
-rw-r--r--tests/cfail/used-free-interrupt.rs21
-rw-r--r--tests/compiletest.rs57
-rw-r--r--tests/cpass/binds.rs27
-rw-r--r--tests/cpass/cfg.rs53
-rw-r--r--tests/cpass/late-not-send.rs34
-rw-r--r--tests/cpass/late-resource.rs20
-rw-r--r--tests/cpass/peripheral.rs18
-rw-r--r--tests/cpass/resource.rs79
-rw-r--r--tests/cpass/schedule.rs58
-rw-r--r--tests/cpass/singleton.rs66
-rw-r--r--tests/cpass/spawn.rs59
-rw-r--r--tests/cpass/unsafe.rs45
-rw-r--r--tests/single.rs7
-rw-r--r--ui/single/exception-invalid.rs7
-rw-r--r--ui/single/exception-invalid.stderr5
-rw-r--r--ui/single/exception-systick-used.rs10
-rw-r--r--ui/single/exception-systick-used.stderr5
-rw-r--r--ui/single/extern-interrupt-not-enough.rs7
-rw-r--r--ui/single/extern-interrupt-not-enough.stderr5
-rw-r--r--ui/single/extern-interrupt-used.rs11
-rw-r--r--ui/single/extern-interrupt-used.stderr5
-rw-r--r--ui/single/locals-cfg.rs53
-rw-r--r--ui/single/locals-cfg.stderr41
-rw-r--r--ui/single/resources-cfg.rs79
-rw-r--r--ui/single/resources-cfg.stderr125
-rw-r--r--ui/single/task-priority-too-high.rs38
-rw-r--r--ui/single/task-priority-too-high.stderr7
199 files changed, 8919 insertions, 7163 deletions
diff --git a/.cargo/config b/.cargo/config
index 2683610d..d0957664 100644
--- a/.cargo/config
+++ b/.cargo/config
@@ -7,7 +7,4 @@ runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semiho
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
"-C", "link-arg=-Tlink.x",
-]
-
-[build]
-target = "thumbv7m-none-eabi" \ No newline at end of file
+] \ No newline at end of file
diff --git a/.github/bors.toml b/.github/bors.toml
index 39ad6042..aee6042f 100644
--- a/.github/bors.toml
+++ b/.github/bors.toml
@@ -1,4 +1,3 @@
+block_labels = ["S-blocked"]
delete_merged_branches = true
-status = [
- "continuous-integration/travis-ci/push",
-] \ No newline at end of file
+status = ["ci"]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..8da98678
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,784 @@
+name: Build
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+ - staging
+ - trying
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ # Run cargo fmt --check, includes macros/
+ style:
+ name: style
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+ components: rustfmt
+
+ - 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:
+ - thumbv7m-none-eabi
+ - thumbv6m-none-eabi
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: cargo check
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: check
+ args: --target=${{ matrix.target }}
+
+ # Verify all examples, checks
+ checkexamples:
+ name: checkexamples
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - thumbv7m-none-eabi
+ - thumbv6m-none-eabi
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+ components: llvm-tools-preview
+
+ - name: Check the examples
+ if: matrix.target == 'thumbv7m-none-eabi'
+ env:
+ V7: __v7
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: check
+ args: --examples --target=${{ matrix.target }} --features __min_r1_43,${{ env.V7 }}
+
+ # Verify the example output with run-pass tests
+ testexamples:
+ name: testexamples
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - thumbv7m-none-eabi
+ - thumbv6m-none-eabi
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+ components: llvm-tools-preview
+
+ # Use precompiled binutils
+ - name: cargo install cargo-binutils
+ uses: actions-rs/install@v0.1
+ with:
+ crate: cargo-binutils
+ version: latest
+ use-tool-cache: true
+
+ - name: Install QEMU
+ run: |
+ sudo apt update
+ sudo apt install -y qemu-system-arm
+
+ - name: Run-pass tests
+ run: |
+ # Print the path
+ echo $PATH
+
+ arm_example() {
+ local COMMAND=$1
+ local EXAMPLE=$2
+ local BUILD_MODE=$3
+ local FEATURES=$4
+ local BUILD_NUM=$5
+
+ if [ $BUILD_MODE = "release" ]; then
+ local RELEASE_FLAG="--release"
+ else
+ local RELEASE_FLAG=""
+ fi
+
+ if [ -n "$FEATURES" ]; then
+ local FEATURES_FLAG="--features $FEATURES"
+ local FEATURES_STR=${FEATURES/,/_}_
+ else
+ local FEATURES_FLAG=""
+ local FEATURES_STR=""
+ fi
+ local CARGO_FLAGS="--example $EXAMPLE --target ${{ matrix.target }} $RELEASE_FLAG $FEATURES_FLAG"
+
+ if [ $COMMAND = "run" ]; then
+ cargo $COMMAND $CARGO_FLAGS | diff -u ci/expected/$EXAMPLE.run -
+ else
+ cargo $COMMAND $CARGO_FLAGS
+ fi
+ cargo objcopy $CARGO_FLAGS -- -O ihex ci/builds/${EXAMPLE}_${FEATURES_STR}${BUILD_MODE}_${BUILD_NUM}.hex
+ }
+
+ mkdir -p ci/builds
+ exs=(
+ idle
+ init
+ hardware
+ preempt
+ binds
+
+ resource
+ lock
+ late
+ only-shared-access
+
+ task
+ message
+ capacity
+
+ types
+ not-send
+ not-sync
+ shared-with-init
+
+ generics
+ cfg
+ pool
+ ramfunc
+
+ peripherals-taken
+ )
+
+ for ex in ${exs[@]}; do
+ if [ $ex = pool ]; then
+ if [ ${{ matrix.target }} = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ td=$(mktemp -d)
+
+ cargo run --example $ex --target ${{ matrix.target }} --features __v7 >\
+ $td/pool.run
+ grep 'foo(0x2' $td/pool.run
+ grep 'bar(0x2' $td/pool.run
+ cargo objcopy --example $ex --target ${{ matrix.target }} --features __v7 -- -O ihex ci/builds/${ex}___v7_debug_1.hex
+
+ cargo run --example $ex --target ${{ matrix.target }} --features __v7 --release >\
+ $td/pool.run
+ grep 'foo(0x2' $td/pool.run
+ grep 'bar(0x2' $td/pool.run
+ cargo objcopy --example $ex --target ${{ matrix.target }} --features __v7 --release -- -O ihex ci/builds/${ex}___v7_release_1.hex
+
+ rm -rf $td
+
+ continue
+ fi
+
+ if [ $ex = types ]; then
+ if [ ${{ matrix.target }} = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ arm_example "run" $ex "debug" "__v7" "1"
+ arm_example "run" $ex "release" "__v7" "1"
+
+ continue
+ fi
+
+ arm_example "run" $ex "debug" "" "1"
+ if [ $ex = types ]; then
+ arm_example "run" $ex "release" "" "1"
+ else
+ arm_example "build" $ex "release" "" "1"
+ fi
+ done
+
+ built=()
+ cargo clean
+ for ex in ${exs[@]}; do
+ if [ $ex = types ] || [ $ex = pool ]; then
+ if [ ${{ matrix.target }} = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ arm_example "build" $ex "debug" "__v7" "2"
+ cmp ci/builds/${ex}___v7_debug_1.hex \
+ ci/builds/${ex}___v7_debug_2.hex
+ arm_example "build" $ex "release" "__v7" "2"
+ cmp ci/builds/${ex}___v7_release_1.hex \
+ ci/builds/${ex}___v7_release_2.hex
+ else
+ arm_example "build" $ex "debug" "" "2"
+ cmp ci/builds/${ex}_debug_1.hex \
+ ci/builds/${ex}_debug_2.hex
+ arm_example "build" $ex "release" "" "2"
+ cmp ci/builds/${ex}_release_1.hex \
+ ci/builds/${ex}_release_2.hex
+ fi
+
+ built+=( $ex )
+ done
+
+ ( cd target/${{ matrix.target }}/release/examples/ && size ${built[@]} )
+
+
+ # Check the correctness of macros/ crate
+ checkmacros:
+ name: checkmacros
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: cargo check
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: check
+ args: --manifest-path macros/Cargo.toml --target=${{ matrix.target }}
+
+ # Run the macros test-suite
+ testmacros:
+ name: testmacros
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target:
+ - x86_64-unknown-linux-gnu
+ toolchain:
+ - stable
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }})
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.toolchain }}
+ target: ${{ matrix.target }}
+ override: true
+
+ - name: cargo check
+ uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --manifest-path macros/Cargo.toml --target=${{ matrix.target }}
+
+ # Run test suite for thumbv7m
+ testv7:
+ name: testv7
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: thumbv7m-none-eabi
+ override: true
+
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --test single --features __v7
+
+ # Run test suite for thumbv6m
+ testv6:
+ name: testv6
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ target: thumbv6m-none-eabi
+ override: true
+
+ - uses: actions-rs/cargo@v1
+ with:
+ use-cross: false
+ command: test
+ args: --test single
+
+ # Build documentation, check links
+ docs:
+ name: docs
+ runs-on: ubuntu-20.04
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Cache cargo dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ - ~/.cargo/bin/
+ - ~/.cargo/registry/index/
+ - ~/.cargo/registry/cache/
+ - ~/.cargo/git/db/
+ key: ${{ runner.OS }}-cargo-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-cargo-
+
+ - name: Cache build output dependencies
+ uses: actions/cache@v2
+ with:
+ path: target
+ key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }}
+ restore-keys: |
+ ${{ runner.OS }}-build-
+
+ - name: Cache pip installed linkchecker
+ uses: actions/cache@v2
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip
+ restore-keys: |
+ ${{ runner.os }}-pip-
+
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Display Python version
+ run: python -c "import sys; print(sys.version)"
+
+ - name: Install dependencies
+ run: pip install git+https://github.com/linkchecker/linkchecker.git
+
+ - name: Remove cargo-config
+ run: rm -f .cargo/config
+
+ - name: Build docs
+ run: cargo doc
+
+ - name: Check links
+ run: |
+ td=$(mktemp -d)
+ cp -r target/doc $td/api
+ linkchecker $td/api/rtic/
+ linkchecker $td/api/cortex_m_rtic_macros/
+
+ # Build the books
+ mdbook:
+ name: mdbook
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Display Python version
+ run: python -c "import sys; print(sys.version)"
+
+ - name: Install dependencies
+ run: pip install git+https://github.com/linkchecker/linkchecker.git
+
+ - name: mdBook Action
+ uses: peaceiris/actions-mdbook@v1.1.11
+ with:
+ mdbook-version: 'latest'
+
+ - name: Build book in English
+ run: cd book/en && mdbook build
+
+ - name: Build book in Russian
+ run: cd book/ru && mdbook build
+
+ - name: Check links
+ run: |
+ td=$(mktemp -d)
+ mkdir $td/book
+ cp -r book/en/book $td/book/en
+ cp -r book/ru/book $td/book/ru
+ cp LICENSE-* $td/book/en
+ cp LICENSE-* $td/book/ru
+
+ linkchecker $td/book/en/
+ linkchecker $td/book/ru/
+
+ # Only runs when pushing to master branch
+ deploy:
+ name: deploy
+ runs-on: ubuntu-20.04
+ needs:
+ - style
+ - check
+ - checkexamples
+ - testexamples
+ - checkmacros
+ - testmacros
+ - testv7
+ - testv6
+ - docs
+ - mdbook
+ # Only run this when pushing to master branch
+ if: github.ref == 'refs/heads/master'
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Display Python version
+ run: python -c "import sys; print(sys.version)"
+
+ - name: mdBook Action
+ uses: peaceiris/actions-mdbook@v1.1.11
+ with:
+ mdbook-version: 'latest'
+
+ - name: Remove cargo-config
+ run: rm -f .cargo/config
+
+ - name: Build docs
+ run: cargo doc
+
+ - name: Build books
+ run: |
+ langs=( en ru )
+ devver=( dev )
+ # The latest stable must be the first element in the array
+ vers=( 0.5.x 0.4.x )
+
+ # All releases start with "v"
+ # followed by MAJOR.MINOR.PATCH, see semver.org
+ # Retain MAJOR.MINOR as $stable
+ stable=${vers%.*}
+
+ echo "Stable version: $stable"
+
+ # Create directories
+ td=$(mktemp -d)
+ mkdir -p $td/$devver/book/
+ cp -r target/doc $td/$devver/api
+
+ # Redirect the main site to the stable release
+ sed "s|URL|$stable|g" redirect.html > $td/index.html
+
+ # Create the redirects for dev-version
+ sed 's|URL|rtic/index.html|g' redirect.html > $td/$devver/api/index.html
+ sed 's|URL|book/en|g' redirect.html > $td/$devver/index.html
+
+ # Build books
+ for lang in ${langs[@]}; do
+ ( cd book/$lang && mdbook build )
+ cp -r book/$lang/book $td/$devver/book/$lang
+ cp LICENSE-* $td/$devver/book/$lang/
+ done
+
+ # Build older versions, including stable
+ root=$(pwd)
+ for ver in ${vers[@]}; do
+ prefix=${ver%.*}
+
+ mkdir -p $td/$prefix/book
+ src=$(mktemp -d)
+ curl -L https://github.com/rtic-rs/cortex-m-rtic/archive/v${ver}.tar.gz | tar xz --strip-components 1 -C $src
+
+ pushd $src
+ rm -f .cargo/config
+ cargo doc || cargo doc --features timer-queue
+ cp -r target/doc $td/$prefix/api
+ sed 's|URL|rtic/index.html|g' $root/redirect.html > $td/$prefix/api/index.html
+ for lang in ${langs[@]}; do
+ ( cd book/$lang && mdbook build )
+ cp -r book/$lang/book $td/$prefix/book/$lang
+ cp LICENSE-* $td/$prefix/book/$lang/
+ done
+ sed 's|URL|book/en|g' $root/redirect.html > $td/$prefix/index.html
+ popd
+
+ rm -rf $src
+ done
+
+ # Copy the stable book to the stable alias
+ cp -r $td/$stable $td/stable
+
+ # Forward CNAME file
+ cp CNAME $td/
+ mv $td/ bookstodeploy
+
+ - name: Deploy to GH-pages
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./bookstodeploy
+
+ # 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
+ - checkexamples
+ - testexamples
+ - checkmacros
+ - testmacros
+ - testv7
+ - testv6
+ - docs
+ - mdbook
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Mark the job as a success
+ run: exit 0
+ ci-failure:
+ name: ci
+ if: github.event_name == 'push' && !success()
+ needs:
+ - style
+ - check
+ - checkexamples
+ - testexamples
+ - checkmacros
+ - testmacros
+ - testv7
+ - testv6
+ - docs
+ - mdbook
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Mark the job as a failure
+ run: exit 1
diff --git a/.github/workflows/properties/build.properties.json b/.github/workflows/properties/build.properties.json
new file mode 100644
index 00000000..fd3eed37
--- /dev/null
+++ b/.github/workflows/properties/build.properties.json
@@ -0,0 +1,6 @@
+{
+ "name": "Build",
+ "description": "RTIC Test Suite",
+ "iconName": "rust",
+ "categories": ["Rust"]
+}
diff --git a/.travis.yml b/.travis.yml
index 31d10e84..c2651307 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,20 +5,26 @@ matrix:
# NOTE used to build docs on successful merges to master
- env: TARGET=x86_64-unknown-linux-gnu
+ # MSRV
+ - env: TARGET=thumbv7m-none-eabi
+ rust: 1.36.0
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
+
- env: TARGET=thumbv6m-none-eabi
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
- env: TARGET=thumbv7m-none-eabi
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
+ # compile-fail tests
- env: TARGET=x86_64-unknown-linux-gnu
rust: nightly
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
+ # heterogeneous multi-core support
- env: TARGET=thumbv6m-none-eabi
rust: nightly
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
-
- env: TARGET=thumbv7m-none-eabi
rust: nightly
if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
@@ -31,6 +37,7 @@ before_install:
install:
- bash ci/install.sh
- export PATH="$PATH:$PWD/qemu"
+ - export PATH="$PATH:$PWD/mdbook-bin"
script:
- bash ci/script.sh
@@ -40,7 +47,7 @@ after_script: set +e
after_success:
- bash ci/after-success.sh
-cache: cache
+cache: cargo
before_cache:
- chmod -R a+r $HOME/.cargo;
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 860dad71..faecd10d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,115 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
+## [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
@@ -20,7 +129,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
`binds` argument that lets you give the handler an arbitrary name. For
example:
-[RFC 128]: https://github.com/japaric/cortex-m-rtfm/issues/128
+[RFC 128]: https://github.com/rtic-rs/cortex-m-rtic/issues/128
``` rust
// on v0.4.1 you had to write
@@ -52,8 +161,8 @@ fn on_new_frame() { .. }
### Added
-- The RTFM book has been translated to Russian. You can find the translation
- online at https://japaric.github.io/cortex-m-rtfm/book/ru/
+- 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.
@@ -93,11 +202,11 @@ Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0
`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 `rtfm`. The package
- name is still `cortex-m-rtfm`.
+- [breaking-change] The name of the library has changed to `rtic`. The package
+ name is still `cortex-m-rtic`.
-- [breaking-change] `cortex_m_rtfm::set_pending` has been renamed to
- `rtfm::pend`.
+- [breaking-change] `cortex_m_rtic::set_pending` has been renamed to
+ `rtic::pend`.
### Added
@@ -116,7 +225,7 @@ Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0
- [breaking-change] The `bkpt` and `wfi` re-exports have been removed.
-- [breaking-change] `rtfm::atomic` has been removed.
+- [breaking-change] `rtic::atomic` has been removed.
## [v0.3.4] - 2018-08-27
@@ -219,16 +328,23 @@ Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0
- Initial release
-[Unreleased]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.2...HEAD
-[v0.4.2]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.1...v0.4.2
-[v0.4.1]: https://github.com/japaric/cortex-m-rtfm/compare/v0.4.0...v0.4.1
-[v0.4.0]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.4...v0.4.0
-[v0.3.4]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.3...v0.3.4
-[v0.3.3]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.2...v0.3.3
-[v0.3.2]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.1...v0.3.2
-[v0.3.1]: https://github.com/japaric/cortex-m-rtfm/compare/v0.3.0...v0.3.1
-[v0.3.0]: https://github.com/japaric/cortex-m-rtfm/compare/v0.2.2...v0.3.0
-[v0.2.2]: https://github.com/japaric/cortex-m-rtfm/compare/v0.2.1...v0.2.2
-[v0.2.1]: https://github.com/japaric/cortex-m-rtfm/compare/v0.2.0...v0.2.1
-[v0.2.0]: https://github.com/japaric/cortex-m-rtfm/compare/v0.1.1...v0.2.0
-[v0.1.1]: https://github.com/japaric/cortex-m-rtfm/compare/v0.1.0...v0.1.1
+[Unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...HEAD
+[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/CNAME b/CNAME
new file mode 100644
index 00000000..ed759ff2
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+rtic.rs
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..b4a8af1f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,16 @@
+# Contributing
+## New features
+New features should go through the [RFC process][rfcs] before a Pull Request is made to this repository.
+
+[rfcs](https://github.com/rtic-rs/rfcs)
+
+## Bugs
+Report bugs by creating an issue in this repository.
+
+## Pull Requests
+Please make pull requests against the master branch.
+
+Always use rebase instead of merge when bringing in changes from master to your feature branch.
+
+## Writing documentation
+Documentation improvements are always welcome. The source for the book is in `book/` and API documentation is generated from the source code.
diff --git a/Cargo.toml b/Cargo.toml
index 1d70823f..6fe5fce3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,69 +1,112 @@
[package]
authors = [
+ "The Real-Time Interrupt-driven Concurrency developers",
"Jorge Aparicio <jorge@japaric.io>",
"Per Lindgren <per.lindgren@ltu.se>",
]
categories = ["concurrency", "embedded", "no-std"]
-description = "Real Time For the Masses (RTFM): a concurrency framework for building real time systems"
-documentation = "https://japaric.github.io/cortex-m-rtfm/book/en"
+description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems"
+documentation = "https://rtic.rs/"
edition = "2018"
keywords = ["arm", "cortex-m"]
license = "MIT OR Apache-2.0"
-name = "cortex-m-rtfm"
+name = "cortex-m-rtic"
readme = "README.md"
-repository = "https://github.com/japaric/cortex-m-rtfm"
-version = "0.4.2"
+repository = "https://github.com/rtic-rs/cortex-m-rtic"
+version = "0.5.5"
[lib]
-name = "rtfm"
+name = "rtic"
[[example]]
name = "baseline"
-required-features = ["timer-queue"]
+required-features = ["__v7"]
[[example]]
name = "periodic"
-required-features = ["timer-queue"]
+required-features = ["__v7"]
+
+[[example]]
+name = "pool"
+required-features = ["__v7"]
[[example]]
name = "schedule"
-required-features = ["timer-queue"]
+required-features = ["__v7"]
+
+[[example]]
+name = "t-cfg"
+required-features = ["__v7"]
+
+[[example]]
+name = "t-cfg-resources"
+required-features = ["__min_r1_43"]
+
+[[example]]
+name = "t-schedule"
+required-features = ["__v7"]
[[example]]
name = "types"
-required-features = ["timer-queue"]
+required-features = ["__v7"]
+
+[[example]]
+name = "double_schedule"
+required-features = ["__v7"]
[dependencies]
-cortex-m = "0.5.8"
-cortex-m-rt = "0.6.7"
-cortex-m-rtfm-macros = { path = "macros", version = "0.4.2" }
-heapless = "0.4.1"
-owned-singleton = "0.1.0"
+cortex-m = "0.6.2"
+cortex-m-rtic-macros = { path = "macros", version = "0.5.2" }
+rtic-core = "0.3.0"
+cortex-m-rt = "0.6.9"
+heapless = "0.5.0"
+bare-metal = "1.0.0"
+
+[build-dependencies]
+version_check = "0.9"
+
+[dependencies.microamp]
+optional = true
+version = "0.1.0-alpha.2"
[dev-dependencies]
-alloc-singleton = "0.1.0"
-cortex-m-semihosting = "0.3.2"
lm3s6965 = "0.1.3"
panic-halt = "0.2.0"
+cortex-m-semihosting = "0.3.3"
[dev-dependencies.panic-semihosting]
features = ["exit"]
-version = "0.5.1"
-
-[features]
-nightly = ["cortex-m-rtfm-macros/nightly", "heapless/const-fn"]
-timer-queue = ["cortex-m-rtfm-macros/timer-queue"]
+version = "0.5.2"
[target.x86_64-unknown-linux-gnu.dev-dependencies]
-compiletest_rs = "0.3.21"
-tempdir = "0.3.7"
+trybuild = "1"
-[package.metadata.docs.rs]
-features = ["timer-queue"]
+[features]
+# used for testing this crate; do not use in applications
+__v7 =[]
+__min_r1_43 =[]
[profile.release]
codegen-units = 1
lto = true
[workspace]
-members = ["macros"] \ No newline at end of file
+members = [
+ "macros",
+]
+
+# 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
diff --git a/LICENSE-MIT b/LICENSE-MIT
index 52cb453f..0c6c910c 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,4 +1,4 @@
-Copyright (c) 2017-2018 Jorge Aparicio
+Copyright (c) 2017-2019 The Real-Time Interrupt-driven Concurrency developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
diff --git a/README.md b/README.md
index b8cbd00f..18f968db 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
-# Real Time For the Masses
+# Real-Time Interrupt-driven Concurrency
-A concurrency framework for building real time systems.
+A concurrency framework for building real-time systems.
+
+Formerly known as Real-Time For the Masses.
+
+[![crates.io](https://img.shields.io/crates/v/cortex-m-rtic)](https://crates.io/crates/cortex-m-rtic)
+[![docs.rs](https://docs.rs/cortex-m-rtic/badge.svg)](https://docs.rs/cortex-m-rtic)
+[![book](https://img.shields.io/badge/web-rtic.rs-red.svg?style=flat&label=book&colorB=d33847)](https://rtic.rs/)
+![rustc](https://img.shields.io/badge/rustc-1.36+-lightgray.svg)
## Features
@@ -31,9 +38,7 @@ A concurrency framework for building real time systems.
- **Highly efficient memory usage**: All the tasks share a single call stack and
there's no hard dependency on a dynamic memory allocator.
-- **All Cortex-M devices are supported**. The core features of RTFM are
- supported on all Cortex-M devices. The timer queue is currently only supported
- on ARMv7-M devices.
+- **All Cortex-M devices are fully supported**.
- This task model is amenable to known WCET (Worst Case Execution Time) analysis
and scheduling analysis techniques. (Though we haven't yet developed Rust
@@ -41,17 +46,27 @@ A concurrency framework for building real time systems.
## Requirements
-- Rust 1.31.0+
+- Rust 1.36.0+
- Applications must be written using the 2018 edition.
-## [User documentation](https://japaric.github.io/cortex-m-rtfm/book/en)
+## [User documentation](https://rtic.rs)
+
+## [API reference](https://rtic.rs/0.5/api/)
+
+## Chat
+Join us and talk about RTIC in the [Matrix room][matrix-room].
+
+[matrix-room]: https://matrix.to/#/#rtic:matrix.org
+
+## Contributing
+New features and big changes should go through the RFC process in the [dedicated RFC repository][rfcs].
-## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html)
+[rfcs]: https://github.com/rtic-rs/rfcs
## Acknowledgments
-This crate is based on [the RTFM language][rtfm-lang] created by the Embedded
+This crate is based on [the Real-Time For the Masses language][rtfm-lang] created by the Embedded
Systems group at [Luleå University of Technology][ltu], led by [Prof. Per
Lindgren][per].
diff --git a/book/en/book.toml b/book/en/book.toml
index c611ce07..caa04ba8 100644
--- a/book/en/book.toml
+++ b/book/en/book.toml
@@ -2,4 +2,8 @@
authors = ["Jorge Aparicio"]
multilingual = false
src = "src"
-title = "Real Time For the Masses"
+title = "Real-Time Interrupt-driven Concurrency"
+
+[output.html]
+git-repository-url = "https://github.com/rtic-rs/cortex-m-rtic"
+git-repository-icon = "fa-github"
diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md
index 051d1acc..e1a4a330 100644
--- a/book/en/src/SUMMARY.md
+++ b/book/en/src/SUMMARY.md
@@ -1,16 +1,25 @@
# Summary
[Preface](./preface.md)
-- [RTFM by example](./by-example.md)
+
+- [RTIC by example](./by-example.md)
- [The `app` attribute](./by-example/app.md)
- [Resources](./by-example/resources.md)
- - [Tasks](./by-example/tasks.md)
+ - [Software tasks](./by-example/tasks.md)
- [Timer queue](./by-example/timer-queue.md)
- - [Singletons](./by-example/singletons.md)
- [Types, Send and Sync](./by-example/types-send-sync.md)
- [Starting a new project](./by-example/new.md)
- [Tips & tricks](./by-example/tips.md)
+- [Migration Guides](./migration.md)
+ - [v0.5.x to v0.6.x](./migration/migration_v5.md)
+ - [v0.4.x to v0.5.x](./migration/migration_v4.md)
+ - [RTFM to RTIC](./migration/migration_rtic.md)
- [Under the hood](./internals.md)
+ - [Interrupt configuration](./internals/interrupt-configuration.md)
+ - [Non-reentrancy](./internals/non-reentrancy.md)
+ - [Access control](./internals/access.md)
+ - [Late resources](./internals/late-resources.md)
+ - [Critical sections](./internals/critical-sections.md)
- [Ceiling analysis](./internals/ceilings.md)
- - [Task dispatcher](./internals/tasks.md)
+ - [Software tasks](./internals/tasks.md)
- [Timer queue](./internals/timer-queue.md)
diff --git a/book/en/src/by-example.md b/book/en/src/by-example.md
index e19f0749..e4441fd9 100644
--- a/book/en/src/by-example.md
+++ b/book/en/src/by-example.md
@@ -1,15 +1,15 @@
-# RTFM by example
+# RTIC by example
-This part of the book introduces the Real Time For the Masses (RTFM) framework
+This part of the book introduces the Real-Time Interrupt-driven Concurrency (RTIC) framework
to new users by walking them through examples of increasing complexity.
All examples in this part of the book can be found in the GitHub [repository] of
the project, and most of the examples can be run on QEMU so no special hardware
is required to follow along.
-[repository]: https://github.com/japaric/cortex-m-rtfm
+[repository]: https://github.com/rtic-rs/cortex-m-rtic
-To run the examples on your laptop / PC you'll need the `qemu-system-arm`
+To run the examples on your computer you'll need the `qemu-system-arm`
program. Check [the embedded Rust book] for instructions on how to set up an
embedded development environment that includes QEMU.
diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md
index 996b8c16..ab6f4524 100644
--- a/book/en/src/by-example/app.md
+++ b/book/en/src/by-example/app.md
@@ -1,50 +1,44 @@
# The `app` attribute
-This is the smallest possible RTFM application:
+This is the smallest possible RTIC application:
``` rust
{{#include ../../../../examples/smallest.rs}}
```
-All RTFM applications use the [`app`] attribute (`#[app(..)]`). This attribute
-must be applied to a `const` item that contains items. The `app` attribute has
+All RTIC applications use the [`app`] attribute (`#[app(..)]`). This attribute
+must be applied to a `mod`-item. The `app` attribute has
a mandatory `device` argument that takes a *path* as a value. This path must
point to a *peripheral access crate* (PAC) generated using [`svd2rust`]
-**v0.14.x**. The `app` attribute will expand into a suitable entry point so it's
-not required to use the [`cortex_m_rt::entry`] attribute.
+**v0.14.x** or newer. The `app` attribute will expand into a suitable entry
+point so it's not required to use the [`cortex_m_rt::entry`] attribute.
-[`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html
+[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
[`svd2rust`]: https://crates.io/crates/svd2rust
-[`cortex_m_rt::entry`]: ../../api/cortex_m_rt_macros/attr.entry.html
-
-> **ASIDE**: Some of you may be wondering why we are using a `const` item as a
-> module and not a proper `mod` item. The reason is that using attributes on
-> modules requires a feature gate, which requires a nightly toolchain. To make
-> RTFM work on stable we use the `const` item instead. When more parts of macros
-> 1.2 are stabilized we'll move from a `const` item to a `mod` item and
-> eventually to a crate level attribute (`#![app]`).
+[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
## `init`
-Within the pseudo-module the `app` attribute expects to find an initialization
+Within the `app` module the attribute expects to find an initialization
function marked with the `init` attribute. This function must have signature
-`[unsafe] fn()`.
+`fn(init::Context) [-> init::LateResources]` (the return type is not always
+required).
This initialization function will be the first part of the application to run.
The `init` function will run *with interrupts disabled* and has exclusive access
-to Cortex-M and device specific peripherals through the `core` and `device`
-variables, which are injected in the scope of `init` by the `app` attribute. Not
-all Cortex-M peripherals are available in `core` because the RTFM runtime takes
-ownership of some of them -- for more details see the [`rtfm::Peripherals`]
-struct.
+to Cortex-M where the `bare_metal::CriticalSection` token is available as `cs`.
+And optionally, device specific peripherals through the `core` and `device` fields
+of `init::Context`.
`static mut` variables declared at the beginning of `init` will be transformed
into `&'static mut` references that are safe to access.
-[`rtfm::Peripherals`]: ../../api/rtfm/struct.Peripherals.html
+[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html
-The example below shows the types of the `core` and `device` variables and
-showcases safe access to a `static mut` variable.
+The example below shows the types of the `core`, `device` and `cs` fields, and
+showcases safe access to a `static mut` variable. The `device` field is only
+available when the `peripherals` argument is set to `true` (it defaults to
+`false`).
``` rust
{{#include ../../../../examples/init.rs}}
@@ -55,51 +49,104 @@ process.
``` console
$ cargo run --example init
-{{#include ../../../../ci/expected/init.run}}```
+{{#include ../../../../ci/expected/init.run}}
+```
## `idle`
A function marked with the `idle` attribute can optionally appear in the
-pseudo-module. This function is used as the special *idle task* and must have
-signature `[unsafe] fn() - > !`.
+module. This function is used as the special *idle task* and must have
+signature `fn(idle::Context) - > !`.
When present, the runtime will execute the `idle` task after `init`. Unlike
`init`, `idle` will run *with interrupts enabled* and it's not allowed to return
-so it runs forever.
+so it must run forever.
When no `idle` function is declared, the runtime sets the [SLEEPONEXIT] bit and
then sends the microcontroller to sleep after running `init`.
-[SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
+[SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
Like in `init`, `static mut` variables will be transformed into `&'static mut`
references that are safe to access.
The example below shows that `idle` runs after `init`.
+**Note:** The `loop {}` in idle cannot be empty as this will crash the microcontroller due to a bug
+in LLVM which miss-optimizes empty loops to a `UDF` instruction in release mode.
+
``` rust
{{#include ../../../../examples/idle.rs}}
```
``` console
$ cargo run --example idle
-{{#include ../../../../ci/expected/idle.run}}```
+{{#include ../../../../ci/expected/idle.run}}
+```
+
+## Hardware tasks
-## `interrupt` / `exception`
+To declare interrupt handlers the framework provides a `#[task]` attribute that
+can be attached to functions. This attribute takes a `binds` argument whose
+value is the name of the interrupt to which the handler will be bound to; the
+function adorned with this attribute becomes the interrupt handler. Within the
+framework these type of tasks are referred to as *hardware* tasks, because they
+start executing in reaction to a hardware event.
-Just like you would do with the `cortex-m-rt` crate you can use the `interrupt`
-and `exception` attributes within the `app` pseudo-module to declare interrupt
-and exception handlers. In RTFM, we refer to interrupt and exception handlers as
-*hardware* tasks.
+The example below demonstrates the use of the `#[task]` attribute to declare an
+interrupt handler. Like in the case of `#[init]` and `#[idle]` local `static
+mut` variables are safe to use within a hardware task.
``` rust
-{{#include ../../../../examples/interrupt.rs}}
+{{#include ../../../../examples/hardware.rs}}
```
``` console
-$ cargo run --example interrupt
-{{#include ../../../../ci/expected/interrupt.run}}```
+$ cargo run --example hardware
+{{#include ../../../../ci/expected/hardware.run}}
+```
+
+So far all the RTIC applications we have seen look no different than the
+applications one can write using only the `cortex-m-rt` crate. From this point
+we start introducing features unique to RTIC.
+
+## Priorities
+
+The static priority of each handler can be declared in the `task` attribute
+using the `priority` argument. Tasks can have priorities in the range `1..=(1 <<
+NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device`
+crate. When the `priority` argument is omitted, the priority is assumed to be
+`1`. The `idle` task has a non-configurable static priority of `0`, the lowest
+priority.
+
+When several tasks are ready to be executed the one with *highest* static
+priority will be executed first. Task prioritization can be observed in the
+following scenario: an interrupt signal arrives during the execution of a low
+priority task; the signal puts the higher priority task in the pending state.
+The difference in priority results in the higher priority task preempting the
+lower priority one: the execution of the lower priority task is suspended and
+the higher priority task is executed to completion. Once the higher priority
+task has terminated the lower priority task is resumed.
+
+The following example showcases the priority based scheduling of tasks.
+
+``` rust
+{{#include ../../../../examples/preempt.rs}}
+```
+
+``` console
+$ cargo run --example preempt
+{{#include ../../../../ci/expected/preempt.run}}
+```
-So far all the RTFM applications we have seen look no different that the
-applications one can write using only the `cortex-m-rt` crate. In the next
-section we start introducing features unique to RTFM.
+Note that the task `gpiob` does *not* preempt task `gpioc` because its priority
+is the *same* as `gpioc`'s. However, once `gpioc` terminates the execution of
+task, `gpiob` is prioritized over `gpioa` due to its higher priority. `gpioa`
+is resumed only after `gpiob` terminates.
+
+One more note about priorities: choosing a priority higher than what the device
+supports (that is `1 << NVIC_PRIO_BITS`) will result in a compile error. Due to
+limitations in the language, the error message is currently far from helpful: it
+will say something along the lines of "evaluation of constant value failed" and
+the span of the error will *not* point out to the problematic interrupt value --
+we are sorry about this!
diff --git a/book/en/src/by-example/new.md b/book/en/src/by-example/new.md
index ae49ef21..866a9fa5 100644
--- a/book/en/src/by-example/new.md
+++ b/book/en/src/by-example/new.md
@@ -1,6 +1,6 @@
# Starting a new project
-Now that you have learned about the main features of the RTFM framework you can
+Now that you have learned about the main features of the RTIC framework you can
try it out on your hardware by following these instructions.
1. Instantiate the [`cortex-m-quickstart`] template.
@@ -36,20 +36,19 @@ $ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs
```
-3. Add the `cortex-m-rtfm` crate as a dependency and, if you need it, enable the
- `timer-queue` feature.
+3. Add the `cortex-m-rtic` crate as a dependency.
``` console
-$ cargo add cortex-m-rtfm
+$ cargo add cortex-m-rtic --allow-prerelease
```
-4. Write your RTFM application.
+4. Write your RTIC application.
-Here I'll use the `init` example from the `cortex-m-rtfm` crate.
+Here I'll use the `init` example from the `cortex-m-rtic` crate.
``` console
$ curl \
- -L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0/examples/init.rs \
+ -L https://github.com/rtic-rs/cortex-m-rtic/raw/v0.5.3/examples/init.rs \
> src/main.rs
```
@@ -64,4 +63,5 @@ $ cargo add panic-semihosting
``` console
$ # NOTE: I have uncommented the `runner` option in `.cargo/config`
$ cargo run
-{{#include ../../../../ci/expected/init.run}}```
+{{#include ../../../../ci/expected/init.run}}
+```
diff --git a/book/en/src/by-example/resources.md b/book/en/src/by-example/resources.md
index 17f4d139..d082dfc1 100644
--- a/book/en/src/by-example/resources.md
+++ b/book/en/src/by-example/resources.md
@@ -1,22 +1,29 @@
## Resources
-One of the limitations of the attributes provided by the `cortex-m-rt` crate is
-that sharing data (or peripherals) between interrupts, or between an interrupt
-and the `entry` function, requires a `cortex_m::interrupt::Mutex`, which
-*always* requires disabling *all* interrupts to access the data. Disabling all
-the interrupts is not always required for memory safety but the compiler doesn't
-have enough information to optimize the access to the shared data.
-
-The `app` attribute has a full view of the application thus it can optimize
-access to `static` variables. In RTFM we refer to the `static` variables
-declared inside the `app` pseudo-module as *resources*. To access a resource the
-context (`init`, `idle`, `interrupt` or `exception`) must first declare the
-resource in the `resources` argument of its attribute.
-
-In the example below two interrupt handlers access the same resource. No `Mutex`
-is required in this case because the two handlers run at the same priority and
-no preemption is possible. The `SHARED` resource can only be accessed by these
-two handlers.
+The framework provides an abstraction to share data between any of the contexts
+we saw in the previous section (task handlers, `init` and `idle`): resources.
+
+Resources are data visible only to functions declared within the `#[app]`
+module. The framework gives the user complete control over which context
+can access which resource.
+
+All resources are declared as a single `struct` within the `#[app]`
+module. Each field in the structure corresponds to a different resource.
+The `struct` must be annotated with the following attribute: `#[resources]`.
+
+Resources can optionally be given an initial value using the `#[init]`
+attribute. Resources that are not given an initial value are referred to as
+*late* resources and are covered in more detail in a follow-up section in this
+page.
+
+Each context (task handler, `init` or `idle`) must declare the resources it
+intends to access in its corresponding metadata attribute using the `resources`
+argument. This argument takes a list of resource names as its value. The listed
+resources are made available to the context under the `resources` field of the
+`Context` structure.
+
+The example application shown below contains two interrupt handlers that share
+access to a resource named `shared`.
``` rust
{{#include ../../../../examples/resource.rs}}
@@ -24,42 +31,42 @@ two handlers.
``` console
$ cargo run --example resource
-{{#include ../../../../ci/expected/resource.run}}```
+{{#include ../../../../ci/expected/resource.run}}
+```
+
+Note that the `shared` resource cannot be accessed from `idle`. Attempting to do
+so results in a compile error.
-## Priorities
+## `lock`
-The priority of each handler can be declared in the `interrupt` and `exception`
-attributes. It's not possible to set the priority in any other way because the
-runtime takes ownership of the `NVIC` peripheral; it's also not possible to
-change the priority of a handler / task at runtime. Thanks to this restriction
-the framework has knowledge about the *static* priorities of all interrupt and
-exception handlers.
+In the presence of preemption critical sections are required to mutate shared
+data in a data race free manner. As the framework has complete knowledge over
+the priorities of tasks and which tasks can access which resources it enforces
+that critical sections are used where required for memory safety.
-Interrupts and exceptions can have priorities in the range `1..=(1 <<
-NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device`
-crate. The `idle` task has a priority of `0`, the lowest priority.
+Where a critical section is required the framework hands out a resource proxy
+instead of a reference. This resource proxy is a structure that implements the
+[`Mutex`] trait. The only method on this trait, [`lock`], runs its closure
+argument in a critical section.
-Resources that are shared between handlers that run at different priorities
-require critical sections for memory safety. The framework ensures that critical
-sections are used but *only where required*: for example, no critical section is
-required by the highest priority handler that has access to the resource.
+[`Mutex`]: ../../../api/rtic/trait.Mutex.html
+[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
-The critical section API provided by the RTFM framework (see [`Mutex`]) is
-based on dynamic priorities rather than on disabling interrupts. The consequence
-is that these critical sections will prevent *some* handlers, including all the
-ones that contend for the resource, from *starting* but will let higher priority
-handlers, that don't contend for the resource, run.
+The critical section created by the `lock` API is based on dynamic priorities:
+it temporarily raises the dynamic priority of the context to a *ceiling*
+priority that prevents other tasks from preempting the critical section. This
+synchronization protocol is known as the [Immediate Ceiling Priority Protocol
+(ICPP)][icpp].
-[`Mutex`]: ../../api/rtfm/trait.Mutex.html
+[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
In the example below we have three interrupt handlers with priorities ranging
from one to three. The two handlers with the lower priorities contend for the
-`SHARED` resource. The lowest priority handler needs to [`lock`] the
-`SHARED` resource to access its data, whereas the mid priority handler can
-directly access its data. The highest priority handler is free to preempt
-the critical section created by the lowest priority handler.
-
-[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock
+`shared` resource. The lowest priority handler needs to `lock` the
+`shared` resource to access its data, whereas the mid priority handler can
+directly access its data. The highest priority handler, which cannot access
+the `shared` resource, is free to preempt the critical section created by the
+lowest priority handler.
``` rust
{{#include ../../../../examples/lock.rs}}
@@ -67,35 +74,26 @@ the critical section created by the lowest priority handler.
``` console
$ cargo run --example lock
-{{#include ../../../../ci/expected/lock.run}}```
-
-One more note about priorities: choosing a priority higher than what the device
-supports (that is `1 << NVIC_PRIO_BITS`) will result in a compile error. Due to
-limitations in the language the error is currently far from helpful: it will say
-something along the lines of "evaluation of constant value failed" and the span
-of the error will *not* point out to the problematic interrupt value -- we are
-sorry about this!
+{{#include ../../../../ci/expected/lock.run}}
+```
## Late resources
-Unlike normal `static` variables, which need to be assigned an initial value
-when declared, resources can be initialized at runtime. We refer to these
-runtime initialized resources as *late resources*. Late resources are useful for
-*moving* (as in transferring ownership) peripherals initialized in `init` into
-interrupt and exception handlers.
+Late resources are resources that are not given an initial value at compile time
+using the `#[init]` attribute but instead are initialized at runtime using the
+`init::LateResources` values returned by the `init` function.
-Late resources are declared like normal resources but that are given an initial
-value of `()` (the unit value). `init` must return the initial values of all
-late resources packed in a `struct` of type `init::LateResources`.
+Late resources are useful for *moving* (as in transferring the ownership of)
+peripherals initialized in `init` into interrupt handlers.
-The example below uses late resources to stablish a lockless, one-way channel
-between the `UART0` interrupt handler and the `idle` function. A single producer
+The example below uses late resources to establish a lockless, one-way channel
+between the `UART0` interrupt handler and the `idle` task. A single producer
single consumer [`Queue`] is used as the channel. The queue is split into
consumer and producer end points in `init` and then each end point is stored
in a different resource; `UART0` owns the producer resource and `idle` owns
the consumer resource.
-[`Queue`]: ../../api/heapless/spsc/struct.Queue.html
+[`Queue`]: ../../../api/heapless/spsc/struct.Queue.html
``` rust
{{#include ../../../../examples/late.rs}}
@@ -103,24 +101,36 @@ the consumer resource.
``` console
$ cargo run --example late
-{{#include ../../../../ci/expected/late.run}}```
+{{#include ../../../../ci/expected/late.run}}
+```
-## `static` resources
+## Only shared access
-`static` variables can also be used as resources. Tasks can only get `&`
-(shared) references to these resources but locks are never required to access
-their data. You can think of `static` resources as plain `static` variables that
-can be initialized at runtime and have better scoping rules: you can control
-which tasks can access the variable, instead of the variable being visible to
-all the functions in the scope it was declared in.
+By default the framework assumes that all tasks require exclusive access
+(`&mut-`) to resources but it is possible to specify that a task only requires
+shared access (`&-`) to a resource using the `&resource_name` syntax in the
+`resources` list.
-In the example below a key is loaded (or created) at runtime and then used from
-two tasks that run at different priorities.
+The advantage of specifying shared access (`&-`) to a resource is that no locks
+are required to access the resource even if the resource is contended by several
+tasks running at different priorities. The downside is that the task only gets a
+shared reference (`&-`) to the resource, limiting the operations it can perform
+on it, but where a shared reference is enough this approach reduces the number
+of required locks.
+
+Note that in this release of RTIC it is not possible to request both exclusive
+access (`&mut-`) and shared access (`&-`) to the *same* resource from different
+tasks. Attempting to do so will result in a compile error.
+
+In the example below a key (e.g. a cryptographic key) is loaded (or created) at
+runtime and then used from two tasks that run at different priorities without
+any kind of lock.
``` rust
-{{#include ../../../../examples/static.rs}}
+{{#include ../../../../examples/only-shared-access.rs}}
```
``` console
-$ cargo run --example static
-{{#include ../../../../ci/expected/static.run}}```
+$ cargo run --example only-shared-access
+{{#include ../../../../ci/expected/only-shared-access.run}}
+```
diff --git a/book/en/src/by-example/singletons.md b/book/en/src/by-example/singletons.md
deleted file mode 100644
index 0823f057..00000000
--- a/book/en/src/by-example/singletons.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Singletons
-
-The `app` attribute is aware of [`owned-singleton`] crate and its [`Singleton`]
-attribute. When this attribute is applied to one of the resources the runtime
-will perform the `unsafe` initialization of the singleton for you, ensuring that
-only a single instance of the singleton is ever created.
-
-[`owned-singleton`]: ../../api/owned_singleton/index.html
-[`Singleton`]: ../../api/owned_singleton_macros/attr.Singleton.html
-
-Note that when using the `Singleton` attribute you'll need to have the
-`owned_singleton` in your dependencies.
-
-Below is an example that uses the `Singleton` attribute on a chunk of memory
-and then uses the singleton instance as a fixed-size memory pool using one of
-the [`alloc-singleton`] abstractions.
-
-[`alloc-singleton`]: https://crates.io/crates/alloc-singleton
-
-``` rust
-{{#include ../../../../examples/singleton.rs}}
-```
-
-``` console
-$ cargo run --example singleton
-{{#include ../../../../ci/expected/singleton.run}}```
diff --git a/book/en/src/by-example/tasks.md b/book/en/src/by-example/tasks.md
index edcdbed0..ba164048 100644
--- a/book/en/src/by-example/tasks.md
+++ b/book/en/src/by-example/tasks.md
@@ -1,22 +1,23 @@
# Software tasks
-RTFM treats interrupt and exception handlers as *hardware* tasks. Hardware tasks
-are invoked by the hardware in response to events, like pressing a button. RTFM
-also supports *software* tasks which can be spawned by the software from any
-execution context.
-
-Software tasks can also be assigned priorities and are dispatched from interrupt
-handlers. RTFM requires that free interrupts are declared in an `extern` block
-when using software tasks; these free interrupts will be used to dispatch the
-software tasks. An advantage of software tasks over hardware tasks is that many
-tasks can be mapped to a single interrupt handler.
-
-Software tasks are declared by applying the `task` attribute to functions. To be
-able to spawn a software task the name of the task must appear in the `spawn`
-argument of the context attribute (`init`, `idle`, `interrupt`, etc.).
+In addition to hardware tasks, which are invoked by the hardware in response to
+hardware events, RTIC also supports *software* tasks which can be spawned by the
+application from any execution context.
+
+Software tasks can also be assigned priorities and, under the hood, are
+dispatched from interrupt handlers. RTIC requires that free interrupts are
+declared in an `extern` block when using software tasks; some of these free
+interrupts will be used to dispatch the software tasks. An advantage of software
+tasks over hardware tasks is that many tasks can be mapped to a single interrupt
+handler.
+
+Software tasks are also declared using the `task` attribute but the `binds`
+argument must be omitted. To be able to spawn a software task from a context
+the name of the task must appear in the `spawn` argument of the context
+attribute (`init`, `idle`, `task`, etc.).
The example below showcases three software tasks that run at 2 different
-priorities. The three tasks map to 2 interrupts handlers.
+priorities. The three software tasks are mapped to 2 interrupts handlers.
``` rust
{{#include ../../../../examples/task.rs}}
@@ -24,7 +25,8 @@ priorities. The three tasks map to 2 interrupts handlers.
``` console
$ cargo run --example task
-{{#include ../../../../ci/expected/task.run}}```
+{{#include ../../../../ci/expected/task.run}}
+```
## Message passing
@@ -40,19 +42,22 @@ The example below showcases three tasks, two of them expect a message.
``` console
$ cargo run --example message
-{{#include ../../../../ci/expected/message.run}}```
+{{#include ../../../../ci/expected/message.run}}
+```
## Capacity
-Task dispatchers do *not* use any dynamic memory allocation. The memory required
-to store messages is statically reserved. The framework will reserve enough
-space for every context to be able to spawn each task at most once. This is a
-sensible default but the "inbox" capacity of each task can be controlled using
-the `capacity` argument of the `task` attribute.
+RTIC does *not* perform any form of heap-based memory allocation. The memory
+required to store messages is statically reserved. By default the framework
+minimizes the memory footprint of the application so each task has a message
+"capacity" of 1: meaning that at most one message can be posted to the task
+before it gets a chance to run. This default can be overridden for each task
+using the `capacity` argument. This argument takes a positive integer that
+indicates how many messages the task message buffer can hold.
The example below sets the capacity of the software task `foo` to 4. If the
capacity is not specified then the second `spawn.foo` call in `UART0` would
-fail.
+fail (panic).
``` rust
{{#include ../../../../examples/capacity.rs}}
@@ -60,4 +65,56 @@ fail.
``` console
$ cargo run --example capacity
-{{#include ../../../../ci/expected/capacity.run}}```
+{{#include ../../../../ci/expected/capacity.run}}
+```
+
+## Error handling
+
+The `spawn` API returns the `Err` variant when there's no space to send the
+message. In most scenarios spawning errors are handled in one of two ways:
+
+- Panicking, using `unwrap`, `expect`, etc. This approach is used to catch the
+ programmer error (i.e. bug) of selecting a capacity that was too small. When
+ this panic is encountered during testing choosing a bigger capacity and
+ recompiling the program may fix the issue but sometimes it's necessary to dig
+ deeper and perform a timing analysis of the application to check if the
+ platform can deal with peak payload or if the processor needs to be replaced
+ with a faster one.
+
+- Ignoring the result. In soft real-time and non real-time applications it may
+ be OK to occasionally lose data or fail to respond to some events during event
+ bursts. In those scenarios silently letting a `spawn` call fail may be
+ acceptable.
+
+It should be noted that retrying a `spawn` call is usually the wrong approach as
+this operation will likely never succeed in practice. Because there are only
+context switches towards *higher* priority tasks retrying the `spawn` call of a
+lower priority task will never let the scheduler dispatch said task meaning that
+its message buffer will never be emptied. This situation is depicted in the
+following snippet:
+
+``` rust
+#[rtic::app(..)]
+mod app {
+ #[init(spawn = [foo, bar])]
+ fn init(cx: init::Context) {
+ cx.spawn.foo().unwrap();
+ cx.spawn.bar().unwrap();
+ }
+
+ #[task(priority = 2, spawn = [bar])]
+ fn foo(cx: foo::Context) {
+ // ..
+
+ // the program will get stuck here
+ while cx.spawn.bar(payload).is_err() {
+ // retry the spawn call if it failed
+ }
+ }
+
+ #[task(priority = 1)]
+ fn bar(cx: bar::Context, payload: i32) {
+ // ..
+ }
+}
+```
diff --git a/book/en/src/by-example/timer-queue.md b/book/en/src/by-example/timer-queue.md
index 167939ce..482aebc1 100644
--- a/book/en/src/by-example/timer-queue.md
+++ b/book/en/src/by-example/timer-queue.md
@@ -1,37 +1,47 @@
# Timer queue
-When the `timer-queue` feature is enabled the RTFM framework includes a *global
-timer queue* that applications can use to *schedule* software tasks to run at
-some time in the future.
-
-> **NOTE**: The timer-queue feature can't be enabled when the target is
-> `thumbv6m-none-eabi` because there's no timer queue support for ARMv6-M. This
-> may change in the future.
-
-> **NOTE**: When the `timer-queue` feature is enabled you will *not* be able to
-> use the `SysTick` exception as a hardware task because the runtime uses it to
-> implement the global timer queue.
-
-To be able to schedule a software task the name of the task must appear in the
-`schedule` argument of the context attribute. When scheduling a task the
-[`Instant`] at which the task should be executed must be passed as the first
-argument of the `schedule` invocation.
-
-[`Instant`]: ../../api/rtfm/struct.Instant.html
-
-The RTFM runtime includes a monotonic, non-decreasing, 32-bit timer which can be
-queried using the `Instant::now` constructor. A [`Duration`] can be added to
-`Instant::now()` to obtain an `Instant` into the future. The monotonic timer is
-disabled while `init` runs so `Instant::now()` always returns the value
-`Instant(0 /* clock cycles */)`; the timer is enabled right before the
-interrupts are re-enabled and `idle` is executed.
-
-[`Duration`]: ../../api/rtfm/struct.Duration.html
+In contrast with the `spawn` API, which immediately spawns a software task onto
+the scheduler, the `schedule` API can be used to schedule a task to run some
+time in the future.
+
+To use the `schedule` API a monotonic timer must be first defined using the
+`monotonic` argument of the `#[app]` attribute. This argument takes a path to a
+type that implements the [`Monotonic`] trait. The associated type, `Instant`, of
+this trait represents a timestamp in arbitrary units and it's used extensively
+in the `schedule` API -- it is suggested to model this type after [the one in
+the standard library][std-instant].
+
+Although not shown in the trait definition (due to limitations in the trait /
+type system) the subtraction of two `Instant`s should return some `Duration`
+type (see [`core::time::Duration`]) and this `Duration` type must implement the
+`TryInto<u32>` trait. The implementation of this trait must convert the
+`Duration` value, which uses some arbitrary unit of time, into the "system timer
+(SYST) clock cycles" time unit. The result of the conversion must be a 32-bit
+integer. If the result of the conversion doesn't fit in a 32-bit number then the
+operation must return an error, any error type.
+
+[`Monotonic`]: ../../../api/rtic/trait.Monotonic.html
+[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html
+[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
+
+For ARMv7+ targets the `rtic` crate provides a `Monotonic` implementation based
+on the built-in CYCle CouNTer (CYCCNT). Note that this is a 32-bit timer clocked
+at the frequency of the CPU and as such it is not suitable for tracking time
+spans in the order of seconds.
+
+To be able to schedule a software task from a context the name of the task must
+first appear in the `schedule` argument of the context attribute. When
+scheduling a task the (user-defined) `Instant` at which the task should be
+executed must be passed as the first argument of the `schedule` invocation.
+
+Additionally, the chosen `monotonic` timer must be configured and initialized
+during the `#[init]` phase. Note that this is *also* the case if you choose to
+use the `CYCCNT` provided by the `cortex-m-rtic` crate.
The example below schedules two tasks from `init`: `foo` and `bar`. `foo` is
scheduled to run 8 million clock cycles in the future. Next, `bar` is scheduled
-to run 4 million clock cycles in the future. `bar` runs before `foo` since it
-was scheduled to run first.
+to run 4 million clock cycles in the future. Thus `bar` runs before `foo` since
+it was scheduled to run first.
> **IMPORTANT**: The examples that use the `schedule` API or the `Instant`
> abstraction will **not** properly work on QEMU because the Cortex-M cycle
@@ -41,12 +51,19 @@ was scheduled to run first.
{{#include ../../../../examples/schedule.rs}}
```
-Running the program on real hardware produces the following output in the console:
+Running the program on real hardware produces the following output in the
+console:
``` text
{{#include ../../../../ci/expected/schedule.run}}
```
+When the `schedule` API is being used the runtime internally uses the `SysTick`
+interrupt handler and the system timer peripheral (`SYST`) so neither can be
+used by the application. This is accomplished by changing the type of
+`init::Context.core` from `cortex_m::Peripherals` to `rtic::Peripherals`. The
+latter structure contains all the fields of the former minus the `SYST` one.
+
## Periodic tasks
Software tasks have access to the `Instant` at which they were scheduled to run
@@ -80,9 +97,10 @@ the task. Depending on the priority of the task and the load of the system the
What do you think will be the value of `scheduled` for software tasks that are
*spawned* instead of scheduled? The answer is that spawned tasks inherit the
*baseline* time of the context that spawned it. The baseline of hardware tasks
-is `start`, the baseline of software tasks is `scheduled` and the baseline of
-`init` is `start = Instant(0)`. `idle` doesn't really have a baseline but tasks
-spawned from it will use `Instant::now()` as their baseline time.
+is their `start` time, the baseline of software tasks is their `scheduled` time
+and the baseline of `init` is the system start time or time zero
+(`Instant::zero()`). `idle` doesn't really have a baseline but tasks spawned
+from it will use `Instant::now()` as their baseline time.
The example below showcases the different meanings of the *baseline*.
diff --git a/book/en/src/by-example/tips.md b/book/en/src/by-example/tips.md
index c0bfc56e..d8264c90 100644
--- a/book/en/src/by-example/tips.md
+++ b/book/en/src/by-example/tips.md
@@ -2,10 +2,21 @@
## Generics
-Resources shared between two or more tasks implement the `Mutex` trait in *all*
-contexts, even on those where a critical section is not required to access the
-data. This lets you easily write generic code that operates on resources and can
-be called from different tasks. Here's one such example:
+Resources may appear in contexts as resource proxies or as unique references
+(`&mut-`) depending on the priority of the task. Because the same resource may
+appear as *different* types in different contexts one cannot refactor a common
+operation that uses resources into a plain function; however, such refactor is
+possible using *generics*.
+
+All resource proxies implement the `rtic::Mutex` trait. On the other hand,
+unique references (`&mut-`) do *not* implement this trait (due to limitations in
+the trait system) but one can wrap these references in the [`rtic::Exclusive`]
+newtype which does implement the `Mutex` trait. With the help of this newtype
+one can write a generic function that operates on generic resources and call it
+from different tasks to perform some operation on the same set of resources.
+Here's one such example:
+
+[`rtic::Exclusive`]: ../../../api/rtic/struct.Exclusive.html
``` rust
{{#include ../../../../examples/generics.rs}}
@@ -13,19 +24,18 @@ be called from different tasks. Here's one such example:
``` console
$ cargo run --example generics
-{{#include ../../../../ci/expected/generics.run}}```
+{{#include ../../../../ci/expected/generics.run}}
+```
-This also lets you change the static priorities of tasks without having to
-rewrite code. If you consistently use `lock`s to access the data behind shared
-resources then your code will continue to compile when you change the priority
-of tasks.
+Using generics also lets you change the static priorities of tasks during
+development without having to rewrite a bunch code every time.
## Conditional compilation
-You can use conditional compilation (`#[cfg]`) on resources (`static [mut]`
-items) and tasks (`fn` items). The effect of using `#[cfg]` attributes is that
-the resource / task will *not* be injected into the prelude of tasks that use
-them (see `resources`, `spawn` and `schedule`) if the condition doesn't hold.
+You can use conditional compilation (`#[cfg]`) on resources (the fields of
+`struct Resources`) and tasks (the `fn` items). The effect of using `#[cfg]`
+attributes is that the resource / task will *not* be available through the
+corresponding `Context` `struct` if the condition doesn't hold.
The example below logs a message whenever the `foo` task is spawned, but only if
the program has been compiled using the `dev` profile.
@@ -34,10 +44,17 @@ the program has been compiled using the `dev` profile.
{{#include ../../../../examples/cfg.rs}}
```
+``` console
+$ cargo run --example cfg --release
+
+$ cargo run --example cfg
+{{#include ../../../../ci/expected/cfg.run}}
+```
+
## Running tasks from RAM
-The main goal of moving the specification of RTFM applications to attributes in
-RTFM v0.4.x was to allow inter-operation with other attributes. For example, the
+The main goal of moving the specification of RTIC applications to attributes in
+RTIC v0.4.0 was to allow inter-operation with other attributes. For example, the
`link_section` attribute can be applied to tasks to place them in RAM; this can
improve performance in some cases.
@@ -63,31 +80,101 @@ Running this program produces the expected output.
``` console
$ cargo run --example ramfunc
-{{#include ../../../../ci/expected/ramfunc.run}}```
+{{#include ../../../../ci/expected/ramfunc.run}}
+```
One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM
(`0x2000_0000`), whereas `foo` ended in Flash (`0x0000_0000`).
``` console
$ cargo nm --example ramfunc --release | grep ' foo::'
-{{#include ../../../../ci/expected/ramfunc.grep.foo}}```
+{{#include ../../../../ci/expected/ramfunc.grep.foo}}
+```
``` console
$ cargo nm --example ramfunc --release | grep ' bar::'
-{{#include ../../../../ci/expected/ramfunc.grep.bar}}```
+{{#include ../../../../ci/expected/ramfunc.grep.bar}}
+```
+
+## Indirection for faster message passing
+
+Message passing always involves copying the payload from the sender into a
+static variable and then from the static variable into the receiver. Thus
+sending a large buffer, like a `[u8; 128]`, as a message involves two expensive
+`memcpy`s. To minimize the message passing overhead one can use indirection:
+instead of sending the buffer by value, one can send an owning pointer into the
+buffer.
+
+One can use a global allocator to achieve indirection (`alloc::Box`,
+`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.37.0,
+or one can use a statically allocated memory pool like [`heapless::Pool`].
+
+[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html
+
+Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes.
+
+``` rust
+{{#include ../../../../examples/pool.rs}}
+```
+``` console
+$ cargo run --example pool
+{{#include ../../../../ci/expected/pool.run}}
+```
+
+## Inspecting the expanded code
+
+`#[rtic::app]` is a procedural macro that produces support code. If for some
+reason you need to inspect the code generated by this macro you have two
+options:
-## `binds`
+You can inspect the file `rtic-expansion.rs` inside the `target` directory. This
+file contains the expansion of the `#[rtic::app]` item (not your whole program!)
+of the *last built* (via `cargo build` or `cargo check`) RTIC application. The
+expanded code is not pretty printed by default so you'll want to run `rustfmt`
+over it before you read it.
-**NOTE**: Requires RTFM ~0.4.2
+``` console
+$ cargo build --example foo
+
+$ rustfmt target/rtic-expansion.rs
-You can give hardware tasks more task-like names using the `binds` argument: you
-name the function as you wish and specify the name of the interrupt / exception
-in the `binds` argument. Types like `Spawn` will be placed in a module named
-after the function, not the interrupt / exception. Example below:
+$ tail target/rtic-expansion.rs
+```
``` rust
-{{#include ../../../../examples/binds.rs}}
+#[doc = r" Implementation details"]
+mod app {
+ #[doc = r" Always include the device crate which contains the vector table"]
+ use lm3s6965 as _;
+ #[no_mangle]
+ unsafe extern "C" fn main() -> ! {
+ rtic::export::interrupt::disable();
+ let mut core: rtic::export::Peripherals = core::mem::transmute(());
+ core.SCB.scr.modify(|r| r | 1 << 1);
+ rtic::export::interrupt::enable();
+ loop {
+ rtic::export::wfi()
+ }
+ }
+}
```
+
+Or, you can use the [`cargo-expand`] sub-command. This sub-command will expand
+*all* the macros, including the `#[rtic::app]` attribute, and modules in your
+crate and print the output to the console.
+
+[`cargo-expand`]: https://crates.io/crates/cargo-expand
+
``` console
-$ cargo run --example binds
-{{#include ../../../../ci/expected/binds.run}}```
+$ # produces the same output as before
+$ cargo expand --example smallest | tail
+```
+
+## Resource de-structure-ing
+
+When having a task taking multiple resources it can help in readability to split
+up the resource struct. Here are two examples on how this can be done:
+
+``` rust
+{{#include ../../../../examples/destructure.rs}}
+```
diff --git a/book/en/src/by-example/types-send-sync.md b/book/en/src/by-example/types-send-sync.md
index da53cf96..9cdb8894 100644
--- a/book/en/src/by-example/types-send-sync.md
+++ b/book/en/src/by-example/types-send-sync.md
@@ -1,14 +1,13 @@
# Types, Send and Sync
-The `app` attribute injects a context, a collection of variables, into every
-function. All these variables have predictable, non-anonymous types so you can
-write plain functions that take them as arguments.
+Every function within the `app` module has a `Context` structure as its
+first parameter. All the fields of these structures have predictable,
+non-anonymous types so you can write plain functions that take them as arguments.
The API reference specifies how these types are generated from the input. You
can also generate documentation for you binary crate (`cargo doc --bin <name>`);
in the documentation you'll find `Context` structs (e.g. `init::Context` and
-`idle::Context`) whose fields represent the variables injected into each
-function.
+`idle::Context`).
The example below shows the different types generates by the `app` attribute.
@@ -19,10 +18,10 @@ The example below shows the different types generates by the `app` attribute.
## `Send`
[`Send`] is a marker trait for "types that can be transferred across thread
-boundaries", according to its definition in `core`. In the context of RTFM the
+boundaries", according to its definition in `core`. In the context of RTIC the
`Send` trait is only required where it's possible to transfer a value between
-tasks that run at *different* priorities. This occurs in a few places: in message
-passing, in shared `static mut` resources and in the initialization of late
+tasks that run at *different* priorities. This occurs in a few places: in
+message passing, in shared resources and in the initialization of late
resources.
[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
@@ -31,7 +30,7 @@ The `app` attribute will enforce that `Send` is implemented where required so
you don't need to worry much about it. It's more important to know where you do
*not* need the `Send` trait: on types that are transferred between tasks that
run at the *same* priority. This occurs in two places: in message passing and in
-shared `static mut` resources.
+shared resources.
The example below shows where a type that doesn't implement `Send` can be used.
@@ -39,19 +38,34 @@ The example below shows where a type that doesn't implement `Send` can be used.
{{#include ../../../../examples/not-send.rs}}
```
+It's important to note that late initialization of resources is effectively a
+send operation where the initial value is sent from the background context,
+which has the lowest priority of `0`, to a task, which will run at a priority
+greater than or equal to `1`. Thus all late resources need to implement the
+`Send` trait, except for those exclusively accessed by `idle`, which runs at a
+priority of `0`.
+
+Sharing a resource with `init` can be used to implement late initialization, see
+example below. For that reason, resources shared with `init` must also implement
+the `Send` trait.
+
+``` rust
+{{#include ../../../../examples/shared-with-init.rs}}
+```
+
## `Sync`
Similarly, [`Sync`] is a marker trait for "types for which it is safe to share
references between threads", according to its definition in `core`. In the
-context of RTFM the `Sync` trait is only required where it's possible for two,
-or more, tasks that run at different priority to hold a shared reference to a
-resource. This only occurs with shared `static` resources.
+context of RTIC the `Sync` trait is only required where it's possible for two,
+or more, tasks that run at different priorities and may get a shared reference
+(`&-`) to a resource. This only occurs with shared access (`&-`) resources.
[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
The `app` attribute will enforce that `Sync` is implemented where required but
-it's important to know where the `Sync` bound is not required: in `static`
-resources shared between tasks that run at the *same* priority.
+it's important to know where the `Sync` bound is not required: shared access
+(`&-`) resources contended by tasks that run at the *same* priority.
The example below shows where a type that doesn't implement `Sync` can be used.
diff --git a/book/en/src/internals.md b/book/en/src/internals.md
index 0ef55e62..3b570248 100644
--- a/book/en/src/internals.md
+++ b/book/en/src/internals.md
@@ -1,6 +1,11 @@
# Under the hood
-This section describes the internals of the RTFM framework at a *high level*.
+This section describes the internals of the RTIC framework at a *high level*.
Low level details like the parsing and code generation done by the procedural
macro (`#[app]`) will not be explained here. The focus will be the analysis of
the user specification and the data structures used by the runtime.
+
+We highly suggest that you read the embedonomicon section on [concurrency]
+before you dive into this material.
+
+[concurrency]: https://github.com/rust-embedded/embedonomicon/pull/48
diff --git a/book/en/src/internals/access.md b/book/en/src/internals/access.md
new file mode 100644
index 00000000..3894470c
--- /dev/null
+++ b/book/en/src/internals/access.md
@@ -0,0 +1,158 @@
+# Access control
+
+One of the core foundations of RTIC is access control. Controlling which parts
+of the program can access which static variables is instrumental to enforcing
+memory safety.
+
+Static variables are used to share state between interrupt handlers, or between
+interrupts handlers and the bottom execution context, `main`. In normal Rust
+code it's hard to have fine grained control over which functions can access a
+static variable because static variables can be accessed from any function that
+resides in the same scope in which they are declared. Modules give some control
+over how a static variable can be accessed by they are not flexible enough.
+
+To achieve the fine-grained access control where tasks can only access the
+static variables (resources) that they have specified in their RTIC attribute
+the RTIC framework performs a source code level transformation. This
+transformation consists of placing the resources (static variables) specified by
+the user *inside* a module and the user code *outside* the module.
+This makes it impossible for the user code to refer to these static variables.
+
+Access to the resources is then given to each task using a `Resources` struct
+whose fields correspond to the resources the task has access to. There's one
+such struct per task and the `Resources` struct is initialized with either a
+unique reference (`&mut-`) to the static variables or with a resource proxy (see
+section on [critical sections](critical-sections.html)).
+
+The code below is an example of the kind of source level transformation that
+happens behind the scenes:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ static mut X: u64: 0;
+ static mut Y: bool: 0;
+
+ #[init(resources = [Y])]
+ fn init(c: init::Context) {
+ // .. user code ..
+ }
+
+ #[interrupt(binds = UART0, resources = [X])]
+ fn foo(c: foo::Context) {
+ // .. user code ..
+ }
+
+ #[interrupt(binds = UART1, resources = [X, Y])]
+ fn bar(c: bar::Context) {
+ // .. user code ..
+ }
+
+ // ..
+}
+```
+
+The framework produces codes like this:
+
+``` rust
+fn init(c: init::Context) {
+ // .. user code ..
+}
+
+fn foo(c: foo::Context) {
+ // .. user code ..
+}
+
+fn bar(c: bar::Context) {
+ // .. user code ..
+}
+
+// Public API
+pub mod init {
+ pub struct Context<'a> {
+ pub resources: Resources<'a>,
+ // ..
+ }
+
+ pub struct Resources<'a> {
+ pub Y: &'a mut bool,
+ }
+}
+
+pub mod foo {
+ pub struct Context<'a> {
+ pub resources: Resources<'a>,
+ // ..
+ }
+
+ pub struct Resources<'a> {
+ pub X: &'a mut u64,
+ }
+}
+
+pub mod bar {
+ pub struct Context<'a> {
+ pub resources: Resources<'a>,
+ // ..
+ }
+
+ pub struct Resources<'a> {
+ pub X: &'a mut u64,
+ pub Y: &'a mut bool,
+ }
+}
+
+/// Implementation details
+mod app {
+ // everything inside this module is hidden from user code
+
+ static mut X: u64 = 0;
+ static mut Y: bool = 0;
+
+ // the real entry point of the program
+ unsafe fn main() -> ! {
+ interrupt::disable();
+
+ // ..
+
+ // call into user code; pass references to the static variables
+ init(init::Context {
+ resources: init::Resources {
+ X: &mut X,
+ },
+ // ..
+ });
+
+ // ..
+
+ interrupt::enable();
+
+ // ..
+ }
+
+ // interrupt handler that `foo` binds to
+ #[no_mangle]
+ unsafe fn UART0() {
+ // call into user code; pass references to the static variables
+ foo(foo::Context {
+ resources: foo::Resources {
+ X: &mut X,
+ },
+ // ..
+ });
+ }
+
+ // interrupt handler that `bar` binds to
+ #[no_mangle]
+ unsafe fn UART1() {
+ // call into user code; pass references to the static variables
+ bar(bar::Context {
+ resources: bar::Resources {
+ X: &mut X,
+ Y: &mut Y,
+ },
+ // ..
+ });
+ }
+}
+```
diff --git a/book/en/src/internals/ceilings.md b/book/en/src/internals/ceilings.md
index 2c645a4d..07bd0add 100644
--- a/book/en/src/internals/ceilings.md
+++ b/book/en/src/internals/ceilings.md
@@ -1,3 +1,84 @@
# Ceiling analysis
-**TODO**
+A resource *priority ceiling*, or just *ceiling*, is the dynamic priority that
+any task must have to safely access the resource memory. Ceiling analysis is
+relatively simple but critical to the memory safety of RTIC applications.
+
+To compute the ceiling of a resource we must first collect a list of tasks that
+have access to the resource -- as the RTIC framework enforces access control to
+resources at compile time it also has access to this information at compile
+time. The ceiling of the resource is simply the highest logical priority among
+those tasks.
+
+`init` and `idle` are not proper tasks but they can access resources so they
+need to be considered in the ceiling analysis. `idle` is considered as a task
+that has a logical priority of `0` whereas `init` is completely omitted from the
+analysis -- the reason for that is that `init` never uses (or needs) critical
+sections to access static variables.
+
+In the previous section we showed that a shared resource may appear as a unique
+reference (`&mut-`) or behind a proxy depending on the task that has access to
+it. Which version is presented to the task depends on the task priority and the
+resource ceiling. If the task priority is the same as the resource ceiling then
+the task gets a unique reference (`&mut-`) to the resource memory, otherwise the
+task gets a proxy -- this also applies to `idle`. `init` is special: it always
+gets a unique reference (`&mut-`) to resources.
+
+An example to illustrate the ceiling analysis:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ struct Resources {
+ // accessed by `foo` (prio = 1) and `bar` (prio = 2)
+ // -> CEILING = 2
+ #[init(0)]
+ x: u64,
+
+ // accessed by `idle` (prio = 0)
+ // -> CEILING = 0
+ #[init(0)]
+ y: u64,
+ }
+
+ #[init(resources = [x])]
+ fn init(c: init::Context) {
+ // unique reference because this is `init`
+ let x: &mut u64 = c.resources.x;
+
+ // unique reference because this is `init`
+ let y: &mut u64 = c.resources.y;
+
+ // ..
+ }
+
+ // PRIORITY = 0
+ #[idle(resources = [y])]
+ fn idle(c: idle::Context) -> ! {
+ // unique reference because priority (0) == resource ceiling (0)
+ let y: &'static mut u64 = c.resources.y;
+
+ loop {
+ // ..
+ }
+ }
+
+ #[interrupt(binds = UART0, priority = 1, resources = [x])]
+ fn foo(c: foo::Context) {
+ // resource proxy because task priority (1) < resource ceiling (2)
+ let x: resources::x = c.resources.x;
+
+ // ..
+ }
+
+ #[interrupt(binds = UART1, priority = 2, resources = [x])]
+ fn bar(c: foo::Context) {
+ // unique reference because task priority (2) == resource ceiling (2)
+ let x: &mut u64 = c.resources.x;
+
+ // ..
+ }
+
+ // ..
+}
+```
diff --git a/book/en/src/internals/critical-sections.md b/book/en/src/internals/critical-sections.md
new file mode 100644
index 00000000..a064ad09
--- /dev/null
+++ b/book/en/src/internals/critical-sections.md
@@ -0,0 +1,523 @@
+# Critical sections
+
+When a resource (static variable) is shared between two, or more, tasks that run
+at different priorities some form of mutual exclusion is required to mutate the
+memory in a data race free manner. In RTIC we use priority-based critical
+sections to guarantee mutual exclusion (see the [Immediate Ceiling Priority
+Protocol][icpp]).
+
+[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol
+
+The critical section consists of temporarily raising the *dynamic* priority of
+the task. While a task is within this critical section all the other tasks that
+may request the resource are *not allowed to start*.
+
+How high must the dynamic priority be to ensure mutual exclusion on a particular
+resource? The [ceiling analysis](ceilings.html) is in charge of
+answering that question and will be discussed in the next section. This section
+will focus on the implementation of the critical section.
+
+## Resource proxy
+
+For simplicity, let's look at a resource shared by two tasks that run at
+different priorities. Clearly one of the task can preempt the other; to prevent
+a data race the *lower priority* task must use a critical section when it needs
+to modify the shared memory. On the other hand, the higher priority task can
+directly modify the shared memory because it can't be preempted by the lower
+priority task. To enforce the use of a critical section on the lower priority
+task we give it a *resource proxy*, whereas we give a unique reference
+(`&mut-`) to the higher priority task.
+
+The example below shows the different types handed out to each task:
+
+``` rust
+#[rtic::app(device = ..)]
+mut app {
+ struct Resources {
+ #[init(0)]
+ x: u64,
+ }
+
+ #[interrupt(binds = UART0, priority = 1, resources = [x])]
+ fn foo(c: foo::Context) {
+ // resource proxy
+ let mut x: resources::x = c.resources.x;
+
+ x.lock(|x: &mut u64| {
+ // critical section
+ *x += 1
+ });
+ }
+
+ #[interrupt(binds = UART1, priority = 2, resources = [x])]
+ fn bar(c: bar::Context) {
+ let mut x: &mut u64 = c.resources.x;
+
+ *x += 1;
+ }
+
+ // ..
+}
+```
+
+Now let's see how these types are created by the framework.
+
+``` rust
+fn foo(c: foo::Context) {
+ // .. user code ..
+}
+
+fn bar(c: bar::Context) {
+ // .. user code ..
+}
+
+pub mod resources {
+ pub struct x {
+ // ..
+ }
+}
+
+pub mod foo {
+ pub struct Resources {
+ pub x: resources::x,
+ }
+
+ pub struct Context {
+ pub resources: Resources,
+ // ..
+ }
+}
+
+pub mod bar {
+ pub struct Resources<'a> {
+ pub x: &'a mut u64,
+ }
+
+ pub struct Context {
+ pub resources: Resources,
+ // ..
+ }
+}
+
+mod app {
+ static mut x: u64 = 0;
+
+ impl rtic::Mutex for resources::x {
+ type T = u64;
+
+ fn lock<R>(&mut self, f: impl FnOnce(&mut u64) -> R) -> R {
+ // we'll check this in detail later
+ }
+ }
+
+ #[no_mangle]
+ unsafe fn UART0() {
+ foo(foo::Context {
+ resources: foo::Resources {
+ x: resources::x::new(/* .. */),
+ },
+ // ..
+ })
+ }
+
+ #[no_mangle]
+ unsafe fn UART1() {
+ bar(bar::Context {
+ resources: bar::Resources {
+ x: &mut x,
+ },
+ // ..
+ })
+ }
+}
+```
+
+## `lock`
+
+Let's now zoom into the critical section itself. In this example, we have to
+raise the dynamic priority to at least `2` to prevent a data race. On the
+Cortex-M architecture the dynamic priority can be changed by writing to the
+`BASEPRI` register.
+
+The semantics of the `BASEPRI` register are as follows:
+
+- Writing a value of `0` to `BASEPRI` disables its functionality.
+- Writing a non-zero value to `BASEPRI` changes the priority level required for
+ interrupt preemption. However, this only has an effect when the written value
+ is *lower* than the priority level of current execution context, but note that
+ a lower hardware priority level means higher logical priority
+
+Thus the dynamic priority at any point in time can be computed as
+
+``` rust
+dynamic_priority = max(hw2logical(BASEPRI), hw2logical(static_priority))
+```
+
+Where `static_priority` is the priority programmed in the NVIC for the current
+interrupt, or a logical `0` when the current context is `idle`.
+
+In this particular example we could implement the critical section as follows:
+
+> **NOTE:** this is a simplified implementation
+
+``` rust
+impl rtic::Mutex for resources::x {
+ type T = u64;
+
+ fn lock<R, F>(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut u64) -> R,
+ {
+ unsafe {
+ // start of critical section: raise dynamic priority to `2`
+ asm!("msr BASEPRI, 192" : : : "memory" : "volatile");
+
+ // run user code within the critical section
+ let r = f(&mut x);
+
+ // end of critical section: restore dynamic priority to its static value (`1`)
+ asm!("msr BASEPRI, 0" : : : "memory" : "volatile");
+
+ r
+ }
+ }
+}
+```
+
+Here it's important to use the `"memory"` clobber in the `asm!` block. It
+prevents the compiler from reordering memory operations across it. This is
+important because accessing the variable `x` outside the critical section would
+result in a data race.
+
+It's important to note that the signature of the `lock` method prevents nesting
+calls to it. This is required for memory safety, as nested calls would produce
+multiple unique references (`&mut-`) to `x` breaking Rust aliasing rules. See
+below:
+
+``` rust
+#[interrupt(binds = UART0, priority = 1, resources = [x])]
+fn foo(c: foo::Context) {
+ // resource proxy
+ let mut res: resources::x = c.resources.x;
+
+ res.lock(|x: &mut u64| {
+ res.lock(|alias: &mut u64| {
+ //~^ error: `res` has already been uniquely borrowed (`&mut-`)
+ // ..
+ });
+ });
+}
+```
+
+## Nesting
+
+Nesting calls to `lock` on the *same* resource must be rejected by the compiler
+for memory safety but nesting `lock` calls on *different* resources is a valid
+operation. In that case we want to make sure that nesting critical sections
+never results in lowering the dynamic priority, as that would be unsound, and we
+also want to optimize the number of writes to the `BASEPRI` register and
+compiler fences. To that end we'll track the dynamic priority of the task using
+a stack variable and use that to decide whether to write to `BASEPRI` or not. In
+practice, the stack variable will be optimized away by the compiler but it still
+provides extra information to the compiler.
+
+Consider this program:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ struct Resources {
+ #[init(0)]
+ x: u64,
+ #[init(0)]
+ y: u64,
+ }
+
+ #[init]
+ fn init() {
+ rtic::pend(Interrupt::UART0);
+ }
+
+ #[interrupt(binds = UART0, priority = 1, resources = [x, y])]
+ fn foo(c: foo::Context) {
+ let mut x = c.resources.x;
+ let mut y = c.resources.y;
+
+ y.lock(|y| {
+ *y += 1;
+
+ *x.lock(|x| {
+ x += 1;
+ });
+
+ *y += 1;
+ });
+
+ // mid-point
+
+ x.lock(|x| {
+ *x += 1;
+
+ y.lock(|y| {
+ *y += 1;
+ });
+
+ *x += 1;
+ })
+ }
+
+ #[interrupt(binds = UART1, priority = 2, resources = [x])]
+ fn bar(c: foo::Context) {
+ // ..
+ }
+
+ #[interrupt(binds = UART2, priority = 3, resources = [y])]
+ fn baz(c: foo::Context) {
+ // ..
+ }
+
+ // ..
+}
+```
+
+The code generated by the framework looks like this:
+
+``` rust
+// omitted: user code
+
+pub mod resources {
+ pub struct x<'a> {
+ priority: &'a Cell<u8>,
+ }
+
+ impl<'a> x<'a> {
+ pub unsafe fn new(priority: &'a Cell<u8>) -> Self {
+ x { priority }
+ }
+
+ pub unsafe fn priority(&self) -> &Cell<u8> {
+ self.priority
+ }
+ }
+
+ // repeat for `y`
+}
+
+pub mod foo {
+ pub struct Context {
+ pub resources: Resources,
+ // ..
+ }
+
+ pub struct Resources<'a> {
+ pub x: resources::x<'a>,
+ pub y: resources::y<'a>,
+ }
+}
+
+mod app {
+ use cortex_m::register::basepri;
+
+ #[no_mangle]
+ unsafe fn UART1() {
+ // the static priority of this interrupt (as specified by the user)
+ const PRIORITY: u8 = 2;
+
+ // take a snashot of the BASEPRI
+ let initial = basepri::read();
+
+ let priority = Cell::new(PRIORITY);
+ bar(bar::Context {
+ resources: bar::Resources::new(&priority),
+ // ..
+ });
+
+ // roll back the BASEPRI to the snapshot value we took before
+ basepri::write(initial); // same as the `asm!` block we saw before
+ }
+
+ // similarly for `UART0` / `foo` and `UART2` / `baz`
+
+ impl<'a> rtic::Mutex for resources::x<'a> {
+ type T = u64;
+
+ fn lock<R>(&mut self, f: impl FnOnce(&mut u64) -> R) -> R {
+ unsafe {
+ // the priority ceiling of this resource
+ const CEILING: u8 = 2;
+
+ let current = self.priority().get();
+ if current < CEILING {
+ // raise dynamic priority
+ self.priority().set(CEILING);
+ basepri::write(logical2hw(CEILING));
+
+ let r = f(&mut y);
+
+ // restore dynamic priority
+ basepri::write(logical2hw(current));
+ self.priority().set(current);
+
+ r
+ } else {
+ // dynamic priority is high enough
+ f(&mut y)
+ }
+ }
+ }
+ }
+
+ // repeat for resource `y`
+}
+```
+
+At the end the compiler will optimize the function `foo` into something like
+this:
+
+``` rust
+fn foo(c: foo::Context) {
+ // NOTE: BASEPRI contains the value `0` (its reset value) at this point
+
+ // raise dynamic priority to `3`
+ unsafe { basepri::write(160) }
+
+ // the two operations on `y` are merged into one
+ y += 2;
+
+ // BASEPRI is not modified to access `x` because the dynamic priority is high enough
+ x += 1;
+
+ // lower (restore) the dynamic priority to `1`
+ unsafe { basepri::write(224) }
+
+ // mid-point
+
+ // raise dynamic priority to `2`
+ unsafe { basepri::write(192) }
+
+ x += 1;
+
+ // raise dynamic priority to `3`
+ unsafe { basepri::write(160) }
+
+ y += 1;
+
+ // lower (restore) the dynamic priority to `2`
+ unsafe { basepri::write(192) }
+
+ // NOTE: it would be sound to merge this operation on `x` with the previous one but
+ // compiler fences are coarse grained and prevent such optimization
+ x += 1;
+
+ // lower (restore) the dynamic priority to `1`
+ unsafe { basepri::write(224) }
+
+ // NOTE: BASEPRI contains the value `224` at this point
+ // the UART0 handler will restore the value to `0` before returning
+}
+```
+
+## The BASEPRI invariant
+
+An invariant that the RTIC framework has to preserve is that the value of the
+BASEPRI at the start of an *interrupt* handler must be the same value it has
+when the interrupt handler returns. BASEPRI may change during the execution of
+the interrupt handler but running an interrupt handler from start to finish
+should not result in an observable change of BASEPRI.
+
+This invariant needs to be preserved to avoid raising the dynamic priority of a
+handler through preemption. This is best observed in the following example:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ struct Resources {
+ #[init(0)]
+ x: u64,
+ }
+
+ #[init]
+ fn init() {
+ // `foo` will run right after `init` returns
+ rtic::pend(Interrupt::UART0);
+ }
+
+ #[task(binds = UART0, priority = 1)]
+ fn foo() {
+ // BASEPRI is `0` at this point; the dynamic priority is currently `1`
+
+ // `bar` will preempt `foo` at this point
+ rtic::pend(Interrupt::UART1);
+
+ // BASEPRI is `192` at this point (due to a bug); the dynamic priority is now `2`
+ // this function returns to `idle`
+ }
+
+ #[task(binds = UART1, priority = 2, resources = [x])]
+ fn bar() {
+ // BASEPRI is `0` (dynamic priority = 2)
+
+ x.lock(|x| {
+ // BASEPRI is raised to `160` (dynamic priority = 3)
+
+ // ..
+ });
+
+ // BASEPRI is restored to `192` (dynamic priority = 2)
+ }
+
+ #[idle]
+ fn idle() -> ! {
+ // BASEPRI is `192` (due to a bug); dynamic priority = 2
+
+ // this has no effect due to the BASEPRI value
+ // the task `foo` will never be executed again
+ rtic::pend(Interrupt::UART0);
+
+ loop {
+ // ..
+ }
+ }
+
+ #[task(binds = UART2, priority = 3, resources = [x])]
+ fn baz() {
+ // ..
+ }
+
+}
+```
+
+IMPORTANT: let's say we *forget* to roll back `BASEPRI` in `UART1` -- this would
+be a bug in the RTIC code generator.
+
+``` rust
+// code generated by RTIC
+
+mod app {
+ // ..
+
+ #[no_mangle]
+ unsafe fn UART1() {
+ // the static priority of this interrupt (as specified by the user)
+ const PRIORITY: u8 = 2;
+
+ // take a snashot of the BASEPRI
+ let initial = basepri::read();
+
+ let priority = Cell::new(PRIORITY);
+ bar(bar::Context {
+ resources: bar::Resources::new(&priority),
+ // ..
+ });
+
+ // BUG: FORGOT to roll back the BASEPRI to the snapshot value we took before
+ basepri::write(initial);
+ }
+}
+```
+
+The consequence is that `idle` will run at a dynamic priority of `2` and in fact
+the system will never again run at a dynamic priority lower than `2`. This
+doesn't compromise the memory safety of the program but affects task scheduling:
+in this particular case tasks with a priority of `1` will never get a chance to
+run.
diff --git a/book/en/src/internals/interrupt-configuration.md b/book/en/src/internals/interrupt-configuration.md
new file mode 100644
index 00000000..7aec9c9f
--- /dev/null
+++ b/book/en/src/internals/interrupt-configuration.md
@@ -0,0 +1,73 @@
+# Interrupt configuration
+
+Interrupts are core to the operation of RTIC applications. Correctly setting
+interrupt priorities and ensuring they remain fixed at runtime is a requisite
+for the memory safety of the application.
+
+The RTIC framework exposes interrupt priorities as something that is declared at
+compile time. However, this static configuration must be programmed into the
+relevant registers during the initialization of the application. The interrupt
+configuration is done before the `init` function runs.
+
+This example gives you an idea of the code that the RTIC framework runs:
+
+``` rust
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(c: init::Context) {
+ // .. user code ..
+ }
+
+ #[idle]
+ fn idle(c: idle::Context) -> ! {
+ // .. user code ..
+ }
+
+ #[interrupt(binds = UART0, priority = 2)]
+ fn foo(c: foo::Context) {
+ // .. user code ..
+ }
+}
+```
+
+The framework generates an entry point that looks like this:
+
+``` rust
+// the real entry point of the program
+#[no_mangle]
+unsafe fn main() -> ! {
+ // transforms a logical priority into a hardware / NVIC priority
+ fn logical2hw(priority: u8) -> u8 {
+ use lm3s6965::NVIC_PRIO_BITS;
+
+ // the NVIC encodes priority in the higher bits of a bit
+ // also a bigger numbers means lower priority
+ ((1 << NVIC_PRIORITY_BITS) - priority) << (8 - NVIC_PRIO_BITS)
+ }
+
+ cortex_m::interrupt::disable();
+
+ let mut core = cortex_m::Peripheral::steal();
+
+ core.NVIC.enable(Interrupt::UART0);
+
+ // value specified by the user
+ let uart0_prio = 2;
+
+ // check at compile time that the specified priority is within the supported range
+ let _ = [(); (1 << NVIC_PRIORITY_BITS) - (uart0_prio as usize)];
+
+ core.NVIC.set_priority(Interrupt::UART0, logical2hw(uart0_prio));
+
+ // call into user code
+ init(/* .. */);
+
+ // ..
+
+ cortex_m::interrupt::enable();
+
+ // call into user code
+ idle(/* .. */)
+}
+```
diff --git a/book/en/src/internals/late-resources.md b/book/en/src/internals/late-resources.md
new file mode 100644
index 00000000..f3a0b0ae
--- /dev/null
+++ b/book/en/src/internals/late-resources.md
@@ -0,0 +1,116 @@
+# Late resources
+
+Some resources are initialized at runtime after the `init` function returns.
+It's important that these resources (static variables) are fully initialized
+before tasks are allowed to run, that is they must be initialized while
+interrupts are disabled.
+
+The example below shows the kind of code that the framework generates to
+initialize late resources.
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ struct Resources {
+ x: Thing,
+ }
+
+ #[init]
+ fn init() -> init::LateResources {
+ // ..
+
+ init::LateResources {
+ x: Thing::new(..),
+ }
+ }
+
+ #[task(binds = UART0, resources = [x])]
+ fn foo(c: foo::Context) {
+ let x: &mut Thing = c.resources.x;
+
+ x.frob();
+
+ // ..
+ }
+
+ // ..
+}
+```
+
+The code generated by the framework looks like this:
+
+``` rust
+fn init(c: init::Context) -> init::LateResources {
+ // .. user code ..
+}
+
+fn foo(c: foo::Context) {
+ // .. user code ..
+}
+
+// Public API
+pub mod init {
+ pub struct LateResources {
+ pub x: Thing,
+ }
+
+ // ..
+}
+
+pub mod foo {
+ pub struct Resources<'a> {
+ pub x: &'a mut Thing,
+ }
+
+ pub struct Context<'a> {
+ pub resources: Resources<'a>,
+ // ..
+ }
+}
+
+/// Implementation details
+mod app {
+ // uninitialized static
+ static mut x: MaybeUninit<Thing> = MaybeUninit::uninit();
+
+ #[no_mangle]
+ unsafe fn main() -> ! {
+ cortex_m::interrupt::disable();
+
+ // ..
+
+ let late = init(..);
+
+ // initialization of late resources
+ x.as_mut_ptr().write(late.x);
+
+ cortex_m::interrupt::enable(); //~ compiler fence
+
+ // exceptions, interrupts and tasks can preempt `main` at this point
+
+ idle(..)
+ }
+
+ #[no_mangle]
+ unsafe fn UART0() {
+ foo(foo::Context {
+ resources: foo::Resources {
+ // `x` has been initialized at this point
+ x: &mut *x.as_mut_ptr(),
+ },
+ // ..
+ })
+ }
+}
+```
+
+An important detail here is that `interrupt::enable` behaves like a *compiler
+fence*, which prevents the compiler from reordering the write to `X` to *after*
+`interrupt::enable`. If the compiler were to do that kind of reordering there
+would be a data race between that write and whatever operation `foo` performs on
+`X`.
+
+Architectures with more complex instruction pipelines may need a memory barrier
+(`atomic::fence`) instead of a compiler fence to fully flush the write operation
+before interrupts are re-enabled. The ARM Cortex-M architecture doesn't need a
+memory barrier in single-core context.
diff --git a/book/en/src/internals/non-reentrancy.md b/book/en/src/internals/non-reentrancy.md
new file mode 100644
index 00000000..17b34d0c
--- /dev/null
+++ b/book/en/src/internals/non-reentrancy.md
@@ -0,0 +1,80 @@
+# Non-reentrancy
+
+In RTIC, tasks handlers are *not* reentrant. Reentering a task handler can break
+Rust aliasing rules and lead to *undefined behavior*. A task handler can be
+reentered in one of two ways: in software or by hardware.
+
+## In software
+
+To reenter a task handler in software its underlying interrupt handler must be
+invoked using FFI (see example below). FFI requires `unsafe` code so end users
+are discouraged from directly invoking an interrupt handler.
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ #[init]
+ fn init(c: init::Context) { .. }
+
+ #[interrupt(binds = UART0)]
+ fn foo(c: foo::Context) {
+ static mut X: u64 = 0;
+
+ let x: &mut u64 = X;
+
+ // ..
+
+ //~ `bar` can preempt `foo` at this point
+
+ // ..
+ }
+
+ #[interrupt(binds = UART1, priority = 2)]
+ fn bar(c: foo::Context) {
+ extern "C" {
+ fn UART0();
+ }
+
+ // this interrupt handler will invoke task handler `foo` resulting
+ // in aliasing of the static variable `X`
+ unsafe { UART0() }
+ }
+}
+```
+
+The RTIC framework must generate the interrupt handler code that calls the user
+defined task handlers. We are careful in making these handlers impossible to
+call from user code.
+
+The above example expands into:
+
+``` rust
+fn foo(c: foo::Context) {
+ // .. user code ..
+}
+
+fn bar(c: bar::Context) {
+ // .. user code ..
+}
+
+mod app {
+ // everything in this block is not visible to user code
+
+ #[no_mangle]
+ unsafe fn USART0() {
+ foo(..);
+ }
+
+ #[no_mangle]
+ unsafe fn USART1() {
+ bar(..);
+ }
+}
+```
+
+## By hardware
+
+A task handler can also be reentered without software intervention. This can
+occur if the same handler is assigned to two or more interrupts in the vector
+table but there's no syntax for this kind of configuration in the RTIC
+framework.
diff --git a/book/en/src/internals/tasks.md b/book/en/src/internals/tasks.md
index 85f783fb..a533dc0c 100644
--- a/book/en/src/internals/tasks.md
+++ b/book/en/src/internals/tasks.md
@@ -1,3 +1,397 @@
-# Task dispatcher
+# Software tasks
-**TODO**
+RTIC supports software tasks and hardware tasks. Each hardware task is bound to
+a different interrupt handler. On the other hand, several software tasks may be
+dispatched by the same interrupt handler -- this is done to minimize the number
+of interrupts handlers used by the framework.
+
+The framework groups `spawn`-able tasks by priority level and generates one
+*task dispatcher* per priority level. Each task dispatcher runs on a different
+interrupt handler and the priority of said interrupt handler is set to match the
+priority level of the tasks managed by the dispatcher.
+
+Each task dispatcher keeps a *queue* of tasks which are *ready* to execute; this
+queue is referred to as the *ready queue*. Spawning a software task consists of
+adding an entry to this queue and pending the interrupt that runs the
+corresponding task dispatcher. Each entry in this queue contains a tag (`enum`)
+that identifies the task to execute and a *pointer* to the message passed to the
+task.
+
+The ready queue is a SPSC (Single Producer Single Consumer) lock-free queue. The
+task dispatcher owns the consumer endpoint of the queue; the producer endpoint
+is treated as a resource contended by the tasks that can `spawn` other tasks.
+
+## The task dispatcher
+
+Let's first take a look the code generated by the framework to dispatch tasks.
+Consider this example:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ // ..
+
+ #[interrupt(binds = UART0, priority = 2, spawn = [bar, baz])]
+ fn foo(c: foo::Context) {
+ foo.spawn.bar().ok();
+
+ foo.spawn.baz(42).ok();
+ }
+
+ #[task(capacity = 2, priority = 1)]
+ fn bar(c: bar::Context) {
+ // ..
+ }
+
+ #[task(capacity = 2, priority = 1, resources = [X])]
+ fn baz(c: baz::Context, input: i32) {
+ // ..
+ }
+
+ extern "C" {
+ fn UART1();
+ }
+}
+```
+
+The framework produces the following task dispatcher which consists of an
+interrupt handler and a ready queue:
+
+``` rust
+fn bar(c: bar::Context) {
+ // .. user code ..
+}
+
+mod app {
+ use heapless::spsc::Queue;
+ use cortex_m::register::basepri;
+
+ struct Ready<T> {
+ task: T,
+ // ..
+ }
+
+ /// `spawn`-able tasks that run at priority level `1`
+ enum T1 {
+ bar,
+ baz,
+ }
+
+ // ready queue of the task dispatcher
+ // `U4` is a type-level integer that represents the capacity of this queue
+ static mut RQ1: Queue<Ready<T1>, U4> = Queue::new();
+
+ // interrupt handler chosen to dispatch tasks at priority `1`
+ #[no_mangle]
+ unsafe UART1() {
+ // the priority of this interrupt handler
+ const PRIORITY: u8 = 1;
+
+ let snapshot = basepri::read();
+
+ while let Some(ready) = RQ1.split().1.dequeue() {
+ match ready.task {
+ T1::bar => {
+ // **NOTE** simplified implementation
+
+ // used to track the dynamic priority
+ let priority = Cell::new(PRIORITY);
+
+ // call into user code
+ bar(bar::Context::new(&priority));
+ }
+
+ T1::baz => {
+ // we'll look at `baz` later
+ }
+ }
+ }
+
+ // BASEPRI invariant
+ basepri::write(snapshot);
+ }
+}
+```
+
+## Spawning a task
+
+The `spawn` API is exposed to the user as the methods of a `Spawn` struct.
+There's one `Spawn` struct per task.
+
+The `Spawn` code generated by the framework for the previous example looks like
+this:
+
+``` rust
+mod foo {
+ // ..
+
+ pub struct Context<'a> {
+ pub spawn: Spawn<'a>,
+ // ..
+ }
+
+ pub struct Spawn<'a> {
+ // tracks the dynamic priority of the task
+ priority: &'a Cell<u8>,
+ }
+
+ impl<'a> Spawn<'a> {
+ // `unsafe` and hidden because we don't want the user to tamper with it
+ #[doc(hidden)]
+ pub unsafe fn priority(&self) -> &Cell<u8> {
+ self.priority
+ }
+ }
+}
+
+mod app {
+ // ..
+
+ // Priority ceiling for the producer endpoint of the `RQ1`
+ const RQ1_CEILING: u8 = 2;
+
+ // used to track how many more `bar` messages can be enqueued
+ // `U2` is the capacity of the `bar` task; a max of two instances can be queued
+ // this queue is filled by the framework before `init` runs
+ static mut bar_FQ: Queue<(), U2> = Queue::new();
+
+ // Priority ceiling for the consumer endpoint of `bar_FQ`
+ const bar_FQ_CEILING: u8 = 2;
+
+ // a priority-based critical section
+ //
+ // this run the given closure `f` at a dynamic priority of at least
+ // `ceiling`
+ fn lock(priority: &Cell<u8>, ceiling: u8, f: impl FnOnce()) {
+ // ..
+ }
+
+ impl<'a> foo::Spawn<'a> {
+ /// Spawns the `bar` task
+ pub fn bar(&self) -> Result<(), ()> {
+ unsafe {
+ match lock(self.priority(), bar_FQ_CEILING, || {
+ bar_FQ.split().1.dequeue()
+ }) {
+ Some(()) => {
+ lock(self.priority(), RQ1_CEILING, || {
+ // put the taks in the ready queue
+ RQ1.split().1.enqueue_unchecked(Ready {
+ task: T1::bar,
+ // ..
+ })
+ });
+
+ // pend the interrupt that runs the task dispatcher
+ rtic::pend(Interrupt::UART0);
+ }
+
+ None => {
+ // maximum capacity reached; spawn failed
+ Err(())
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Using `bar_FQ` to limit the number of `bar` tasks that can be spawned may seem
+like an artificial limitation but it will make more sense when we talk about
+task capacities.
+
+## Messages
+
+We have omitted how message passing actually works so let's revisit the `spawn`
+implementation but this time for task `baz` which receives a `u64` message.
+
+``` rust
+fn baz(c: baz::Context, input: u64) {
+ // .. user code ..
+}
+
+mod app {
+ // ..
+
+ // Now we show the full contents of the `Ready` struct
+ struct Ready {
+ task: Task,
+ // message index; used to index the `INPUTS` buffer
+ index: u8,
+ }
+
+ // memory reserved to hold messages passed to `baz`
+ static mut baz_INPUTS: [MaybeUninit<u64>; 2] =
+ [MaybeUninit::uninit(), MaybeUninit::uninit()];
+
+ // the free queue: used to track free slots in the `baz_INPUTS` array
+ // this queue is initialized with values `0` and `1` before `init` is executed
+ static mut baz_FQ: Queue<u8, U2> = Queue::new();
+
+ // Priority ceiling for the consumer endpoint of `baz_FQ`
+ const baz_FQ_CEILING: u8 = 2;
+
+ impl<'a> foo::Spawn<'a> {
+ /// Spawns the `baz` task
+ pub fn baz(&self, message: u64) -> Result<(), u64> {
+ unsafe {
+ match lock(self.priority(), baz_FQ_CEILING, || {
+ baz_FQ.split().1.dequeue()
+ }) {
+ Some(index) => {
+ // NOTE: `index` is an ownining pointer into this buffer
+ baz_INPUTS[index as usize].write(message);
+
+ lock(self.priority(), RQ1_CEILING, || {
+ // put the task in the ready queue
+ RQ1.split().1.enqueue_unchecked(Ready {
+ task: T1::baz,
+ index,
+ });
+ });
+
+ // pend the interrupt that runs the task dispatcher
+ rtic::pend(Interrupt::UART0);
+ }
+
+ None => {
+ // maximum capacity reached; spawn failed
+ Err(message)
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+And now let's look at the real implementation of the task dispatcher:
+
+``` rust
+mod app {
+ // ..
+
+ #[no_mangle]
+ unsafe UART1() {
+ const PRIORITY: u8 = 1;
+
+ let snapshot = basepri::read();
+
+ while let Some(ready) = RQ1.split().1.dequeue() {
+ match ready.task {
+ Task::baz => {
+ // NOTE: `index` is an ownining pointer into this buffer
+ let input = baz_INPUTS[ready.index as usize].read();
+
+ // the message has been read out so we can return the slot
+ // back to the free queue
+ // (the task dispatcher has exclusive access to the producer
+ // endpoint of this queue)
+ baz_FQ.split().0.enqueue_unchecked(ready.index);
+
+ let priority = Cell::new(PRIORITY);
+ baz(baz::Context::new(&priority), input)
+ }
+
+ Task::bar => {
+ // looks just like the `baz` branch
+ }
+
+ }
+ }
+
+ // BASEPRI invariant
+ basepri::write(snapshot);
+ }
+}
+```
+
+`INPUTS` plus `FQ`, the free queue, is effectively a memory pool. However,
+instead of using the usual *free list* (linked list) to track empty slots
+in the `INPUTS` buffer we use a SPSC queue; this lets us reduce the number of
+critical sections. In fact, thanks to this choice the task dispatching code is
+lock-free.
+
+## Queue capacity
+
+The RTIC framework uses several queues like ready queues and free queues. When
+the free queue is empty trying to `spawn` a task results in an error; this
+condition is checked at runtime. Not all the operations performed by the
+framework on these queues check if the queue is empty / full. For example,
+returning an slot to the free queue (see the task dispatcher) is unchecked
+because there's a fixed number of such slots circulating in the system that's
+equal to the capacity of the free queue. Similarly, adding an entry to the ready
+queue (see `Spawn`) is unchecked because of the queue capacity chosen by the
+framework.
+
+Users can specify the capacity of software tasks; this capacity is the maximum
+number of messages one can post to said task from a higher priority task before
+`spawn` returns an error. This user-specified capacity is the capacity of the
+free queue of the task (e.g. `foo_FQ`) and also the size of the array that holds
+the inputs to the task (e.g. `foo_INPUTS`).
+
+The capacity of the ready queue (e.g. `RQ1`) is chosen to be the *sum* of the
+capacities of all the different tasks managed by the dispatcher; this sum is
+also the number of messages the queue will hold in the worst case scenario of
+all possible messages being posted before the task dispatcher gets a chance to
+run. For this reason, getting a slot from the free queue in any `spawn`
+operation implies that the ready queue is not yet full so inserting an entry
+into the ready queue can omit the "is it full?" check.
+
+In our running example the task `bar` takes no input so we could have omitted
+both `bar_INPUTS` and `bar_FQ` and let the user post an unbounded number of
+messages to this task, but if we did that it would have not be possible to pick
+a capacity for `RQ1` that lets us omit the "is it full?" check when spawning a
+`baz` task. In the section about the [timer queue](timer-queue.html) we'll see
+how the free queue is used by tasks that have no inputs.
+
+## Ceiling analysis
+
+The queues internally used by the `spawn` API are treated like normal resources
+and included in the ceiling analysis. It's important to note that these are SPSC
+queues and that only one of the endpoints is behind a resource; the other
+endpoint is owned by a task dispatcher.
+
+Consider the following example:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ #[idle(spawn = [foo, bar])]
+ fn idle(c: idle::Context) -> ! {
+ // ..
+ }
+
+ #[task]
+ fn foo(c: foo::Context) {
+ // ..
+ }
+
+ #[task]
+ fn bar(c: bar::Context) {
+ // ..
+ }
+
+ #[task(priority = 2, spawn = [foo])]
+ fn baz(c: baz::Context) {
+ // ..
+ }
+
+ #[task(priority = 3, spawn = [bar])]
+ fn quux(c: quux::Context) {
+ // ..
+ }
+}
+```
+
+This is how the ceiling analysis would go:
+
+- `idle` (prio = 0) and `baz` (prio = 2) contend for the consumer endpoint of
+ `foo_FQ`; this leads to a priority ceiling of `2`.
+
+- `idle` (prio = 0) and `quux` (prio = 3) contend for the consumer endpoint of
+ `bar_FQ`; this leads to a priority ceiling of `3`.
+
+- `idle` (prio = 0), `baz` (prio = 2) and `quux` (prio = 3) all contend for the
+ producer endpoint of `RQ1`; this leads to a priority ceiling of `3`
diff --git a/book/en/src/internals/timer-queue.md b/book/en/src/internals/timer-queue.md
index 70592852..fcd345c5 100644
--- a/book/en/src/internals/timer-queue.md
+++ b/book/en/src/internals/timer-queue.md
@@ -1,3 +1,368 @@
# Timer queue
-**TODO**
+The timer queue functionality lets the user schedule tasks to run at some time
+in the future. Unsurprisingly, this feature is also implemented using a queue:
+a priority queue where the scheduled tasks are kept sorted by earliest scheduled
+time. This feature requires a timer capable of setting up timeout interrupts.
+The timer is used to trigger an interrupt when the scheduled time of a task is
+up; at that point the task is removed from the timer queue and moved into the
+appropriate ready queue.
+
+Let's see how this in implemented in code. Consider the following program:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ // ..
+
+ #[task(capacity = 2, schedule = [foo])]
+ fn foo(c: foo::Context, x: u32) {
+ // schedule this task to run again in 1M cycles
+ c.schedule.foo(c.scheduled + Duration::cycles(1_000_000), x + 1).ok();
+ }
+
+ extern "C" {
+ fn UART0();
+ }
+}
+```
+
+## `schedule`
+
+Let's first look at the `schedule` API.
+
+``` rust
+mod foo {
+ pub struct Schedule<'a> {
+ priority: &'a Cell<u8>,
+ }
+
+ impl<'a> Schedule<'a> {
+ // unsafe and hidden because we don't want the user to tamper with this
+ #[doc(hidden)]
+ pub unsafe fn priority(&self) -> &Cell<u8> {
+ self.priority
+ }
+ }
+}
+
+mod app {
+ type Instant = <path::to::user::monotonic::timer as rtic::Monotonic>::Instant;
+
+ // all tasks that can be `schedule`-d
+ enum T {
+ foo,
+ }
+
+ struct NotReady {
+ index: u8,
+ instant: Instant,
+ task: T,
+ }
+
+ // The timer queue is a binary (min) heap of `NotReady` tasks
+ static mut TQ: TimerQueue<U2> = ..;
+ const TQ_CEILING: u8 = 1;
+
+ static mut foo_FQ: Queue<u8, U2> = Queue::new();
+ const foo_FQ_CEILING: u8 = 1;
+
+ static mut foo_INPUTS: [MaybeUninit<u32>; 2] =
+ [MaybeUninit::uninit(), MaybeUninit::uninit()];
+
+ static mut foo_INSTANTS: [MaybeUninit<Instant>; 2] =
+ [MaybeUninit::uninit(), MaybeUninit::uninit()];
+
+ impl<'a> foo::Schedule<'a> {
+ fn foo(&self, instant: Instant, input: u32) -> Result<(), u32> {
+ unsafe {
+ let priority = self.priority();
+ if let Some(index) = lock(priority, foo_FQ_CEILING, || {
+ foo_FQ.split().1.dequeue()
+ }) {
+ // `index` is an owning pointer into these buffers
+ foo_INSTANTS[index as usize].write(instant);
+ foo_INPUTS[index as usize].write(input);
+
+ let nr = NotReady {
+ index,
+ instant,
+ task: T::foo,
+ };
+
+ lock(priority, TQ_CEILING, || {
+ TQ.enqueue_unchecked(nr);
+ });
+ } else {
+ // No space left to store the input / instant
+ Err(input)
+ }
+ }
+ }
+ }
+}
+```
+
+This looks very similar to the `Spawn` implementation. In fact, the same
+`INPUTS` buffer and free queue (`FQ`) are shared between the `spawn` and
+`schedule` APIs. The main difference between the two is that `schedule` also
+stores the `Instant` at which the task was scheduled to run in a separate buffer
+(`foo_INSTANTS` in this case).
+
+`TimerQueue::enqueue_unchecked` does a bit more work that just adding the entry
+into a min-heap: it also pends the system timer interrupt (`SysTick`) if the new
+entry ended up first in the queue.
+
+## The system timer
+
+The system timer interrupt (`SysTick`) takes cares of two things: moving tasks
+that have become ready from the timer queue into the right ready queue and
+setting up a timeout interrupt to fire when the scheduled time of the next task
+is up.
+
+Let's see the associated code.
+
+``` rust
+mod app {
+ #[no_mangle]
+ fn SysTick() {
+ const PRIORITY: u8 = 1;
+
+ let priority = &Cell::new(PRIORITY);
+ while let Some(ready) = lock(priority, TQ_CEILING, || TQ.dequeue()) {
+ match ready.task {
+ T::foo => {
+ // move this task into the `RQ1` ready queue
+ lock(priority, RQ1_CEILING, || {
+ RQ1.split().0.enqueue_unchecked(Ready {
+ task: T1::foo,
+ index: ready.index,
+ })
+ });
+
+ // pend the task dispatcher
+ rtic::pend(Interrupt::UART0);
+ }
+ }
+ }
+ }
+}
+```
+
+This looks similar to a task dispatcher except that instead of running the
+ready task this only places the task in the corresponding ready queue, that
+way it will run at the right priority.
+
+`TimerQueue::dequeue` will set up a new timeout interrupt when it returns
+`None`. This ties in with `TimerQueue::enqueue_unchecked`, which pends this
+handler; basically, `enqueue_unchecked` delegates the task of setting up a new
+timeout interrupt to the `SysTick` handler.
+
+## Resolution and range of `cyccnt::Instant` and `cyccnt::Duration`
+
+RTIC provides a `Monotonic` implementation based on the `DWT`'s (Data Watchpoint
+and Trace) cycle counter. `Instant::now` returns a snapshot of this timer; these
+DWT snapshots (`Instant`s) are used to sort entries in the timer queue. The
+cycle counter is a 32-bit counter clocked at the core clock frequency. This
+counter wraps around every `(1 << 32)` clock cycles; there's no interrupt
+associated to this counter so nothing worth noting happens when it wraps around.
+
+To order `Instant`s in the queue we need to compare two 32-bit integers. To
+account for the wrap-around behavior we use the difference between two
+`Instant`s, `a - b`, and treat the result as a 32-bit signed integer. If the
+result is less than zero then `b` is a later `Instant`; if the result is greater
+than zero then `b` is an earlier `Instant`. This means that scheduling a task at
+an `Instant` that's `(1 << 31) - 1` cycles greater than the scheduled time
+(`Instant`) of the first (earliest) entry in the queue will cause the task to be
+inserted at the wrong place in the queue. There some debug assertions in place
+to prevent this user error but it can't be avoided because the user can write
+`(instant + duration_a) + duration_b` and overflow the `Instant`.
+
+The system timer, `SysTick`, is a 24-bit counter also clocked at the core clock
+frequency. When the next scheduled task is more than `1 << 24` clock cycles in
+the future an interrupt is set to go off in `1 << 24` cycles. This process may
+need to happen several times until the next scheduled task is within the range
+of the `SysTick` counter.
+
+In conclusion, both `Instant` and `Duration` have a resolution of 1 core clock
+cycle and `Duration` effectively has a (half-open) range of `0..(1 << 31)` (end
+not included) core clock cycles.
+
+## Queue capacity
+
+The capacity of the timer queue is chosen to be the sum of the capacities of all
+`schedule`-able tasks. Like in the case of the ready queues, this means that
+once we have claimed a free slot in the `INPUTS` buffer we are guaranteed to be
+able to insert the task in the timer queue; this lets us omit runtime checks.
+
+## System timer priority
+
+The priority of the system timer can't be set by the user; it is chosen by the
+framework. To ensure that lower priority tasks don't prevent higher priority
+tasks from running we choose the priority of the system timer to be the maximum
+of all the `schedule`-able tasks.
+
+To see why this is required consider the case where two previously scheduled
+tasks with priorities `2` and `3` become ready at about the same time but the
+lower priority task is moved into the ready queue first. If the system timer
+priority was, for example, `1` then after moving the lower priority (`2`) task
+it would run to completion (due to it being higher priority than the system
+timer) delaying the execution of the higher priority (`3`) task. To prevent
+scenarios like these the system timer must match the highest priority of the
+`schedule`-able tasks; in this example that would be `3`.
+
+## Ceiling analysis
+
+The timer queue is a resource shared between all the tasks that can `schedule` a
+task and the `SysTick` handler. Also the `schedule` API contends with the
+`spawn` API over the free queues. All this must be considered in the ceiling
+analysis.
+
+To illustrate, consider the following example:
+
+``` rust
+#[rtic::app(device = ..)]
+mod app {
+ #[task(priority = 3, spawn = [baz])]
+ fn foo(c: foo::Context) {
+ // ..
+ }
+
+ #[task(priority = 2, schedule = [foo, baz])]
+ fn bar(c: bar::Context) {
+ // ..
+ }
+
+ #[task(priority = 1)]
+ fn baz(c: baz::Context) {
+ // ..
+ }
+}
+```
+
+The ceiling analysis would go like this:
+
+- `foo` (prio = 3) and `baz` (prio = 1) are `schedule`-able task so the
+ `SysTick` must run at the highest priority between these two, that is `3`.
+
+- `foo::Spawn` (prio = 3) and `bar::Schedule` (prio = 2) contend over the
+ consumer endpoint of `baz_FQ`; this leads to a priority ceiling of `3`.
+
+- `bar::Schedule` (prio = 2) has exclusive access over the consumer endpoint of
+`foo_FQ`; thus the priority ceiling of `foo_FQ` is effectively `2`.
+
+- `SysTick` (prio = 3) and `bar::Schedule` (prio = 2) contend over the timer
+ queue `TQ`; this leads to a priority ceiling of `3`.
+
+- `SysTick` (prio = 3) and `foo::Spawn` (prio = 3) both have lock-free access to
+ the ready queue `RQ3`, which holds `foo` entries; thus the priority ceiling of
+ `RQ3` is effectively `3`.
+
+- The `SysTick` has exclusive access to the ready queue `RQ1`, which holds `baz`
+ entries; thus the priority ceiling of `RQ1` is effectively `3`.
+
+## Changes in the `spawn` implementation
+
+When the `schedule` API is used the `spawn` implementation changes a bit to
+track the baseline of tasks. As you saw in the `schedule` implementation there's
+an `INSTANTS` buffers used to store the time at which a task was scheduled to
+run; this `Instant` is read in the task dispatcher and passed to the user code
+as part of the task context.
+
+``` rust
+mod app {
+ // ..
+
+ #[no_mangle]
+ unsafe UART1() {
+ const PRIORITY: u8 = 1;
+
+ let snapshot = basepri::read();
+
+ while let Some(ready) = RQ1.split().1.dequeue() {
+ match ready.task {
+ Task::baz => {
+ let input = baz_INPUTS[ready.index as usize].read();
+ // ADDED
+ let instant = baz_INSTANTS[ready.index as usize].read();
+
+ baz_FQ.split().0.enqueue_unchecked(ready.index);
+
+ let priority = Cell::new(PRIORITY);
+ // CHANGED the instant is passed as part the task context
+ baz(baz::Context::new(&priority, instant), input)
+ }
+
+ Task::bar => {
+ // looks just like the `baz` branch
+ }
+
+ }
+ }
+
+ // BASEPRI invariant
+ basepri::write(snapshot);
+ }
+}
+```
+
+Conversely, the `spawn` implementation needs to write a value to the `INSTANTS`
+buffer. The value to be written is stored in the `Spawn` struct and its either
+the `start` time of the hardware task or the `scheduled` time of the software
+task.
+
+``` rust
+mod foo {
+ // ..
+
+ pub struct Spawn<'a> {
+ priority: &'a Cell<u8>,
+ // ADDED
+ instant: Instant,
+ }
+
+ impl<'a> Spawn<'a> {
+ pub unsafe fn priority(&self) -> &Cell<u8> {
+ &self.priority
+ }
+
+ // ADDED
+ pub unsafe fn instant(&self) -> Instant {
+ self.instant
+ }
+ }
+}
+
+mod app {
+ impl<'a> foo::Spawn<'a> {
+ /// Spawns the `baz` task
+ pub fn baz(&self, message: u64) -> Result<(), u64> {
+ unsafe {
+ match lock(self.priority(), baz_FQ_CEILING, || {
+ baz_FQ.split().1.dequeue()
+ }) {
+ Some(index) => {
+ baz_INPUTS[index as usize].write(message);
+ // ADDED
+ baz_INSTANTS[index as usize].write(self.instant());
+
+ lock(self.priority(), RQ1_CEILING, || {
+ RQ1.split().1.enqueue_unchecked(Ready {
+ task: Task::foo,
+ index,
+ });
+ });
+
+ rtic::pend(Interrupt::UART0);
+ }
+
+ None => {
+ // maximum capacity reached; spawn failed
+ Err(message)
+ }
+ }
+ }
+ }
+ }
+}
+```
diff --git a/book/en/src/migration.md b/book/en/src/migration.md
new file mode 100644
index 00000000..08feb81e
--- /dev/null
+++ b/book/en/src/migration.md
@@ -0,0 +1,4 @@
+# Migration Guides
+
+This section describes how to migrate between different version of RTIC.
+It also acts as a comparing reference between versions.
diff --git a/book/en/src/migration/migration_rtic.md b/book/en/src/migration/migration_rtic.md
new file mode 100644
index 00000000..555f1bb7
--- /dev/null
+++ b/book/en/src/migration/migration_rtic.md
@@ -0,0 +1,54 @@
+# Migrating from RTFM to RTIC
+
+This section covers how to upgrade an application written against RTFM v0.5.x to
+the same version of RTIC. This applies since the renaming of the framework as per [RFC #33].
+
+**Note:** There are no code differences between RTFM v0.5.3 and RTIC v0.5.3, it is purely a name
+change.
+
+[RFC #33]: https://github.com/rtic-rs/rfcs/pull/33
+
+
+
+## `Cargo.toml`
+
+First, the `cortex-m-rtfm` dependency needs to be updated to
+`cortex-m-rtic`.
+
+
+``` toml
+[dependencies]
+# change this
+cortex-m-rtfm = "0.5.3"
+
+# into this
+cortex-m-rtic = "0.5.3"
+```
+
+## Code changes
+
+The only code change that needs to be made is that any reference to `rtfm` before now need to point
+to `rtic` as follows:
+
+``` rust
+//
+// Change this
+//
+
+#[rtfm::app(/* .. */, monotonic = rtfm::cyccnt::CYCCNT)]
+const APP: () = {
+ // ...
+
+};
+
+//
+// Into this
+//
+
+#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)]
+const APP: () = {
+ // ...
+
+};
+```
+
diff --git a/book/en/src/migration/migration_v4.md b/book/en/src/migration/migration_v4.md
new file mode 100644
index 00000000..2c4e3ade
--- /dev/null
+++ b/book/en/src/migration/migration_v4.md
@@ -0,0 +1,232 @@
+# Migrating from v0.4.x to v0.5.0
+
+This section covers how to upgrade an application written against RTIC v0.4.x to
+the version v0.5.0 of the framework.
+
+### `Cargo.toml`
+
+First, the version of the `cortex-m-rtic` dependency needs to be updated to
+`"0.5.0"`. The `timer-queue` feature needs to be removed.
+
+``` toml
+[dependencies.cortex-m-rtic]
+# change this
+version = "0.4.3"
+
+# into this
+version = "0.5.0"
+
+# and remove this Cargo feature
+features = ["timer-queue"]
+# ^^^^^^^^^^^^^
+```
+
+### `Context` argument
+
+All functions inside the `#[rtic::app]` item need to take as first argument a
+`Context` structure. This `Context` type will contain the variables that were
+magically injected into the scope of the function by version v0.4.x of the
+framework: `resources`, `spawn`, `schedule` -- these variables will become
+fields of the `Context` structure. Each function within the `#[rtic::app]` item
+gets a different `Context` type.
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ // change this
+ #[task(resources = [x], spawn = [a], schedule = [b])]
+ fn foo() {
+ resources.x.lock(|x| /* .. */);
+ spawn.a(message);
+ schedule.b(baseline);
+ }
+
+ // into this
+ #[task(resources = [x], spawn = [a], schedule = [b])]
+ fn foo(mut cx: foo::Context) {
+ // ^^^^^^^^^^^^^^^^^^^^
+
+ cx.resources.x.lock(|x| /* .. */);
+ // ^^^
+
+ cx.spawn.a(message);
+ // ^^^
+
+ cx.schedule.b(message, baseline);
+ // ^^^
+ }
+
+ // change this
+ #[init]
+ fn init() {
+ // ..
+ }
+
+ // into this
+ #[init]
+ fn init(cx: init::Context) {
+ // ^^^^^^^^^^^^^^^^^
+ // ..
+ }
+
+ // ..
+};
+```
+
+### Resources
+
+The syntax used to declare resources has been changed from `static mut`
+variables to a `struct Resources`.
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ // change this
+ static mut X: u32 = 0;
+ static mut Y: u32 = (); // late resource
+
+ // into this
+ struct Resources {
+ #[init(0)] // <- initial value
+ X: u32, // NOTE: we suggest changing the naming style to `snake_case`
+
+ Y: u32, // late resource
+ }
+
+ // ..
+};
+```
+
+### Device peripherals
+
+If your application was accessing the device peripherals in `#[init]` through
+the `device` variable then you'll need to add `peripherals = true` to the
+`#[rtic::app]` attribute to continue to access the device peripherals through
+the `device` field of the `init::Context` structure.
+
+Change this:
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ #[init]
+ fn init() {
+ device.SOME_PERIPHERAL.write(something);
+ }
+
+ // ..
+};
+```
+
+Into this:
+
+``` rust
+#[rtic::app(/* .. */, peripherals = true)]
+// ^^^^^^^^^^^^^^^^^^
+const APP: () = {
+ #[init]
+ fn init(cx: init::Context) {
+ // ^^^^^^^^^^^^^^^^^
+ cx.device.SOME_PERIPHERAL.write(something);
+ // ^^^
+ }
+
+ // ..
+};
+```
+
+### `#[interrupt]` and `#[exception]`
+
+The `#[interrupt]` and `#[exception]` attributes have been removed. To declare
+hardware tasks in v0.5.x use the `#[task]` attribute with the `binds` argument.
+
+Change this:
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ // hardware tasks
+ #[exception]
+ fn SVCall() { /* .. */ }
+
+ #[interrupt]
+ fn UART0() { /* .. */ }
+
+ // software task
+ #[task]
+ fn foo() { /* .. */ }
+
+ // ..
+};
+```
+
+Into this:
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ #[task(binds = SVCall)]
+ // ^^^^^^^^^^^^^^
+ fn svcall(cx: svcall::Context) { /* .. */ }
+ // ^^^^^^ we suggest you use a `snake_case` name here
+
+ #[task(binds = UART0)]
+ // ^^^^^^^^^^^^^
+ fn uart0(cx: uart0::Context) { /* .. */ }
+
+ #[task]
+ fn foo(cx: foo::Context) { /* .. */ }
+
+ // ..
+};
+```
+
+### `schedule`
+
+The `timer-queue` feature has been removed. To use the `schedule` API one must
+first define the monotonic timer the runtime will use using the `monotonic`
+argument of the `#[rtic::app]` attribute. To continue using the cycle counter
+(CYCCNT) as the monotonic timer, and match the behavior of version v0.4.x, add
+the `monotonic = rtic::cyccnt::CYCCNT` argument to the `#[rtic::app]` attribute.
+
+Also, the `Duration` and `Instant` types and the `U32Ext` trait have been moved
+into the `rtic::cyccnt` module. This module is only available on ARMv7-M+
+devices. The removal of the `timer-queue` also brings back the `DWT` peripheral
+inside the core peripherals struct, this will need to be enabled by the application
+inside `init`.
+
+Change this:
+
+``` rust
+use rtic::{Duration, Instant, U32Ext};
+
+#[rtic::app(/* .. */)]
+const APP: () = {
+ #[task(schedule = [b])]
+ fn a() {
+ // ..
+ }
+};
+```
+
+Into this:
+
+``` rust
+use rtic::cyccnt::{Duration, Instant, U32Ext};
+// ^^^^^^^^
+
+#[rtic::app(/* .. */, monotonic = rtic::cyccnt::CYCCNT)]
+// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+const APP: () = {
+ #[init]
+ fn init(cx: init::Context) {
+ cx.core.DWT.enable_cycle_counter();
+ // optional, configure the DWT run without a debugger connected
+ cx.core.DCB.enable_trace();
+ }
+ #[task(schedule = [b])]
+ fn a(cx: a::Context) {
+ // ..
+ }
+};
+```
diff --git a/book/en/src/migration/migration_v5.md b/book/en/src/migration/migration_v5.md
new file mode 100644
index 00000000..749ddecd
--- /dev/null
+++ b/book/en/src/migration/migration_v5.md
@@ -0,0 +1,96 @@
+# Migrating from v0.5.x to v0.6.0
+
+This section describes how to upgrade from v0.5.x to v0.6.0 of the RTIC framework.
+
+### `Cargo.toml` - version bump
+
+Change the version of `cortex-m-rtic` to `"0.6.0"`.
+
+### Module instead of Const
+
+With the support of attributes on modules the `const APP` workaround is not needed.
+
+Change
+
+``` rust
+#[rtic::app(/* .. */)]
+const APP: () = {
+ [code here]
+};
+```
+
+into
+
+``` rust
+#[rtic::app(/* .. */)]
+mod app {
+ [code here]
+}
+```
+
+Now that a regular Rust module is used it means it is possible to have custom
+user code within that module.
+Additionally, it means that `use`-statements for resources etc may be required.
+
+### Init always returns late resources
+
+In order to make the API more symmetric the #[init]-task always returns a late resource.
+
+From this:
+
+``` rust
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) {
+ rtic::pend(Interrupt::UART0);
+ }
+ [more code]
+}
+```
+
+to this:
+
+``` rust
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+
+ init::LateResources {}
+ }
+ [more code]
+}
+```
+
+### Resources struct - #[resources]
+
+Previously the RTIC resources had to be in in a struct named exactly "Resources":
+
+``` rust
+struct Resources {
+ // Resources defined in here
+}
+```
+
+With RTIC v0.6.0 the resources struct is annotated similarly like
+`#[task]`, `#[init]`, `#[idle]`: with an attribute `#[resources]`
+
+``` rust
+#[resources]
+struct Resources {
+ // Resources defined in here
+}
+```
+
+In fact, the name of the struct is now up to the developer:
+
+``` rust
+#[resources]
+struct whateveryouwant {
+ // Resources defined in here
+}
+```
+
+would work equally well.
diff --git a/book/en/src/preface.md b/book/en/src/preface.md
index d8f64fd4..041b3bd4 100644
--- a/book/en/src/preface.md
+++ b/book/en/src/preface.md
@@ -1,16 +1,23 @@
-<h1 align="center">Real Time For the Masses</h1>
+<h1 align="center">Real-Time Interrupt-driven Concurrency</h1>
-<p align="center">A concurrency framework for building real time systems</p>
+<p align="center">A concurrency framework for building real-time systems</p>
# Preface
-This book contains user level documentation for the Real Time For the Masses
-(RTFM) framework. The API reference can be found [here](../api/rtfm/index.html).
+This book contains user level documentation for the Real-Time Interrupt-driven Concurrency
+(RTIC) framework. The API reference can be found [here](../../api/).
+
+Formerly known as Real-Time For the Masses.
There is a translation of this book in [Russian].
[Russian]: ../ru/index.html
-{{#include ../../../README.md:5:46}}
+This is the documentation of v0.6.x of RTIC; for the documentation of version
+
+* v0.5.x go [here](/0.5).
+* v0.4.x go [here](/0.4).
+
+{{#include ../../../README.md:7:46}}
{{#include ../../../README.md:52:}}
diff --git a/book/ru/book.toml b/book/ru/book.toml
index c611ce07..6c3a5e64 100644
--- a/book/ru/book.toml
+++ b/book/ru/book.toml
@@ -2,4 +2,4 @@
authors = ["Jorge Aparicio"]
multilingual = false
src = "src"
-title = "Real Time For the Masses"
+title = "Real-Time Interrupt-driven Concurrency"
diff --git a/book/ru/src/README_RU.md b/book/ru/src/README_RU.md
index 921837a9..4cc24e68 100644
--- a/book/ru/src/README_RU.md
+++ b/book/ru/src/README_RU.md
@@ -1,4 +1,4 @@
-# Real Time For the Masses
+# Real-Time Interrupt-driven Concurrency
Конкурентный фреймворк для создания систем реального времени.
@@ -43,17 +43,17 @@
- Программы нужно писать используя 2018 edition.
-## [User documentation](https://japaric.github.io/cortex-m-rtfm/book)
+## [User documentation](https://japaric.github.io/cortex-m-rtic/book)
-## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html)
+## [API reference](https://japaric.github.io/cortex-m-rtic/api/rtic/index.html)
## Благодарности
-Эта библиотека основана на [языке RTFM][rtfm-lang], созданном Embedded
+Эта библиотека основана на [языке RTIC][rtic-lang], созданном Embedded
Systems group в [Техническом Университете Luleå][ltu], под рук.
[Prof. Per Lindgren][per].
-[rtfm-lang]: http://www.rtfm-lang.org/
+[rtic-lang]: http://www.rtic-lang.org/
[ltu]: https://www.ltu.se/?l=en
[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
diff --git a/book/ru/src/SUMMARY.md b/book/ru/src/SUMMARY.md
index 7831e415..7df745eb 100644
--- a/book/ru/src/SUMMARY.md
+++ b/book/ru/src/SUMMARY.md
@@ -1,7 +1,7 @@
# Summary
[Введение](./preface.md)
-- [RTFM в примерах](./by-example.md)
+- [RTIC в примерах](./by-example.md)
- [Атрибут `app`](./by-example/app.md)
- [Ресурсы](./by-example/resources.md)
- [Задачи](./by-example/tasks.md)
diff --git a/book/ru/src/by-example.md b/book/ru/src/by-example.md
index c7a2a4ae..0e0fde2f 100644
--- a/book/ru/src/by-example.md
+++ b/book/ru/src/by-example.md
@@ -1,13 +1,13 @@
-# RTFM в примерах
+# RTIC в примерах
-Эта часть книги представляет фреймворк Real Time For the Masses (RTFM)
+Эта часть книги представляет фреймворк Real-Time Interrupt-driven Concurrency (RTIC)
новым пользователям через примеры с растущей сложностью.
Все примеры в этой книге можно найти в [репозитории] проекта на GitHub,
и большинство примеров можно запустить на эмуляторе QEMU, поэтому никакого
специального оборудования не требуется их выполнять.
-[репозитории]: https://github.com/japaric/cortex-m-rtfm
+[репозитории]: https://github.com/japaric/cortex-m-rtic
Чтобы запустить примеры на Вашем ноутбуке / ПК, Вам нужна программа
`qemu-system-arm`. Инструкции по настройке окружения для разработки
diff --git a/book/ru/src/by-example/app.md b/book/ru/src/by-example/app.md
index 18147dc3..04dd5b24 100644
--- a/book/ru/src/by-example/app.md
+++ b/book/ru/src/by-example/app.md
@@ -1,26 +1,26 @@
# The `app` attribute
-Это наименьшая возможная программа на RTFM:
+Это наименьшая возможная программа на RTIC:
``` rust
{{#include ../../../../examples/smallest.rs}}
```
-Все программы на RTFM используют атрибут [`app`] (`#[app(..)]`). Этот атрибут
+Все программы на RTIC используют атрибут [`app`] (`#[app(..)]`). Этот атрибут
нужно применять к `const`-элементам, содержащим элементы. Атрибут `app` имеет
обязательный аргумент `device`, в качестве значения которому передается *путь*.
Этот путь должен указывать на библиотеку *устройства*, сгенерированную с помощью
[`svd2rust`] **v0.14.x**. Атрибут `app` развернется в удобную точку входа,
поэтому нет необходимости использовать атрибут [`cortex_m_rt::entry`].
-[`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html
+[`app`]: ../../../api/cortex_m_rtic_macros/attr.app.html
[`svd2rust`]: https://crates.io/crates/svd2rust
-[`cortex_m_rt::entry`]: ../../api/cortex_m_rt_macros/attr.entry.html
+[`cortex_m_rt::entry`]: ../../../api/cortex_m_rt_macros/attr.entry.html
> **ОТСТУПЛЕНИЕ**: Некоторые из вас удивятся, почему мы используем ключевое слово `const` как
> модуль, а не правильное `mod`. Причина в том, что использование атрибутов на
> модулях требует feature gate, который требует ночную сборку. Чтобы заставить
-> RTFM работать на стабильной сборке, мы используем вместо него слово `const`.
+> RTIC работать на стабильной сборке, мы используем вместо него слово `const`.
> Когда большая часть макросов 1.2 стабилизируются, мы прейдем от `const` к `mod` и в конце концов в атрибуту уровне приложения (`#![app]`).
## `init`
@@ -32,13 +32,13 @@
Функция `init` запустится *с отключенными прерываниями* и будет иметь эксклюзивный
доступ к периферии Cortex-M и специфичной для устройства периферии через переменные
`core` and `device`, которые внедряются в область видимости `init` атрибутом `app`.
-Не вся периферия Cortex-M доступна в `core`, потому что рантайм RTFM принимает владение
-частью из неё -- более подробно см. структуру [`rtfm::Peripherals`].
+Не вся периферия Cortex-M доступна в `core`, потому что рантайм RTIC принимает владение
+частью из неё -- более подробно см. структуру [`rtic::Peripherals`].
Переменные `static mut`, определённые в начале `init` будут преобразованы
в ссылки `&'static mut` с безопасным доступом.
-[`rtfm::Peripherals`]: ../../api/rtfm/struct.Peripherals.html
+[`rtic::Peripherals`]: ../../api/rtic/struct.Peripherals.html
Пример ниже показывает типы переменных `core` и `device` и
демонстрирует безопасный доступ к переменной `static mut`.
@@ -63,10 +63,10 @@ $ cargo run --example init
`init`, `idle` запустится *с включенными прерываниями* и не может завершиться,
поэтому будет работать бесконечно.
-Когда функция `idle` определена, рантайм устанавливает бит [SLEEPONEXIT], после чего
+Когда функция `idle` не определена, рантайм устанавливает бит [SLEEPONEXIT], после чего
отправляет микроконтроллер в состояние сна после выполнения `init`.
-[SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
+[SLEEPONEXIT]: https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
Как и в `init`, переменные `static mut`будут преобразованы в ссылки `&'static mut`
с безопасным доступом.
@@ -85,7 +85,7 @@ $ cargo run --example idle
Как Вы бы сделали с помощью библиотеки `cortex-m-rt`, Вы можете использовать атрибуты
`interrupt` и `exception` внутри псевдо-модуля `app`, чтобы определить обработчики
-прерываний и исключений. В RTFM, мы называем обработчики прерываний и исключений
+прерываний и исключений. В RTIC, мы называем обработчики прерываний и исключений
*аппаратными* задачами.
``` rust
@@ -96,6 +96,6 @@ $ cargo run --example idle
$ cargo run --example interrupt
{{#include ../../../../ci/expected/interrupt.run}}```
-До сих пор программы RTFM, которые мы видели не отличались от программ, которые
+До сих пор программы RTIC, которые мы видели не отличались от программ, которые
можно написать, используя только библиотеку `cortex-m-rt`. В следующем разделе
-мы начнем знакомиться с функционалом, присущим только RTFM.
+мы начнем знакомиться с функционалом, присущим только RTIC.
diff --git a/book/ru/src/by-example/new.md b/book/ru/src/by-example/new.md
index 688935e5..cba84c16 100644
--- a/book/ru/src/by-example/new.md
+++ b/book/ru/src/by-example/new.md
@@ -1,6 +1,6 @@
# Создание нового проекта
-Теперь, когда Вы изучили основные возможности фреймворка RTFM, Вы можете
+Теперь, когда Вы изучили основные возможности фреймворка RTIC, Вы можете
попробовать его использовать на Вашем оборудовании следуя этим инструкциям.
1. Создайте экземпляр из шаблона [`cortex-m-quickstart`].
@@ -36,20 +36,20 @@ $ cargo add lm3s6965 --vers 0.1.3
$ rm memory.x build.rs
```
-3. Добавьте библиотеку `cortex-m-rtfm` как зависимость, и если необходимо,
+3. Добавьте библиотеку `cortex-m-rtic` как зависимость, и если необходимо,
включите опцию `timer-queue`.
``` console
-$ cargo add cortex-m-rtfm --allow-prerelease --upgrade=none
+$ cargo add cortex-m-rtic --allow-prerelease --upgrade=none
```
-4. Напишите программу RTFM.
+4. Напишите программу RTIC.
-Здесь я буду использовать пример `init` из библиотеки `cortex-m-rtfm`.
+Здесь я буду использовать пример `init` из библиотеки `cortex-m-rtic`.
``` console
$ curl \
- -L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0-beta.1/examples/init.rs \
+ -L https://github.com/japaric/cortex-m-rtic/raw/v0.4.0-beta.1/examples/init.rs \
> src/main.rs
```
diff --git a/book/ru/src/by-example/resources.md b/book/ru/src/by-example/resources.md
index c13822f7..b53ef40e 100644
--- a/book/ru/src/by-example/resources.md
+++ b/book/ru/src/by-example/resources.md
@@ -8,7 +8,7 @@
достаточно информации, чтобы оптимизировать доступ к разделяемым данным.
Атрибут `app` имеет полную картину приложения, поэтому может оптимизировать доступ к
-`static`-переменным. В RTFM мы обращаемся к `static`-переменным, объявленным внутри
+`static`-переменным. В RTIC мы обращаемся к `static`-переменным, объявленным внутри
псевдо-модуля `app` как к *ресурсам*. Чтобы получить доступ к ресурсу, контекст
(`init`, `idle`, `interrupt` или `exception`) должен сначала определить
аргумент `resources` в соответствующем атрибуте.
@@ -45,13 +45,13 @@ $ cargo run --example resource
критические секции не нужны для обработчика с наивысшим приоритетом, имеющим
доступ к ресурсу.
-API критической секции, предоставляемое фреймворком RTFM (см. [`Mutex`]),
+API критической секции, предоставляемое фреймворком RTIC (см. [`Mutex`]),
основано на динамических приоритетах вместо отключения прерываний. Из этого следует,
что критические секции не будут допускать *запуск некоторых* обработчиков,
включая все соперничающие за ресурс, но будут позволять запуск обработчиков с
большим приоритетом не соперничащих за ресурс.
-[`Mutex`]: ../../api/rtfm/trait.Mutex.html
+[`Mutex`]: ../../../api/rtic/trait.Mutex.html
В примере ниже у нас есть 3 обработчика прерываний с приоритетами от одного
до трех. Два обработчика с низким приоритетом соперничают за ресурс `SHARED`.
@@ -61,7 +61,7 @@ API критической секции, предоставляемое фрей
с наивысшим приоритетом может свободно вытеснять критическую секцию,
созданную обработчиком с низшим приоритетом.
-[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock
+[`lock`]: ../../../api/rtic/trait.Mutex.html#method.lock
``` rust
{{#include ../../../../examples/lock.rs}}
@@ -90,7 +90,7 @@ $ cargo run --example lock
расположен в отдельном ресурсе; `UART0` владеет ресурсом произодителя, а `idle`
владеет ресурсом потребителя.
-[`Queue`]: ../../api/heapless/spsc/struct.Queue.html
+[`Queue`]: ../../../api/heapless/spsc/struct.Queue.html
``` rust
{{#include ../../../../examples/late.rs}}
diff --git a/book/ru/src/by-example/tasks.md b/book/ru/src/by-example/tasks.md
index 355bd4ee..37828043 100644
--- a/book/ru/src/by-example/tasks.md
+++ b/book/ru/src/by-example/tasks.md
@@ -1,12 +1,12 @@
# Программные задачи
-RTFM обрабатывает прерывания и исключения как *аппаратные* задачи. Аппаратные
+RTIC обрабатывает прерывания и исключения как *аппаратные* задачи. Аппаратные
задачи могут вызываться устройством в ответ на события, такие как нажатие кнопки.
-RTFM также поддерживает *программные* задачи, порождаемые программой из любого
+RTIC также поддерживает *программные* задачи, порождаемые программой из любого
контекста выполнения.
Программным задачам также можно назначать приоритет и диспетчеризовать из
-обработчиков прерываний. RTFM требует определения свободных прерываний в блоке
+обработчиков прерываний. RTIC требует определения свободных прерываний в блоке
`extern`, когда используются программные задачи; эти свободные прерывания будут использованы, чтобы диспетчеризовать программные задачи. Преимущество программных
задач перед аппаратными в том, что на один обработчик прерывания можно назначить
множество задач.
diff --git a/book/ru/src/by-example/timer-queue.md b/book/ru/src/by-example/timer-queue.md
index 8995bd05..3c35e290 100644
--- a/book/ru/src/by-example/timer-queue.md
+++ b/book/ru/src/by-example/timer-queue.md
@@ -1,6 +1,6 @@
# Очередь таймера
-Когда включена опция `timer-queue`, фреймворк RTFM включает
+Когда включена опция `timer-queue`, фреймворк RTIC включает
*глобальную очередь таймера*, которую приложения могут использовать, чтобы
*планировать* программные задачи на запуск через некоторое время в будущем.
@@ -9,16 +9,16 @@
планируется, момент ([`Instant`]), в который задачу нужно запустить, нужно передать
как первый аргумент вызова `schedule`.
-[`Instant`]: ../../api/rtfm/struct.Instant.html
+[`Instant`]: ../../../api/rtic/struct.Instant.html
-Рантайм RTFM включает монотонный, растущий только вверх, 32-битный таймер,
+Рантайм RTIC включает монотонный, растущий только вверх, 32-битный таймер,
значение которого можно запросить конструктором `Instant::now`. Время ([`Duration`])
можно передать в `Instant::now()`, чтобы получить `Instant` в будущем. Монотонный
таймер отключен пока запущен `init`, поэтому `Instant::now()` всегда возвращает
значение `Instant(0 /* циклов тактовой частоты */)`; таймер включается сразу перед
включением прерываний и запуском `idle`.
-[`Duration`]: ../../api/rtfm/struct.Duration.html
+[`Duration`]: ../../../api/rtic/struct.Duration.html
В примере ниже две задачи планируются из `init`: `foo` и `bar`. `foo` -
запланирована на запуск через 8 миллионов тактов в будущем. Кроме того, `bar`
diff --git a/book/ru/src/by-example/tips.md b/book/ru/src/by-example/tips.md
index 4a39cbc8..249e8f4d 100644
--- a/book/ru/src/by-example/tips.md
+++ b/book/ru/src/by-example/tips.md
@@ -22,8 +22,8 @@ $ cargo run --example generics
## Запуск задач из ОЗУ
-Главной целью переноса описания программы на RTFM в атрибуты в
-RTFM v0.4.x была возможность взаимодействия с другими атрибутами.
+Главной целью переноса описания программы на RTIC в атрибуты в
+RTIC v0.4.x была возможность взаимодействия с другими атрибутами.
Напримерe, атрибут `link_section` можно применять к задачам, чтобы разместить
их в ОЗУ; это может улучшить производительность в некоторых случаях.
@@ -64,7 +64,7 @@ $ cargo nm --example ramfunc --release | grep ' bar::'
## `binds`
-**ПРИМЕЧАНИЕ**: Требуется RTFM не ниже 0.4.2
+**ПРИМЕЧАНИЕ**: Требуется RTIC не ниже 0.4.2
Вы можете давать аппаратным задачам имена похожие на имена обычных задач.
Для этого нужно использовать аргумент `binds`: Вы называете функцию
diff --git a/book/ru/src/by-example/types-send-sync.md b/book/ru/src/by-example/types-send-sync.md
index 77c9af01..85118897 100644
--- a/book/ru/src/by-example/types-send-sync.md
+++ b/book/ru/src/by-example/types-send-sync.md
@@ -19,7 +19,7 @@
## `Send`
[`Send`] - маркерный типаж (trait) для "типов, которые можно передавать через границы
-потоков", как это определено в `core`. В контексте RTFM типаж `Send` необходим
+потоков", как это определено в `core`. В контексте RTIC типаж `Send` необходим
только там, где возможна передача значения между задачами, запускаемыми на
*разных* приоритетах. Это возникает в нескольких случаях: при передаче сообщений,
в совместно используемых `static mut` ресурсах и инициализации поздних ресурсов.
@@ -41,7 +41,7 @@
## `Sync`
Похожая ситуация, [`Sync`] - маркерный типаж для "типов, на которых можно
-ссылаться в разных потоках", как это определено в `core`. В контексте RTFM
+ссылаться в разных потоках", как это определено в `core`. В контексте RTIC
типаж `Sync` необходим только там, где возможны две или более задачи,
запускаемые на разных приоритетах, чтобы захватить разделяемую ссылку на
ресурс. Это возникает только совместно используемых `static`-ресурсах.
diff --git a/book/ru/src/preface.md b/book/ru/src/preface.md
index 0f9bd67e..bfae0043 100644
--- a/book/ru/src/preface.md
+++ b/book/ru/src/preface.md
@@ -1,11 +1,11 @@
-<h1 align="center">Real Time For the Masses</h1>
+<h1 align="center">Real-Time Interrupt-driven Concurrency</h1>
<p align="center">Конкурентный фреймворк для создания систем реального времени</p>
# Введение
-Эта книга содержит документацию уровня пользователя фреймворком Real Time For the Masses
-(RTFM). Описание API можно найти [здесь](../api/rtfm/index.html).
+Эта книга содержит документацию уровня пользователя фреймворком Real-Time Interrupt-driven Concurrency
+(RTIC). Описание API можно найти [здесь](../../api/rtic/index.html).
{{#include README_RU.md:5:44}}
diff --git a/build.rs b/build.rs
index 2419b4eb..fee1a6a5 100644
--- a/build.rs
+++ b/build.rs
@@ -1,13 +1,21 @@
use std::env;
+use version_check;
fn main() {
let target = env::var("TARGET").unwrap();
+ if version_check::Channel::read().unwrap().is_nightly() {
+ println!("cargo:rustc-cfg=rustc_is_nightly")
+ }
+
if target.starts_with("thumbv6m") {
println!("cargo:rustc-cfg=armv6m")
}
- if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") {
+ if target.starts_with("thumbv7m")
+ | target.starts_with("thumbv7em")
+ | target.starts_with("thumbv8m")
+ {
println!("cargo:rustc-cfg=armv7m")
}
diff --git a/ci/after-success.sh b/ci/after-success.sh
index 65ddb904..bbe0522b 100644
--- a/ci/after-success.sh
+++ b/ci/after-success.sh
@@ -2,20 +2,55 @@ set -euxo pipefail
main() {
local langs=( en ru )
+ local latest=0.5
+ local vers=( 0.4.x )
rm -f .cargo/config
- cargo doc --features timer-queue
+ cargo doc
local td=$(mktemp -d)
- cp -r target/doc $td/api
- mkdir $td/book/
- cp redirect.html $td/book/index.html
+
+ # build latest docs
+ mkdir -p $td/$latest/book/
+ cp -r target/doc $td/$latest/api
+ sed 's|URL|rtic/index.html|g' redirect.html > $td/$latest/api/index.html
+
+ sed 's|URL|0.5|g' redirect.html > $td/index.html
+ sed 's|URL|book/en|g' redirect.html > $td/$latest/index.html
for lang in ${langs[@]}; do
( cd book/$lang && mdbook build )
- cp -r book/$lang/book $td/book/$lang
- cp LICENSE-* $td/book/$lang/
+ cp -r book/$lang/book $td/$latest/book/$lang
+ cp LICENSE-* $td/$latest/book/$lang/
+ done
+
+ local root=$(pwd)
+ # build older docs
+ for ver in ${vers[@]}; do
+ local prefix=${ver%.*}
+
+ mkdir -p $td/$prefix/book
+ local src=$(mktemp -d)
+ curl -L https://github.com/rtic-rs/cortex-m-rtic/archive/v${ver}.tar.gz | tar xz --strip-components 1 -C $src
+
+ pushd $src
+ rm -f .cargo/config
+ cargo doc || cargo doc --features timer-queue
+ cp -r target/doc $td/$prefix/api
+ sed 's|URL|rtic/index.html|g' $root/redirect.html > $td/$prefix/api/index.html
+ for lang in ${langs[@]}; do
+ ( cd book/$lang && mdbook build )
+ cp -r book/$lang/book $td/$prefix/book/$lang
+ cp LICENSE-* $td/$prefix/book/$lang/
+ done
+ sed 's|URL|book/en|g' $root/redirect.html > $td/$prefix/index.html
+ popd
+
+ rm -rf $src
done
+ # forward CNAME file
+ cp CNAME $td/
+
mkdir ghp-import
curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz |
tar --strip-components 1 -C ghp-import -xz
@@ -23,7 +58,7 @@ main() {
./ghp-import/ghp_import.py $td
set +x
- git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK
+ git push -fq https://$GH_TOKEN@github.com/rtic-rs/cortex-m-rtic.git gh-pages && echo OK
rm -rf $td
}
diff --git a/ci/expected/cfg.run b/ci/expected/cfg.run
new file mode 100644
index 00000000..b584958b
--- /dev/null
+++ b/ci/expected/cfg.run
@@ -0,0 +1,2 @@
+foo has been called 1 time
+foo has been called 2 times
diff --git a/ci/expected/generics.run b/ci/expected/generics.run
index 7fa97758..fb31731e 100644
--- a/ci/expected/generics.run
+++ b/ci/expected/generics.run
@@ -1,6 +1,6 @@
UART1(STATE = 0)
-SHARED: 0 -> 1
+shared: 0 -> 1
UART0(STATE = 0)
-SHARED: 1 -> 2
+shared: 1 -> 2
UART1(STATE = 1)
-SHARED: 2 -> 4
+shared: 2 -> 4
diff --git a/ci/expected/interrupt.run b/ci/expected/hardware.run
index ef00864b..ef00864b 100644
--- a/ci/expected/interrupt.run
+++ b/ci/expected/hardware.run
diff --git a/ci/expected/lock.run b/ci/expected/lock.run
index 156ac222..a987b372 100644
--- a/ci/expected/lock.run
+++ b/ci/expected/lock.run
@@ -1,5 +1,5 @@
A
-B - SHARED = 1
+B - shared = 1
C
-D - SHARED = 2
+D - shared = 2
E
diff --git a/ci/expected/only-shared-access.run b/ci/expected/only-shared-access.run
new file mode 100644
index 00000000..1d4eed00
--- /dev/null
+++ b/ci/expected/only-shared-access.run
@@ -0,0 +1,2 @@
+UART1(key = 0xdeadbeef)
+UART0(key = 0xdeadbeef)
diff --git a/ci/expected/peripherals-taken.run b/ci/expected/peripherals-taken.run
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ci/expected/peripherals-taken.run
diff --git a/ci/expected/pool.run b/ci/expected/pool.run
new file mode 100644
index 00000000..040dcee8
--- /dev/null
+++ b/ci/expected/pool.run
@@ -0,0 +1,2 @@
+bar(0x2000008c)
+foo(0x20000110)
diff --git a/ci/expected/preempt.run b/ci/expected/preempt.run
new file mode 100644
index 00000000..87777410
--- /dev/null
+++ b/ci/expected/preempt.run
@@ -0,0 +1,5 @@
+GPIOA - start
+ GPIOC - start
+ GPIOC - end
+ GPIOB
+GPIOA - end
diff --git a/ci/expected/ramfunc.grep.bar b/ci/expected/ramfunc.grep.bar
index 7f69d25c..1fa5bad1 100644
--- a/ci/expected/ramfunc.grep.bar
+++ b/ci/expected/ramfunc.grep.bar
@@ -1,3 +1 @@
-20000100 B bar::FREE_QUEUE::lk14244m263eivix
-200000dc B bar::INPUTS::mi89534s44r1mnj1
-20000000 T bar::ns9009yhw2dc2y25
+20000000 t ramfunc::bar::h9d6714fe5a3b0c89 \ No newline at end of file
diff --git a/ci/expected/ramfunc.grep.foo b/ci/expected/ramfunc.grep.foo
index a076ac0a..845f277f 100644
--- a/ci/expected/ramfunc.grep.foo
+++ b/ci/expected/ramfunc.grep.foo
@@ -1,3 +1 @@
-20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20
-200000dc B foo::INPUTS::thvubs85b91dg365
-000002c6 T foo::sidaht420cg1mcm8
+00000162 t ramfunc::foo::h30e7789b08c08e19 \ No newline at end of file
diff --git a/ci/expected/resource.run b/ci/expected/resource.run
index 9c70856a..a587a942 100644
--- a/ci/expected/resource.run
+++ b/ci/expected/resource.run
@@ -1,2 +1,2 @@
-UART0: SHARED = 1
-UART1: SHARED = 2
+UART0: shared = 1
+UART1: shared = 2
diff --git a/ci/expected/shared-with-init.run b/ci/expected/shared-with-init.run
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ci/expected/shared-with-init.run
diff --git a/ci/expected/static.run b/ci/expected/static.run
deleted file mode 100644
index 2c295c99..00000000
--- a/ci/expected/static.run
+++ /dev/null
@@ -1,2 +0,0 @@
-UART1(KEY = 0xdeadbeef)
-UART0(KEY = 0xdeadbeef)
diff --git a/ci/expected/task.run b/ci/expected/task.run
index 309fdb99..de45dce6 100644
--- a/ci/expected/task.run
+++ b/ci/expected/task.run
@@ -1,3 +1,5 @@
-foo
+foo - start
+foo - middle
baz
+foo - end
bar
diff --git a/ci/install.sh b/ci/install.sh
index 90007723..624efd87 100644
--- a/ci/install.sh
+++ b/ci/install.sh
@@ -1,19 +1,34 @@
set -euxo pipefail
+install_crate() {
+ local pkg=$1 vers=$2
+
+ cargo install --list | grep "$pkg v$vers" || ( cd .. && cargo install -f --vers $vers $pkg )
+}
+
main() {
- if [ $TARGET != x86_64-unknown-linux-gnu ]; then
+ # these are not needed for doc builds
+ if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST != false ]; then
+ if [ $TARGET = x86_64-unknown-linux-gnu ]; then
+ install_crate microamp-tools 0.1.0-alpha.3
+ rustup target add thumbv6m-none-eabi thumbv7m-none-eabi
+ fi
+
rustup target add $TARGET
- fi
+ mkdir qemu
+ curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 > qemu/qemu-system-arm
+ chmod +x qemu/qemu-system-arm
- mkdir qemu
- curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 > qemu/qemu-system-arm
- chmod +x qemu/qemu-system-arm
+ pip install linkchecker --user
+ fi
- # install mdbook
- curl -LSfs https://japaric.github.io/trust/install.sh | \
- sh -s -- --git rust-lang-nursery/mdbook --tag v0.2.1
+ # Download binary mdbook and add to path
+ curl -L https://github.com/rust-lang/mdBook/releases/download/v0.3.1/mdbook-v0.3.1-x86_64-unknown-linux-gnu.tar.gz > mdbook.tar.gz
+ tar -xf mdbook.tar.gz
+ mkdir -p mdbook-bin
+ mv mdbook mdbook-bin/
- pip install linkchecker --user
+ #install_crate mdbook 0.3.1
}
main
diff --git a/ci/script.sh b/ci/script.sh
index 7cda1e5e..6c099097 100644
--- a/ci/script.sh
+++ b/ci/script.sh
@@ -33,137 +33,191 @@ arm_example() {
main() {
local T=$TARGET
- local nightly=""
-
- if [ $TRAVIS_RUST_VERSION = nightly ]; then
- nightly="nightly"
- fi
mkdir -p ci/builds
- if [ $T = x86_64-unknown-linux-gnu ]; then
- # compile-fail and compile-pass tests
- case $TRAVIS_RUST_VERSION in
- nightly*)
- # TODO how to run a subset of these tests when timer-queue is disabled?
- cargo test --features "$nightly,timer-queue" --test compiletest --target $T
- esac
+ # Current MSRV cannot handle profiles, remove compilation optimisations
+ if [[ $TRAVIS_RUST_VERSION == 1.*.* ]]; then
+ echo "Removing optimisation profiles"
+ sed -i '/^\[profile.*build-override]$/,/^$/{/^#/!{/^$/!d}}' Cargo.toml
+ fi
- cargo check --target $T
- if [ $TARGET != thumbv6m-none-eabi ]; then
- cargo check --features "$nightly,timer-queue" --target $T
+ if [ $T = x86_64-unknown-linux-gnu ]; then
+ if [[ $TRAVIS_RUST_VERSION == 1.*.* ]]; then
+ # test on a fixed version (MSRV) to avoid problems with changes in rustc diagnostics
+ # compile-fail tests
+ cargo test --test single --target $T
fi
- if [ $TRAVIS_RUST_VERSION != nightly ]; then
- rm -f .cargo/config
- if [ $TARGET != thumbv6m-none-eabi ]; then
- cargo doc --features timer-queue
- else
+ if [ $TRAVIS_RUST_VERSION = nightly ]; then
+ # Tests where required MSRV > 1.36
+ #local exs=(
+ #)
+ #for ex in ${exs[@]}; do
+ # cargo check --example $ex --target $T
+ #done
+
+ # multi-core compile-pass tests
+ pushd heterogeneous
+ local exs=(
+ smallest
+ x-init-2
+ x-init
+ x-schedule
+ x-spawn
+ )
+ for ex in ${exs[@]}; do
+ cargo microamp --example $ex --target thumbv7m-none-eabi,thumbv6m-none-eabi --check
+ done
+
+ popd
+
+ else
+ if [ $TRAVIS_RUST_VERSION != nightly ]; then
+ rm -f .cargo/config
cargo doc
+ ( cd book/en && mdbook build )
+ ( cd book/ru && mdbook build )
+
+ local td=$(mktemp -d)
+ cp -r target/doc $td/api
+ mkdir $td/book
+ cp -r book/en/book $td/book/en
+ cp -r book/ru/book $td/book/ru
+ cp LICENSE-* $td/book/en
+ cp LICENSE-* $td/book/ru
+
+ linkchecker $td/book/en/
+ linkchecker $td/book/ru/
+ linkchecker $td/api/rtic/
+ linkchecker $td/api/cortex_m_rtic_macros/
fi
- ( cd book/en && mdbook build )
- ( cd book/ru && mdbook build )
-
- local td=$(mktemp -d)
- cp -r target/doc $td/api
- mkdir $td/book
- cp -r book/en/book $td/book/en
- cp -r book/ru/book $td/book/ru
- cp LICENSE-* $td/book/en
- cp LICENSE-* $td/book/ru
-
- linkchecker $td/book/en/
- linkchecker $td/book/ru/
- linkchecker $td/api/rtfm/
- linkchecker $td/api/cortex_m_rtfm_macros/
fi
+ cargo check --target $T
+ ( cd macros && cargo test --target $T )
+
return
fi
- cargo check --features "$nightly" --target $T --examples
- if [ $TARGET != thumbv6m-none-eabi ]; then
- cargo check --features "$nightly,timer-queue" --target $T --examples
+ if [ $TARGET = thumbv6m-none-eabi ]; then
+ cargo check --target $T --examples
+
+ # Check examples with specific features not compatible with MSRV
+ if [[ $TRAVIS_RUST_VERSION != 1.*.* ]]; then
+ cargo check --target $T --examples --features __min_r1_43
+ fi
+ else
+ cargo check --target $T --examples --features __v7
+
+ # Check examples with specific features not compatible with MSRV
+ if [[ $TRAVIS_RUST_VERSION != 1.*.* ]]; then
+ cargo check --target $T --examples --features __v7,__min_r1_43
+ fi
fi
+ cargo check -p homogeneous --target $T --examples
+
# run-pass tests
case $T in
thumbv6m-none-eabi | thumbv7m-none-eabi)
local exs=(
idle
init
- interrupt
+ hardware
+ preempt
binds
resource
lock
late
- static
+ only-shared-access
task
message
capacity
- singleton
-
types
not-send
not-sync
+ shared-with-init
generics
+ cfg
+ pool
ramfunc
)
for ex in ${exs[@]}; do
- if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then
- # LLD doesn't support this at the moment
+ if [ $ex = pool ]; then
+ if [ $TARGET = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ local td=$(mktemp -d)
+
+ cargo run --example $ex --target $TARGET --features __v7 >\
+ $td/pool.run
+ grep 'foo(0x2' $td/pool.run
+ grep 'bar(0x2' $td/pool.run
+ arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \
+ ci/builds/${ex}___v7_debug_1.hex
+
+ cargo run --example $ex --target $TARGET --features __v7 --release >\
+ $td/pool.run
+ grep 'foo(0x2' $td/pool.run
+ grep 'bar(0x2' $td/pool.run
+ arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \
+ ci/builds/${ex}___v7_release_1.hex
+
+ rm -rf $td
+
continue
fi
- if [ $ex != types ]; then
- arm_example "run" $ex "debug" "$nightly" "1"
- arm_example "run" $ex "release" "$nightly" "1"
+ if [ $ex = types ]; then
+ if [ $TARGET = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ arm_example "run" $ex "debug" "__v7" "1"
+ arm_example "run" $ex "release" "__v7" "1"
+
+ continue
fi
- if [ $TARGET != thumbv6m-none-eabi ]; then
- arm_example "run" $ex "debug" "$nightly,timer-queue" "1"
- arm_example "run" $ex "release" "$nightly,timer-queue" "1"
+ arm_example "run" $ex "debug" "" "1"
+ if [ $ex = types ]; then
+ arm_example "run" $ex "release" "" "1"
+ else
+ arm_example "build" $ex "release" "" "1"
fi
done
local built=()
cargo clean
for ex in ${exs[@]}; do
- if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then
- # LLD doesn't support this at the moment
- continue
- fi
-
- if [ $ex = singleton ]; then
- # singleton build is currently not reproducible due to
- # https://github.com/japaric/owned-singleton/issues/2
- continue
+ if [ $ex = types ] || [ $ex = pool ]; then
+ if [ $TARGET = thumbv6m-none-eabi ]; then
+ continue
+ fi
+
+ arm_example "build" $ex "debug" "__v7" "2"
+ cmp ci/builds/${ex}___v7_debug_1.hex \
+ ci/builds/${ex}___v7_debug_2.hex
+ arm_example "build" $ex "release" "__v7" "2"
+ cmp ci/builds/${ex}___v7_release_1.hex \
+ ci/builds/${ex}___v7_release_2.hex
+ else
+ arm_example "build" $ex "debug" "" "2"
+ cmp ci/builds/${ex}_debug_1.hex \
+ ci/builds/${ex}_debug_2.hex
+ arm_example "build" $ex "release" "" "2"
+ cmp ci/builds/${ex}_release_1.hex \
+ ci/builds/${ex}_release_2.hex
fi
- if [ $ex != types ]; then
- arm_example "build" $ex "debug" "$nightly" "2"
- cmp ci/builds/${ex}_${nightly/nightly/nightly_}debug_1.hex \
- ci/builds/${ex}_${nightly/nightly/nightly_}debug_2.hex
- arm_example "build" $ex "release" "$nightly" "2"
- cmp ci/builds/${ex}_${nightly/nightly/nightly_}release_1.hex \
- ci/builds/${ex}_${nightly/nightly/nightly_}release_2.hex
-
- built+=( $ex )
- fi
-
- if [ $TARGET != thumbv6m-none-eabi ]; then
- arm_example "build" $ex "debug" "$nightly,timer-queue" "2"
- cmp ci/builds/${ex}_${nightly}_timer-queue_debug_1.hex \
- ci/builds/${ex}_${nightly}_timer-queue_debug_2.hex
- arm_example "build" $ex "release" "$nightly,timer-queue" "2"
- cmp ci/builds/${ex}_${nightly}_timer-queue_release_1.hex \
- ci/builds/${ex}_${nightly}_timer-queue_release_2.hex
- fi
+ built+=( $ex )
done
( cd target/$TARGET/release/examples/ && size ${built[@]} )
diff --git a/examples/baseline.rs b/examples/baseline.rs
index fdf36838..3ab40dbb 100644
--- a/examples/baseline.rs
+++ b/examples/baseline.rs
@@ -5,47 +5,52 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
+use panic_semihosting as _;
// NOTE: does NOT properly work on QEMU
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
#[init(spawn = [foo])]
- fn init() {
- hprintln!("init(baseline = {:?})", start).unwrap();
+ fn init(cx: init::Context) -> init::LateResources {
+ // omitted: initialization of `CYCCNT`
+
+ hprintln!("init(baseline = {:?})", cx.start).unwrap();
// `foo` inherits the baseline of `init`: `Instant(0)`
- spawn.foo().unwrap();
+ cx.spawn.foo().unwrap();
+
+ init::LateResources {}
}
#[task(schedule = [foo])]
- fn foo() {
+ fn foo(cx: foo::Context) {
static mut ONCE: bool = true;
- hprintln!("foo(baseline = {:?})", scheduled).unwrap();
+ hprintln!("foo(baseline = {:?})", cx.scheduled).unwrap();
if *ONCE {
*ONCE = false;
- rtfm::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART0);
} else {
debug::exit(debug::EXIT_SUCCESS);
}
}
- #[interrupt(spawn = [foo])]
- fn UART0() {
- hprintln!("UART0(baseline = {:?})", start).unwrap();
+ #[task(binds = UART0, spawn = [foo])]
+ fn uart0(cx: uart0::Context) {
+ hprintln!("UART0(baseline = {:?})", cx.start).unwrap();
// `foo` inherits the baseline of `UART0`: its `start` time
- spawn.foo().unwrap();
+ cx.spawn.foo().unwrap();
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART1();
+ fn SSI0();
}
-};
+}
diff --git a/examples/binds.rs b/examples/binds.rs
index a8b386fb..42010ae2 100644
--- a/examples/binds.rs
+++ b/examples/binds.rs
@@ -5,35 +5,37 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
+use panic_semihosting as _;
// `examples/interrupt.rs` rewritten to use `binds`
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init]
- fn init() {
- rtfm::pend(Interrupt::UART0);
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
hprintln!("init").unwrap();
+
+ init::LateResources {}
}
#[idle]
- fn idle() -> ! {
+ fn idle(_: idle::Context) -> ! {
hprintln!("idle").unwrap();
- rtfm::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART0);
debug::exit(debug::EXIT_SUCCESS);
- loop {}
+ loop {
+ cortex_m::asm::nop();
+ }
}
- #[interrupt(binds = UART0)]
- fn foo() {
+ #[task(binds = UART0)]
+ fn foo(_: foo::Context) {
static mut TIMES: u32 = 0;
*TIMES += 1;
@@ -45,4 +47,4 @@ const APP: () = {
)
.unwrap();
}
-};
+}
diff --git a/examples/capacity.rs b/examples/capacity.rs
index a7132ba0..ba8b15b0 100644
--- a/examples/capacity.rs
+++ b/examples/capacity.rs
@@ -5,43 +5,45 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init]
- fn init() {
- rtfm::pend(Interrupt::UART0);
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+
+ init::LateResources {}
}
- #[interrupt(spawn = [foo, bar])]
- fn UART0() {
- spawn.foo(0).unwrap();
- spawn.foo(1).unwrap();
- spawn.foo(2).unwrap();
- spawn.foo(3).unwrap();
+ #[task(binds = UART0, spawn = [foo, bar])]
+ fn uart0(c: uart0::Context) {
+ c.spawn.foo(0).unwrap();
+ c.spawn.foo(1).unwrap();
+ c.spawn.foo(2).unwrap();
+ c.spawn.foo(3).unwrap();
- spawn.bar().unwrap();
+ c.spawn.bar().unwrap();
}
#[task(capacity = 4)]
- fn foo(x: u32) {
+ fn foo(_: foo::Context, x: u32) {
hprintln!("foo({})", x).unwrap();
}
#[task]
- fn bar() {
+ fn bar(_: bar::Context) {
hprintln!("bar").unwrap();
debug::exit(debug::EXIT_SUCCESS);
}
- // Interrupt handlers used to dispatch software tasks
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART1();
+ fn SSI0();
}
-};
+}
diff --git a/examples/cfg.rs b/examples/cfg.rs
index 3f4ca904..d49f54c7 100644
--- a/examples/cfg.rs
+++ b/examples/cfg.rs
@@ -5,40 +5,55 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
+use cortex_m_semihosting::debug;
#[cfg(debug_assertions)]
use cortex_m_semihosting::hprintln;
-use rtfm::app;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[cfg(debug_assertions)] // <- `true` when using the `dev` profile
+ #[init(0)]
+ count: u32,
+ }
-#[app(device = lm3s6965)]
-const APP: () = {
- #[cfg(debug_assertions)] // <- `true` when using the `dev` profile
- static mut COUNT: u32 = 0;
+ #[init(spawn = [foo])]
+ fn init(cx: init::Context) -> init::LateResources {
+ cx.spawn.foo().unwrap();
+ cx.spawn.foo().unwrap();
- #[init]
- fn init() {
- // ..
+ init::LateResources {}
+ }
+
+ #[idle]
+ fn idle(_: idle::Context) -> ! {
+ debug::exit(debug::EXIT_SUCCESS);
+
+ loop {
+ cortex_m::asm::nop();
+ }
}
- #[task(priority = 3, resources = [COUNT], spawn = [log])]
- fn foo() {
+ #[task(capacity = 2, resources = [count], spawn = [log])]
+ fn foo(_cx: foo::Context) {
#[cfg(debug_assertions)]
{
- *resources.COUNT += 1;
+ *_cx.resources.count += 1;
- spawn.log(*resources.COUNT).ok();
+ _cx.spawn.log(*_cx.resources.count).unwrap();
}
// this wouldn't compile in `release` mode
- // *resources.COUNT += 1;
+ // *_cx.resources.count += 1;
// ..
}
#[cfg(debug_assertions)]
- #[task]
- fn log(n: u32) {
+ #[task(capacity = 2)]
+ fn log(_: log::Context, n: u32) {
hprintln!(
"foo has been called {} time{}",
n,
@@ -47,8 +62,11 @@ const APP: () = {
.ok();
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
- fn UART1();
+ fn SSI0();
+ fn QEI0();
}
-};
+}
diff --git a/examples/destructure.rs b/examples/destructure.rs
new file mode 100644
index 00000000..e7c53237
--- /dev/null
+++ b/examples/destructure.rs
@@ -0,0 +1,50 @@
+//! examples/destructure.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::hprintln;
+use lm3s6965::Interrupt;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ // Some resources to work with
+ #[init(0)]
+ a: u32,
+ #[init(0)]
+ b: u32,
+ #[init(0)]
+ c: u32,
+ }
+
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART1);
+
+ init::LateResources {}
+ }
+
+ // Direct destructure
+ #[task(binds = UART0, resources = [a, b, c])]
+ fn uart0(cx: uart0::Context) {
+ let a = cx.resources.a;
+ let b = cx.resources.b;
+ let c = cx.resources.c;
+
+ hprintln!("UART0: a = {}, b = {}, c = {}", a, b, c).unwrap();
+ }
+
+ // De-structure-ing syntax
+ #[task(binds = UART1, resources = [a, b, c])]
+ fn uart1(cx: uart1::Context) {
+ let uart1::Resources { a, b, c } = cx.resources;
+
+ hprintln!("UART0: a = {}, b = {}, c = {}", a, b, c).unwrap();
+ }
+}
diff --git a/examples/double_schedule.rs b/examples/double_schedule.rs
new file mode 100644
index 00000000..b1b78b80
--- /dev/null
+++ b/examples/double_schedule.rs
@@ -0,0 +1,39 @@
+//! examples/double_schedule.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use panic_semihosting as _;
+use rtic::cyccnt::U32Ext;
+
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
+
+ #[resources]
+ struct Resources {
+ nothing: (),
+ }
+
+ #[init(spawn = [task1])]
+ fn init(cx: init::Context) -> init::LateResources {
+ cx.spawn.task1().ok();
+
+ init::LateResources { nothing: () }
+ }
+
+ #[task(schedule = [task2])]
+ fn task1(_cx: task1::Context) {
+ _cx.schedule.task2(_cx.scheduled + 100.cycles()).ok();
+ }
+
+ #[task(schedule = [task1])]
+ fn task2(_cx: task2::Context) {
+ _cx.schedule.task1(_cx.scheduled + 100.cycles()).ok();
+ }
+
+ extern "C" {
+ fn SSI0();
+ }
+}
diff --git a/examples/generics.rs b/examples/generics.rs
index c8ce8393..3107dd11 100644
--- a/examples/generics.rs
+++ b/examples/generics.rs
@@ -5,58 +5,64 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::{app, Mutex};
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut SHARED: u32 = 0;
+use panic_semihosting as _;
+use rtic::{Exclusive, Mutex};
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[init(0)]
+ shared: u32,
+ }
#[init]
- fn init() {
- rtfm::pend(Interrupt::UART0);
- rtfm::pend(Interrupt::UART1);
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART1);
+
+ init::LateResources {}
}
- #[interrupt(resources = [SHARED])]
- fn UART0() {
+ #[task(binds = UART0, resources = [shared])]
+ fn uart0(c: uart0::Context) {
static mut STATE: u32 = 0;
hprintln!("UART0(STATE = {})", *STATE).unwrap();
- advance(STATE, resources.SHARED);
+ // second argument has type `resources::shared`
+ advance(STATE, c.resources.shared);
- rtfm::pend(Interrupt::UART1);
+ rtic::pend(Interrupt::UART1);
debug::exit(debug::EXIT_SUCCESS);
}
- #[interrupt(priority = 2, resources = [SHARED])]
- fn UART1() {
+ #[task(binds = UART1, priority = 2, resources = [shared])]
+ fn uart1(c: uart1::Context) {
static mut STATE: u32 = 0;
hprintln!("UART1(STATE = {})", *STATE).unwrap();
- // just to show that `SHARED` can be accessed directly and ..
- *resources.SHARED += 0;
- // .. also through a (no-op) `lock`
- resources.SHARED.lock(|shared| *shared += 0);
+ // just to show that `shared` can be accessed directly
+ *c.resources.shared += 0;
- advance(STATE, resources.SHARED);
+ // second argument has type `Exclusive<u32>`
+ advance(STATE, Exclusive(c.resources.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<T = u32>) {
*state += 1;
- let (old, new) = shared.lock(|shared| {
+ let (old, new) = shared.lock(|shared: &mut u32| {
let old = *shared;
*shared += *state;
(old, *shared)
});
- hprintln!("SHARED: {} -> {}", old, new).unwrap();
+ hprintln!("shared: {} -> {}", old, new).unwrap();
}
diff --git a/examples/interrupt.rs b/examples/hardware.rs
index 3c669d9e..f6a2d375 100644
--- a/examples/interrupt.rs
+++ b/examples/hardware.rs
@@ -1,42 +1,44 @@
-//! examples/interrupt.rs
+//! examples/hardware.rs
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init]
- fn init() {
+ fn init(_: init::Context) -> init::LateResources {
// Pends the UART0 interrupt but its handler won't run until *after*
// `init` returns because interrupts are disabled
- rtfm::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend
hprintln!("init").unwrap();
+
+ init::LateResources {}
}
#[idle]
- fn idle() -> ! {
+ fn idle(_: idle::Context) -> ! {
// interrupts are enabled again; the `UART0` handler runs at this point
hprintln!("idle").unwrap();
- rtfm::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART0);
debug::exit(debug::EXIT_SUCCESS);
- loop {}
+ loop {
+ cortex_m::asm::nop();
+ }
}
- #[interrupt]
- fn UART0() {
+ #[task(binds = UART0)]
+ fn uart0(_: uart0::Context) {
static mut TIMES: u32 = 0;
// Safe access to local `static mut` variable
@@ -49,4 +51,4 @@ const APP: () = {
)
.unwrap();
}
-};
+}
diff --git a/examples/idle.rs b/examples/idle.rs
index 1f21a37f..58c3c87d 100644
--- a/examples/idle.rs
+++ b/examples/idle.rs
@@ -5,20 +5,20 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init]
- fn init() {
+ fn init(_: init::Context) -> init::LateResources {
hprintln!("init").unwrap();
+
+ init::LateResources {}
}
#[idle]
- fn idle() -> ! {
+ fn idle(_: idle::Context) -> ! {
static mut X: u32 = 0;
// Safe access to local `static mut` variable
@@ -28,6 +28,8 @@ const APP: () = {
debug::exit(debug::EXIT_SUCCESS);
- loop {}
+ loop {
+ cortex_m::asm::nop();
+ }
}
-};
+}
diff --git a/examples/init.rs b/examples/init.rs
index be6cfe3e..6ac284a1 100644
--- a/examples/init.rs
+++ b/examples/init.rs
@@ -5,28 +5,32 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965, peripherals = true)]
+mod app {
#[init]
- fn init() {
+ fn init(cx: init::Context) -> init::LateResources {
static mut X: u32 = 0;
// Cortex-M peripherals
- let _core: rtfm::Peripherals = core;
+ let _core: cortex_m::Peripherals = cx.core;
// Device specific peripherals
- let _device: lm3s6965::Peripherals = device;
+ let _device: lm3s6965::Peripherals = cx.device;
// Safe access to local `static mut` variable
let _x: &'static mut u32 = X;
+ // Access to the critical section token,
+ // to indicate that this is a critical seciton
+ let _cs_token: bare_metal::CriticalSection = cx.cs;
+
hprintln!("init").unwrap();
debug::exit(debug::EXIT_SUCCESS);
+
+ init::LateResources {}
}
-};
+}
diff --git a/examples/late.rs b/examples/late.rs
index 622008a7..761c68f5 100644
--- a/examples/late.rs
+++ b/examples/late.rs
@@ -5,50 +5,53 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use heapless::{
consts::*,
+ i,
spsc::{Consumer, Producer, Queue},
};
use lm3s6965::Interrupt;
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ use heapless::{
+ consts::*,
+ spsc::{Consumer, Producer},
+ };
// Late resources
- static mut P: Producer<'static, u32, U4> = ();
- static mut C: Consumer<'static, u32, U4> = ();
+ #[resources]
+ struct Resources {
+ p: Producer<'static, u32, U4>,
+ c: Consumer<'static, u32, U4>,
+ }
#[init]
- fn init() -> init::LateResources {
- // NOTE: we use `Option` here to work around the lack of
- // a stable `const` constructor
- static mut Q: Option<Queue<u32, U4>> = None;
+ fn init(_: init::Context) -> init::LateResources {
+ static mut Q: Queue<u32, U4> = Queue(i::Queue::new());
- *Q = Some(Queue::new());
- let (p, c) = Q.as_mut().unwrap().split();
+ let (p, c) = Q.split();
// Initialization of late resources
- init::LateResources { P: p, C: c }
+ init::LateResources { p, c }
}
- #[idle(resources = [C])]
- fn idle() -> ! {
+ #[idle(resources = [c])]
+ fn idle(c: idle::Context) -> ! {
loop {
- if let Some(byte) = resources.C.dequeue() {
+ if let Some(byte) = c.resources.c.dequeue() {
hprintln!("received message: {}", byte).unwrap();
debug::exit(debug::EXIT_SUCCESS);
} else {
- rtfm::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART0);
}
}
}
- #[interrupt(resources = [P])]
- fn UART0() {
- resources.P.enqueue(42).unwrap();
+ #[task(binds = UART0, resources = [p])]
+ fn uart0(c: uart0::Context) {
+ c.resources.p.enqueue(42).unwrap();
}
-};
+}
diff --git a/examples/lock.rs b/examples/lock.rs
index 4ca862e3..669b1aed 100644
--- a/examples/lock.rs
+++ b/examples/lock.rs
@@ -5,38 +5,42 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut SHARED: u32 = 0;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[init(0)]
+ shared: u32,
+ }
#[init]
- fn init() {
- rtfm::pend(Interrupt::GPIOA);
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::GPIOA);
+
+ init::LateResources {}
}
// when omitted priority is assumed to be `1`
- #[interrupt(resources = [SHARED])]
- fn GPIOA() {
+ #[task(binds = GPIOA, resources = [shared])]
+ fn gpioa(mut c: gpioa::Context) {
hprintln!("A").unwrap();
// the lower priority task requires a critical section to access the data
- resources.SHARED.lock(|shared| {
+ c.resources.shared.lock(|shared| {
// data can only be modified within this critical section (closure)
*shared += 1;
// GPIOB will *not* run right now due to the critical section
- rtfm::pend(Interrupt::GPIOB);
+ rtic::pend(Interrupt::GPIOB);
- hprintln!("B - SHARED = {}", *shared).unwrap();
+ hprintln!("B - shared = {}", *shared).unwrap();
- // GPIOC does not contend for `SHARED` so it's allowed to run now
- rtfm::pend(Interrupt::GPIOC);
+ // GPIOC does not contend for `shared` so it's allowed to run now
+ rtic::pend(Interrupt::GPIOC);
});
// critical section is over: GPIOB can now start
@@ -46,16 +50,16 @@ const APP: () = {
debug::exit(debug::EXIT_SUCCESS);
}
- #[interrupt(priority = 2, resources = [SHARED])]
- fn GPIOB() {
+ #[task(binds = GPIOB, priority = 2, resources = [shared])]
+ fn gpiob(c: gpiob::Context) {
// the higher priority task does *not* need a critical section
- *resources.SHARED += 1;
+ *c.resources.shared += 1;
- hprintln!("D - SHARED = {}", *resources.SHARED).unwrap();
+ hprintln!("D - shared = {}", *c.resources.shared).unwrap();
}
- #[interrupt(priority = 3)]
- fn GPIOC() {
+ #[task(binds = GPIOC, priority = 3)]
+ fn gpioc(_: gpioc::Context) {
hprintln!("C").unwrap();
}
-};
+}
diff --git a/examples/message.rs b/examples/message.rs
index b5d68a60..f9736728 100644
--- a/examples/message.rs
+++ b/examples/message.rs
@@ -5,47 +5,50 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init(spawn = [foo])]
- fn init() {
- spawn.foo(/* no message */).unwrap();
+ fn init(c: init::Context) -> init::LateResources {
+ c.spawn.foo(/* no message */).unwrap();
+
+ init::LateResources {}
}
#[task(spawn = [bar])]
- fn foo() {
+ fn foo(c: foo::Context) {
static mut COUNT: u32 = 0;
hprintln!("foo").unwrap();
- spawn.bar(*COUNT).unwrap();
+ c.spawn.bar(*COUNT).unwrap();
*COUNT += 1;
}
#[task(spawn = [baz])]
- fn bar(x: u32) {
+ fn bar(c: bar::Context, x: u32) {
hprintln!("bar({})", x).unwrap();
- spawn.baz(x + 1, x + 2).unwrap();
+ c.spawn.baz(x + 1, x + 2).unwrap();
}
#[task(spawn = [foo])]
- fn baz(x: u32, y: u32) {
+ fn baz(c: baz::Context, x: u32, y: u32) {
hprintln!("baz({}, {})", x, y).unwrap();
if x + y > 4 {
debug::exit(debug::EXIT_SUCCESS);
}
- spawn.foo().unwrap();
+ c.spawn.foo().unwrap();
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
+ fn SSI0();
}
-};
+}
diff --git a/examples/not-send.rs b/examples/not-send.rs
index be78c332..18071fc5 100644
--- a/examples/not-send.rs
+++ b/examples/not-send.rs
@@ -5,54 +5,64 @@
#![no_main]
#![no_std]
-extern crate panic_halt;
-
use core::marker::PhantomData;
use cortex_m_semihosting::debug;
-use rtfm::app;
+use panic_halt as _;
+use rtic::app;
pub struct NotSend {
_0: PhantomData<*const ()>,
}
#[app(device = lm3s6965)]
-const APP: () = {
- static mut SHARED: Option<NotSend> = None;
+mod app {
+ use super::NotSend;
+
+ #[resources]
+ struct Resources {
+ #[init(None)]
+ shared: Option<NotSend>,
+ }
#[init(spawn = [baz, quux])]
- fn init() {
- spawn.baz().unwrap();
- spawn.quux().unwrap();
+ fn init(c: init::Context) -> init::LateResources {
+ c.spawn.baz().unwrap();
+ c.spawn.quux().unwrap();
+
+ init::LateResources {}
}
#[task(spawn = [bar])]
- fn foo() {
+ fn foo(c: foo::Context) {
// scenario 1: message passed to task that runs at the same priority
- spawn.bar(NotSend { _0: PhantomData }).ok();
+ c.spawn.bar(NotSend { _0: PhantomData }).ok();
}
#[task]
- fn bar(_x: NotSend) {
+ fn bar(_: bar::Context, _x: NotSend) {
// scenario 1
}
- #[task(priority = 2, resources = [SHARED])]
- fn baz() {
+ #[task(priority = 2, resources = [shared])]
+ fn baz(c: baz::Context) {
// scenario 2: resource shared between tasks that run at the same priority
- *resources.SHARED = Some(NotSend { _0: PhantomData });
+ *c.resources.shared = Some(NotSend { _0: PhantomData });
}
- #[task(priority = 2, resources = [SHARED])]
- fn quux() {
+ #[task(priority = 2, resources = [shared])]
+ fn quux(c: quux::Context) {
// scenario 2
- let _not_send = resources.SHARED.take().unwrap();
+ let _not_send = c.resources.shared.take().unwrap();
debug::exit(debug::EXIT_SUCCESS);
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
- fn UART1();
+ fn SSI0();
+ fn QEI0();
}
-};
+}
diff --git a/examples/not-sync.rs b/examples/not-sync.rs
index d94e0a04..75412e63 100644
--- a/examples/not-sync.rs
+++ b/examples/not-sync.rs
@@ -5,37 +5,47 @@
#![no_main]
#![no_std]
-extern crate panic_halt;
-
use core::marker::PhantomData;
use cortex_m_semihosting::debug;
-use rtfm::app;
+use panic_halt as _;
pub struct NotSync {
_0: PhantomData<*const ()>,
}
-#[app(device = lm3s6965)]
-const APP: () = {
- static SHARED: NotSync = NotSync { _0: PhantomData };
+#[rtic::app(device = lm3s6965)]
+mod app {
+ use super::NotSync;
+ use core::marker::PhantomData;
+
+ #[resources]
+ struct Resources {
+ #[init(NotSync { _0: PhantomData })]
+ shared: NotSync,
+ }
#[init]
- fn init() {
+ fn init(_: init::Context) -> init::LateResources {
debug::exit(debug::EXIT_SUCCESS);
+
+ init::LateResources {}
}
- #[task(resources = [SHARED])]
- fn foo() {
- let _: &NotSync = resources.SHARED;
+ #[task(resources = [&shared])]
+ fn foo(c: foo::Context) {
+ let _: &NotSync = c.resources.shared;
}
- #[task(resources = [SHARED])]
- fn bar() {
- let _: &NotSync = resources.SHARED;
+ #[task(resources = [&shared])]
+ fn bar(c: bar::Context) {
+ let _: &NotSync = c.resources.shared;
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
+ fn SSI0();
}
-};
+}
diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs
new file mode 100644
index 00000000..91d0b7ad
--- /dev/null
+++ b/examples/only-shared-access.rs
@@ -0,0 +1,39 @@
+//! examples/static.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::{debug, hprintln};
+use lm3s6965::Interrupt;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ key: u32,
+ }
+
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART1);
+
+ init::LateResources { key: 0xdeadbeef }
+ }
+
+ #[task(binds = UART0, resources = [&key])]
+ fn uart0(cx: uart0::Context) {
+ let key: &u32 = cx.resources.key;
+ hprintln!("UART0(key = {:#x})", key).unwrap();
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(binds = UART1, priority = 2, resources = [&key])]
+ fn uart1(cx: uart1::Context) {
+ hprintln!("UART1(key = {:#x})", cx.resources.key).unwrap();
+ }
+}
diff --git a/examples/periodic.rs b/examples/periodic.rs
index ba2b4933..d3aedd32 100644
--- a/examples/periodic.rs
+++ b/examples/periodic.rs
@@ -5,30 +5,37 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::hprintln;
-use rtfm::{app, Instant};
+use panic_semihosting as _;
+use rtic::cyccnt::{Instant, U32Ext};
const PERIOD: u32 = 8_000_000;
// NOTE: does NOT work on QEMU!
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
+
#[init(schedule = [foo])]
- fn init() {
- schedule.foo(Instant::now() + PERIOD.cycles()).unwrap();
+ fn init(cx: init::Context) -> init::LateResources {
+ // omitted: initialization of `CYCCNT`
+
+ cx.schedule.foo(cx.start + PERIOD.cycles()).unwrap();
+
+ init::LateResources {}
}
#[task(schedule = [foo])]
- fn foo() {
+ fn foo(cx: foo::Context) {
let now = Instant::now();
- hprintln!("foo(scheduled = {:?}, now = {:?})", scheduled, now).unwrap();
+ hprintln!("foo(scheduled = {:?}, now = {:?})", cx.scheduled, now).unwrap();
- schedule.foo(scheduled + PERIOD.cycles()).unwrap();
+ cx.schedule.foo(cx.scheduled + PERIOD.cycles()).unwrap();
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
+ fn SSI0();
}
-};
+}
diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs
new file mode 100644
index 00000000..09f92427
--- /dev/null
+++ b/examples/peripherals-taken.rs
@@ -0,0 +1,18 @@
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ assert!(cortex_m::Peripherals::take().is_none());
+ debug::exit(debug::EXIT_SUCCESS);
+
+ init::LateResources {}
+ }
+}
diff --git a/examples/pool.rs b/examples/pool.rs
new file mode 100644
index 00000000..cdbabca7
--- /dev/null
+++ b/examples/pool.rs
@@ -0,0 +1,76 @@
+//! examples/pool.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::{debug, hprintln};
+use heapless::{
+ pool,
+ pool::singleton::{Box, Pool},
+};
+use lm3s6965::Interrupt;
+use panic_semihosting as _;
+use rtic::app;
+
+// Declare a pool of 128-byte memory blocks
+pool!(P: [u8; 128]);
+
+#[app(device = lm3s6965)]
+mod app {
+ use crate::Box;
+
+ // Import the memory pool into scope
+ use super::P;
+
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ static mut MEMORY: [u8; 512] = [0; 512];
+
+ // Increase the capacity of the memory pool by ~4
+ P::grow(MEMORY);
+
+ rtic::pend(Interrupt::I2C0);
+
+ init::LateResources {}
+ }
+
+ #[task(binds = I2C0, priority = 2, spawn = [foo, bar])]
+ fn i2c0(c: i2c0::Context) {
+ // claim a memory block, leave it uninitialized and ..
+ let x = P::alloc().unwrap().freeze();
+
+ // .. send it to the `foo` task
+ c.spawn.foo(x).ok().unwrap();
+
+ // send another block to the task `bar`
+ c.spawn.bar(P::alloc().unwrap().freeze()).ok().unwrap();
+ }
+
+ #[task]
+ fn foo(_: foo::Context, x: Box<P>) {
+ hprintln!("foo({:?})", x.as_ptr()).unwrap();
+
+ // explicitly return the block to the pool
+ drop(x);
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(priority = 2)]
+ fn bar(_: bar::Context, x: Box<P>) {
+ hprintln!("bar({:?})", x.as_ptr()).unwrap();
+
+ // this is done automatically so we can omit the call to `drop`
+ // drop(x);
+ }
+
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
+ extern "C" {
+ fn SSI0();
+ fn QEI0();
+ }
+}
diff --git a/examples/preempt.rs b/examples/preempt.rs
new file mode 100644
index 00000000..f6fc4b05
--- /dev/null
+++ b/examples/preempt.rs
@@ -0,0 +1,39 @@
+//! examples/preempt.rs
+
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::{debug, hprintln};
+use lm3s6965::Interrupt;
+use panic_semihosting as _;
+use rtic::app;
+
+#[app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::GPIOA);
+
+ init::LateResources {}
+ }
+
+ #[task(binds = GPIOA, priority = 1)]
+ fn gpioa(_: gpioa::Context) {
+ hprintln!("GPIOA - start").unwrap();
+ rtic::pend(Interrupt::GPIOC);
+ hprintln!("GPIOA - end").unwrap();
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(binds = GPIOB, priority = 2)]
+ fn gpiob(_: gpiob::Context) {
+ hprintln!(" GPIOB").unwrap();
+ }
+
+ #[task(binds = GPIOC, priority = 2)]
+ fn gpioc(_: gpioc::Context) {
+ hprintln!(" GPIOC - start").unwrap();
+ rtic::pend(Interrupt::GPIOB);
+ hprintln!(" GPIOC - end").unwrap();
+ }
+}
diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs
index 37ea82a7..5ff167a3 100644
--- a/examples/ramfunc.rs
+++ b/examples/ramfunc.rs
@@ -5,21 +5,21 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init(spawn = [bar])]
- fn init() {
- spawn.bar().unwrap();
+ fn init(c: init::Context) -> init::LateResources {
+ c.spawn.bar().unwrap();
+
+ init::LateResources {}
}
#[inline(never)]
#[task]
- fn foo() {
+ fn foo(_: foo::Context) {
hprintln!("foo").unwrap();
debug::exit(debug::EXIT_SUCCESS);
@@ -29,8 +29,8 @@ const APP: () = {
#[inline(never)]
#[link_section = ".data.bar"]
#[task(priority = 2, spawn = [foo])]
- fn bar() {
- spawn.foo().unwrap();
+ fn bar(c: bar::Context) {
+ c.spawn.foo().unwrap();
}
extern "C" {
@@ -40,4 +40,4 @@ const APP: () = {
#[link_section = ".data.UART1"]
fn UART1();
}
-};
+}
diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs
new file mode 100644
index 00000000..a5bd0ddf
--- /dev/null
+++ b/examples/resource-user-struct.rs
@@ -0,0 +1,63 @@
+//! examples/resource.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::{debug, hprintln};
+use lm3s6965::Interrupt;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ // A resource
+ #[init(0)]
+ shared: u32,
+ }
+
+ // Should not collide with the struct above
+ #[allow(dead_code)]
+ struct Resources2 {
+ // A resource
+ shared: u32,
+ }
+
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART1);
+
+ init::LateResources {}
+ }
+
+ // `shared` cannot be accessed from this context
+ #[idle]
+ fn idle(_cx: idle::Context) -> ! {
+ debug::exit(debug::EXIT_SUCCESS);
+
+ // error: no `resources` field in `idle::Context`
+ // _cx.resources.shared += 1;
+
+ loop {}
+ }
+
+ // `shared` can be accessed from this context
+ #[task(binds = UART0, resources = [shared])]
+ fn uart0(cx: uart0::Context) {
+ let shared: &mut u32 = cx.resources.shared;
+ *shared += 1;
+
+ hprintln!("UART0: shared = {}", shared).unwrap();
+ }
+
+ // `shared` can be accessed from this context
+ #[task(binds = UART1, resources = [shared])]
+ fn uart1(cx: uart1::Context) {
+ *cx.resources.shared += 1;
+
+ hprintln!("UART1: shared = {}", cx.resources.shared).unwrap();
+ }
+}
diff --git a/examples/resource.rs b/examples/resource.rs
index 5ddab9e8..273af26a 100644
--- a/examples/resource.rs
+++ b/examples/resource.rs
@@ -5,46 +5,54 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- // A resource
- static mut SHARED: u32 = 0;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ // A resource
+ #[init(0)]
+ shared: u32,
+ }
#[init]
- fn init() {
- rtfm::pend(Interrupt::UART0);
- rtfm::pend(Interrupt::UART1);
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(Interrupt::UART0);
+ rtic::pend(Interrupt::UART1);
+
+ init::LateResources {}
}
+ // `shared` cannot be accessed from this context
#[idle]
- fn idle() -> ! {
+ fn idle(_cx: idle::Context) -> ! {
debug::exit(debug::EXIT_SUCCESS);
- // error: `SHARED` can't be accessed from this context
- // SHARED += 1;
+ // error: no `resources` field in `idle::Context`
+ // _cx.resources.shared += 1;
- loop {}
+ loop {
+ cortex_m::asm::nop();
+ }
}
- // `SHARED` can be access from this context
- #[interrupt(resources = [SHARED])]
- fn UART0() {
- *resources.SHARED += 1;
+ // `shared` can be accessed from this context
+ #[task(binds = UART0, resources = [shared])]
+ fn uart0(cx: uart0::Context) {
+ let shared: &mut u32 = cx.resources.shared;
+ *shared += 1;
- hprintln!("UART0: SHARED = {}", resources.SHARED).unwrap();
+ hprintln!("UART0: shared = {}", shared).unwrap();
}
- // `SHARED` can be access from this context
- #[interrupt(resources = [SHARED])]
- fn UART1() {
- *resources.SHARED += 1;
+ // `shared` can be accessed from this context
+ #[task(binds = UART1, resources = [shared])]
+ fn uart1(cx: uart1::Context) {
+ *cx.resources.shared += 1;
- hprintln!("UART1: SHARED = {}", resources.SHARED).unwrap();
+ hprintln!("UART1: shared = {}", cx.resources.shared).unwrap();
}
-};
+}
diff --git a/examples/schedule.rs b/examples/schedule.rs
index fd633473..7e6adc1a 100644
--- a/examples/schedule.rs
+++ b/examples/schedule.rs
@@ -5,38 +5,51 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
+use cortex_m::peripheral::DWT;
use cortex_m_semihosting::hprintln;
-use rtfm::{app, Instant};
+use panic_halt as _;
+use rtic::cyccnt::{Instant, U32Ext as _};
// NOTE: does NOT work on QEMU!
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
#[init(schedule = [foo, bar])]
- fn init() {
- let now = Instant::now();
+ fn init(mut cx: init::Context) -> init::LateResources {
+ // Initialize (enable) the monotonic timer (CYCCNT)
+ cx.core.DCB.enable_trace();
+ // required on Cortex-M7 devices that software lock the DWT (e.g. STM32F7)
+ DWT::unlock();
+ cx.core.DWT.enable_cycle_counter();
+
+ // semantically, the monotonic timer is frozen at time "zero" during `init`
+ // NOTE do *not* call `Instant::now` in this context; it will return a nonsense value
+ let now = cx.start; // the start time of the system
hprintln!("init @ {:?}", now).unwrap();
// Schedule `foo` to run 8e6 cycles (clock cycles) in the future
- schedule.foo(now + 8_000_000.cycles()).unwrap();
+ cx.schedule.foo(now + 8_000_000.cycles()).unwrap();
// Schedule `bar` to run 4e6 cycles in the future
- schedule.bar(now + 4_000_000.cycles()).unwrap();
+ cx.schedule.bar(now + 4_000_000.cycles()).unwrap();
+
+ init::LateResources {}
}
#[task]
- fn foo() {
+ fn foo(_: foo::Context) {
hprintln!("foo @ {:?}", Instant::now()).unwrap();
}
#[task]
- fn bar() {
+ fn bar(_: bar::Context) {
hprintln!("bar @ {:?}", Instant::now()).unwrap();
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
+ fn SSI0();
}
-};
+}
diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs
new file mode 100644
index 00000000..85c72761
--- /dev/null
+++ b/examples/shared-with-init.rs
@@ -0,0 +1,45 @@
+//! `examples/shared-with-init.rs`
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use panic_halt as _;
+use rtic::app;
+
+pub struct MustBeSend;
+
+#[app(device = lm3s6965)]
+mod app {
+ use super::MustBeSend;
+
+ #[resources]
+ struct Resources {
+ #[init(None)]
+ shared: Option<MustBeSend>,
+ }
+
+ #[init(resources = [shared])]
+ fn init(c: init::Context) -> init::LateResources {
+ // this `message` will be sent to task `UART0`
+ let message = MustBeSend;
+ *c.resources.shared = Some(message);
+
+ rtic::pend(Interrupt::UART0);
+
+ init::LateResources {}
+ }
+
+ #[task(binds = UART0, resources = [shared])]
+ fn uart0(c: uart0::Context) {
+ if let Some(message) = c.resources.shared.take() {
+ // `message` has been received
+ drop(message);
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+ }
+}
diff --git a/examples/singleton.rs b/examples/singleton.rs
deleted file mode 100644
index 9e48e541..00000000
--- a/examples/singleton.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-//! examples/singleton.rs
-
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_main]
-#![no_std]
-
-extern crate panic_semihosting;
-
-use alloc_singleton::stable::pool::{Box, Pool};
-use cortex_m_semihosting::{debug, hprintln};
-use lm3s6965::Interrupt;
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[Singleton(Send)]
- static mut M: [u32; 2] = [0; 2];
-
- static mut P: Pool<M> = ();
-
- #[init(resources = [M])]
- fn init() -> init::LateResources {
- rtfm::pend(Interrupt::I2C0);
-
- init::LateResources {
- P: Pool::new(resources.M),
- }
- }
-
- #[interrupt(
- priority = 2,
- resources = [P],
- spawn = [foo, bar],
- )]
- fn I2C0() {
- spawn.foo(resources.P.alloc(1).unwrap()).unwrap();
- spawn.bar(resources.P.alloc(2).unwrap()).unwrap();
- }
-
- #[task(resources = [P])]
- fn foo(x: Box<M>) {
- hprintln!("foo({})", x).unwrap();
-
- resources.P.lock(|p| p.dealloc(x));
-
- debug::exit(debug::EXIT_SUCCESS);
- }
-
- #[task(priority = 2, resources = [P])]
- fn bar(x: Box<M>) {
- hprintln!("bar({})", x).unwrap();
-
- resources.P.dealloc(x);
- }
-
- extern "C" {
- fn UART0();
- fn UART1();
- }
-};
diff --git a/examples/smallest.rs b/examples/smallest.rs
index e4d86be9..b8cbf87e 100644
--- a/examples/smallest.rs
+++ b/examples/smallest.rs
@@ -1,17 +1,10 @@
//! examples/smallest.rs
-#![deny(unsafe_code)]
-#![deny(warnings)]
#![no_main]
#![no_std]
-// panic-handler crate
-extern crate panic_semihosting;
-
-use rtfm::app;
+use panic_semihosting as _; // panic handler
+use rtic::app;
#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-};
+mod app {}
diff --git a/examples/static.rs b/examples/static.rs
deleted file mode 100644
index 0309b681..00000000
--- a/examples/static.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-//! examples/static.rs
-
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_main]
-#![no_std]
-
-extern crate panic_semihosting;
-
-use cortex_m_semihosting::{debug, hprintln};
-use lm3s6965::Interrupt;
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static KEY: u32 = ();
-
- #[init]
- fn init() -> init::LateResources {
- rtfm::pend(Interrupt::UART0);
- rtfm::pend(Interrupt::UART1);
-
- init::LateResources { KEY: 0xdeadbeef }
- }
-
- #[interrupt(resources = [KEY])]
- fn UART0() {
- hprintln!("UART0(KEY = {:#x})", resources.KEY).unwrap();
-
- debug::exit(debug::EXIT_SUCCESS);
- }
-
- #[interrupt(priority = 2, resources = [KEY])]
- fn UART1() {
- hprintln!("UART1(KEY = {:#x})", resources.KEY).unwrap();
- }
-};
diff --git a/examples/t-binds.rs b/examples/t-binds.rs
new file mode 100644
index 00000000..3ca4c66e
--- /dev/null
+++ b/examples/t-binds.rs
@@ -0,0 +1,34 @@
+//! [compile-pass] Check that `binds` works as advertised
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ init::LateResources {}
+ }
+
+ // Cortex-M exception
+ #[task(binds = SVCall)]
+ fn foo(c: foo::Context) {
+ foo_trampoline(c)
+ }
+
+ // LM3S6965 interrupt
+ #[task(binds = UART0)]
+ fn bar(c: bar::Context) {
+ bar_trampoline(c)
+ }
+}
+
+#[allow(dead_code)]
+fn foo_trampoline(_: foo::Context) {}
+
+#[allow(dead_code)]
+fn bar_trampoline(_: bar::Context) {}
diff --git a/examples/t-cfg-resources.rs b/examples/t-cfg-resources.rs
new file mode 100644
index 00000000..61eb4c7b
--- /dev/null
+++ b/examples/t-cfg-resources.rs
@@ -0,0 +1,36 @@
+//! [compile-pass] check that `#[cfg]` attributes applied on resources work
+//!
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ // A resource
+ #[init(0)]
+ shared: u32,
+ // A conditionally compiled resource behind feature_x
+ #[cfg(feature = "feature_x")]
+ x: u32,
+ dummy: (), // dummy such that we have at least one late resource
+ }
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ init::LateResources {
+ // The feature needs to be applied everywhere x is defined or used
+ #[cfg(feature = "feature_x")]
+ x: 0,
+ dummy: (), // dummy such that we have at least one late resource
+ }
+ }
+
+ #[idle]
+ fn idle(_cx: idle::Context) -> ! {
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+}
diff --git a/examples/t-cfg.rs b/examples/t-cfg.rs
new file mode 100644
index 00000000..3da20d4e
--- /dev/null
+++ b/examples/t-cfg.rs
@@ -0,0 +1,58 @@
+//! [compile-pass] check that `#[cfg]` attributes are respected
+
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[cfg(never)]
+ #[init(0)]
+ foo: u32,
+ }
+
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ #[cfg(never)]
+ static mut BAR: u32 = 0;
+
+ init::LateResources {}
+ }
+
+ #[idle]
+ fn idle(_: idle::Context) -> ! {
+ #[cfg(never)]
+ static mut BAR: u32 = 0;
+
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+
+ #[task(resources = [foo], schedule = [quux], spawn = [quux])]
+ fn foo(_: foo::Context) {
+ #[cfg(never)]
+ static mut BAR: u32 = 0;
+ }
+
+ #[task(priority = 3, resources = [foo], schedule = [quux], spawn = [quux])]
+ fn bar(_: bar::Context) {
+ #[cfg(never)]
+ static mut BAR: u32 = 0;
+ }
+
+ #[cfg(never)]
+ #[task]
+ fn quux(_: quux::Context) {}
+
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
+ extern "C" {
+ fn SSI0();
+ fn QEI0();
+ }
+}
diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs
new file mode 100644
index 00000000..1e38e317
--- /dev/null
+++ b/examples/t-htask-main.rs
@@ -0,0 +1,22 @@
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ rtic::pend(lm3s6965::Interrupt::UART0);
+
+ init::LateResources {}
+ }
+
+ #[task(binds = UART0)]
+ fn taskmain(_: taskmain::Context) {
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+}
diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs
new file mode 100644
index 00000000..9078628e
--- /dev/null
+++ b/examples/t-idle-main.rs
@@ -0,0 +1,23 @@
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ init::LateResources {}
+ }
+
+ #[idle]
+ fn taskmain(_: taskmain::Context) -> ! {
+ debug::exit(debug::EXIT_SUCCESS);
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+}
diff --git a/examples/t-init-main.rs b/examples/t-init-main.rs
new file mode 100644
index 00000000..7c23cc83
--- /dev/null
+++ b/examples/t-init-main.rs
@@ -0,0 +1,17 @@
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ debug::exit(debug::EXIT_SUCCESS);
+
+ init::LateResources {}
+ }
+}
diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs
new file mode 100644
index 00000000..345d9aef
--- /dev/null
+++ b/examples/t-late-not-send.rs
@@ -0,0 +1,41 @@
+//! [compile-pass] late resources don't need to be `Send` if they are owned by `idle`
+
+#![no_main]
+#![no_std]
+
+use core::marker::PhantomData;
+
+use panic_halt as _;
+
+pub struct NotSend {
+ _0: PhantomData<*const ()>,
+}
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ use super::NotSend;
+
+ #[resources]
+ struct Resources {
+ x: NotSend,
+ #[init(None)]
+ y: Option<NotSend>,
+ }
+
+ #[init(resources = [y])]
+ fn init(c: init::Context) -> init::LateResources {
+ // equivalent to late resource initialization
+ *c.resources.y = Some(NotSend { _0: PhantomData });
+
+ init::LateResources {
+ x: NotSend { _0: PhantomData },
+ }
+ }
+
+ #[idle(resources = [x, y])]
+ fn idle(_: idle::Context) -> ! {
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+}
diff --git a/examples/t-resource.rs b/examples/t-resource.rs
new file mode 100644
index 00000000..91950d3e
--- /dev/null
+++ b/examples/t-resource.rs
@@ -0,0 +1,92 @@
+//! [compile-pass] Check code generation of resources
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[init(0)]
+ o1: u32, // init
+ #[init(0)]
+ o2: u32, // idle
+ #[init(0)]
+ o3: u32, // EXTI0
+ #[init(0)]
+ o4: u32, // idle
+ #[init(0)]
+ o5: u32, // EXTI1
+ #[init(0)]
+ o6: u32, // init
+ #[init(0)]
+ s1: u32, // idle & uart0
+ #[init(0)]
+ s2: u32, // uart0 & uart1
+ #[init(0)]
+ s3: u32, // idle & uart0
+ }
+
+ #[init(resources = [o1, o4, o5, o6, s3])]
+ fn init(c: init::Context) -> init::LateResources {
+ // owned by `init` == `&'static mut`
+ let _: &'static mut u32 = c.resources.o1;
+
+ // owned by `init` == `&'static` if read-only
+ let _: &'static u32 = c.resources.o6;
+
+ // `init` has exclusive access to all resources
+ let _: &mut u32 = c.resources.o4;
+ let _: &mut u32 = c.resources.o5;
+ let _: &mut u32 = c.resources.s3;
+
+ init::LateResources {}
+ }
+
+ #[idle(resources = [o2, &o4, s1, &s3])]
+ fn idle(mut c: idle::Context) -> ! {
+ // owned by `idle` == `&'static mut`
+ let _: &'static mut u32 = c.resources.o2;
+
+ // owned by `idle` == `&'static` if read-only
+ let _: &'static u32 = c.resources.o4;
+
+ // shared with `idle` == `Mutex`
+ c.resources.s1.lock(|_| {});
+
+ // `&` if read-only
+ let _: &u32 = c.resources.s3;
+
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+
+ #[task(binds = UART0, resources = [o3, s1, s2, &s3])]
+ fn uart0(c: uart0::Context) {
+ // owned by interrupt == `&mut`
+ let _: &mut u32 = c.resources.o3;
+
+ // no `Mutex` proxy when access from highest priority task
+ let _: &mut u32 = c.resources.s1;
+
+ // no `Mutex` proxy when co-owned by cooperative (same priority) tasks
+ let _: &mut u32 = c.resources.s2;
+
+ // `&` if read-only
+ let _: &u32 = c.resources.s3;
+ }
+
+ #[task(binds = UART1, resources = [s2, &o5])]
+ fn uart1(c: uart1::Context) {
+ // owned by interrupt == `&` if read-only
+ let _: &u32 = c.resources.o5;
+
+ // no `Mutex` proxy when co-owned by cooperative (same priority) tasks
+ let _: &mut u32 = c.resources.s2;
+ }
+}
diff --git a/examples/t-schedule.rs b/examples/t-schedule.rs
new file mode 100644
index 00000000..d5a6d3ff
--- /dev/null
+++ b/examples/t-schedule.rs
@@ -0,0 +1,66 @@
+//! [compile-pass] Check `schedule` code generation
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+use rtic::cyccnt::{Instant, U32Ext as _};
+
+#[rtic::app(device = lm3s6965, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
+ #[init(schedule = [foo, bar, baz])]
+ fn init(c: init::Context) -> init::LateResources {
+ let _: Result<(), ()> = c.schedule.foo(c.start + 10.cycles());
+ let _: Result<(), u32> = c.schedule.bar(c.start + 20.cycles(), 0);
+ let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 30.cycles(), 0, 1);
+
+ init::LateResources {}
+ }
+
+ #[idle(schedule = [foo, bar, baz])]
+ fn idle(c: idle::Context) -> ! {
+ let _: Result<(), ()> = c.schedule.foo(Instant::now() + 40.cycles());
+ let _: Result<(), u32> = c.schedule.bar(Instant::now() + 50.cycles(), 0);
+ let _: Result<(), (u32, u32)> = c.schedule.baz(Instant::now() + 60.cycles(), 0, 1);
+
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+
+ #[task(binds = SVCall, schedule = [foo, bar, baz])]
+ fn svcall(c: svcall::Context) {
+ let _: Result<(), ()> = c.schedule.foo(c.start + 70.cycles());
+ let _: Result<(), u32> = c.schedule.bar(c.start + 80.cycles(), 0);
+ let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 90.cycles(), 0, 1);
+ }
+
+ #[task(binds = UART0, schedule = [foo, bar, baz])]
+ fn uart0(c: uart0::Context) {
+ let _: Result<(), ()> = c.schedule.foo(c.start + 100.cycles());
+ let _: Result<(), u32> = c.schedule.bar(c.start + 110.cycles(), 0);
+ let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 120.cycles(), 0, 1);
+ }
+
+ #[task(schedule = [foo, bar, baz])]
+ fn foo(c: foo::Context) {
+ let _: Result<(), ()> = c.schedule.foo(c.scheduled + 130.cycles());
+ let _: Result<(), u32> = c.schedule.bar(c.scheduled + 140.cycles(), 0);
+ let _: Result<(), (u32, u32)> = c.schedule.baz(c.scheduled + 150.cycles(), 0, 1);
+ }
+
+ #[task]
+ fn bar(_: bar::Context, _x: u32) {}
+
+ #[task]
+ fn baz(_: baz::Context, _x: u32, _y: u32) {}
+
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
+ extern "C" {
+ fn SSI0();
+ }
+}
diff --git a/examples/t-spawn.rs b/examples/t-spawn.rs
new file mode 100644
index 00000000..efb748bc
--- /dev/null
+++ b/examples/t-spawn.rs
@@ -0,0 +1,65 @@
+//! [compile-pass] Check code generation of `spawn`
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init(spawn = [foo, bar, baz])]
+ fn init(c: init::Context) -> init::LateResources {
+ let _: Result<(), ()> = c.spawn.foo();
+ let _: Result<(), u32> = c.spawn.bar(0);
+ let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1);
+
+ init::LateResources {}
+ }
+
+ #[idle(spawn = [foo, bar, baz])]
+ fn idle(c: idle::Context) -> ! {
+ let _: Result<(), ()> = c.spawn.foo();
+ let _: Result<(), u32> = c.spawn.bar(0);
+ let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1);
+
+ loop {
+ cortex_m::asm::nop();
+ }
+ }
+
+ #[task(binds = SVCall, spawn = [foo, bar, baz])]
+ fn svcall(c: svcall::Context) {
+ let _: Result<(), ()> = c.spawn.foo();
+ let _: Result<(), u32> = c.spawn.bar(0);
+ let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1);
+ }
+
+ #[task(binds = UART0, spawn = [foo, bar, baz])]
+ fn uart0(c: uart0::Context) {
+ let _: Result<(), ()> = c.spawn.foo();
+ let _: Result<(), u32> = c.spawn.bar(0);
+ let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1);
+ }
+
+ #[task(spawn = [foo, bar, baz])]
+ fn foo(c: foo::Context) {
+ let _: Result<(), ()> = c.spawn.foo();
+ let _: Result<(), u32> = c.spawn.bar(0);
+ let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1);
+ }
+
+ #[task]
+ fn bar(_: bar::Context, _x: u32) {}
+
+ #[task]
+ fn baz(_: baz::Context, _x: u32, _y: u32) {}
+
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
+ extern "C" {
+ fn SSI0();
+ }
+}
diff --git a/examples/t-stask-main.rs b/examples/t-stask-main.rs
new file mode 100644
index 00000000..74335c18
--- /dev/null
+++ b/examples/t-stask-main.rs
@@ -0,0 +1,29 @@
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+use cortex_m_semihosting::debug;
+use panic_semihosting as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init(spawn = [taskmain])]
+ fn init(cx: init::Context) -> init::LateResources {
+ cx.spawn.taskmain().ok();
+
+ init::LateResources {}
+ }
+
+ #[task]
+ fn taskmain(_: taskmain::Context) {
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
+ extern "C" {
+ fn SSI0();
+ }
+}
diff --git a/examples/task.rs b/examples/task.rs
index 4f168bb8..80a9c431 100644
--- a/examples/task.rs
+++ b/examples/task.rs
@@ -5,47 +5,53 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::{debug, hprintln};
-use rtfm::app;
+use panic_semihosting as _;
-#[app(device = lm3s6965)]
-const APP: () = {
+#[rtic::app(device = lm3s6965)]
+mod app {
#[init(spawn = [foo])]
- fn init() {
- spawn.foo().unwrap();
+ fn init(c: init::Context) -> init::LateResources {
+ c.spawn.foo().unwrap();
+
+ init::LateResources {}
}
#[task(spawn = [bar, baz])]
- fn foo() {
- hprintln!("foo").unwrap();
+ fn foo(c: foo::Context) {
+ hprintln!("foo - start").unwrap();
// spawns `bar` onto the task scheduler
// `foo` and `bar` have the same priority so `bar` will not run until
// after `foo` terminates
- spawn.bar().unwrap();
+ c.spawn.bar().unwrap();
+
+ hprintln!("foo - middle").unwrap();
// spawns `baz` onto the task scheduler
// `baz` has higher priority than `foo` so it immediately preempts `foo`
- spawn.baz().unwrap();
+ c.spawn.baz().unwrap();
+
+ hprintln!("foo - end").unwrap();
}
#[task]
- fn bar() {
+ fn bar(_: bar::Context) {
hprintln!("bar").unwrap();
debug::exit(debug::EXIT_SUCCESS);
}
#[task(priority = 2)]
- fn baz() {
+ fn baz(_: baz::Context) {
hprintln!("baz").unwrap();
}
- // Interrupt handlers used to dispatch software tasks
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART0();
- fn UART1();
+ fn SSI0();
+ fn QEI0();
}
-};
+}
diff --git a/examples/types.rs b/examples/types.rs
index c1b8cd69..251d004c 100644
--- a/examples/types.rs
+++ b/examples/types.rs
@@ -5,51 +5,62 @@
#![no_main]
#![no_std]
-extern crate panic_semihosting;
-
use cortex_m_semihosting::debug;
-use rtfm::{app, Exclusive, Instant};
+use panic_semihosting as _;
+use rtic::cyccnt;
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut SHARED: u32 = 0;
+#[rtic::app(device = lm3s6965, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[init(0)]
+ shared: u32,
+ }
#[init(schedule = [foo], spawn = [foo])]
- fn init() {
- let _: Instant = start;
- let _: rtfm::Peripherals = core;
- let _: lm3s6965::Peripherals = device;
- let _: init::Schedule = schedule;
- let _: init::Spawn = spawn;
+ fn init(cx: init::Context) -> init::LateResources {
+ let _: cyccnt::Instant = cx.start;
+ let _: rtic::Peripherals = cx.core;
+ let _: lm3s6965::Peripherals = cx.device;
+ let _: init::Schedule = cx.schedule;
+ let _: init::Spawn = cx.spawn;
debug::exit(debug::EXIT_SUCCESS);
+
+ init::LateResources {}
}
- #[exception(schedule = [foo], spawn = [foo])]
- fn SVCall() {
- let _: Instant = start;
- let _: SVCall::Schedule = schedule;
- let _: SVCall::Spawn = spawn;
+ #[idle(schedule = [foo], spawn = [foo])]
+ fn idle(cx: idle::Context) -> ! {
+ let _: idle::Schedule = cx.schedule;
+ let _: idle::Spawn = cx.spawn;
+
+ loop {
+ cortex_m::asm::nop();
+ }
}
- #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])]
- fn UART0() {
- let _: Instant = start;
- let _: resources::SHARED = resources.SHARED;
- let _: UART0::Schedule = schedule;
- let _: UART0::Spawn = spawn;
+ #[task(binds = UART0, resources = [shared], schedule = [foo], spawn = [foo])]
+ fn uart0(cx: uart0::Context) {
+ let _: cyccnt::Instant = cx.start;
+ let _: resources::shared = cx.resources.shared;
+ let _: uart0::Schedule = cx.schedule;
+ let _: uart0::Spawn = cx.spawn;
}
- #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])]
- fn foo() {
- let _: Instant = scheduled;
- let _: Exclusive<u32> = resources.SHARED;
- let _: foo::Resources = resources;
- let _: foo::Schedule = schedule;
- let _: foo::Spawn = spawn;
+ #[task(priority = 2, resources = [shared], schedule = [foo], spawn = [foo])]
+ fn foo(cx: foo::Context) {
+ let _: cyccnt::Instant = cx.scheduled;
+ let _: &mut u32 = cx.resources.shared;
+ let _: foo::Resources = cx.resources;
+ let _: foo::Schedule = cx.schedule;
+ let _: foo::Spawn = cx.spawn;
}
+ // RTIC requires that unused interrupts are declared in an extern block when
+ // using software tasks; these free interrupts will be used to dispatch the
+ // software tasks.
extern "C" {
- fn UART1();
+ fn SSI0();
}
-};
+}
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 93e5ee72..610890bb 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -1,31 +1,25 @@
[package]
-authors = ["Jorge Aparicio <jorge@japaric.io>"]
+authors = [
+ "The Real-Time Interrupt-driven Concurrency developers",
+ "Jorge Aparicio <jorge@japaric.io>",
+]
categories = ["concurrency", "embedded", "no-std"]
-description = "Procedural macros of the cortex-m-rtfm crate"
-documentation = "https://japaric.github.io/cortex-m-rtfm/api/cortex_m_rtfm"
+description = "Procedural macros of the cortex-m-rtic crate"
+documentation = "https://rtic-rs.github.io/cortex-m-rtic/api/cortex_m_rtic"
edition = "2018"
keywords = ["arm", "cortex-m"]
license = "MIT OR Apache-2.0"
-name = "cortex-m-rtfm-macros"
+name = "cortex-m-rtic-macros"
readme = "../README.md"
-repository = "https://github.com/japaric/cortex-m-rtfm"
-version = "0.4.2"
+repository = "https://github.com/rtic-rs/cortex-m-rtic"
+version = "0.5.2"
[lib]
proc-macro = true
[dependencies]
-quote = "0.6.10"
-proc-macro2 = "0.4.24"
+proc-macro2 = "1"
+quote = "1"
+syn = "1"
+rtic-syntax = { git = "https://github.com/rtic-rs/rtic-syntax", branch = "master", version = "0.4.0" }
-[dependencies.syn]
-features = ["extra-traits", "full"]
-version = "0.15.23"
-
-[dependencies.rand]
-default-features = false
-version = "0.5.5"
-
-[features]
-timer-queue = []
-nightly = [] \ No newline at end of file
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs
index cfd8ebc9..38018c8c 100644
--- a/macros/src/analyze.rs
+++ b/macros/src/analyze.rs
@@ -1,257 +1,48 @@
-use std::{
- cmp,
- collections::{BTreeMap, HashMap, HashSet},
-};
-
-use syn::{Attribute, Ident, Type};
-
-use crate::syntax::{App, Idents};
+use core::ops;
+use std::collections::{BTreeMap, BTreeSet};
-pub type Ownerships = HashMap<Ident, Ownership>;
+use rtic_syntax::{
+ analyze::{self, Priority},
+ ast::App,
+ P,
+};
+use syn::Ident;
+/// Extend the upstream `Analysis` struct with our field
pub struct Analysis {
- /// Capacities of free queues
- pub capacities: Capacities,
- pub dispatchers: Dispatchers,
- // Ceilings of free queues
- pub free_queues: HashMap<Ident, u8>,
- pub resources_assert_send: HashSet<Box<Type>>,
- pub tasks_assert_send: HashSet<Ident>,
- /// Types of RO resources that need to be Sync
- pub assert_sync: HashSet<Box<Type>>,
- // Resource ownership
- pub ownerships: Ownerships,
- // Ceilings of ready queues
- pub ready_queues: HashMap<u8, u8>,
- pub timer_queue: TimerQueue,
-}
-
-#[derive(Clone, Copy, PartialEq)]
-pub enum Ownership {
- // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority)
- Owned { priority: u8 },
- CoOwned { priority: u8 },
- Shared { ceiling: u8 },
-}
-
-impl Ownership {
- pub fn needs_lock(&self, priority: u8) -> bool {
- match *self {
- Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
- Ownership::Shared { ceiling } => {
- debug_assert!(ceiling >= priority);
-
- priority < ceiling
- }
- }
- }
-
- pub fn is_owned(&self) -> bool {
- match *self {
- Ownership::Owned { .. } => true,
- _ => false,
- }
- }
-}
-
-pub struct Dispatcher {
- /// Attributes to apply to the dispatcher
- pub attrs: Vec<Attribute>,
- pub interrupt: Ident,
- /// Tasks dispatched at this priority level
- pub tasks: Vec<Ident>,
- // Queue capacity
- pub capacity: u8,
+ parent: P<analyze::Analysis>,
+ pub interrupts: BTreeMap<Priority, Ident>,
}
-/// Priority -> Dispatcher
-pub type Dispatchers = BTreeMap<u8, Dispatcher>;
-
-pub type Capacities = HashMap<Ident, u8>;
-
-pub fn app(app: &App) -> Analysis {
- // Ceiling analysis of R/W resource and Sync analysis of RO resources
- // (Resource shared by tasks that run at different priorities need to be `Sync`)
- let mut ownerships = Ownerships::new();
- let mut resources_assert_send = HashSet::new();
- let mut tasks_assert_send = HashSet::new();
- let mut assert_sync = HashSet::new();
-
- for (priority, res) in app.resource_accesses() {
- if let Some(ownership) = ownerships.get_mut(res) {
- match *ownership {
- Ownership::Owned { priority: ceiling }
- | Ownership::CoOwned { priority: ceiling }
- | Ownership::Shared { ceiling }
- if priority != ceiling =>
- {
- *ownership = Ownership::Shared {
- ceiling: cmp::max(ceiling, priority),
- };
-
- let res = &app.resources[res];
- if res.mutability.is_none() {
- assert_sync.insert(res.ty.clone());
- }
- }
- Ownership::Owned { priority: ceiling } if ceiling == priority => {
- *ownership = Ownership::CoOwned { priority };
- }
- _ => {}
- }
-
- continue;
- }
-
- ownerships.insert(res.clone(), Ownership::Owned { priority });
- }
-
- // Compute sizes of free queues
- // We assume at most one message per `spawn` / `schedule`
- let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
- for (_, task) in app.spawn_calls().chain(app.schedule_calls()) {
- *capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1;
- }
-
- // Override computed capacities if user specified a capacity in `#[task]`
- for (name, task) in &app.tasks {
- if let Some(cap) = task.args.capacity {
- *capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap;
- }
- }
-
- // Compute the size of the timer queue
- // Compute the priority of the timer queue, which matches the priority of the highest
- // `schedule`-able task
- let mut tq_capacity = 0;
- let mut tq_priority = 1;
- let mut tq_tasks = Idents::new();
- for (_, task) in app.schedule_calls() {
- tq_capacity += capacities[task];
- tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority);
- tq_tasks.insert(task.clone());
- }
-
- // Compute dispatchers capacities
- // Determine which tasks are dispatched by which dispatcher
- // Compute the timer queue priority which matches the priority of the highest priority
- // dispatcher
- let mut dispatchers = Dispatchers::new();
- let mut free_interrupts = app.free_interrupts.iter();
- let mut tasks = app.tasks.iter().collect::<Vec<_>>();
- tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority));
- for (name, task) in tasks {
- let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| {
- let (name, fi) = free_interrupts
- .next()
- .expect("BUG: not enough free_interrupts");
-
- Dispatcher {
- attrs: fi.attrs.clone(),
- capacity: 0,
- interrupt: name.clone(),
- tasks: vec![],
- }
- });
-
- dispatcher.capacity += capacities[name];
- dispatcher.tasks.push(name.clone());
- }
-
- // All messages sent from `init` need to be `Send`
- for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) {
- tasks_assert_send.insert(task.clone());
- }
-
- // All late resources need to be `Send`, unless they are owned by `idle`
- for (name, res) in &app.resources {
- let owned_by_idle = Ownership::Owned { priority: 0 };
- if res.expr.is_none()
- && ownerships
- .get(name)
- .map(|ship| *ship != owned_by_idle)
- .unwrap_or(false)
- {
- resources_assert_send.insert(res.ty.clone());
- }
- }
-
- // All resources shared with init need to be `Send`, unless they are owned by `idle`
- // This is equivalent to late initialization (e.g. `static mut LATE: Option<T> = None`)
- for name in &app.init.args.resources {
- let owned_by_idle = Ownership::Owned { priority: 0 };
- if ownerships
- .get(name)
- .map(|ship| *ship != owned_by_idle)
- .unwrap_or(false)
- {
- resources_assert_send.insert(app.resources[name].ty.clone());
- }
- }
-
- // Ceiling analysis of free queues (consumer end point) -- first pass
- // Ceiling analysis of ready queues (producer end point)
- // Also compute more Send-ness requirements
- let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
- let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect();
- for (priority, task) in app.spawn_calls() {
- if let Some(priority) = priority {
- // Users of `spawn` contend for the to-be-spawned task FREE_QUEUE
- let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut");
- *c = cmp::max(*c, priority);
-
- let c = ready_queues
- .get_mut(&app.tasks[task].args.priority)
- .expect("BUG: ready_queues.get_mut");
- *c = cmp::max(*c, priority);
-
- // Send is required when sending messages from a task whose priority doesn't match the
- // priority of the receiving task
- if app.tasks[task].args.priority != priority {
- tasks_assert_send.insert(task.clone());
- }
- } else {
- // spawns from `init` are excluded from the ceiling analysis
- }
- }
-
- // Ceiling analysis of free queues (consumer end point) -- second pass
- // Ceiling analysis of the timer queue
- let mut tq_ceiling = tq_priority;
- for (priority, task) in app.schedule_calls() {
- if let Some(priority) = priority {
- // Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point)
- let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut");
- *c = cmp::max(*c, priority);
-
- // Users of `schedule` contend for the timer queu
- tq_ceiling = cmp::max(tq_ceiling, priority);
- } else {
- // spawns from `init` are excluded from the ceiling analysis
- }
- }
+impl ops::Deref for Analysis {
+ type Target = analyze::Analysis;
- Analysis {
- capacities,
- dispatchers,
- free_queues,
- tasks_assert_send,
- resources_assert_send,
- assert_sync,
- ownerships,
- ready_queues,
- timer_queue: TimerQueue {
- capacity: tq_capacity,
- ceiling: tq_ceiling,
- priority: tq_priority,
- tasks: tq_tasks,
- },
+ fn deref(&self) -> &Self::Target {
+ &self.parent
}
}
-pub struct TimerQueue {
- pub capacity: u8,
- pub ceiling: u8,
- pub priority: u8,
- pub tasks: Idents,
+// Assign an `extern` interrupt to each priority level
+pub fn app(analysis: P<analyze::Analysis>, app: &App) -> P<Analysis> {
+ let mut interrupts = BTreeMap::new();
+ let priorities = app
+ .software_tasks
+ .values()
+ .filter_map(|task| Some(task.args.priority))
+ .chain(analysis.timer_queues.first().map(|tq| tq.priority))
+ .collect::<BTreeSet<_>>();
+
+ if !priorities.is_empty() {
+ interrupts = priorities
+ .iter()
+ .cloned()
+ .rev()
+ .zip(app.extern_interrupts.keys().cloned())
+ .collect();
+ }
+
+ P::new(Analysis {
+ parent: analysis,
+ interrupts,
+ })
}
diff --git a/macros/src/check.rs b/macros/src/check.rs
index 4adc2c17..0e57bb73 100644
--- a/macros/src/check.rs
+++ b/macros/src/check.rs
@@ -1,374 +1,163 @@
-use std::{collections::HashSet, iter};
+use std::collections::HashSet;
use proc_macro2::Span;
-use syn::{parse, spanned::Spanned, Block, Expr, Stmt};
-
-use crate::syntax::App;
+use rtic_syntax::{
+ analyze::Analysis,
+ ast::{App, CustomArg},
+};
+use syn::{parse, Path};
+
+pub struct Extra<'a> {
+ pub device: &'a Path,
+ pub monotonic: Option<&'a Path>,
+ pub peripherals: bool,
+}
-pub fn app(app: &App) -> parse::Result<()> {
- // Check that all referenced resources have been declared
- for res in app
- .idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> { Box::new(idle.args.resources.iter()) })
- .unwrap_or_else(|| Box::new(iter::empty()))
- .chain(&app.init.args.resources)
- .chain(app.exceptions.values().flat_map(|e| &e.args.resources))
- .chain(app.interrupts.values().flat_map(|i| &i.args.resources))
- .chain(app.tasks.values().flat_map(|t| &t.args.resources))
- {
- if !app.resources.contains_key(res) {
- return Err(parse::Error::new(
- res.span(),
- "this resource has NOT been declared",
- ));
- }
+impl<'a> Extra<'a> {
+ pub fn monotonic(&self) -> &'a Path {
+ self.monotonic.expect("UNREACHABLE")
}
+}
- // Check that late resources have not been assigned to `init`
- for res in &app.init.args.resources {
- if app.resources.get(res).unwrap().expr.is_none() {
- return Err(parse::Error::new(
- res.span(),
- "late resources can NOT be assigned to `init`",
- ));
- }
- }
+pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result<Extra<'a>> {
+ // 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 {
+ "SysTick" => {
+ // If the timer queue is used, then SysTick is unavailable
+ if !analysis.timer_queues.is_empty() {
+ return Err(parse::Error::new(
+ name.span(),
+ "this exception can't be used because it's being used by the runtime",
+ ));
+ } else {
+ // OK
+ }
+ }
- // Check that all late resources have been initialized in `#[init]` if `init` has signature
- // `fn()`
- if !app.init.returns_late_resources {
- for res in
- app.resources
- .iter()
- .filter_map(|(name, res)| if res.expr.is_none() { Some(name) } else { None })
- {
- if app.init.assigns.iter().all(|assign| assign.left != *res) {
+ "NonMaskableInt" | "HardFault" => {
return Err(parse::Error::new(
- res.span(),
- "late resources MUST be initialized at the end of `init`",
+ name.span(),
+ "only exceptions with configurable priority can be used as hardware tasks",
));
}
- }
- }
- // Check that all referenced tasks have been declared
- for task in app
- .idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(idle.args.schedule.iter().chain(&idle.args.spawn))
- })
- .unwrap_or_else(|| Box::new(iter::empty()))
- .chain(&app.init.args.schedule)
- .chain(&app.init.args.spawn)
- .chain(
- app.exceptions
- .values()
- .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)),
- )
- .chain(
- app.interrupts
- .values()
- .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)),
- )
- .chain(
- app.tasks
- .values()
- .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)),
- )
- {
- if !app.tasks.contains_key(task) {
- return Err(parse::Error::new(
- task.span(),
- "this task has NOT been declared",
- ));
+ _ => {}
}
}
- // Check that there are enough free interrupts to dispatch all tasks
- let ndispatchers = app
- .tasks
- .values()
- .map(|t| t.args.priority)
- .collect::<HashSet<_>>()
- .len();
- if ndispatchers > app.free_interrupts.len() {
- return Err(parse::Error::new(
- Span::call_site(),
- &*format!(
- "{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks",
- ndispatchers,
- if ndispatchers > 1 { "s" } else { "" },
- if ndispatchers > 1 { "are" } else { "is" },
- ),
- ));
- }
-
- // Check that free interrupts are not being used
- for (handler, interrupt) in &app.interrupts {
- let name = interrupt.args.binds(handler);
+ // Check that external (device-specific) interrupts are not named after known (Cortex-M)
+ // exceptions
+ for name in app.extern_interrupts.keys() {
+ let name_s = name.to_string();
- if app.free_interrupts.contains_key(name) {
- return Err(parse::Error::new(
- name.span(),
- "free interrupts (`extern { .. }`) can't be used as interrupt handlers",
- ));
- }
- }
-
- // Check that `init` contains no early returns *if* late resources exist and `init` signature is
- // `fn()`
- if app.resources.values().any(|res| res.expr.is_none()) {
- if !app.init.returns_late_resources {
- for stmt in &app.init.stmts {
- noreturn_stmt(stmt)?;
+ 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",
+ ));
}
- }
- } else if app.init.returns_late_resources {
- return Err(parse::Error::new(
- Span::call_site(),
- "`init` signature must be `[unsafe] fn()` if there are no late resources",
- ));
- }
-
- Ok(())
-}
-
-// checks that the given block contains no instance of `return`
-fn noreturn_block(block: &Block) -> Result<(), parse::Error> {
- for stmt in &block.stmts {
- noreturn_stmt(stmt)?;
- }
-
- Ok(())
-}
-// checks that the given statement contains no instance of `return`
-fn noreturn_stmt(stmt: &Stmt) -> Result<(), parse::Error> {
- match stmt {
- // `let x = ..` -- this may contain a return in the RHS
- Stmt::Local(local) => {
- if let Some(ref init) = local.init {
- noreturn_expr(&init.1)?
- }
+ _ => {}
}
-
- // items have no effect on control flow
- Stmt::Item(..) => {}
-
- Stmt::Expr(expr) => noreturn_expr(expr)?,
-
- Stmt::Semi(expr, ..) => noreturn_expr(expr)?,
}
- Ok(())
-}
-
-// checks that the given expression contains no `return`
-fn noreturn_expr(expr: &Expr) -> Result<(), parse::Error> {
- match expr {
- Expr::Box(b) => noreturn_expr(&b.expr)?,
-
- Expr::InPlace(ip) => {
- noreturn_expr(&ip.place)?;
- noreturn_expr(&ip.value)?;
- }
-
- Expr::Array(a) => {
- for elem in &a.elems {
- noreturn_expr(elem)?;
- }
- }
-
- Expr::Call(c) => {
- noreturn_expr(&c.func)?;
-
- for arg in &c.args {
- noreturn_expr(arg)?;
- }
- }
-
- Expr::MethodCall(mc) => {
- noreturn_expr(&mc.receiver)?;
-
- for arg in &mc.args {
- noreturn_expr(arg)?;
- }
- }
-
- Expr::Tuple(t) => {
- for elem in &t.elems {
- noreturn_expr(elem)?;
- }
- }
-
- Expr::Binary(b) => {
- noreturn_expr(&b.left)?;
- noreturn_expr(&b.right)?;
- }
-
- Expr::Unary(u) => {
- noreturn_expr(&u.expr)?;
- }
-
- Expr::Lit(..) => {}
-
- Expr::Cast(c) => {
- noreturn_expr(&c.expr)?;
- }
-
- Expr::Type(t) => {
- noreturn_expr(&t.expr)?;
- }
-
- Expr::Let(l) => {
- noreturn_expr(&l.expr)?;
- }
-
- Expr::If(i) => {
- noreturn_expr(&i.cond)?;
+ // 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()
+ .filter_map(|(name, task)| {
+ first = Some(name);
+ Some(task.args.priority)
+ })
+ .chain(analysis.timer_queues.first().map(|tq| tq.priority))
+ .collect::<HashSet<_>>();
+
+ let need = priorities.len();
+ let given = app.extern_interrupts.len();
+ if need > given {
+ let s = {
+ format!(
+ "not enough `extern` interrupts to dispatch \
+ all software tasks (need: {}; given: {})",
+ need, 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));
+ }
- noreturn_block(&i.then_branch)?;
+ let mut device = None;
+ let mut monotonic = None;
+ let mut peripherals = false;
- if let Some(ref e) = i.else_branch {
- noreturn_expr(&e.1)?;
- }
- }
+ for (k, v) in &app.args.custom {
+ let ks = k.to_string();
- Expr::While(w) => {
- noreturn_expr(&w.cond)?;
- noreturn_block(&w.body)?;
- }
-
- Expr::ForLoop(fl) => {
- noreturn_expr(&fl.expr)?;
- noreturn_block(&fl.body)?;
- }
+ match &*ks {
+ "device" => match v {
+ CustomArg::Path(p) => device = Some(p),
- Expr::Loop(l) => {
- noreturn_block(&l.body)?;
- }
-
- Expr::Match(m) => {
- noreturn_expr(&m.expr)?;
-
- for arm in &m.arms {
- if let Some(g) = &arm.guard {
- noreturn_expr(&g.1)?;
+ _ => {
+ return Err(parse::Error::new(
+ k.span(),
+ "unexpected argument value; this should be a path",
+ ));
}
+ },
- noreturn_expr(&arm.body)?;
- }
- }
-
- // we don't care about `return`s inside closures
- Expr::Closure(..) => {}
-
- Expr::Unsafe(u) => {
- noreturn_block(&u.block)?;
- }
+ "monotonic" => match v {
+ CustomArg::Path(p) => monotonic = Some(p),
- Expr::Block(b) => {
- noreturn_block(&b.block)?;
- }
-
- Expr::Assign(a) => {
- noreturn_expr(&a.left)?;
- noreturn_expr(&a.right)?;
- }
-
- Expr::AssignOp(ao) => {
- noreturn_expr(&ao.left)?;
- noreturn_expr(&ao.right)?;
- }
-
- Expr::Field(f) => {
- noreturn_expr(&f.base)?;
- }
-
- Expr::Index(i) => {
- noreturn_expr(&i.expr)?;
- noreturn_expr(&i.index)?;
- }
-
- Expr::Range(r) => {
- if let Some(ref f) = r.from {
- noreturn_expr(f)?;
- }
-
- if let Some(ref t) = r.to {
- noreturn_expr(t)?;
- }
- }
-
- Expr::Path(..) => {}
-
- Expr::Reference(r) => {
- noreturn_expr(&r.expr)?;
- }
-
- Expr::Break(b) => {
- if let Some(ref e) = b.expr {
- noreturn_expr(e)?;
- }
- }
-
- Expr::Continue(..) => {}
-
- Expr::Return(r) => {
- return Err(parse::Error::new(
- r.span(),
- "`init` is *not* allowed to early return",
- ));
- }
-
- // we can not analyze this
- Expr::Macro(..) => {}
-
- Expr::Struct(s) => {
- for field in &s.fields {
- noreturn_expr(&field.expr)?;
- }
-
- if let Some(ref rest) = s.rest {
- noreturn_expr(rest)?;
- }
- }
-
- Expr::Repeat(r) => {
- noreturn_expr(&r.expr)?;
- noreturn_expr(&r.len)?;
- }
-
- Expr::Paren(p) => {
- noreturn_expr(&p.expr)?;
- }
-
- Expr::Group(g) => {
- noreturn_expr(&g.expr)?;
- }
-
- Expr::Try(t) => {
- noreturn_expr(&t.expr)?;
- }
-
- // we don't care about `return`s inside async blocks
- Expr::Async(..) => {}
-
- Expr::TryBlock(tb) => {
- noreturn_block(&tb.block)?;
- }
+ _ => {
+ return Err(parse::Error::new(
+ k.span(),
+ "unexpected argument value; this should be a path",
+ ));
+ }
+ },
+
+ "peripherals" => match v {
+ CustomArg::Bool(x) => peripherals = if *x { true } else { false },
+ _ => {
+ return Err(parse::Error::new(
+ k.span(),
+ "unexpected argument value; this should be a boolean",
+ ));
+ }
+ },
- Expr::Yield(y) => {
- if let Some(expr) = &y.expr {
- noreturn_expr(expr)?;
+ _ => {
+ return Err(parse::Error::new(k.span(), "unexpected argument"));
}
}
+ }
- // we can not analyze this
- Expr::Verbatim(..) => {}
+ if !&analysis.timer_queues.is_empty() && monotonic.is_none() {
+ return Err(parse::Error::new(
+ Span::call_site(),
+ "a `monotonic` timer must be specified to use the `schedule` API",
+ ));
}
- Ok(())
+ if let Some(device) = device {
+ Ok(Extra {
+ device,
+ monotonic,
+ peripherals,
+ })
+ } else {
+ Err(parse::Error::new(
+ Span::call_site(),
+ "a `device` argument must be specified in `#[rtic::app]`",
+ ))
+ }
}
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs
index 1b3f67b8..f230d395 100644
--- a/macros/src/codegen.rs
+++ b/macros/src/codegen.rs
@@ -1,2267 +1,182 @@
-#![deny(warnings)]
-
-use proc_macro::TokenStream;
-use std::{
- collections::{BTreeMap, HashMap},
- time::{SystemTime, UNIX_EPOCH},
-};
-
-use proc_macro2::Span;
+use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
-use rand::{Rng, SeedableRng};
-use syn::{parse_quote, ArgCaptured, Attribute, Ident, IntSuffix, LitInt};
-
-use crate::{
- analyze::{Analysis, Ownership},
- syntax::{App, Idents, Static},
-};
-
-// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names.
-// In some instances we also use the pseudo-hygienic names for safety, for example the user should
-// not modify the priority field of resources.
-type Aliases = BTreeMap<Ident, Ident>;
-
-struct Context {
- // Alias
- #[cfg(feature = "timer-queue")]
- baseline: Ident,
- dispatchers: BTreeMap<u8, Dispatcher>,
- // Alias (`fn`)
- idle: Ident,
- // Alias (`fn`)
- init: Ident,
- // Alias
- priority: Ident,
- // For non-singletons this maps the resource name to its `static mut` variable name
- statics: Aliases,
- /// Task -> Alias (`struct`)
- resources: HashMap<Kind, Resources>,
- // Alias (`enum`)
- schedule_enum: Ident,
- // Task -> Alias (`fn`)
- schedule_fn: Aliases,
- tasks: BTreeMap<Ident, Task>,
- // Alias (`struct` / `static mut`)
- timer_queue: Ident,
- // Generator of Ident names or suffixes
- ident_gen: IdentGenerator,
-}
-
-struct Dispatcher {
- enum_: Ident,
- ready_queue: Ident,
-}
-
-struct Task {
- alias: Ident,
- free_queue: Ident,
- inputs: Ident,
- spawn_fn: Ident,
-
- #[cfg(feature = "timer-queue")]
- scheduleds: Ident,
-}
-
-impl Default for Context {
- fn default() -> Self {
- let mut ident_gen = IdentGenerator::new();
-
- Context {
- #[cfg(feature = "timer-queue")]
- baseline: ident_gen.mk_ident(None, false),
- dispatchers: BTreeMap::new(),
- idle: ident_gen.mk_ident(Some("idle"), false),
- init: ident_gen.mk_ident(Some("init"), false),
- priority: ident_gen.mk_ident(None, false),
- statics: Aliases::new(),
- resources: HashMap::new(),
- schedule_enum: ident_gen.mk_ident(None, false),
- schedule_fn: Aliases::new(),
- tasks: BTreeMap::new(),
- timer_queue: ident_gen.mk_ident(None, false),
- ident_gen,
- }
- }
-}
-
-struct Resources {
- alias: Ident,
- decl: proc_macro2::TokenStream,
-}
-
-pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
- let mut ctxt = Context::default();
-
- let resources = resources(&mut ctxt, &app, analysis);
-
- let tasks = tasks(&mut ctxt, &app, analysis);
-
- let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis);
-
- let (init_fn, has_late_resources) = init(&mut ctxt, &app, analysis);
- let init_arg = if cfg!(feature = "timer-queue") {
- quote!(rtfm::Peripherals {
- CBP: p.CBP,
- CPUID: p.CPUID,
- DCB: &mut p.DCB,
- FPB: p.FPB,
- FPU: p.FPU,
- ITM: p.ITM,
- MPU: p.MPU,
- SCB: &mut p.SCB,
- TPIU: p.TPIU,
- })
- } else {
- quote!(rtfm::Peripherals {
- CBP: p.CBP,
- CPUID: p.CPUID,
- DCB: p.DCB,
- DWT: p.DWT,
- FPB: p.FPB,
- FPU: p.FPU,
- ITM: p.ITM,
- MPU: p.MPU,
- SCB: &mut p.SCB,
- SYST: p.SYST,
- TPIU: p.TPIU,
- })
- };
-
- let init = &ctxt.init;
- let init_phase = if has_late_resources {
- let assigns = app
- .resources
- .iter()
- .filter_map(|(name, res)| {
- if res.expr.is_none() {
- let alias = &ctxt.statics[name];
-
- Some(quote!(#alias.write(res.#name);))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- quote!(
- let res = #init(#init_arg);
- #(#assigns)*
- )
- } else {
- quote!(#init(#init_arg);)
- };
-
- let post_init = post_init(&ctxt, &app, analysis);
-
- let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis);
-
- let exceptions = exceptions(&mut ctxt, app, analysis);
-
- let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis);
-
- let spawn = spawn(&mut ctxt, app, analysis);
-
- let schedule = match () {
- #[cfg(feature = "timer-queue")]
- () => schedule(&ctxt, app),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let timer_queue = timer_queue(&mut ctxt, app, analysis);
-
- let pre_init = pre_init(&ctxt, &app, analysis);
-
- let assertions = assertions(app, analysis);
-
- let main = ctxt.ident_gen.mk_ident(None, false);
- quote!(
- #resources
-
- #spawn
-
- #timer_queue
-
- #schedule
-
- #dispatchers_data
-
- #(#exceptions)*
-
- #root_interrupts
-
- const APP: () = {
- #scoped_interrupts
-
- #(#dispatchers)*
- };
-
- #(#tasks)*
-
- #init_fn
-
- #idle_fn
-
- #[export_name = "main"]
- #[allow(unsafe_code)]
- #[doc(hidden)]
- unsafe fn #main() -> ! {
- #assertions
-
- rtfm::export::interrupt::disable();
-
- #pre_init
-
- #init_phase
-
- #post_init
-
- rtfm::export::interrupt::enable();
-
- #idle_expr
- }
- )
- .into()
-}
-
-fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut items = vec![];
- let mut module = vec![];
- for (name, res) in &app.resources {
- let cfgs = &res.cfgs;
- let attrs = &res.attrs;
- let mut_ = &res.mutability;
- let ty = &res.ty;
- let expr = &res.expr;
-
- if res.singleton {
- items.push(quote!(
- #(#attrs)*
- pub static #mut_ #name: #ty = #expr;
- ));
-
- let alias = ctxt.ident_gen.mk_ident(None, true); // XXX is randomness required?
- if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
- items.push(mk_resource(
- ctxt,
- cfgs,
- name,
- quote!(#name),
- *ceiling,
- quote!(&mut <#name as owned_singleton::Singleton>::new()),
- app,
- Some(&mut module),
- ))
- }
-
- ctxt.statics.insert(name.clone(), alias);
- } else {
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let symbol = format!("{}::{}", name, alias);
-
- items.push(
- expr.as_ref()
- .map(|expr| {
- quote!(
- #(#attrs)*
- #(#cfgs)*
- #[doc = #symbol]
- static mut #alias: #ty = #expr;
- )
- })
- .unwrap_or_else(|| {
- quote!(
- #(#attrs)*
- #(#cfgs)*
- #[doc = #symbol]
- static mut #alias: rtfm::export::MaybeUninit<#ty> =
- rtfm::export::MaybeUninit::uninit();
- )
- }),
- );
-
- if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
- if res.mutability.is_some() {
- let ptr = if res.expr.is_none() {
- quote!(unsafe { &mut *#alias.as_mut_ptr() })
- } else {
- quote!(unsafe { &mut #alias })
- };
-
- items.push(mk_resource(
- ctxt,
- cfgs,
- name,
- quote!(#ty),
- *ceiling,
- ptr,
- app,
- Some(&mut module),
- ));
- }
- }
-
- ctxt.statics.insert(name.clone(), alias);
- }
- }
-
- if !module.is_empty() {
- items.push(quote!(
- /// Resource proxies
- pub mod resources {
- #(#module)*
- }
- ));
- }
-
- quote!(#(#items)*)
-}
-
-fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> (proc_macro2::TokenStream, bool) {
- let attrs = &app.init.attrs;
- let locals = mk_locals(&app.init.statics, true);
- let stmts = &app.init.stmts;
- // TODO remove in v0.5.x
- let assigns = app
- .init
- .assigns
- .iter()
- .map(|assign| {
- let attrs = &assign.attrs;
- if app
- .resources
- .get(&assign.left)
- .map(|r| r.expr.is_none())
- .unwrap_or(false)
- {
- let alias = &ctxt.statics[&assign.left];
- let expr = &assign.right;
- quote!(
- #(#attrs)*
- unsafe { #alias.write(#expr); }
- )
- } else {
- let left = &assign.left;
- let right = &assign.right;
- quote!(
- #(#attrs)*
- #left = #right;
- )
- }
- })
- .collect::<Vec<_>>();
-
- let prelude = prelude(
- ctxt,
- Kind::Init,
- &app.init.args.resources,
- &app.init.args.spawn,
- &app.init.args.schedule,
- app,
- 255,
- analysis,
- );
-
- let (late_resources, late_resources_ident, ret) = if app.init.returns_late_resources {
- // create `LateResources` struct in the root of the crate
- let ident = ctxt.ident_gen.mk_ident(None, false);
-
- let fields = app
- .resources
- .iter()
- .filter_map(|(name, res)| {
- if res.expr.is_none() {
- let ty = &res.ty;
- Some(quote!(pub #name: #ty))
- } else {
- None
- }
- })
- .collect::<Vec<_>>();
-
- let late_resources = quote!(
- #[allow(non_snake_case)]
- pub struct #ident {
- #(#fields),*
- }
- );
-
- (
- Some(late_resources),
- Some(ident),
- Some(quote!(-> init::LateResources)),
- )
- } else {
- (None, None, None)
- };
- let has_late_resources = late_resources.is_some();
-
- let module = module(
- ctxt,
- Kind::Init,
- !app.init.args.schedule.is_empty(),
- !app.init.args.spawn.is_empty(),
- app,
- late_resources_ident,
- );
-
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::artificial(0);),
-
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let unsafety = &app.init.unsafety;
- let device = &app.args.device;
- let init = &ctxt.init;
- (
- quote!(
- #late_resources
-
- #module
-
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #(#attrs)*
- unsafe fn #init(core: rtfm::Peripherals) #ret {
- #[inline(always)]
- #unsafety fn init(mut core: rtfm::Peripherals) #ret {
- #(#locals)*
-
- #baseline_let
-
- #prelude
-
- let mut device = unsafe { #device::Peripherals::steal() };
-
- #start_let
-
- #(#stmts)*
-
- #(#assigns)*
- }
-
- init(core)
- }
- ),
- has_late_resources,
- )
-}
-
-fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut exprs = vec![];
-
- // TODO turn the assertions that check that the priority is not larger than what's supported by
- // the device into compile errors
- let device = &app.args.device;
- let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
- for (handler, exception) in &app.exceptions {
- let name = exception.args.binds(handler);
- let priority = exception.args.priority;
- exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
- exprs.push(quote!(p.SCB.set_priority(
- rtfm::export::SystemHandler::#name,
- ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
- )));
- }
-
- if !analysis.timer_queue.tasks.is_empty() {
- let priority = analysis.timer_queue.priority;
- exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
- exprs.push(quote!(p.SCB.set_priority(
- rtfm::export::SystemHandler::SysTick,
- ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
- )));
- }
-
- if app.idle.is_none() {
- // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
- exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1)));
- }
-
- // Enable and start the system timer
- if !analysis.timer_queue.tasks.is_empty() {
- let tq = &ctxt.timer_queue;
- exprs.push(
- quote!((*#tq.as_mut_ptr()).syst.set_clock_source(rtfm::export::SystClkSource::Core)),
- );
- exprs.push(quote!((*#tq.as_mut_ptr()).syst.enable_counter()));
- }
-
- // Enable cycle counter
- if cfg!(feature = "timer-queue") {
- exprs.push(quote!(p.DCB.enable_trace()));
- exprs.push(quote!(p.DWT.enable_cycle_counter()));
- }
-
- quote!(#(#exprs;)*)
-}
-
-/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument)
-fn module(
- ctxt: &mut Context,
- kind: Kind,
- schedule: bool,
- spawn: bool,
- app: &App,
- late_resources: Option<Ident>,
-) -> proc_macro2::TokenStream {
- let mut items = vec![];
- let mut fields = vec![];
-
- let name = kind.ident();
- let priority = &ctxt.priority;
- let device = &app.args.device;
-
- let mut lt = None;
- match kind {
- Kind::Init => {
- if cfg!(feature = "timer-queue") {
- fields.push(quote!(
- /// System start time = `Instant(0 /* cycles */)`
- pub start: rtfm::Instant,
- ));
- }
-
- fields.push(quote!(
- /// Core (Cortex-M) peripherals
- pub core: rtfm::Peripherals<'a>,
- /// Device specific peripherals
- pub device: #device::Peripherals,
- ));
- lt = Some(quote!('a));
- }
- Kind::Idle => {}
- Kind::Exception(_) | Kind::Interrupt(_) => {
- if cfg!(feature = "timer-queue") {
- fields.push(quote!(
- /// Time at which this handler started executing
- pub start: rtfm::Instant,
- ));
- }
- }
- Kind::Task(_) => {
- if cfg!(feature = "timer-queue") {
- fields.push(quote!(
- /// The time at which this task was scheduled to run
- pub scheduled: rtfm::Instant,
- ));
- }
- }
- }
-
- if schedule {
- lt = Some(quote!('a));
-
- fields.push(quote!(
- /// Tasks that can be scheduled from this context
- pub schedule: Schedule<'a>,
- ));
-
- items.push(quote!(
- /// Tasks that can be scheduled from this context
- #[derive(Clone, Copy)]
- pub struct Schedule<'a> {
- #[doc(hidden)]
- pub #priority: &'a rtfm::export::Priority,
- }
- ));
- }
-
- if spawn {
- lt = Some(quote!('a));
-
- fields.push(quote!(
- /// Tasks that can be spawned from this context
- pub spawn: Spawn<'a>,
- ));
-
- if kind.is_idle() {
- items.push(quote!(
- /// Tasks that can be spawned from this context
- #[derive(Clone, Copy)]
- pub struct Spawn<'a> {
- #[doc(hidden)]
- pub #priority: &'a rtfm::export::Priority,
- }
- ));
- } else {
- let baseline_field = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- quote!(
- // NOTE this field is visible so we use a shared reference to make it
- // immutable
- #[doc(hidden)]
- pub #baseline: &'a rtfm::Instant,
- )
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- items.push(quote!(
- /// Tasks that can be spawned from this context
- #[derive(Clone, Copy)]
- pub struct Spawn<'a> {
- #baseline_field
- #[doc(hidden)]
- pub #priority: &'a rtfm::export::Priority,
- }
- ));
- }
- }
-
- let mut root = None;
- if let Some(resources) = ctxt.resources.get(&kind) {
- lt = Some(quote!('a));
-
- root = Some(resources.decl.clone());
-
- let alias = &resources.alias;
- items.push(quote!(
- #[doc(inline)]
- pub use super::#alias as Resources;
- ));
-
- fields.push(quote!(
- /// Resources available in this context
- pub resources: Resources<'a>,
- ));
- };
-
- let doc = match kind {
- Kind::Exception(_) => "Exception handler",
- Kind::Idle => "Idle loop",
- Kind::Init => "Initialization function",
- Kind::Interrupt(_) => "Interrupt handler",
- Kind::Task(_) => "Software task",
- };
-
- if let Some(late_resources) = late_resources {
- items.push(quote!(
- pub use super::#late_resources as LateResources;
- ));
- }
-
- quote!(
- #root
-
- #[doc = #doc]
- #[allow(non_snake_case)]
- pub mod #name {
- /// Variables injected into this context by the `app` attribute
- pub struct Context<#lt> {
- #(#fields)*
- }
-
- #(#items)*
- }
- )
-}
-
-/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into
-/// a function scope
-fn prelude(
- ctxt: &mut Context,
- kind: Kind,
- resources: &Idents,
- spawn: &Idents,
- schedule: &Idents,
- app: &App,
- logical_prio: u8,
- analysis: &Analysis,
-) -> proc_macro2::TokenStream {
- let mut items = vec![];
-
- let lt = if kind.runs_once() {
- quote!('static)
- } else {
- quote!('a)
- };
-
- let module = kind.ident();
-
- let priority = &ctxt.priority;
- if !resources.is_empty() {
- let mut defs = vec![];
- let mut exprs = vec![];
-
- // NOTE This field is just to avoid unused type parameter errors around `'a`
- defs.push(quote!(#[allow(dead_code)] pub #priority: &'a rtfm::export::Priority));
- exprs.push(parse_quote!(#priority));
-
- let mut may_call_lock = false;
- let mut needs_unsafe = false;
- for name in resources {
- let res = &app.resources[name];
- let cfgs = &res.cfgs;
-
- let initialized = res.expr.is_some();
- let singleton = res.singleton;
- let mut_ = res.mutability;
- let ty = &res.ty;
-
- if kind.is_init() {
- let mut force_mut = false;
- if !analysis.ownerships.contains_key(name) {
- // owned by Init
- if singleton {
- needs_unsafe = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: #name
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: <#name as owned_singleton::Singleton>::new()
- ));
- continue;
- } else {
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'static #mut_ #ty
- ));
- }
- } else {
- // owned by someone else
- if singleton {
- needs_unsafe = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'a mut #name
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: &mut <#name as owned_singleton::Singleton>::new()
- ));
- continue;
- } else {
- force_mut = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'a mut #ty
- ));
- }
- }
-
- let alias = &ctxt.statics[name];
- // Resources assigned to init are always const initialized
- needs_unsafe = true;
- if force_mut {
- exprs.push(quote!(
- #(#cfgs)*
- #name: &mut #alias
- ));
- } else {
- exprs.push(quote!(
- #(#cfgs)*
- #name: &#mut_ #alias
- ));
- }
- } else {
- let ownership = &analysis.ownerships[name];
- let mut exclusive = false;
-
- if ownership.needs_lock(logical_prio) {
- may_call_lock = true;
- if singleton {
- if mut_.is_none() {
- needs_unsafe = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'a #name
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: &<#name as owned_singleton::Singleton>::new()
- ));
- continue;
- } else {
- // Generate a resource proxy
- defs.push(quote!(
- #(#cfgs)*
- pub #name: resources::#name<'a>
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: resources::#name { #priority }
- ));
- continue;
- }
- } else {
- if mut_.is_none() {
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'a #ty
- ));
- } else {
- // Generate a resource proxy
- defs.push(quote!(
- #(#cfgs)*
- pub #name: resources::#name<'a>
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: resources::#name { #priority }
- ));
- continue;
- }
- }
- } else {
- if singleton {
- if kind.runs_once() {
- needs_unsafe = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: #name
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: <#name as owned_singleton::Singleton>::new()
- ));
- } else {
- needs_unsafe = true;
- if ownership.is_owned() || mut_.is_none() {
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &'a #mut_ #name
- ));
- // XXX is randomness required?
- let alias = ctxt.ident_gen.mk_ident(None, true);
- items.push(quote!(
- #(#cfgs)*
- let #mut_ #alias = unsafe {
- <#name as owned_singleton::Singleton>::new()
- };
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: &#mut_ #alias
- ));
- } else {
- may_call_lock = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: rtfm::Exclusive<'a, #name>
- ));
- // XXX is randomness required?
- let alias = ctxt.ident_gen.mk_ident(None, true);
- items.push(quote!(
- #(#cfgs)*
- let #mut_ #alias = unsafe {
- <#name as owned_singleton::Singleton>::new()
- };
- ));
- exprs.push(quote!(
- #(#cfgs)*
- #name: rtfm::Exclusive(&mut #alias)
- ));
- }
- }
- continue;
- } else {
- if ownership.is_owned() || mut_.is_none() {
- defs.push(quote!(
- #(#cfgs)*
- pub #name: &#lt #mut_ #ty
- ));
- } else {
- exclusive = true;
- may_call_lock = true;
- defs.push(quote!(
- #(#cfgs)*
- pub #name: rtfm::Exclusive<#lt, #ty>
- ));
- }
- }
- }
-
- let alias = &ctxt.statics[name];
- needs_unsafe = true;
- if initialized {
- if exclusive {
- exprs.push(quote!(
- #(#cfgs)*
- #name: rtfm::Exclusive(&mut #alias)
- ));
- } else {
- exprs.push(quote!(
- #(#cfgs)*
- #name: &#mut_ #alias
- ));
- }
- } else {
- let expr = if mut_.is_some() {
- quote!(&mut *#alias.as_mut_ptr())
- } else {
- quote!(&*#alias.as_ptr())
- };
-
- if exclusive {
- exprs.push(quote!(
- #(#cfgs)*
- #name: rtfm::Exclusive(#expr)
- ));
- } else {
- exprs.push(quote!(
- #(#cfgs)*
- #name: #expr
- ));
- }
- }
- }
- }
-
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let unsafety = if needs_unsafe {
- Some(quote!(unsafe))
- } else {
- None
- };
-
- let defs = &defs;
- let doc = format!("`{}::Resources`", kind.ident().to_string());
- let decl = quote!(
- #[doc = #doc]
- #[allow(non_snake_case)]
- pub struct #alias<'a> { #(#defs,)* }
- );
- items.push(quote!(
- #[allow(unused_variables)]
- #[allow(unsafe_code)]
- #[allow(unused_mut)]
- let mut resources = #unsafety { #alias { #(#exprs,)* } };
- ));
-
- ctxt.resources
- .insert(kind.clone(), Resources { alias, decl });
-
- if may_call_lock {
- items.push(quote!(
- use rtfm::Mutex;
- ));
- }
- }
-
- if !spawn.is_empty() {
- if kind.is_idle() {
- items.push(quote!(
- #[allow(unused_variables)]
- let spawn = #module::Spawn { #priority };
- ));
- } else {
- let baseline_expr = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- quote!(#baseline)
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
- items.push(quote!(
- #[allow(unused_variables)]
- let spawn = #module::Spawn { #priority, #baseline_expr };
- ));
- }
- }
-
- if !schedule.is_empty() {
- // Populate `schedule_fn`
- for task in schedule {
- if ctxt.schedule_fn.contains_key(task) {
- continue;
- }
-
- ctxt.schedule_fn
- .insert(task.clone(), ctxt.ident_gen.mk_ident(None, false));
- }
-
- items.push(quote!(
- #[allow(unused_imports)]
- use rtfm::U32Ext;
-
- #[allow(unused_variables)]
- let schedule = #module::Schedule { #priority };
- ));
- }
-
- if items.is_empty() {
- quote!()
- } else {
- quote!(
- let ref #priority = unsafe { rtfm::export::Priority::new(#logical_prio) };
-
- #(#items)*
- )
- }
-}
-
-fn idle(
- ctxt: &mut Context,
- app: &App,
- analysis: &Analysis,
-) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
- if let Some(idle) = app.idle.as_ref() {
- let attrs = &idle.attrs;
- let locals = mk_locals(&idle.statics, true);
- let stmts = &idle.stmts;
-
- let prelude = prelude(
- ctxt,
- Kind::Idle,
- &idle.args.resources,
- &idle.args.spawn,
- &idle.args.schedule,
- app,
- 0,
- analysis,
- );
-
- let module = module(
- ctxt,
- Kind::Idle,
- !idle.args.schedule.is_empty(),
- !idle.args.spawn.is_empty(),
- app,
- None,
- );
-
- let unsafety = &idle.unsafety;
- let idle = &ctxt.idle;
-
- (
- quote!(
- #module
-
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #(#attrs)*
- unsafe fn #idle() -> ! {
- #[inline(always)]
- #unsafety fn idle() -> ! {
- #(#locals)*
-
- #prelude
-
- #(#stmts)*
- }
-
- idle()
- }
- ),
- quote!(#idle()),
- )
- } else {
- (
- quote!(),
- quote!(loop {
- rtfm::export::wfi();
- }),
- )
- }
-}
-
-fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
- app.exceptions
- .iter()
- .map(|(ident, exception)| {
- let attrs = &exception.attrs;
- let stmts = &exception.stmts;
-
- let kind = Kind::Exception(ident.clone());
- let prelude = prelude(
- ctxt,
- kind.clone(),
- &exception.args.resources,
- &exception.args.spawn,
- &exception.args.schedule,
- app,
- exception.args.priority,
- analysis,
- );
-
- let module = module(
- ctxt,
- kind,
- !exception.args.schedule.is_empty(),
- !exception.args.spawn.is_empty(),
- app,
- None,
- );
-
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::now();),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let locals = mk_locals(&exception.statics, false);
- let symbol = exception.args.binds(ident).to_string();
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let unsafety = &exception.unsafety;
- quote!(
- #module
-
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #[export_name = #symbol]
- #(#attrs)*
- unsafe fn #alias() {
- #[inline(always)]
- #unsafety fn exception() {
- #(#locals)*
-
- #baseline_let
-
- #prelude
-
- #start_let
-
- rtfm::export::run(move || {
- #(#stmts)*
- })
- }
-
- exception()
- }
- )
- })
- .collect()
-}
-
-fn interrupts(
- ctxt: &mut Context,
- app: &App,
- analysis: &Analysis,
-) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra};
+
+mod assertions;
+mod dispatchers;
+mod hardware_tasks;
+mod idle;
+mod init;
+mod locals;
+mod module;
+mod post_init;
+mod pre_init;
+mod resources;
+mod resources_struct;
+mod schedule;
+mod schedule_body;
+mod software_tasks;
+mod spawn;
+mod spawn_body;
+mod timer_queue;
+mod util;
+
+// TODO document the syntax here or in `rtic-syntax`
+pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 {
+ let mut mod_app = vec![];
+ let mut mod_app_imports = vec![];
+ let mut mains = vec![];
let mut root = vec![];
- let mut scoped = vec![];
-
- for (ident, interrupt) in &app.interrupts {
- let attrs = &interrupt.attrs;
- let stmts = &interrupt.stmts;
-
- let kind = Kind::Interrupt(ident.clone());
- let prelude = prelude(
- ctxt,
- kind.clone(),
- &interrupt.args.resources,
- &interrupt.args.spawn,
- &interrupt.args.schedule,
- app,
- interrupt.args.priority,
- analysis,
- );
-
- root.push(module(
- ctxt,
- kind,
- !interrupt.args.schedule.is_empty(),
- !interrupt.args.spawn.is_empty(),
- app,
- None,
- ));
-
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- let baseline_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(let ref #baseline = rtfm::Instant::now();),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let start_let = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(
- #[allow(unused_variables)]
- let start = *#baseline;
- ),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let locals = mk_locals(&interrupt.statics, false);
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let symbol = interrupt.args.binds(ident).to_string();
- let unsafety = &interrupt.unsafety;
- scoped.push(quote!(
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #(#attrs)*
- #[export_name = #symbol]
- unsafe fn #alias() {
- #[inline(always)]
- #unsafety fn interrupt() {
- #(#locals)*
-
- #baseline_let
-
- #prelude
-
- #start_let
-
- rtfm::export::run(move || {
- #(#stmts)*
- })
- }
-
- interrupt()
- }
- ));
- }
-
- (quote!(#(#root)*), quote!(#(#scoped)*))
-}
-
-fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut items = vec![];
-
- // first pass to generate buffers (statics and resources) and spawn aliases
- for (name, task) in &app.tasks {
- #[cfg(feature = "timer-queue")]
- let scheduleds_alias = ctxt.ident_gen.mk_ident(None, false);
- let free_alias = ctxt.ident_gen.mk_ident(None, false);
- let inputs_alias = ctxt.ident_gen.mk_ident(None, false);
- let task_alias = ctxt.ident_gen.mk_ident(Some(&name.to_string()), false);
-
- let inputs = &task.inputs;
-
- let ty = tuple_ty(inputs);
-
- let capacity = analysis.capacities[name];
- let capacity_lit = mk_capacity_literal(capacity);
- let capacity_ty = mk_typenum_capacity(capacity, true);
-
- let resource = mk_resource(
- ctxt,
- &[],
- &free_alias,
- quote!(rtfm::export::FreeQueue<#capacity_ty>),
- *analysis.free_queues.get(name).unwrap_or(&0),
- if cfg!(feature = "nightly") {
- quote!(&mut #free_alias)
- } else {
- quote!(#free_alias.get_mut())
- },
- app,
- None,
- );
-
- let scheduleds_static = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias);
-
- if cfg!(feature = "nightly") {
- let inits =
- (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit()));
-
- quote!(
- #[doc = #scheduleds_symbol]
- static mut #scheduleds_alias:
- [rtfm::export::MaybeUninit<rtfm::Instant>; #capacity_lit] =
- [#(#inits),*];
- )
- } else {
- quote!(
- #[doc = #scheduleds_symbol]
- static mut #scheduleds_alias:
- rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> =
- rtfm::export::MaybeUninit::uninit();
- )
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias);
- let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias);
- if cfg!(feature = "nightly") {
- let inits = (0..capacity).map(|_| quote!(rtfm::export::MaybeUninit::uninit()));
-
- items.push(quote!(
- #[doc = #free_symbol]
- static mut #free_alias: rtfm::export::FreeQueue<#capacity_ty> = unsafe {
- rtfm::export::FreeQueue::new_sc()
- };
-
- #[doc = #inputs_symbol]
- static mut #inputs_alias: [rtfm::export::MaybeUninit<#ty>; #capacity_lit] =
- [#(#inits),*];
- ));
- } else {
- items.push(quote!(
- #[doc = #free_symbol]
- static mut #free_alias: rtfm::export::MaybeUninit<
- rtfm::export::FreeQueue<#capacity_ty>
- > = rtfm::export::MaybeUninit::uninit();
-
- #[doc = #inputs_symbol]
- static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> =
- rtfm::export::MaybeUninit::uninit();
-
- ));
- }
-
- items.push(quote!(
- #resource
-
- #scheduleds_static
- ));
-
- ctxt.tasks.insert(
- name.clone(),
- Task {
- alias: task_alias,
- free_queue: free_alias,
- inputs: inputs_alias,
- spawn_fn: ctxt.ident_gen.mk_ident(None, false),
-
- #[cfg(feature = "timer-queue")]
- scheduleds: scheduleds_alias,
- },
- );
- }
+ let mut user = vec![];
+ let mut imports = vec![];
- // second pass to generate the actual task function
- for (name, task) in &app.tasks {
- let inputs = &task.inputs;
- let locals = mk_locals(&task.statics, false);
- let stmts = &task.stmts;
- let unsafety = &task.unsafety;
+ // Generate the `main` function
+ let assertion_stmts = assertions::codegen(analysis);
- let scheduled_let = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- quote!(let scheduled = *#baseline;)
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
+ let pre_init_stmts = pre_init::codegen(&app, analysis, extra);
- let prelude = prelude(
- ctxt,
- Kind::Task(name.clone()),
- &task.args.resources,
- &task.args.spawn,
- &task.args.schedule,
- app,
- task.args.priority,
- analysis,
- );
+ let (mod_app_init, root_init, user_init, user_init_imports, call_init) =
+ init::codegen(app, analysis, extra);
- items.push(module(
- ctxt,
- Kind::Task(name.clone()),
- !task.args.schedule.is_empty(),
- !task.args.spawn.is_empty(),
- app,
- None,
- ));
+ let post_init_stmts = post_init::codegen(&app, analysis);
- let attrs = &task.attrs;
- let cfgs = &task.cfgs;
- let task_alias = &ctxt.tasks[name].alias;
- let (baseline, baseline_arg) = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let baseline = &ctxt.baseline;
- (quote!(#baseline,), quote!(#baseline: &rtfm::Instant,))
- }
- #[cfg(not(feature = "timer-queue"))]
- () => (quote!(), quote!()),
- };
- let pats = tuple_pat(inputs);
- items.push(quote!(
- // unsafe trampoline to deter end-users from calling this non-reentrant function
- #(#attrs)*
- #(#cfgs)*
- unsafe fn #task_alias(#baseline_arg #(#inputs,)*) {
- #[inline(always)]
- #unsafety fn task(#baseline_arg #(#inputs,)*) {
- #(#locals)*
+ let (mod_app_idle, root_idle, user_idle, user_idle_imports, call_idle) =
+ idle::codegen(app, analysis, extra);
- #prelude
-
- #scheduled_let
-
- #(#stmts)*
- }
-
- task(#baseline #pats)
- }
- ));
- }
-
- quote!(#(#items)*)
-}
-
-fn dispatchers(
- ctxt: &mut Context,
- app: &App,
- analysis: &Analysis,
-) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
- let mut data = vec![];
- let mut dispatchers = vec![];
-
- let device = &app.args.device;
- for (level, dispatcher) in &analysis.dispatchers {
- let ready_alias = ctxt.ident_gen.mk_ident(None, false);
- let enum_alias = ctxt.ident_gen.mk_ident(None, false);
- let capacity = mk_typenum_capacity(dispatcher.capacity, true);
-
- let variants = dispatcher
- .tasks
- .iter()
- .map(|task| {
- let task_ = &app.tasks[task];
- let cfgs = &task_.cfgs;
-
- quote!(
- #(#cfgs)*
- #task
- )
- })
- .collect::<Vec<_>>();
- let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias);
- let e = quote!(rtfm::export);
- let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>);
- let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0);
- let resource = mk_resource(
- ctxt,
- &[],
- &ready_alias,
- ty.clone(),
- ceiling,
- if cfg!(feature = "nightly") {
- quote!(&mut #ready_alias)
- } else {
- quote!(#ready_alias.get_mut())
- },
- app,
- None,
- );
-
- if cfg!(feature = "nightly") {
- data.push(quote!(
- #[doc = #symbol]
- static mut #ready_alias: #ty = unsafe { #e::ReadyQueue::new_sc() };
- ));
- } else {
- data.push(quote!(
- #[doc = #symbol]
- static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninit();
- ));
- }
- data.push(quote!(
- #[allow(dead_code)]
- #[allow(non_camel_case_types)]
- enum #enum_alias { #(#variants,)* }
-
- #resource
- ));
-
- let arms = dispatcher
- .tasks
- .iter()
- .map(|task| {
- let task_ = &ctxt.tasks[task];
- let inputs = &task_.inputs;
- let free = &task_.free_queue;
- let alias = &task_.alias;
-
- let task__ = &app.tasks[task];
- let pats = tuple_pat(&task__.inputs);
- let cfgs = &task__.cfgs;
-
- let baseline_let;
- let call;
- match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds = &task_.scheduleds;
- let scheduled = if cfg!(feature = "nightly") {
- quote!(#scheduleds.get_unchecked(usize::from(index)).as_ptr())
- } else {
- quote!(#scheduleds.get_ref().get_unchecked(usize::from(index)))
- };
-
- baseline_let = quote!(
- let baseline = ptr::read(#scheduled);
- );
- call = quote!(#alias(&baseline, #pats));
- }
- #[cfg(not(feature = "timer-queue"))]
- () => {
- baseline_let = quote!();
- call = quote!(#alias(#pats));
- }
- };
-
- let (free_, input) = if cfg!(feature = "nightly") {
- (
- quote!(#free),
- quote!(#inputs.get_unchecked(usize::from(index)).as_ptr()),
- )
- } else {
- (
- quote!(#free.get_mut()),
- quote!(#inputs.get_ref().get_unchecked(usize::from(index))),
- )
- };
-
- quote!(
- #(#cfgs)*
- #enum_alias::#task => {
- #baseline_let
- let input = ptr::read(#input);
- #free_.split().0.enqueue_unchecked(index);
- let (#pats) = input;
- #call
- }
- )
- })
- .collect::<Vec<_>>();
-
- let attrs = &dispatcher.attrs;
- let interrupt = &dispatcher.interrupt;
- let symbol = interrupt.to_string();
- let alias = ctxt.ident_gen.mk_ident(None, false);
- let ready_alias_ = if cfg!(feature = "nightly") {
- quote!(#ready_alias)
- } else {
- quote!(#ready_alias.get_mut())
- };
- dispatchers.push(quote!(
- #(#attrs)*
- #[export_name = #symbol]
- unsafe fn #alias() {
- use core::ptr;
-
- // check that this interrupt exists
- let _ = #device::interrupt::#interrupt;
-
- rtfm::export::run(|| {
- while let Some((task, index)) = #ready_alias_.split().1.dequeue() {
- match task {
- #(#arms)*
- }
- }
- });
- }
- ));
-
- ctxt.dispatchers.insert(
- *level,
- Dispatcher {
- ready_queue: ready_alias,
- enum_: enum_alias,
- },
- );
- }
-
- (quote!(#(#data)*), quote!(#(#dispatchers)*))
-}
-
-fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut items = vec![];
-
- // Generate `spawn` functions
- let device = &app.args.device;
- let priority = &ctxt.priority;
- #[cfg(feature = "timer-queue")]
- let baseline = &ctxt.baseline;
- for (name, task) in &ctxt.tasks {
- let alias = &task.spawn_fn;
- let task_ = &app.tasks[name];
- let cfgs = &task_.cfgs;
- let free = &task.free_queue;
- let level = task_.args.priority;
- let dispatcher = &ctxt.dispatchers[&level];
- let ready = &dispatcher.ready_queue;
- let enum_ = &dispatcher.enum_;
- let dispatcher = &analysis.dispatchers[&level].interrupt;
- let inputs = &task.inputs;
- let args = &task_.inputs;
- let ty = tuple_ty(args);
- let pats = tuple_pat(args);
-
- let scheduleds_write = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- let scheduleds = &ctxt.tasks[name].scheduleds;
- if cfg!(feature = "nightly") {
- quote!(
- ptr::write(
- #scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr(),
- #baseline,
- );
- )
- } else {
- quote!(
- ptr::write(
- #scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
- #baseline,
- );
- )
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let baseline_arg = match () {
- #[cfg(feature = "timer-queue")]
- () => quote!(#baseline: rtfm::Instant,),
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
-
- let input = if cfg!(feature = "nightly") {
- quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr())
- } else {
- quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index)))
- };
- items.push(quote!(
- #[inline(always)]
- #(#cfgs)*
- unsafe fn #alias(
- #baseline_arg
- #priority: &rtfm::export::Priority,
- #(#args,)*
- ) -> Result<(), #ty> {
- use core::ptr;
-
- use rtfm::Mutex;
-
- if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
- ptr::write(#input, (#pats));
- #scheduleds_write
-
- #ready { #priority }.lock(|rq| {
- rq.split().0.enqueue_unchecked((#enum_::#name, index))
- });
-
- rtfm::pend(#device::Interrupt::#dispatcher);
-
- Ok(())
- } else {
- Err((#pats))
- }
- }
+ if user_init.is_some() {
+ mod_app_imports.push(quote!(
+ use super::init;
))
}
-
- // Generate `spawn` structs; these call the `spawn` functions generated above
- for (name, spawn) in app.spawn_callers() {
- if spawn.is_empty() {
- continue;
- }
-
- #[cfg(feature = "timer-queue")]
- let is_idle = name.to_string() == "idle";
-
- let mut methods = vec![];
- for task in spawn {
- let task_ = &app.tasks[task];
- let alias = &ctxt.tasks[task].spawn_fn;
- let inputs = &task_.inputs;
- let cfgs = &task_.cfgs;
- let ty = tuple_ty(inputs);
- let pats = tuple_pat(inputs);
-
- let instant = match () {
- #[cfg(feature = "timer-queue")]
- () => {
- if is_idle {
- quote!(rtfm::Instant::now(),)
- } else {
- quote!(*self.#baseline,)
- }
- }
- #[cfg(not(feature = "timer-queue"))]
- () => quote!(),
- };
- methods.push(quote!(
- #[allow(unsafe_code)]
- #[inline]
- #(#cfgs)*
- pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> {
- unsafe { #alias(#instant &self.#priority, #pats) }
- }
- ));
- }
-
- items.push(quote!(
- impl<'a> #name::Spawn<'a> {
- #(#methods)*
- }
- ));
- }
-
- quote!(#(#items)*)
-}
-
-#[cfg(feature = "timer-queue")]
-fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
- let mut items = vec![];
-
- // Generate `schedule` functions
- let priority = &ctxt.priority;
- let timer_queue = &ctxt.timer_queue;
- for (task, alias) in &ctxt.schedule_fn {
- let task_ = &ctxt.tasks[task];
- let free = &task_.free_queue;
- let enum_ = &ctxt.schedule_enum;
- let inputs = &task_.inputs;
- let scheduleds = &task_.scheduleds;
- let task__ = &app.tasks[task];
- let args = &task__.inputs;
- let cfgs = &task__.cfgs;
- let ty = tuple_ty(args);
- let pats = tuple_pat(args);
-
- let input = if cfg!(feature = "nightly") {
- quote!(#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr())
- } else {
- quote!(#inputs.get_mut().get_unchecked_mut(usize::from(index)))
- };
-
- let scheduled = if cfg!(feature = "nightly") {
- quote!(#scheduleds.get_unchecked_mut(usize::from(index)).as_mut_ptr())
- } else {
- quote!(#scheduleds.get_mut().get_unchecked_mut(usize::from(index)))
- };
- items.push(quote!(
- #[inline(always)]
- #(#cfgs)*
- unsafe fn #alias(
- #priority: &rtfm::export::Priority,
- instant: rtfm::Instant,
- #(#args,)*
- ) -> Result<(), #ty> {
- use core::ptr;
-
- use rtfm::Mutex;
-
- if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
- ptr::write(#input, (#pats));
- ptr::write(#scheduled, instant);
-
- let nr = rtfm::export::NotReady {
- instant,
- index,
- task: #enum_::#task,
- };
-
- ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr));
-
- Ok(())
- } else {
- Err((#pats))
- }
- }
+ if user_idle.is_some() {
+ mod_app_imports.push(quote!(
+ use super::idle;
))
}
- // Generate `Schedule` structs; these call the `schedule` functions generated above
- for (name, schedule) in app.schedule_callers() {
- if schedule.is_empty() {
- continue;
- }
-
- debug_assert!(!schedule.is_empty());
-
- let mut methods = vec![];
- for task in schedule {
- let alias = &ctxt.schedule_fn[task];
- let task_ = &app.tasks[task];
- let inputs = &task_.inputs;
- let cfgs = &task_.cfgs;
- let ty = tuple_ty(inputs);
- let pats = tuple_pat(inputs);
+ user.push(quote!(
+ #user_init
- methods.push(quote!(
- #[inline]
- #(#cfgs)*
- pub fn #task(
- &self,
- instant: rtfm::Instant,
- #(#inputs,)*
- ) -> Result<(), #ty> {
- unsafe { #alias(&self.#priority, instant, #pats) }
- }
- ));
- }
-
- items.push(quote!(
- impl<'a> #name::Schedule<'a> {
- #(#methods)*
- }
- ));
- }
-
- quote!(#(#items)*)
-}
-
-fn timer_queue(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let tasks = &analysis.timer_queue.tasks;
-
- if tasks.is_empty() {
- return quote!();
- }
-
- let mut items = vec![];
-
- let variants = tasks
- .iter()
- .map(|task| {
- let cfgs = &app.tasks[task].cfgs;
- quote!(
- #(#cfgs)*
- #task
- )
- })
- .collect::<Vec<_>>();
- let enum_ = &ctxt.schedule_enum;
- items.push(quote!(
- #[allow(dead_code)]
- #[allow(non_camel_case_types)]
- #[derive(Clone, Copy)]
- enum #enum_ { #(#variants,)* }
+ #user_idle
));
- let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false);
- let tq = &ctxt.timer_queue;
- let symbol = format!("TIMER_QUEUE::{}", tq);
- if cfg!(feature = "nightly") {
- items.push(quote!(
- #[doc = #symbol]
- static mut #tq: rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
- rtfm::export::MaybeUninit::uninit();
- ));
- } else {
- items.push(quote!(
- #[doc = #symbol]
- static mut #tq:
- rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
- rtfm::export::MaybeUninit::uninit();
- ));
- }
-
- items.push(mk_resource(
- ctxt,
- &[],
- tq,
- quote!(rtfm::export::TimerQueue<#enum_, #cap>),
- analysis.timer_queue.ceiling,
- quote!(&mut *#tq.as_mut_ptr()),
- app,
- None,
+ imports.push(quote!(
+ #(#user_init_imports)*
+ #(#user_idle_imports)*
));
- let priority = &ctxt.priority;
- let device = &app.args.device;
- let arms = tasks
- .iter()
- .map(|task| {
- let task_ = &app.tasks[task];
- let level = task_.args.priority;
- let cfgs = &task_.cfgs;
- let dispatcher_ = &ctxt.dispatchers[&level];
- let tenum = &dispatcher_.enum_;
- let ready = &dispatcher_.ready_queue;
- let dispatcher = &analysis.dispatchers[&level].interrupt;
-
- quote!(
- #(#cfgs)*
- #enum_::#task => {
- (#ready { #priority }).lock(|rq| {
- rq.split().0.enqueue_unchecked((#tenum::#task, index))
- });
-
- rtfm::pend(#device::Interrupt::#dispatcher);
- }
- )
- })
- .collect::<Vec<_>>();
+ root.push(quote!(
+ #(#root_init)*
- let logical_prio = analysis.timer_queue.priority;
- let alias = ctxt.ident_gen.mk_ident(None, false);
- items.push(quote!(
- #[export_name = "SysTick"]
- #[doc(hidden)]
- unsafe fn #alias() {
- use rtfm::Mutex;
-
- let ref #priority = rtfm::export::Priority::new(#logical_prio);
-
- rtfm::export::run(|| {
- rtfm::export::sys_tick(#tq { #priority }, |task, index| {
- match task {
- #(#arms)*
- }
- });
- })
- }
+ #(#root_idle)*
));
- quote!(#(#items)*)
-}
-
-fn pre_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut exprs = vec![];
-
- if !cfg!(feature = "nightly") {
- // these are `MaybeUninit` arrays
- for task in ctxt.tasks.values() {
- let inputs = &task.inputs;
- exprs.push(quote!(#inputs.write(core::mem::uninitialized());))
- }
-
- #[cfg(feature = "timer-queue")]
- for task in ctxt.tasks.values() {
- let scheduleds = &task.scheduleds;
- exprs.push(quote!(#scheduleds.write(core::mem::uninitialized());))
- }
+ mod_app.push(quote!(
+ #mod_app_init
- // these are `MaybeUninit` `ReadyQueue`s
- for dispatcher in ctxt.dispatchers.values() {
- let rq = &dispatcher.ready_queue;
- exprs.push(quote!(#rq.write(rtfm::export::ReadyQueue::new_sc());))
- }
-
- // these are `MaybeUninit` `FreeQueue`s
- for task in ctxt.tasks.values() {
- let fq = &task.free_queue;
- exprs.push(quote!(#fq.write(rtfm::export::FreeQueue::new_sc());))
- }
- }
-
- // Initialize the timer queue
- if !analysis.timer_queue.tasks.is_empty() {
- let tq = &ctxt.timer_queue;
- exprs.push(quote!(#tq.write(rtfm::export::TimerQueue::new(p.SYST));));
- }
-
- // Populate the `FreeQueue`s
- for (name, task) in &ctxt.tasks {
- let fq = &task.free_queue;
- let fq_ = if cfg!(feature = "nightly") {
- quote!(#fq)
- } else {
- quote!(#fq.get_mut())
- };
- let capacity = analysis.capacities[name];
- exprs.push(quote!(
- for i in 0..#capacity {
- #fq_.enqueue_unchecked(i);
- }
- ))
- }
-
- let device = &app.args.device;
- let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
- for (handler, interrupt) in &app.interrupts {
- let name = interrupt.args.binds(handler);
- let priority = interrupt.args.priority;
- exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);));
- exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
- exprs.push(quote!(p.NVIC.set_priority(
- #device::Interrupt::#name,
- ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
- );));
- }
-
- for (priority, dispatcher) in &analysis.dispatchers {
- let name = &dispatcher.interrupt;
- exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name);));
- exprs.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
- exprs.push(quote!(p.NVIC.set_priority(
- #device::Interrupt::#name,
- ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
- );));
- }
-
- // Set the cycle count to 0 and disable it while `init` executes
- if cfg!(feature = "timer-queue") {
- exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);));
- exprs.push(quote!(p.DWT.cyccnt.write(0);));
- }
-
- quote!(
- let mut p = rtfm::export::Peripherals::steal();
- #(#exprs)*
- )
-}
-
-fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
- let mut items = vec![];
-
- for ty in &analysis.assert_sync {
- items.push(quote!(rtfm::export::assert_sync::<#ty>()));
- }
-
- for task in &analysis.tasks_assert_send {
- let ty = tuple_ty(&app.tasks[task].inputs);
- items.push(quote!(rtfm::export::assert_send::<#ty>()));
- }
-
- // all late resources need to be `Send`
- for ty in &analysis.resources_assert_send {
- items.push(quote!(rtfm::export::assert_send::<#ty>()));
- }
-
- quote!(#(#items;)*)
-}
-
-fn mk_resource(
- ctxt: &Context,
- cfgs: &[Attribute],
- struct_: &Ident,
- ty: proc_macro2::TokenStream,
- ceiling: u8,
- ptr: proc_macro2::TokenStream,
- app: &App,
- module: Option<&mut Vec<proc_macro2::TokenStream>>,
-) -> proc_macro2::TokenStream {
- let priority = &ctxt.priority;
- let device = &app.args.device;
+ #mod_app_idle
+ ));
- let mut items = vec![];
+ let main = util::suffixed("main");
+ mains.push(quote!(
+ #[no_mangle]
+ unsafe extern "C" fn #main() -> ! {
+ let _TODO: () = ();
- let path = if let Some(module) = module {
- let doc = format!("`{}`", ty);
- module.push(quote!(
- #[allow(non_camel_case_types)]
- #[doc = #doc]
- #(#cfgs)*
- pub struct #struct_<'a> {
- #[doc(hidden)]
- pub #priority: &'a rtfm::export::Priority,
- }
- ));
+ #(#assertion_stmts)*
- quote!(resources::#struct_)
- } else {
- items.push(quote!(
- #(#cfgs)*
- struct #struct_<'a> {
- #priority: &'a rtfm::export::Priority,
- }
- ));
+ #(#pre_init_stmts)*
- quote!(#struct_)
- };
+ #call_init
- items.push(quote!(
- #(#cfgs)*
- impl<'a> rtfm::Mutex for #path<'a> {
- type T = #ty;
+ #(#post_init_stmts)*
- #[inline]
- fn lock<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
- unsafe {
- rtfm::export::claim(
- #ptr,
- &self.#priority,
- #ceiling,
- #device::NVIC_PRIO_BITS,
- f,
- )
- }
- }
+ #call_idle
}
));
- quote!(#(#items)*)
-}
+ let (mod_app_resources, mod_resources, mod_resources_imports) =
+ resources::codegen(app, analysis, extra);
-fn mk_capacity_literal(capacity: u8) -> LitInt {
- LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
-}
+ let (
+ mod_app_hardware_tasks,
+ root_hardware_tasks,
+ user_hardware_tasks,
+ user_hardware_tasks_imports,
+ ) = hardware_tasks::codegen(app, analysis, extra);
-fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
- let capacity = if power_of_two {
- capacity
- .checked_next_power_of_two()
- .expect("capacity.next_power_of_two()")
- } else {
- capacity
- };
+ let (
+ mod_app_software_tasks,
+ root_software_tasks,
+ user_software_tasks,
+ user_software_tasks_imports,
+ ) = software_tasks::codegen(app, analysis, extra);
- let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+ let mod_app_dispatchers = dispatchers::codegen(app, analysis, extra);
- quote!(rtfm::export::consts::#ident)
-}
+ let mod_app_spawn = spawn::codegen(app, analysis, extra);
-struct IdentGenerator {
- call_count: u32,
- rng: rand::rngs::SmallRng,
-}
+ let mod_app_timer_queue = timer_queue::codegen(app, analysis, extra);
-impl IdentGenerator {
- fn new() -> IdentGenerator {
- let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+ let mod_app_schedule = schedule::codegen(app, extra);
- let secs = elapsed.as_secs();
- let nanos = elapsed.subsec_nanos();
+ let user_imports = app.user_imports.clone();
+ let user_code = app.user_code.clone();
+ let name = &app.name;
+ let device = extra.device;
+ quote!(
+ #(#user)*
- let mut seed: [u8; 16] = [0; 16];
+ #(#user_hardware_tasks)*
- for (i, v) in seed.iter_mut().take(8).enumerate() {
- *v = ((secs >> (i * 8)) & 0xFF) as u8
- }
+ #(#user_software_tasks)*
- for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() {
- *v = ((nanos >> (i * 8)) & 0xFF) as u8
- }
+ #(#root)*
- let rng = rand::rngs::SmallRng::from_seed(seed);
+ #mod_resources
- IdentGenerator { call_count: 0, rng }
- }
+ #(#root_hardware_tasks)*
- fn mk_ident(&mut self, name: Option<&str>, random: bool) -> Ident {
- let s = if let Some(name) = name {
- format!("{}_", name)
- } else {
- "__rtfm_internal_".to_string()
- };
+ #(#root_software_tasks)*
- let mut s = format!("{}{}", s, self.call_count);
- self.call_count += 1;
+ /// Implementation details
+ mod #name {
+ /// Always include the device crate which contains the vector table
+ use #device as _;
+ #(#imports)*
+ #(#user_imports)*
- if random {
- s.push('_');
+ /// User code from within the module
+ #(#user_code)*
+ /// User code end
- for i in 0..4 {
- if i == 0 || self.rng.gen() {
- s.push(('a' as u8 + self.rng.gen::<u8>() % 25) as char)
- } else {
- s.push(('0' as u8 + self.rng.gen::<u8>() % 10) as char)
- }
- }
- }
- Ident::new(&s, Span::call_site())
- }
-}
-
-// `once = true` means that these locals will be called from a function that will run *once*
-fn mk_locals(locals: &BTreeMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
- let lt = if once { Some(quote!('static)) } else { None };
-
- let locals = locals
- .iter()
- .map(|(name, static_)| {
- let attrs = &static_.attrs;
- let cfgs = &static_.cfgs;
- let expr = &static_.expr;
- let ident = name;
- let ty = &static_.ty;
+ #(#user_hardware_tasks_imports)*
- quote!(
- #[allow(non_snake_case)]
- #(#cfgs)*
- let #ident: &#lt mut #ty = {
- #(#attrs)*
- #(#cfgs)*
- static mut #ident: #ty = #expr;
+ #(#user_software_tasks_imports)*
- unsafe { &mut #ident }
- };
- )
- })
- .collect::<Vec<_>>();
-
- quote!(#(#locals)*)
-}
+ #(#mod_resources_imports)*
-fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
- if inputs.len() == 1 {
- let pat = &inputs[0].pat;
- quote!(#pat)
- } else {
- let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
+ /// app module
+ #(#mod_app)*
- quote!(#(#pats,)*)
- }
-}
+ #(#mod_app_resources)*
-fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
- if inputs.len() == 1 {
- let ty = &inputs[0].ty;
- quote!(#ty)
- } else {
- let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>();
+ #(#mod_app_hardware_tasks)*
- quote!((#(#tys,)*))
- }
-}
+ #(#mod_app_software_tasks)*
-#[derive(Clone, Debug, Eq, Hash, PartialEq)]
-enum Kind {
- Exception(Ident),
- Idle,
- Init,
- Interrupt(Ident),
- Task(Ident),
-}
+ #(#mod_app_dispatchers)*
-impl Kind {
- fn ident(&self) -> Ident {
- match self {
- Kind::Init => Ident::new("init", Span::call_site()),
- Kind::Idle => Ident::new("idle", Span::call_site()),
- Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(),
- }
- }
+ #(#mod_app_spawn)*
- fn is_idle(&self) -> bool {
- *self == Kind::Idle
- }
+ #(#mod_app_timer_queue)*
- fn is_init(&self) -> bool {
- *self == Kind::Init
- }
+ #(#mod_app_schedule)*
- fn runs_once(&self) -> bool {
- match *self {
- Kind::Init | Kind::Idle => true,
- _ => false,
+ #(#mains)*
}
- }
+ )
}
diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs
new file mode 100644
index 00000000..4d9aae47
--- /dev/null
+++ b/macros/src/codegen/assertions.rs
@@ -0,0 +1,19 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+use crate::analyze::Analysis;
+
+/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
+pub fn codegen(analysis: &Analysis) -> Vec<TokenStream2> {
+ 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>();));
+ }
+
+ stmts
+}
diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs
new file mode 100644
index 00000000..300aa996
--- /dev/null
+++ b/macros/src/codegen/dispatchers.rs
@@ -0,0 +1,155 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates task dispatchers
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let interrupts = &analysis.interrupts;
+
+ for (&level, channel) in &analysis.channels {
+ let mut stmts = vec![];
+
+ let variants = channel
+ .tasks
+ .iter()
+ .map(|name| {
+ let cfgs = &app.software_tasks[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ #name
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let doc = format!(
+ "Software tasks to be dispatched at priority level {}",
+ level,
+ );
+ let t = util::spawn_t_ident(level);
+ items.push(quote!(
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ #[doc = #doc]
+ enum #t {
+ #(#variants,)*
+ }
+ ));
+
+ let n = util::capacity_typenum(channel.capacity, true);
+ let rq = util::rq_ident(level);
+ let (rq_ty, rq_expr) = {
+ (
+ quote!(rtic::export::SCRQ<#t, #n>),
+ quote!(rtic::export::Queue(unsafe {
+ rtic::export::iQueue::u8_sc()
+ })),
+ )
+ };
+
+ let doc = format!(
+ "Queue of tasks ready to be dispatched at priority level {}",
+ level
+ );
+ items.push(quote!(
+ #[doc = #doc]
+ static mut #rq: #rq_ty = #rq_expr;
+ ));
+
+ if let Some(ceiling) = channel.ceiling {
+ items.push(quote!(
+ struct #rq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &rq,
+ rq_ty,
+ ceiling,
+ quote!(&mut #rq),
+ ));
+ }
+
+ let arms = channel
+ .tasks
+ .iter()
+ .map(|name| {
+ let task = &app.software_tasks[name];
+ let cfgs = &task.cfgs;
+ let fq = util::fq_ident(name);
+ let inputs = util::inputs_ident(name);
+ let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
+
+ let (let_instant, instant) = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ (
+ quote!(
+ let instant =
+ #instants.get_unchecked(usize::from(index)).as_ptr().read();
+ ),
+ quote!(, instant),
+ )
+ } else {
+ (quote!(), quote!())
+ };
+
+ let locals_new = if task.locals.is_empty() {
+ quote!()
+ } else {
+ quote!(#name::Locals::new(),)
+ };
+
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ let #tupled =
+ #inputs.get_unchecked(usize::from(index)).as_ptr().read();
+ #let_instant
+ #fq.split().0.enqueue_unchecked(index);
+ let priority = &rtic::export::Priority::new(PRIORITY);
+ crate::#name(
+ #locals_new
+ #name::Context::new(priority #instant)
+ #(,#pats)*
+ )
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ stmts.push(quote!(
+ while let Some((task, index)) = #rq.split().1.dequeue() {
+ match task {
+ #(#arms)*
+ }
+ }
+ ));
+
+ let doc = format!("Interrupt handler to dispatch tasks at priority {}", level);
+ let interrupt = util::suffixed(&interrupts[&level].to_string());
+ items.push(quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ #[no_mangle]
+ unsafe fn #interrupt() {
+ /// The priority of this interrupt handler
+ const PRIORITY: u8 = #level;
+
+ rtic::export::run(PRIORITY, || {
+ #(#stmts)*
+ });
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs
new file mode 100644
index 00000000..25f1df41
--- /dev/null
+++ b/macros/src/codegen/hardware_tasks.rs
@@ -0,0 +1,134 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct},
+};
+
+/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
+ Vec<TokenStream2>,
+ // root_hardware_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_hardware_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+ // user_hardware_tasks_imports -- the imports for `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+ let mut hardware_tasks_imports = vec![];
+
+ for (name, task) in &app.hardware_tasks {
+ let (let_instant, instant) = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ (
+ Some(quote!(let instant = <#m as rtic::Monotonic>::now();)),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+
+ let locals_new = if task.locals.is_empty() {
+ quote!()
+ } else {
+ quote!(#name::Locals::new(),)
+ };
+
+ let symbol = task.args.binds.clone();
+ let priority = task.args.priority;
+
+ mod_app.push(quote!(
+ #[allow(non_snake_case)]
+ #[no_mangle]
+ unsafe fn #symbol() {
+ const PRIORITY: u8 = #priority;
+
+ #let_instant
+
+ rtic::export::run(PRIORITY, || {
+ crate::#name(
+ #locals_new
+ #name::Context::new(&rtic::export::Priority::new(PRIORITY) #instant)
+ )
+ });
+ }
+ ));
+
+ let mut needs_lt = false;
+
+ // `${task}Resources`
+ if !task.args.resources.is_empty() {
+ let (item, constructor) = resources_struct::codegen(
+ Context::HardwareTask(name),
+ priority,
+ &mut needs_lt,
+ app,
+ analysis,
+ );
+
+ // Add resources to imports
+ let name_res = format_ident!("{}Resources", name);
+ hardware_tasks_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_res;
+ ));
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ root.push(module::codegen(
+ Context::HardwareTask(name),
+ needs_lt,
+ app,
+ extra,
+ ));
+
+ // `${task}Locals`
+ let mut locals_pat = None;
+ if !task.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app);
+
+ root.push(struct_);
+ locals_pat = Some(pat);
+ }
+
+ let attrs = &task.attrs;
+ let context = &task.context;
+ let stmts = &task.stmts;
+ let locals_pat = locals_pat.iter();
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+
+ hardware_tasks_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+ }
+
+ (mod_app, root, user_tasks, hardware_tasks_imports)
+}
diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs
new file mode 100644
index 00000000..2e2932d7
--- /dev/null
+++ b/macros/src/codegen/idle.rs
@@ -0,0 +1,104 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct},
+};
+
+/// Generates support code for `#[idle]` functions
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_idle -- the `${idle}Resources` constructor
+ Option<TokenStream2>,
+ // root_idle -- items that must be placed in the root of the crate:
+ // - the `${idle}Locals` struct
+ // - the `${idle}Resources` struct
+ // - the `${idle}` module, which contains types like `${idle}::Context`
+ Vec<TokenStream2>,
+ // user_idle
+ Option<TokenStream2>,
+ // user_idle_imports
+ Vec<TokenStream2>,
+ // call_idle
+ TokenStream2,
+) {
+ if app.idles.len() > 0 {
+ let idle = &app.idles.first().unwrap();
+ let mut needs_lt = false;
+ let mut mod_app = None;
+ let mut root_idle = vec![];
+ let mut locals_pat = None;
+ let mut locals_new = None;
+
+ let mut user_idle_imports = vec![];
+
+ let name = &idle.name;
+
+ if !idle.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Idle, 0, &mut needs_lt, app, analysis);
+
+ root_idle.push(item);
+ mod_app = Some(constructor);
+
+ let name_resource = format_ident!("{}Resources", name);
+ user_idle_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_resource;
+ ));
+ }
+
+ if !idle.locals.is_empty() {
+ let (locals, pat) = locals::codegen(Context::Idle, &idle.locals, app);
+
+ locals_new = Some(quote!(#name::Locals::new()));
+ locals_pat = Some(pat);
+ root_idle.push(locals);
+ }
+
+ root_idle.push(module::codegen(Context::Idle, needs_lt, app, extra));
+
+ let attrs = &idle.attrs;
+ let context = &idle.context;
+ let stmts = &idle.stmts;
+ let locals_pat = locals_pat.iter();
+ let user_idle = Some(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) -> ! {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+ user_idle_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ let locals_new = locals_new.iter();
+ let call_idle = quote!(crate::#name(
+ #(#locals_new,)*
+ #name::Context::new(&rtic::export::Priority::new(0))
+ ));
+
+ (mod_app, root_idle, user_idle, user_idle_imports, call_idle)
+ } else {
+ (
+ None,
+ vec![],
+ None,
+ vec![],
+ quote!(loop {
+ rtic::export::wfi()
+ }),
+ )
+ }
+}
diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs
new file mode 100644
index 00000000..8942439b
--- /dev/null
+++ b/macros/src/codegen/init.rs
@@ -0,0 +1,125 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+/// Generates support code for `#[init]` functions
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_idle -- the `${init}Resources` constructor
+ Option<TokenStream2>,
+ // root_init -- items that must be placed in the root of the crate:
+ // - the `${init}Locals` struct
+ // - the `${init}Resources` struct
+ // - the `${init}LateResources` struct
+ // - the `${init}` module, which contains types like `${init}::Context`
+ Vec<TokenStream2>,
+ // user_init -- the `#[init]` function written by the user
+ Option<TokenStream2>,
+ // user_init_imports -- the imports for `#[init]` functio written by the user
+ Vec<TokenStream2>,
+ // call_init -- the call to the user `#[init]` if there's one
+ Option<TokenStream2>,
+) {
+ if app.inits.len() > 0 {
+ let init = &app.inits.first().unwrap();
+ let mut needs_lt = false;
+ let name = &init.name;
+
+ let mut root_init = vec![];
+
+ let late_fields = analysis
+ .late_resources
+ .iter()
+ .flat_map(|resources| {
+ resources.iter().map(|name| {
+ let ty = &app.late_resources[name].ty;
+ let cfgs = &app.late_resources[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ pub #name: #ty
+ )
+ })
+ })
+ .collect::<Vec<_>>();
+
+ let mut user_init_imports = vec![];
+ let late_resources = util::late_resources_ident(&name);
+
+ root_init.push(quote!(
+ /// Resources initialized at runtime
+ #[allow(non_snake_case)]
+ pub struct #late_resources {
+ #(#late_fields),*
+ }
+ ));
+
+ let name_late = format_ident!("{}LateResources", name);
+ user_init_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_late;
+ ));
+
+ let mut locals_pat = None;
+ let mut locals_new = None;
+ if !init.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::Init, &init.locals, app);
+
+ locals_new = Some(quote!(#name::Locals::new()));
+ locals_pat = Some(pat);
+ root_init.push(struct_);
+ }
+
+ let context = &init.context;
+ let attrs = &init.attrs;
+ let stmts = &init.stmts;
+ let locals_pat = locals_pat.iter();
+ let user_init = Some(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ fn #name(#(#locals_pat,)* #context: #name::Context) -> #name::LateResources {
+ #(#stmts)*
+ }
+ ));
+ user_init_imports.push(quote!(
+ #(#attrs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ let mut mod_app = None;
+ if !init.args.resources.is_empty() {
+ let (item, constructor) =
+ resources_struct::codegen(Context::Init, 0, &mut needs_lt, app, analysis);
+
+ root_init.push(item);
+ mod_app = Some(constructor);
+
+ let name_late = format_ident!("{}Resources", name);
+ user_init_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_late;
+ ));
+ }
+
+ let locals_new = locals_new.iter();
+ let call_init = Some(
+ quote!(let late = crate::#name(#(#locals_new,)* #name::Context::new(core.into()));),
+ );
+
+ root_init.push(module::codegen(Context::Init, needs_lt, app, extra));
+
+ (mod_app, root_init, user_init, user_init_imports, call_init)
+ } else {
+ (None, vec![], None, vec![], None)
+ }
+}
diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs
new file mode 100644
index 00000000..336c0b21
--- /dev/null
+++ b/macros/src/codegen/locals.rs
@@ -0,0 +1,94 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{
+ ast::{App, Local},
+ Context, Map,
+};
+
+use crate::codegen::util;
+
+pub fn codegen(
+ ctxt: Context,
+ locals: &Map<Local>,
+ app: &App,
+) -> (
+ // locals
+ TokenStream2,
+ // pat
+ TokenStream2,
+) {
+ assert!(!locals.is_empty());
+
+ let runs_once = ctxt.runs_once();
+ let ident = util::locals_ident(ctxt, app);
+
+ let mut lt = None;
+ let mut fields = vec![];
+ let mut items = vec![];
+ let mut names = vec![];
+ let mut values = vec![];
+ let mut pats = vec![];
+ let mut has_cfgs = false;
+
+ for (name, local) in locals {
+ let lt = if runs_once {
+ quote!('static)
+ } else {
+ lt = Some(quote!('a));
+ quote!('a)
+ };
+
+ let cfgs = &local.cfgs;
+ has_cfgs |= !cfgs.is_empty();
+
+ let expr = &local.expr;
+ let ty = &local.ty;
+ fields.push(quote!(
+ #(#cfgs)*
+ #name: &#lt mut #ty
+ ));
+ items.push(quote!(
+ #(#cfgs)*
+ static mut #name: #ty = #expr
+ ));
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &mut #name
+ ));
+ names.push(name);
+ pats.push(quote!(
+ #(#cfgs)*
+ #name
+ ));
+ }
+
+ if lt.is_some() && has_cfgs {
+ fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>));
+ values.push(quote!(__marker__: core::marker::PhantomData));
+ }
+
+ let locals = quote!(
+ #[allow(non_snake_case)]
+ #[doc(hidden)]
+ pub struct #ident<#lt> {
+ #(#fields),*
+ }
+
+ impl<#lt> #ident<#lt> {
+ #[inline(always)]
+ unsafe fn new() -> Self {
+ #(#items;)*
+
+ #ident {
+ #(#values),*
+ }
+ }
+ }
+ );
+
+ let ident = ctxt.ident(app);
+ (
+ locals,
+ quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals),
+ )
+}
diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs
new file mode 100644
index 00000000..2e51e7db
--- /dev/null
+++ b/macros/src/codegen/module.rs
@@ -0,0 +1,330 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+
+use crate::{check::Extra, codegen::util};
+
+pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 {
+ let mut items = vec![];
+ let mut fields = vec![];
+ let mut values = vec![];
+
+ let name = ctxt.ident(app);
+
+ let mut needs_instant = false;
+ let mut lt = None;
+ match ctxt {
+ Context::Init => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// System start time = `Instant(0 /* cycles */)`
+ pub start: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: <#m as rtic::Monotonic>::zero()));
+
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals minus the SysTick
+ pub core: rtic::Peripherals
+ ));
+ } else {
+ fields.push(quote!(
+ /// Core (Cortex-M) peripherals
+ pub core: rtic::export::Peripherals
+ ));
+ }
+
+ if extra.peripherals {
+ let device = extra.device;
+
+ fields.push(quote!(
+ /// Device peripherals
+ pub device: #device::Peripherals
+ ));
+
+ values.push(quote!(device: #device::Peripherals::steal()));
+ }
+
+ lt = Some(quote!('a));
+ fields.push(quote!(
+ /// Critical section token for init
+ pub cs: rtic::export::CriticalSection<#lt>
+ ));
+
+ values.push(quote!(cs: rtic::export::CriticalSection::new()));
+
+ values.push(quote!(core));
+ }
+
+ Context::Idle => {}
+
+ Context::HardwareTask(..) => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// Time at which this handler started executing
+ pub start: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(start: instant));
+
+ needs_instant = true;
+ }
+ }
+
+ Context::SoftwareTask(..) => {
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ fields.push(quote!(
+ /// The time at which this task was scheduled to run
+ pub scheduled: <#m as rtic::Monotonic>::Instant
+ ));
+
+ values.push(quote!(scheduled: instant));
+
+ needs_instant = true;
+ }
+ }
+ }
+
+ if ctxt.has_locals(app) {
+ let ident = util::locals_ident(ctxt, app);
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as Locals;
+ ));
+ }
+
+ if ctxt.has_resources(app) {
+ let ident = util::resources_ident(ctxt, app);
+ let lt = if resources_tick {
+ lt = Some(quote!('a));
+ Some(quote!('a))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#ident as Resources;
+ ));
+
+ fields.push(quote!(
+ /// Resources this task has access to
+ pub resources: Resources<#lt>
+ ));
+
+ let priority = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority))
+ };
+ values.push(quote!(resources: Resources::new(#priority)));
+ }
+
+ if ctxt.uses_schedule(app) {
+ let doc = "Tasks that can be `schedule`-d from this context";
+ if ctxt.is_init() {
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Schedule {
+ _not_send: core::marker::PhantomData<*mut ()>,
+ }
+ ));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub schedule: Schedule
+ ));
+
+ values.push(quote!(
+ schedule: Schedule { _not_send: core::marker::PhantomData }
+ ));
+ } else {
+ lt = Some(quote!('a));
+
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Schedule<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+
+ impl<'a> Schedule<'a> {
+ #[doc(hidden)]
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &rtic::export::Priority {
+ &self.priority
+ }
+ }
+ ));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub schedule: Schedule<'a>
+ ));
+
+ values.push(quote!(
+ schedule: Schedule { priority }
+ ));
+ }
+ }
+
+ if ctxt.uses_spawn(app) {
+ let doc = "Tasks that can be `spawn`-ed from this context";
+ if ctxt.is_init() {
+ fields.push(quote!(
+ #[doc = #doc]
+ pub spawn: Spawn
+ ));
+
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Spawn {
+ _not_send: core::marker::PhantomData<*mut ()>,
+ }
+ ));
+
+ values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData }));
+ } else {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #[doc = #doc]
+ pub spawn: Spawn<'a>
+ ));
+
+ let mut instant_method = None;
+ if ctxt.is_idle() {
+ items.push(quote!(
+ #[doc = #doc]
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ values.push(quote!(spawn: Spawn { priority }));
+ } else {
+ let instant_field = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ needs_instant = true;
+ instant_method = Some(quote!(
+ pub unsafe fn instant(&self) -> <#m as rtic::Monotonic>::Instant {
+ self.instant
+ }
+ ));
+ Some(quote!(instant: <#m as rtic::Monotonic>::Instant,))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ /// Tasks that can be spawned from this context
+ #[derive(Clone, Copy)]
+ pub struct Spawn<'a> {
+ #instant_field
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ let _instant = if needs_instant {
+ Some(quote!(, instant))
+ } else {
+ None
+ };
+ values.push(quote!(
+ spawn: Spawn { priority #_instant }
+ ));
+ }
+
+ items.push(quote!(
+ impl<'a> Spawn<'a> {
+ #[doc(hidden)]
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &rtic::export::Priority {
+ self.priority
+ }
+
+ #instant_method
+ }
+ ));
+ }
+ }
+
+ if let Context::Init = ctxt {
+ let init = &app.inits.first().unwrap();
+ let late_resources = util::late_resources_ident(&init.name);
+
+ items.push(quote!(
+ #[doc(inline)]
+ pub use super::#late_resources as LateResources;
+ ));
+ }
+
+ let doc = match ctxt {
+ Context::Idle => "Idle loop",
+ Context::Init => "Initialization function",
+ Context::HardwareTask(_) => "Hardware task",
+ Context::SoftwareTask(_) => "Software task",
+ };
+
+ let core = if ctxt.is_init() {
+ if app.uses_schedule() {
+ Some(quote!(core: rtic::Peripherals,))
+ } else {
+ Some(quote!(core: rtic::export::Peripherals,))
+ }
+ } else {
+ None
+ };
+
+ let priority = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtic::export::Priority))
+ };
+
+ let instant = if needs_instant {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtic::Monotonic>::Instant))
+ } else {
+ None
+ };
+
+ items.push(quote!(
+ /// Execution context
+ pub struct Context<#lt> {
+ #(#fields,)*
+ }
+
+ impl<#lt> Context<#lt> {
+ #[inline(always)]
+ pub unsafe fn new(#core #priority #instant) -> Self {
+ Context {
+ #(#values,)*
+ }
+ }
+ }
+ ));
+
+ if !items.is_empty() {
+ quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub mod #name {
+ #(#items)*
+ }
+ )
+ } else {
+ quote!()
+ }
+}
diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs
new file mode 100644
index 00000000..c35c6976
--- /dev/null
+++ b/macros/src/codegen/post_init.rs
@@ -0,0 +1,31 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::analyze::Analysis;
+
+/// Generates code that runs after `#[init]` returns
+pub fn codegen(app: &App, analysis: &Analysis) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // Initialize late resources
+ if analysis.late_resources.len() > 0 {
+ // BTreeSet wrapped in a vector
+ for name in analysis.late_resources.first().unwrap() {
+ // If it's live
+ let cfgs = app.late_resources[name].cfgs.clone();
+ if analysis.locations.get(name).is_some() {
+ // Need to also include the cfgs
+ stmts.push(quote!(
+ #(#cfgs)*
+ #name.as_mut_ptr().write(late.#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
new file mode 100644
index 00000000..9c5f35ec
--- /dev/null
+++ b/macros/src/codegen/pre_init.rs
@@ -0,0 +1,109 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates code that runs before `#[init]`
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut stmts = vec![];
+
+ // Disable interrupts -- `init` must run with interrupts disabled
+ stmts.push(quote!(rtic::export::interrupt::disable();));
+
+ // Populate the FreeQueue
+ for fq in &analysis.free_queues {
+ // Get the task name
+ let name = fq.0;
+ let task = &app.software_tasks[name];
+ let cap = task.args.capacity;
+
+ let fq_ident = util::fq_ident(name);
+
+ stmts.push(quote!(
+ (0..#cap).for_each(|i| #fq_ident.enqueue_unchecked(i));
+ ));
+ }
+
+ 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 = extra.device;
+ let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
+
+ // Unmask interrupts and set their priorities
+ for (&priority, name) in analysis
+ .interrupts
+ .iter()
+ .chain(app.hardware_tasks.values().flat_map(|task| {
+ if !util::is_exception(&task.args.binds) {
+ Some((&task.args.priority, &task.args.binds))
+ } else {
+ // We do exceptions in another pass
+ None
+ }
+ }))
+ {
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration
+ let interrupt = util::interrupt_ident();
+ stmts.push(quote!(
+ core.NVIC.set_priority(
+ #device::#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(#device::#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
+ }
+ }) {
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ stmts.push(quote!(core.SCB.set_priority(
+ rtic::export::SystemHandler::#name,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+ }
+
+ // Initialize the SysTick if there exist a TimerQueue
+ if let Some(tq) = analysis.timer_queues.first() {
+ let priority = tq.priority;
+
+ // Compile time assert that this priority is supported by the device
+ stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
+
+ stmts.push(quote!(core.SCB.set_priority(
+ rtic::export::SystemHandler::SysTick,
+ rtic::export::logical2hw(#priority, #nvic_prio_bits),
+ );));
+
+ stmts.push(quote!(
+ core.SYST.set_clock_source(rtic::export::SystClkSource::Core);
+ core.SYST.enable_counter();
+ core.DCB.enable_trace();
+ ));
+ }
+
+ // If there's no user `#[idle]` then optimize returning from interrupt handlers
+ if app.idles.is_empty() {
+ // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
+ stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);));
+ }
+
+ stmts
+}
diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs
new file mode 100644
index 00000000..38ea5245
--- /dev/null
+++ b/macros/src/codegen/resources.rs
@@ -0,0 +1,122 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{analyze::Ownership, ast::App};
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates `static [mut]` variables and resource proxies
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app -- the `static [mut]` variables behind the proxies
+ Vec<TokenStream2>,
+ // mod_resources -- the `resources` module
+ TokenStream2,
+ // mod_resources_imports -- the `resources` module imports
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut mod_resources = vec![];
+ let mut mod_resources_imports = vec![];
+
+ for (name, res, expr, _) in app.resources(analysis) {
+ let cfgs = &res.cfgs;
+ let ty = &res.ty;
+
+ {
+ let section = if expr.is_none() {
+ util::link_section_uninit(true)
+ } else {
+ None
+ };
+
+ let (ty, expr) = if let Some(expr) = expr {
+ (quote!(#ty), quote!(#expr))
+ } else {
+ (
+ quote!(core::mem::MaybeUninit<#ty>),
+ quote!(core::mem::MaybeUninit::uninit()),
+ )
+ };
+
+ let attrs = &res.attrs;
+ mod_app.push(quote!(
+ #[allow(non_upper_case_globals)]
+ #(#attrs)*
+ #(#cfgs)*
+ #section
+ static mut #name: #ty = #expr;
+ ));
+ }
+
+ if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) {
+ mod_resources.push(quote!(
+ #[allow(non_camel_case_types)]
+ #(#cfgs)*
+ pub struct #name<'a> {
+ priority: &'a Priority,
+ }
+
+ #(#cfgs)*
+ impl<'a> #name<'a> {
+ #[inline(always)]
+ pub unsafe fn new(priority: &'a Priority) -> Self {
+ #name { priority }
+ }
+
+ #[inline(always)]
+ pub unsafe fn priority(&self) -> &Priority {
+ self.priority
+ }
+ }
+ ));
+
+ let ptr = if expr.is_none() {
+ quote!(
+ #(#cfgs)*
+ #name.as_mut_ptr()
+ )
+ } else {
+ quote!(
+ #(#cfgs)*
+ &mut #name
+ )
+ };
+
+ mod_resources_imports.push(quote!(
+ #[allow(non_camel_case_types)]
+ #(#cfgs)*
+ use super::resources::#name;
+ ));
+
+ mod_app.push(util::impl_mutex(
+ extra,
+ cfgs,
+ true,
+ name,
+ quote!(#ty),
+ *ceiling,
+ ptr,
+ ));
+ }
+ }
+
+ let mod_resources = if mod_resources.is_empty() {
+ quote!()
+ } else {
+ // Also import the resource module
+ mod_resources_imports.push(quote!(
+ use super::resources;
+ ));
+
+ quote!(mod resources {
+ use rtic::export::Priority;
+
+ #(#mod_resources)*
+ })
+ };
+
+ (mod_app, mod_resources, mod_resources_imports)
+}
diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs
new file mode 100644
index 00000000..92d5b666
--- /dev/null
+++ b/macros/src/codegen/resources_struct.rs
@@ -0,0 +1,177 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+
+use crate::{analyze::Analysis, codegen::util};
+
+pub fn codegen(
+ ctxt: Context,
+ priority: u8,
+ needs_lt: &mut bool,
+ app: &App,
+ analysis: &Analysis,
+) -> (TokenStream2, TokenStream2) {
+ let mut lt = None;
+
+ let resources = match ctxt {
+ Context::Init => &app.inits.first().unwrap().args.resources,
+ Context::Idle => &app.idles.first().unwrap().args.resources,
+ Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources,
+ Context::SoftwareTask(name) => &app.software_tasks[name].args.resources,
+ };
+
+ let mut fields = vec![];
+ let mut values = vec![];
+ let mut has_cfgs = false;
+
+ for (name, access) in resources {
+ let (res, expr) = app.resource(name).expect("UNREACHABLE");
+
+ let cfgs = &res.cfgs;
+ has_cfgs |= !cfgs.is_empty();
+
+ let mut_ = if access.is_exclusive() {
+ Some(quote!(mut))
+ } else {
+ None
+ };
+ let ty = &res.ty;
+
+ if ctxt.is_init() {
+ if !analysis.ownerships.contains_key(name) {
+ // Owned by `init`
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'static #mut_ #ty
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &#mut_ #name
+ ));
+ } else {
+ // Owned by someone else
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'a mut #ty
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &mut #name
+ ));
+ }
+ } else {
+ let ownership = &analysis.ownerships[name];
+
+ if ownership.needs_lock(priority) {
+ if mut_.is_none() {
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &'a #ty
+ ));
+ } else {
+ // Resource proxy
+ lt = Some(quote!('a));
+
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: resources::#name<'a>
+ ));
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: resources::#name::new(priority)
+
+ ));
+
+ continue;
+ }
+ } else {
+ let lt = if ctxt.runs_once() {
+ quote!('static)
+ } else {
+ lt = Some(quote!('a));
+ quote!('a)
+ };
+
+ if ownership.is_owned() || mut_.is_none() {
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &#lt #mut_ #ty
+ ));
+ } else {
+ fields.push(quote!(
+ #(#cfgs)*
+ pub #name: &#lt mut #ty
+ ));
+ }
+ }
+
+ let is_late = expr.is_none();
+ if is_late {
+ let expr = if mut_.is_some() {
+ quote!(&mut *#name.as_mut_ptr())
+ } else {
+ quote!(&*#name.as_ptr())
+ };
+
+ values.push(quote!(
+ #(#cfgs)*
+ #name: #expr
+ ));
+ } else {
+ values.push(quote!(
+ #(#cfgs)*
+ #name: &#mut_ #name
+ ));
+ }
+ }
+ }
+
+ if lt.is_some() {
+ *needs_lt = true;
+
+ // The struct could end up empty due to `cfg`s leading to an error due to `'a` being unused
+ if has_cfgs {
+ fields.push(quote!(
+ #[doc(hidden)]
+ pub __marker__: core::marker::PhantomData<&'a ()>
+ ));
+
+ values.push(quote!(__marker__: core::marker::PhantomData))
+ }
+ }
+
+ let doc = format!("Resources `{}` has access to", ctxt.ident(app));
+ let ident = util::resources_ident(ctxt, app);
+ let item = quote!(
+ #[allow(non_snake_case)]
+ #[doc = #doc]
+ pub struct #ident<#lt> {
+ #(#fields,)*
+ }
+ );
+
+ let arg = if ctxt.is_init() {
+ None
+ } else {
+ Some(quote!(priority: &#lt rtic::export::Priority))
+ };
+ let constructor = quote!(
+ impl<#lt> #ident<#lt> {
+ #[inline(always)]
+ pub unsafe fn new(#arg) -> Self {
+ #ident {
+ #(#values,)*
+ }
+ }
+ }
+ );
+
+ (item, constructor)
+}
diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs
new file mode 100644
index 00000000..5a887496
--- /dev/null
+++ b/macros/src/codegen/schedule.rs
@@ -0,0 +1,90 @@
+use std::collections::HashSet;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{
+ check::Extra,
+ codegen::{schedule_body, util},
+};
+
+/// Generates all `${ctxt}::Schedule` methods
+pub fn codegen(app: &App, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = HashSet::<_>::new();
+ for (scheduler, schedulees) in app.schedule_callers() {
+ let m = extra.monotonic();
+ let instant = quote!(<#m as rtic::Monotonic>::Instant);
+
+ let mut methods = vec![];
+
+ for name in schedulees {
+ let schedulee = &app.software_tasks[name];
+ let cfgs = &schedulee.cfgs;
+ let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs);
+ let args = &args;
+
+ if scheduler.is_init() {
+ // `init` uses a special `schedule` implementation; it doesn't use the
+ // `schedule_${name}` functions which are shared by other contexts
+
+ let body = schedule_body::codegen(scheduler, &name, app);
+
+ methods.push(quote!(
+ #(#cfgs)*
+ pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ } else {
+ let schedule = util::schedule_ident(name);
+
+ if !seen.contains(name) {
+ // Generate a `schedule_${name}_S${sender}` function
+ seen.insert(name);
+
+ let body = schedule_body::codegen(scheduler, &name, app);
+
+ items.push(quote!(
+ #(#cfgs)*
+ pub unsafe fn #schedule(
+ priority: &rtic::export::Priority,
+ instant: #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ pub fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
+ unsafe {
+ #schedule(self.priority(), instant #(,#untupled)*)
+ }
+ }
+ ));
+ }
+ }
+
+ let lt = if scheduler.is_init() {
+ None
+ } else {
+ Some(quote!('a))
+ };
+
+ let scheduler = scheduler.ident(app);
+ debug_assert!(!methods.is_empty());
+ items.push(quote!(
+ impl<#lt> #scheduler::Schedule<#lt> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs
new file mode 100644
index 00000000..644930d7
--- /dev/null
+++ b/macros/src/codegen/schedule_body.rs
@@ -0,0 +1,59 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::Ident;
+
+use crate::codegen::util;
+
+pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 {
+ let schedulee = &app.software_tasks[name];
+
+ let fq = util::fq_ident(name);
+ let tq = util::tq_ident();
+ let (dequeue, enqueue) = if scheduler.is_init() {
+ (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);))
+ } else {
+ (
+ quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
+ quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));),
+ )
+ };
+
+ let write_instant = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ Some(quote!(
+ #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
+ ))
+ } else {
+ None
+ };
+
+ let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs);
+ let inputs = util::inputs_ident(name);
+ let t = util::schedule_t_ident();
+ quote!(
+ unsafe {
+ use rtic::Mutex as _;
+
+ let input = #tupled;
+ if let Some(index) = #dequeue {
+ #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
+
+ #write_instant
+
+ let nr = rtic::export::NotReady {
+ instant,
+ index,
+ task: #t::#name,
+ };
+
+ #enqueue
+
+ Ok(())
+ } else {
+ Err(input)
+ }
+ }
+ )
+}
diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs
new file mode 100644
index 00000000..4ae37e4e
--- /dev/null
+++ b/macros/src/codegen/software_tasks.rs
@@ -0,0 +1,169 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use rtic_syntax::{ast::App, Context};
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{locals, module, resources_struct, util},
+};
+
+pub fn codegen(
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> (
+ // mod_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
+ Vec<TokenStream2>,
+ // root_software_tasks -- items that must be placed in the root of the crate:
+ // - `${task}Locals` structs
+ // - `${task}Resources` structs
+ // - `${task}` modules
+ Vec<TokenStream2>,
+ // user_software_tasks -- the `#[task]` functions written by the user
+ Vec<TokenStream2>,
+ // user_software_tasks_imports -- the imports for `#[task]` functions written by the user
+ Vec<TokenStream2>,
+) {
+ let mut mod_app = vec![];
+ let mut root = vec![];
+ let mut user_tasks = vec![];
+ let mut software_tasks_imports = vec![];
+
+ for (name, task) in &app.software_tasks {
+ let inputs = &task.inputs;
+ let (_, _, _, input_ty) = util::regroup_inputs(inputs);
+
+ let cap = task.args.capacity;
+ let cap_lit = util::capacity_literal(cap);
+ let cap_ty = util::capacity_typenum(cap, true);
+
+ // Create free queues and inputs / instants buffers
+ if let Some(&ceiling) = analysis.free_queues.get(name) {
+ let fq = util::fq_ident(name);
+
+ let (fq_ty, fq_expr, mk_uninit): (_, _, Box<dyn Fn() -> Option<_>>) = {
+ (
+ quote!(rtic::export::SCFQ<#cap_ty>),
+ quote!(rtic::export::Queue(unsafe {
+ rtic::export::iQueue::u8_sc()
+ })),
+ Box::new(|| util::link_section_uninit(true)),
+ )
+ };
+ mod_app.push(quote!(
+ /// Queue version of a free-list that keeps track of empty slots in
+ /// the following buffers
+ static mut #fq: #fq_ty = #fq_expr;
+ ));
+
+ // Generate a resource proxy if needed
+ if let Some(ceiling) = ceiling {
+ mod_app.push(quote!(
+ struct #fq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ mod_app.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &fq,
+ fq_ty,
+ ceiling,
+ quote!(&mut #fq),
+ ));
+ }
+
+ let ref elems = (0..cap)
+ .map(|_| quote!(core::mem::MaybeUninit::uninit()))
+ .collect::<Vec<_>>();
+
+ if app.uses_schedule() {
+ let m = extra.monotonic();
+ let instants = util::instants_ident(name);
+
+ let uninit = mk_uninit();
+ mod_app.push(quote!(
+ #uninit
+ /// Buffer that holds the instants associated to the inputs of a task
+ static mut #instants:
+ [core::mem::MaybeUninit<<#m as rtic::Monotonic>::Instant>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
+
+ let uninit = mk_uninit();
+ let inputs = util::inputs_ident(name);
+ mod_app.push(quote!(
+ #uninit
+ /// Buffer that holds the inputs of a task
+ static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] =
+ [#(#elems,)*];
+ ));
+ }
+
+ // `${task}Resources`
+ let mut needs_lt = false;
+ if !task.args.resources.is_empty() {
+ let (item, constructor) = resources_struct::codegen(
+ Context::SoftwareTask(name),
+ task.args.priority,
+ &mut needs_lt,
+ app,
+ analysis,
+ );
+
+ // Add resources to imports
+ let name_res = format_ident!("{}Resources", name);
+ software_tasks_imports.push(quote!(
+ #[allow(non_snake_case)]
+ use super::#name_res;
+ ));
+
+ root.push(item);
+
+ mod_app.push(constructor);
+ }
+
+ // `${task}Locals`
+ let mut locals_pat = None;
+ if !task.locals.is_empty() {
+ let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app);
+
+ locals_pat = Some(pat);
+ root.push(struct_);
+ }
+
+ let context = &task.context;
+ let attrs = &task.attrs;
+ let cfgs = &task.cfgs;
+ let stmts = &task.stmts;
+ let locals_pat = locals_pat.iter();
+ user_tasks.push(quote!(
+ #(#attrs)*
+ #(#cfgs)*
+ #[allow(non_snake_case)]
+ pub fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) {
+ use rtic::Mutex as _;
+
+ #(#stmts)*
+ }
+ ));
+ software_tasks_imports.push(quote!(
+ #(#cfgs)*
+ #[allow(non_snake_case)]
+ use super::#name;
+ ));
+
+ root.push(module::codegen(
+ Context::SoftwareTask(name),
+ needs_lt,
+ app,
+ extra,
+ ));
+ }
+
+ (mod_app, root, user_tasks, software_tasks_imports)
+}
diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs
new file mode 100644
index 00000000..da281516
--- /dev/null
+++ b/macros/src/codegen/spawn.rs
@@ -0,0 +1,121 @@
+use std::collections::HashSet;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{
+ analyze::Analysis,
+ check::Extra,
+ codegen::{spawn_body, util},
+};
+
+/// Generates all `${ctxt}::Spawn` methods
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ let mut seen = HashSet::<_>::new();
+ for (spawner, spawnees) in app.spawn_callers() {
+ let mut methods = vec![];
+
+ for name in spawnees {
+ let spawnee = &app.software_tasks[name];
+ let cfgs = &spawnee.cfgs;
+ let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
+ let args = &args;
+
+ if spawner.is_init() {
+ // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}`
+ // functions which are shared by other contexts
+
+ let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
+
+ let let_instant = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ Some(quote!(let instant = unsafe { <#m as rtic::Monotonic>::zero() };))
+ } else {
+ None
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ pub fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ #let_instant
+ #body
+ }
+ ));
+ } else {
+ let spawn = util::spawn_ident(name);
+
+ if !seen.contains(name) {
+ // Generate a `spawn_${name}_S${sender}` function
+ seen.insert(name);
+
+ let instant = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ Some(quote!(, instant: <#m as rtic::Monotonic>::Instant))
+ } else {
+ None
+ };
+
+ let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
+
+ items.push(quote!(
+ #(#cfgs)*
+ unsafe fn #spawn(
+ priority: &rtic::export::Priority
+ #instant
+ #(,#args)*
+ ) -> Result<(), #ty> {
+ #body
+ }
+ ));
+ }
+
+ let (let_instant, instant) = if app.uses_schedule() {
+ let m = extra.monotonic();
+
+ (
+ Some(if spawner.is_idle() {
+ quote!(let instant = <#m as rtic::Monotonic>::now();)
+ } else {
+ quote!(let instant = self.instant();)
+ }),
+ Some(quote!(, instant)),
+ )
+ } else {
+ (None, None)
+ };
+
+ methods.push(quote!(
+ #(#cfgs)*
+ #[inline(always)]
+ pub fn #name(&self #(,#args)*) -> Result<(), #ty> {
+ unsafe {
+ #let_instant
+ #spawn(self.priority() #instant #(,#untupled)*)
+ }
+ }
+ ));
+ }
+ }
+
+ let lt = if spawner.is_init() {
+ None
+ } else {
+ Some(quote!('a))
+ };
+
+ let spawner = spawner.ident(app);
+ debug_assert!(!methods.is_empty());
+ items.push(quote!(
+ impl<#lt> #spawner::Spawn<#lt> {
+ #(#methods)*
+ }
+ ));
+ }
+
+ items
+}
diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs
new file mode 100644
index 00000000..4ecd0757
--- /dev/null
+++ b/macros/src/codegen/spawn_body.rs
@@ -0,0 +1,76 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::Ident;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+pub fn codegen(
+ spawner: Context,
+ name: &Ident,
+ app: &App,
+ analysis: &Analysis,
+ extra: &Extra,
+) -> TokenStream2 {
+ let spawnee = &app.software_tasks[name];
+ let priority = spawnee.args.priority;
+
+ let write_instant = if app.uses_schedule() {
+ let instants = util::instants_ident(name);
+
+ Some(quote!(
+ #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
+ ))
+ } else {
+ None
+ };
+
+ let t = util::spawn_t_ident(priority);
+ let fq = util::fq_ident(name);
+ let rq = util::rq_ident(priority);
+ let (dequeue, enqueue) = if spawner.is_init() {
+ (
+ quote!(#fq.dequeue()),
+ quote!(#rq.enqueue_unchecked((#t::#name, index));),
+ )
+ } else {
+ (
+ quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))),
+ quote!((#rq { priority }.lock(|rq| {
+ rq.split().0.enqueue_unchecked((#t::#name, index))
+ }));),
+ )
+ };
+
+ let device = extra.device;
+ let enum_ = util::interrupt_ident();
+ let interrupt = &analysis.interrupts.get(&priority);
+ let pend = {
+ quote!(
+ rtic::pend(#device::#enum_::#interrupt);
+ )
+ };
+
+ let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs);
+ let inputs = util::inputs_ident(name);
+ quote!(
+ unsafe {
+ use rtic::Mutex as _;
+
+ let input = #tupled;
+ if let Some(index) = #dequeue {
+ #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
+
+ #write_instant
+
+ #enqueue
+
+ #pend
+
+ Ok(())
+ } else {
+ Err(input)
+ }
+ }
+ )
+}
diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs
new file mode 100644
index 00000000..030158e2
--- /dev/null
+++ b/macros/src/codegen/timer_queue.rs
@@ -0,0 +1,137 @@
+use proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+use rtic_syntax::ast::App;
+
+use crate::{analyze::Analysis, check::Extra, codegen::util};
+
+/// Generates timer queues and timer queue handlers
+pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
+ let mut items = vec![];
+
+ if let Some(timer_queue) = &analysis.timer_queues.first() {
+ let t = util::schedule_t_ident();
+
+ // Enumeration of `schedule`-able tasks
+ {
+ let variants = timer_queue
+ .tasks
+ .iter()
+ .map(|name| {
+ let cfgs = &app.software_tasks[name].cfgs;
+
+ quote!(
+ #(#cfgs)*
+ #name
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let doc = format!("Tasks that can be scheduled");
+ items.push(quote!(
+ #[doc = #doc]
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ enum #t {
+ #(#variants,)*
+ }
+ ));
+ }
+
+ let tq = util::tq_ident();
+
+ // Static variable and resource proxy
+ {
+ let doc = format!("Timer queue");
+ let m = extra.monotonic();
+ let n = util::capacity_typenum(timer_queue.capacity, false);
+ let tq_ty = quote!(rtic::export::TimerQueue<#m, #t, #n>);
+
+ items.push(quote!(
+ #[doc = #doc]
+ static mut #tq: #tq_ty = rtic::export::TimerQueue(
+ rtic::export::BinaryHeap(
+ rtic::export::iBinaryHeap::new()
+ )
+ );
+
+ struct #tq<'a> {
+ priority: &'a rtic::export::Priority,
+ }
+ ));
+
+ items.push(util::impl_mutex(
+ extra,
+ &[],
+ false,
+ &tq,
+ tq_ty,
+ timer_queue.ceiling,
+ quote!(&mut #tq),
+ ));
+ }
+
+ // Timer queue handler
+ {
+ let device = extra.device;
+ let arms = timer_queue
+ .tasks
+ .iter()
+ .map(|name| {
+ let task = &app.software_tasks[name];
+
+ let cfgs = &task.cfgs;
+ let priority = task.args.priority;
+ let rq = util::rq_ident(priority);
+ let rqt = util::spawn_t_ident(priority);
+ let enum_ = util::interrupt_ident();
+ let interrupt = &analysis.interrupts.get(&priority);
+
+ let pend = {
+ quote!(
+ rtic::pend(#device::#enum_::#interrupt);
+ )
+ };
+
+ quote!(
+ #(#cfgs)*
+ #t::#name => {
+ (#rq { priority: &rtic::export::Priority::new(PRIORITY) }).lock(|rq| {
+ rq.split().0.enqueue_unchecked((#rqt::#name, index))
+ });
+
+ #pend
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let priority = timer_queue.priority;
+ let sys_tick = util::suffixed("SysTick");
+ items.push(quote!(
+ #[no_mangle]
+ unsafe fn #sys_tick() {
+ use rtic::Mutex as _;
+
+ /// The priority of this handler
+ const PRIORITY: u8 = #priority;
+
+ rtic::export::run(PRIORITY, || {
+ while let Some((task, index)) = (#tq {
+ // NOTE dynamic priority is always the static priority at this point
+ priority: &rtic::export::Priority::new(PRIORITY),
+ })
+ // NOTE `inline(always)` produces faster and smaller code
+ .lock(#[inline(always)]
+ |tq| tq.dequeue())
+ {
+ match task {
+ #(#arms)*
+ }
+ }
+ });
+ }
+ ));
+ }
+ }
+ items
+}
diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs
new file mode 100644
index 00000000..2f9f3cce
--- /dev/null
+++ b/macros/src/codegen/util.rs
@@ -0,0 +1,247 @@
+use core::sync::atomic::{AtomicUsize, Ordering};
+
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use quote::quote;
+use rtic_syntax::{ast::App, Context};
+use syn::{Attribute, Ident, LitInt, PatType};
+
+use crate::check::Extra;
+
+/// Turns `capacity` into an unsuffixed integer literal
+pub fn capacity_literal(capacity: u8) -> LitInt {
+ LitInt::new(&capacity.to_string(), Span::call_site())
+}
+
+/// Turns `capacity` into a type-level (`typenum`) integer
+pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 {
+ let capacity = if round_up_to_power_of_two {
+ capacity.checked_next_power_of_two().expect("UNREACHABLE")
+ } else {
+ capacity
+ };
+
+ let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+
+ quote!(rtic::export::consts::#ident)
+}
+
+/// Identifier for the free queue
+pub fn fq_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_FQ", task.to_string()), Span::call_site())
+}
+
+/// Generates a `Mutex` implementation
+pub fn impl_mutex(
+ extra: &Extra,
+ cfgs: &[Attribute],
+ resources_prefix: bool,
+ name: &Ident,
+ ty: TokenStream2,
+ ceiling: u8,
+ ptr: TokenStream2,
+) -> TokenStream2 {
+ let (path, priority) = if resources_prefix {
+ (quote!(resources::#name), quote!(self.priority()))
+ } else {
+ (quote!(#name), quote!(self.priority))
+ };
+
+ let device = extra.device;
+ quote!(
+ #(#cfgs)*
+ impl<'a> rtic::Mutex for #path<'a> {
+ type T = #ty;
+
+ #[inline(always)]
+ fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R {
+ /// Priority ceiling
+ const CEILING: u8 = #ceiling;
+
+ unsafe {
+ rtic::export::lock(
+ #ptr,
+ #priority,
+ CEILING,
+ #device::NVIC_PRIO_BITS,
+ f,
+ )
+ }
+ }
+ }
+ )
+}
+
+/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API)
+pub fn inputs_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_INPUTS", task), Span::call_site())
+}
+
+/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
+pub fn instants_ident(task: &Ident) -> Ident {
+ Ident::new(&format!("{}_INSTANTS", task), Span::call_site())
+}
+
+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();
+
+ match &*s {
+ "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
+ | "DebugMonitor" | "PendSV" | "SysTick" => true,
+
+ _ => false,
+ }
+}
+
+/// Generates a pre-reexport identifier for the "late resources" struct
+pub fn late_resources_ident(init: &Ident) -> Ident {
+ Ident::new(
+ &format!("{}LateResources", init.to_string()),
+ Span::call_site(),
+ )
+}
+
+fn link_section_index() -> usize {
+ static INDEX: AtomicUsize = AtomicUsize::new(0);
+
+ INDEX.fetch_add(1, Ordering::Relaxed)
+}
+
+// NOTE `None` means in shared memory
+pub fn link_section_uninit(empty_expr: bool) -> Option<TokenStream2> {
+ let section = if empty_expr {
+ let index = link_section_index();
+ format!(".uninit.rtic{}", index)
+ } else {
+ format!(".uninit.rtic{}", link_section_index())
+ };
+
+ Some(quote!(#[link_section = #section]))
+}
+
+/// Generates a pre-reexport identifier for the "locals" struct
+pub fn locals_ident(ctxt: Context, app: &App) -> Ident {
+ let mut s = match ctxt {
+ Context::Init => app.inits.first().unwrap().name.to_string(),
+ Context::Idle => app.idles.first().unwrap().name.to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("Locals");
+
+ Ident::new(&s, Span::call_site())
+}
+
+// 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<TokenStream2>,
+ // tupled e.g. `_0`, `(_0, _1)`
+ TokenStream2,
+ // untupled e.g. &[`_0`], &[`_0`, `_1`]
+ Vec<TokenStream2>,
+ // 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)
+ }
+}
+
+/// Generates a pre-reexport identifier for the "resources" struct
+pub fn resources_ident(ctxt: Context, app: &App) -> Ident {
+ let mut s = match ctxt {
+ Context::Init => app.inits.first().unwrap().name.to_string(),
+ Context::Idle => app.idles.first().unwrap().name.to_string(),
+ Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
+ };
+
+ s.push_str("Resources");
+
+ Ident::new(&s, Span::call_site())
+}
+
+/// Generates an identifier for a ready queue
+///
+/// There may be several task dispatchers, one for each priority level.
+/// The ready queues are SPSC queues
+pub fn rq_ident(priority: u8) -> Ident {
+ Ident::new(&format!("P{}_RQ", priority), Span::call_site())
+}
+
+/// Generates an identifier for a "schedule" function
+///
+/// The methods of the `Schedule` structs invoke these functions.
+pub fn schedule_ident(name: &Ident) -> Ident {
+ Ident::new(&format!("schedule_{}", name.to_string()), Span::call_site())
+}
+
+/// Generates an identifier for the `enum` of `schedule`-able tasks
+pub fn schedule_t_ident() -> Ident {
+ Ident::new(&format!("T"), Span::call_site())
+}
+
+/// Generates an identifier for a "spawn" function
+///
+/// The methods of the `Spawn` structs invoke these functions.
+pub fn spawn_ident(name: &Ident) -> Ident {
+ Ident::new(&format!("spawn_{}", name.to_string()), Span::call_site())
+}
+
+/// Generates an identifier for the `enum` of `spawn`-able tasks
+///
+/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
+/// for each of these `T` enums
+pub fn spawn_t_ident(priority: u8) -> Ident {
+ Ident::new(&format!("P{}_T", priority), Span::call_site())
+}
+
+pub fn suffixed(name: &str) -> Ident {
+ let span = Span::call_site();
+ Ident::new(name, span)
+}
+
+/// Generates an identifier for a timer queue
+///
+/// At most there is one timer queue
+pub fn tq_ident() -> Ident {
+ Ident::new(&format!("TQ"), Span::call_site())
+}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index c8d9fee1..e659559e 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,123 +1,119 @@
#![deny(warnings)]
-#![recursion_limit = "128"]
extern crate proc_macro;
use proc_macro::TokenStream;
-use syn::parse_macro_input;
+use std::{fs, path::Path};
+
+use rtic_syntax::Settings;
mod analyze;
mod check;
mod codegen;
-mod syntax;
+#[cfg(test)]
+mod tests;
-/// Attribute used to declare a RTFM application
+/// Attribute used to declare a RTIC application
///
-/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively
-/// used as a `mod` item: its value must be a block that contains items commonly found in modules,
+/// This attribute must be applied to a module block that contains items commonly found in modules,
/// like functions and `static` variables.
///
/// The `app` attribute has one mandatory argument:
///
/// - `device = <path>`. The path must point to a device crate generated using [`svd2rust`]
-/// **v0.14.x**.
+/// **v0.14.x** or newer.
///
/// [`svd2rust`]: https://crates.io/crates/svd2rust
///
-/// The items allowed in the block value of the `const` item are specified below:
-///
-/// # 1. `static [mut]` variables
-///
-/// These variables are used as *resources*. Resources can be owned by tasks or shared between them.
-/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared)
-/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut`
-/// reference to a `static mut` resource shared with higher priority tasks.
-///
-/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock
+/// and a few optional arguments:
///
-/// `static mut` resources that are shared by tasks that run at *different* priorities need to
-/// implement the [`Send`] trait. Similarly, `static` resources that are shared by tasks that run at
-/// *different* priorities need to implement the [`Sync`] trait.
+/// - `peripherals = <bool>`. Indicates whether the runtime takes the device peripherals and makes
+/// them available to the `init` context.
///
-/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
-/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
+/// - `monotonic = <path>`. This is a path to a zero-sized structure (e.g. `struct Foo;`) that
+/// implements the `Monotonic` trait. This argument must be provided to use the `schedule` API.
///
-/// Resources can be initialized at runtime by assigning them `()` (the unit value) as their initial
-/// value in their declaration. These "late" resources need to be initialized an the end of the
-/// `init` function.
+/// The items allowed in the module block are specified below:
///
-/// The `app` attribute will inject a `resources` module in the root of the crate. This module
-/// contains proxy `struct`s that implement the [`Mutex`] trait. The `struct` are named after the
-/// `static mut` resources. For example, `static mut FOO: u32 = 0` will map to a `resources::FOO`
-/// `struct` that implements the `Mutex<Data = u32>` trait.
+/// # 1. `#[resources] struct <resource-name>`
///
-/// [`Mutex`]: ../rtfm/trait.Mutex.html
+/// This structure contains the declaration of all the resources used by the application. Each field
+/// in this structure corresponds to a different resource. Each resource may optionally be given an
+/// initial value using the `#[init(<value>)]` attribute. Resources with no compile-time initial
+/// value as referred to as *late* resources.
///
/// # 2. `fn`
///
-/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`,
-/// `exception` or `task`. The attribute defines the role of the function in the application.
+/// Functions must contain *one* of the following attributes: `init`, `idle` or `task`. The
+/// attribute defines the role of the function in the application.
///
/// ## a. `#[init]`
///
/// This attribute indicates that the function is to be used as the *initialization function*. There
/// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The
-/// signature of the `init` function must be `[unsafe] fn ()`.
+/// signature of the `init` function must be `fn (<fn-name>::Context) [-> <fn-name>::LateResources]`
+/// where `<fn-name>` is the name of the function adorned with the `#[init]` attribute.
///
/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled.
/// Interrupts are re-enabled after `init` returns.
///
/// The `init` attribute accepts the following optional arguments:
///
-/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has
+/// - `resources = [resource_a, resource_b, ..]`. This is the list of resources this context has
/// access to.
///
-/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can
-/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue`
-/// feature has been enabled.
+/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can
+/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `monotonic`
+/// argument is passed to the `#[app]` attribute.
///
-/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can
+/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can
/// immediately spawn.
///
-/// The `app` attribute will injected a *context* into this function that comprises the following
-/// variables:
+/// The first argument of the function, `<fn-name>::Context`, is a structure that contains the
+/// following fields:
///
-/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for
-/// more details.
+/// - `core`. Exclusive access to core peripherals. The type of this field is [`rtic::Peripherals`]
+/// when the `schedule` API is used and [`cortex_m::Peripherals`] when it's not.
///
-/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html
+/// [`rtic::Peripherals`]: ../rtic/struct.Peripherals.html
+/// [`cortex_m::Peripherals`]: https://docs.rs/cortex-m/0.6/cortex_m/peripheral/struct.Peripherals.html
///
-/// - `device: <device-path>::Peripherals`. Exclusive access to device-specific peripherals.
-/// `<device-path>` is the path to the device crate declared in the top `app` attribute.
+/// - `device: <device>::Peripherals`. Exclusive access to device-specific peripherals. This
+/// field is only present when the `peripherals` argument of the `#[app]` attribute is set to
+/// `true`. `<device>` is the path to the device crate specified in the top `app` attribute.
///
-/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**:
-/// only present if the `timer-queue` feature is enabled.
+/// - `start: <Instant>`. The `start` time of the system: `<Instant>::zero()`. `<Instant>` is the
+/// `Instant` type associated to the `Monotonic` implementation specified in the top `#[app]`
+/// attribute. **NOTE**: this field is only present when the `schedule` is used.
///
-/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function.
-/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy
-/// (`impl Mutex`).
+/// - `resources: <fn-name>::Resources`. A `struct` that contains all the resources that can be
+/// accessed from this context. Each field is a different resource; each resource may appear as a
+/// reference (`&[mut]-`) or as proxy structure that implements the [`rftm::Mutex`][rtic-mutex] trait.
///
-/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks.
-/// **NOTE**: only present if the `timer-queue` feature is enabled.
+/// [rtic-mutex]: ../rtic/trait.Mutex.html
///
-/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks.
+/// - `schedule: <fn-name>::Schedule`. A `struct` that can be used to schedule *software* tasks.
///
-/// Other properties / constraints:
+/// - `spawn: <fn-name>::Spawn`. A `struct` that can be used to spawn *software* tasks.
+///
+/// The return type `<fn-name>::LateResources` must only be specified when late resources, resources
+/// with no initial value declared at compile time, are used. `<fn-name>::LateResources` is a
+/// structure where each field corresponds to a different late resource. The
+/// `<fn-name>::LateResources` value returned by the `#[init]` function is used to initialize the
+/// late resources before `idle` or any task can start.
///
-/// - The `init` function can **not** be called from software.
+/// Other properties:
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &'static mut u32`.
///
-/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late*
-/// resources.
-///
/// ## b. `#[idle]`
///
/// This attribute indicates that the function is to be used as the *idle task*. There can be at
/// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the
-/// `idle` function must be `fn() -> !`.
+/// `idle` function must be `fn(<fn-name>::Context) -> !` where `<fn-name>` is the name of the
+/// function adorned with the `#[idle]` attribute.
///
/// The `idle` task is a special task that always runs in the background. The `idle` task runs at
/// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the
@@ -133,38 +129,37 @@ mod syntax;
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
-/// The `app` attribute will injected a *context* into this function that comprises the following
-/// variables:
-///
-/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
+/// The first argument of the function, `idle::Context`, is a structure that contains the following
+/// fields:
///
-/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
+/// - `resources: _`. Same meaning / function as [`<init>::Context.resources`](#a-init).
///
-/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
+/// - `schedule: idle::Schedule`. Same meaning / function as [`<init>::Context.schedule`](#a-init).
///
-/// Other properties / constraints:
+/// - `spawn: idle::Spawn`. Same meaning / function as [`<init>::Context.spawn`](#a-init).
///
-/// - The `idle` function can **not** be called from software.
+/// Other properties:
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &'static mut u32`.
///
-/// ## c. `#[exception]`
-///
-/// This attribute indicates that the function is to be used as an *exception handler*, a type of
-/// hardware task. The signature of `exception` handlers must be `[unsafe] fn()`.
+/// ## c. `#[task]`
///
-/// The name of the function must match one of the Cortex-M exceptions that has [configurable
-/// priority][system-handler].
+/// This attribute indicates that the function is either a hardware task or a software task. The
+/// signature of hardware tasks must be `fn(<fn-name>::Context)` whereas the signature of software
+/// tasks must be `fn(<fn-name>::Context, <inputs>)`. `<fn-name>` refers to the name of the function
+/// adorned with the `#[task]` attribute.
///
-/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html
+/// The `task` attribute accepts the following optional arguments.
///
-/// The `exception` attribute accepts the following optional arguments.
+/// - `binds = <interrupt-name>`. Binds this task to a particular interrupt. When this argument is
+/// present the task is treated as a hardware task; when it's omitted the task treated is treated as
+/// a software task.
///
/// - `priority = <integer>`. This is the static priority of the exception handler. The value must
/// be in the range `1..=(1 << <device-path>::NVIC_PRIO_BITS)` where `<device-path>` is the path to
-/// the device crate declared in the top `app` attribute. If this argument is omitted the priority
+/// the device crate specified in the top `app` attribute. If this argument is omitted the priority
/// is assumed to be 1.
///
/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
@@ -173,105 +168,26 @@ mod syntax;
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
-/// The `app` attribute will injected a *context* into this function that comprises the following
-/// variables:
+/// The first argument of the function, `<fn-name>::Context`, is a structure that contains the
+/// following fields:
///
-/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only
-/// present if the `timer-queue` feature is enabled.
+/// - `start: <Instant>`. For hardware tasks this is the time at which this handler started
+/// executing. For software tasks this is the time at which the task was scheduled to run. **NOTE**:
+/// only present when the `schedule` API is used.
///
-/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
+/// - `resources: _`. Same meaning / function as [`<init>::Context.resources`](#a-init).
///
-/// - `schedule: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
+/// - `schedule: <exception-name>::Schedule`. Same meaning / function as
+/// [`<init>::Context.schedule`](#a-init).
///
-/// - `spawn: <exception-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
+/// - `spawn: <exception-name>::Spawn`. Same meaning / function as
+/// [`<init>::Context.spawn`](#a-init).
///
/// Other properties / constraints:
///
-/// - `exception` handlers can **not** be called from software.
-///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
-/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
-/// become `FOO: &mut u32`.
-///
-/// ## d. `#[interrupt]`
-///
-/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of
-/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`.
-///
-/// The name of the function must match one of the device specific interrupts. See your device crate
-/// documentation (`Interrupt` enum) for more details.
-///
-/// The `interrupt` attribute accepts the following optional arguments.
-///
-/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception).
-///
-/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
-///
-/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
-///
-/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
-///
-/// The `app` attribute will injected a *context* into this function that comprises the following
-/// variables:
-///
-/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception).
-///
-/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
-///
-/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
-///
-/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
-///
-/// Other properties / constraints:
-///
-/// - `interrupt` handlers can **not** be called from software, but they can be [`pend`]-ed by the
-/// software from any context.
-///
-/// [`pend`]: ../rtfm/fn.pend.html
-///
-/// - The `static mut` variables declared at the beginning of this function will be transformed into
-/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
-/// become `FOO: &mut u32`.
-///
-/// ## e. `#[task]`
-///
-/// This attribute indicates that the function is to be used as a *software task*. The signature of
-/// software `task`s must be `[unsafe] fn(<inputs>)`.
-///
-/// The `task` attribute accepts the following optional arguments.
-///
-/// - `capacity = <integer>`. The maximum number of instances of this task that can be queued onto
-/// the task scheduler for execution. The value must be in the range `1..=255`. If the `capacity`
-/// argument is omitted then the capacity will be inferred.
-///
-/// - `priority = <integer>`. Same meaning / function as [`#[exception].priority`](#b-exception).
-///
-/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
-///
-/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
-///
-/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
-///
-/// The `app` attribute will injected a *context* into this function that comprises the following
-/// variables:
-///
-/// - `scheduled: rtfm::Instant`. The time at which this task was scheduled to run. **NOTE**: Only
-/// present if `timer-queue` is enabled.
-///
-/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
-///
-/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
-///
-/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
-///
-/// Other properties / constraints:
-///
-/// - Software `task`s can **not** be called from software, but they can be `spawn`-ed and
-/// `schedule`-d by the software from any context.
-///
-/// - The `static mut` variables declared at the beginning of this function will be transformed into
-/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
-/// become `FOO: &mut u32`.
+/// *non*-static `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0`
+/// will become `FOO: &mut u32`.
///
/// # 3. `extern` block
///
@@ -282,27 +198,35 @@ mod syntax;
/// This `extern` block must only contain functions with signature `fn ()`. The names of these
/// functions must match the names of the target device interrupts.
///
-/// Importantly, attributes can be applied to the functions inside this block. These attributes will
-/// be forwarded to the interrupt handlers generated by the `app` attribute.
+/// Attributes can be applied to the functions inside this block. These attributes will be forwarded
+/// to the interrupt handlers generated by the `app` attribute.
+
#[proc_macro_attribute]
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
- // Parse
- let args = parse_macro_input!(args as syntax::AppArgs);
- let items = parse_macro_input!(input as syntax::Input).items;
+ let mut settings = Settings::default();
+ settings.optimize_priorities = true;
+ settings.parse_binds = true;
+ settings.parse_extern_interrupt = true;
+ settings.parse_schedule = true;
- let app = match syntax::App::parse(items, args) {
+ let (app, analysis) = match rtic_syntax::parse(args, input, settings) {
Err(e) => return e.to_compile_error().into(),
- Ok(app) => app,
+ Ok(x) => x,
};
- // Check the specification
- if let Err(e) = check::app(&app) {
- return e.to_compile_error().into();
- }
+ let extra = match check::app(&app, &analysis) {
+ Err(e) => return e.to_compile_error().into(),
+ Ok(x) => x,
+ };
- // Ceiling analysis
- let analysis = analyze::app(&app);
+ let analysis = analyze::app(analysis, &app);
+
+ let ts = codegen::app(&app, &analysis, &extra);
+
+ // Try to write the expanded code to disk
+ if Path::new("target").exists() {
+ fs::write("target/rtic-expansion.rs", ts.to_string()).ok();
+ }
- // Code generation
- codegen::app(&app, &analysis).into()
+ ts.into()
}
diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs
deleted file mode 100644
index 228d9588..00000000
--- a/macros/src/syntax.rs
+++ /dev/null
@@ -1,1394 +0,0 @@
-use std::{
- collections::{BTreeMap, BTreeSet},
- iter, u8,
-};
-
-use proc_macro2::Span;
-use syn::{
- braced, bracketed, parenthesized,
- parse::{self, Parse, ParseStream},
- punctuated::Punctuated,
- spanned::Spanned,
- token::Brace,
- ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn,
- ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token,
- Type, TypeTuple, Visibility,
-};
-
-pub struct AppArgs {
- pub device: Path,
-}
-
-impl Parse for AppArgs {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- let mut device = None;
- loop {
- if input.is_empty() {
- break;
- }
-
- // #ident = ..
- let ident: Ident = input.parse()?;
- let _eq_token: Token![=] = input.parse()?;
-
- let ident_s = ident.to_string();
- match &*ident_s {
- "device" => {
- if device.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- device = Some(input.parse()?);
- }
- _ => {
- return Err(parse::Error::new(
- ident.span(),
- "expected `device`; other keys are not accepted",
- ));
- }
- }
-
- if input.is_empty() {
- break;
- }
-
- // ,
- let _: Token![,] = input.parse()?;
- }
-
- Ok(AppArgs {
- device: device.ok_or(parse::Error::new(
- Span::call_site(),
- "`device` argument is required",
- ))?,
- })
- }
-}
-
-pub struct Input {
- _const_token: Token![const],
- _ident: Ident,
- _colon_token: Token![:],
- _ty: TypeTuple,
- _eq_token: Token![=],
- _brace_token: Brace,
- pub items: Vec<Item>,
- _semi_token: Token![;],
-}
-
-impl Parse for Input {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- fn parse_items(input: ParseStream<'_>) -> parse::Result<Vec<Item>> {
- let mut items = vec![];
-
- while !input.is_empty() {
- items.push(input.parse()?);
- }
-
- Ok(items)
- }
-
- let content;
- Ok(Input {
- _const_token: input.parse()?,
- _ident: input.parse()?,
- _colon_token: input.parse()?,
- _ty: input.parse()?,
- _eq_token: input.parse()?,
- _brace_token: braced!(content in input),
- items: content.call(parse_items)?,
- _semi_token: input.parse()?,
- })
- }
-}
-
-pub struct App {
- pub args: AppArgs,
- pub idle: Option<Idle>,
- pub init: Init,
- pub exceptions: Exceptions,
- pub interrupts: Interrupts,
- pub resources: Resources,
- pub tasks: Tasks,
- pub free_interrupts: FreeInterrupts,
-}
-
-impl App {
- pub fn parse(items: Vec<Item>, args: AppArgs) -> parse::Result<Self> {
- let mut idle = None;
- let mut init = None;
- let mut exceptions = BTreeMap::new();
- let mut interrupts = BTreeMap::new();
- let mut resources = BTreeMap::new();
- let mut tasks = BTreeMap::new();
- let mut free_interrupts = None;
-
- for item in items {
- match item {
- Item::Fn(mut item) => {
- if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) {
- if idle.is_some() {
- return Err(parse::Error::new(
- item.span(),
- "`#[idle]` function must appear at most once",
- ));
- }
-
- let args = syn::parse2(item.attrs.swap_remove(pos).tts)?;
-
- idle = Some(Idle::check(args, item)?);
- } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) {
- if init.is_some() {
- return Err(parse::Error::new(
- item.span(),
- "`#[init]` function must appear exactly once",
- ));
- }
-
- let args = syn::parse2(item.attrs.swap_remove(pos).tts)?;
-
- init = Some(Init::check(args, item)?);
- } else if let Some(pos) =
- item.attrs.iter().position(|attr| eq(attr, "exception"))
- {
- if exceptions.contains_key(&item.ident)
- || interrupts.contains_key(&item.ident)
- || tasks.contains_key(&item.ident)
- {
- return Err(parse::Error::new(
- item.ident.span(),
- "this task is defined multiple times",
- ));
- }
-
- let args = syn::parse2(item.attrs.swap_remove(pos).tts)?;
-
- exceptions.insert(item.ident.clone(), Exception::check(args, item)?);
- } else if let Some(pos) =
- item.attrs.iter().position(|attr| eq(attr, "interrupt"))
- {
- if exceptions.contains_key(&item.ident)
- || interrupts.contains_key(&item.ident)
- || tasks.contains_key(&item.ident)
- {
- return Err(parse::Error::new(
- item.ident.span(),
- "this task is defined multiple times",
- ));
- }
-
- let args = syn::parse2(item.attrs.swap_remove(pos).tts)?;
-
- interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?);
- } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) {
- if exceptions.contains_key(&item.ident)
- || interrupts.contains_key(&item.ident)
- || tasks.contains_key(&item.ident)
- {
- return Err(parse::Error::new(
- item.ident.span(),
- "this task is defined multiple times",
- ));
- }
-
- let args = syn::parse2(item.attrs.swap_remove(pos).tts)?;
-
- tasks.insert(item.ident.clone(), Task::check(args, item)?);
- } else {
- return Err(parse::Error::new(
- item.span(),
- "this item must live outside the `#[app]` module",
- ));
- }
- }
- Item::Static(item) => {
- if resources.contains_key(&item.ident) {
- return Err(parse::Error::new(
- item.ident.span(),
- "this resource is listed twice",
- ));
- }
-
- resources.insert(item.ident.clone(), Resource::check(item)?);
- }
- Item::ForeignMod(item) => {
- if free_interrupts.is_some() {
- return Err(parse::Error::new(
- item.abi.extern_token.span(),
- "`extern` block can only appear at most once",
- ));
- }
-
- free_interrupts = Some(FreeInterrupt::parse(item)?);
- }
- _ => {
- return Err(parse::Error::new(
- item.span(),
- "this item must live outside the `#[app]` module",
- ));
- }
- }
- }
-
- Ok(App {
- args,
- idle,
- init: init.ok_or_else(|| {
- parse::Error::new(Span::call_site(), "`#[init]` function is missing")
- })?,
- exceptions,
- interrupts,
- resources,
- tasks,
- free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()),
- })
- }
-
- /// Returns an iterator over all resource accesses.
- ///
- /// Each resource access include the priority it's accessed at (`u8`) and the name of the
- /// resource (`Ident`). A resource may appear more than once in this iterator
- pub fn resource_accesses(&self) -> impl Iterator<Item = (u8, &Ident)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(idle.args.resources.iter().map(|res| (0, res)))
- })
- .unwrap_or_else(|| Box::new(iter::empty()))
- .chain(self.exceptions.values().flat_map(|e| {
- e.args
- .resources
- .iter()
- .map(move |res| (e.args.priority, res))
- }))
- .chain(self.interrupts.values().flat_map(|i| {
- i.args
- .resources
- .iter()
- .map(move |res| (i.args.priority, res))
- }))
- .chain(self.tasks.values().flat_map(|t| {
- t.args
- .resources
- .iter()
- .map(move |res| (t.args.priority, res))
- }))
- }
-
- /// Returns an iterator over all `spawn` calls
- ///
- /// Each spawn call includes the priority of the task from which it's issued and the name of the
- /// task that's spawned. A task may appear more that once in this iterator.
- ///
- /// A priority of `None` means that this being called from `init`
- pub fn spawn_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> {
- self.init
- .args
- .spawn
- .iter()
- .map(|s| (None, s))
- .chain(
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(idle.args.spawn.iter().map(|s| (Some(0), s)))
- })
- .unwrap_or_else(|| Box::new(iter::empty())),
- )
- .chain(
- self.exceptions
- .values()
- .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))),
- )
- .chain(
- self.interrupts
- .values()
- .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))),
- )
- .chain(
- self.tasks
- .values()
- .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))),
- )
- }
-
- /// Returns an iterator over all `schedule` calls
- ///
- /// Each spawn call includes the priority of the task from which it's issued and the name of the
- /// task that's spawned. A task may appear more that once in this iterator.
- #[allow(dead_code)]
- pub fn schedule_calls(&self) -> impl Iterator<Item = (Option<u8>, &Ident)> {
- self.init
- .args
- .schedule
- .iter()
- .map(|s| (None, s))
- .chain(
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(idle.args.schedule.iter().map(|s| (Some(0), s)))
- })
- .unwrap_or_else(|| Box::new(iter::empty())),
- )
- .chain(self.exceptions.values().flat_map(|e| {
- e.args
- .schedule
- .iter()
- .map(move |s| (Some(e.args.priority), s))
- }))
- .chain(self.interrupts.values().flat_map(|i| {
- i.args
- .schedule
- .iter()
- .map(move |s| (Some(i.args.priority), s))
- }))
- .chain(self.tasks.values().flat_map(|t| {
- t.args
- .schedule
- .iter()
- .map(move |s| (Some(t.args.priority), s))
- }))
- }
-
- #[allow(dead_code)]
- pub fn schedule_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(iter::once((
- Ident::new("idle", Span::call_site()),
- &idle.args.schedule,
- )))
- })
- .unwrap_or_else(|| Box::new(iter::empty()))
- .chain(iter::once((
- Ident::new("init", Span::call_site()),
- &self.init.args.schedule,
- )))
- .chain(
- self.exceptions
- .iter()
- .map(|(name, exception)| (name.clone(), &exception.args.schedule)),
- )
- .chain(
- self.interrupts
- .iter()
- .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)),
- )
- .chain(
- self.tasks
- .iter()
- .map(|(name, task)| (name.clone(), &task.args.schedule)),
- )
- }
-
- pub fn spawn_callers(&self) -> impl Iterator<Item = (Ident, &Idents)> {
- self.idle
- .as_ref()
- .map(|idle| -> Box<dyn Iterator<Item = _>> {
- Box::new(iter::once((
- Ident::new("idle", Span::call_site()),
- &idle.args.spawn,
- )))
- })
- .unwrap_or_else(|| Box::new(iter::empty()))
- .chain(iter::once((
- Ident::new("init", Span::call_site()),
- &self.init.args.spawn,
- )))
- .chain(
- self.exceptions
- .iter()
- .map(|(name, exception)| (name.clone(), &exception.args.spawn)),
- )
- .chain(
- self.interrupts
- .iter()
- .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)),
- )
- .chain(
- self.tasks
- .iter()
- .map(|(name, task)| (name.clone(), &task.args.spawn)),
- )
- }
-}
-
-pub type Idents = BTreeSet<Ident>;
-
-pub type Exceptions = BTreeMap<Ident, Exception>;
-
-pub type Interrupts = BTreeMap<Ident, Interrupt>;
-
-pub type Resources = BTreeMap<Ident, Resource>;
-
-pub type Statics = Vec<ItemStatic>;
-
-pub type Tasks = BTreeMap<Ident, Task>;
-
-pub type FreeInterrupts = BTreeMap<Ident, FreeInterrupt>;
-
-pub struct Idle {
- pub args: IdleArgs,
- pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-pub type IdleArgs = InitArgs;
-
-impl Idle {
- fn check(args: IdleArgs, item: ItemFn) -> parse::Result<Self> {
- let valid_signature = item.vis == Visibility::Inherited
- && item.constness.is_none()
- && item.asyncness.is_none()
- && item.abi.is_none()
- && item.decl.generics.params.is_empty()
- && item.decl.generics.where_clause.is_none()
- && item.decl.inputs.is_empty()
- && item.decl.variadic.is_none()
- && is_bottom(&item.decl.output);
-
- let span = item.span();
-
- if !valid_signature {
- return Err(parse::Error::new(
- span,
- "`idle` must have type signature `[unsafe] fn() -> !`",
- ));
- }
-
- let (statics, stmts) = extract_statics(item.block.stmts);
-
- Ok(Idle {
- args,
- attrs: item.attrs,
- unsafety: item.unsafety,
- statics: Static::parse(statics)?,
- stmts,
- })
- }
-}
-
-pub struct InitArgs {
- pub resources: Idents,
- pub schedule: Idents,
- pub spawn: Idents,
-}
-
-impl Default for InitArgs {
- fn default() -> Self {
- InitArgs {
- resources: Idents::new(),
- schedule: Idents::new(),
- spawn: Idents::new(),
- }
- }
-}
-
-impl Parse for InitArgs {
- fn parse(input: ParseStream<'_>) -> parse::Result<InitArgs> {
- if input.is_empty() {
- return Ok(InitArgs::default());
- }
-
- let mut resources = None;
- let mut schedule = None;
- let mut spawn = None;
-
- let content;
- parenthesized!(content in input);
- loop {
- if content.is_empty() {
- break;
- }
-
- // #ident = ..
- let ident: Ident = content.parse()?;
- let _: Token![=] = content.parse()?;
-
- let ident_s = ident.to_string();
- match &*ident_s {
- "schedule" if cfg!(not(feature = "timer-queue")) => {
- return Err(parse::Error::new(
- ident.span(),
- "The `schedule` API requires that the `timer-queue` feature is \
- enabled in the `cortex-m-rtfm` crate",
- ));
- }
- "resources" | "schedule" | "spawn" => {} // OK
- _ => {
- return Err(parse::Error::new(
- ident.span(),
- "expected one of: resources, schedule or spawn",
- ));
- }
- }
-
- // .. [#(#idents)*]
- let inner;
- bracketed!(inner in content);
- let mut idents = Idents::new();
- for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? {
- if idents.contains(&ident) {
- return Err(parse::Error::new(
- ident.span(),
- "element appears more than once in list",
- ));
- }
-
- idents.insert(ident);
- }
-
- let ident_s = ident.to_string();
- match &*ident_s {
- "resources" => {
- if resources.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- resources = Some(idents);
- }
- "schedule" => {
- if schedule.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- schedule = Some(idents);
- }
- "spawn" => {
- if spawn.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- spawn = Some(idents);
- }
- _ => unreachable!(),
- }
-
- if content.is_empty() {
- break;
- }
-
- // ,
- let _: Token![,] = content.parse()?;
- }
-
- Ok(InitArgs {
- resources: resources.unwrap_or(Idents::new()),
- schedule: schedule.unwrap_or(Idents::new()),
- spawn: spawn.unwrap_or(Idents::new()),
- })
- }
-}
-
-// TODO remove in v0.5.x
-pub struct Assign {
- pub attrs: Vec<Attribute>,
- pub left: Ident,
- pub right: Box<Expr>,
-}
-
-pub struct Init {
- pub args: InitArgs,
- pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
- // TODO remove in v0.5.x
- pub assigns: Vec<Assign>,
- pub returns_late_resources: bool,
-}
-
-impl Init {
- fn check(args: InitArgs, item: ItemFn) -> parse::Result<Self> {
- let mut valid_signature = item.vis == Visibility::Inherited
- && item.constness.is_none()
- && item.asyncness.is_none()
- && item.abi.is_none()
- && item.decl.generics.params.is_empty()
- && item.decl.generics.where_clause.is_none()
- && item.decl.inputs.is_empty()
- && item.decl.variadic.is_none();
-
- let returns_late_resources = match &item.decl.output {
- ReturnType::Default => false,
- ReturnType::Type(_, ty) => {
- match &**ty {
- Type::Tuple(t) => {
- if t.elems.is_empty() {
- // -> ()
- true
- } else {
- valid_signature = false;
-
- false // don't care
- }
- }
-
- Type::Path(p) => {
- let mut segments = p.path.segments.iter();
- if p.qself.is_none()
- && p.path.leading_colon.is_none()
- && p.path.segments.len() == 2
- && segments.next().map(|s| {
- s.arguments == PathArguments::None && s.ident.to_string() == "init"
- }) == Some(true)
- && segments.next().map(|s| {
- s.arguments == PathArguments::None
- && s.ident.to_string() == "LateResources"
- }) == Some(true)
- {
- // -> init::LateResources
- true
- } else {
- valid_signature = false;
-
- false // don't care
- }
- }
-
- _ => {
- valid_signature = false;
-
- false // don't care
- }
- }
- }
- };
-
- let span = item.span();
-
- if !valid_signature {
- return Err(parse::Error::new(
- span,
- "`init` must have type signature `[unsafe] fn() [-> init::LateResources]`",
- ));
- }
-
- let (statics, stmts) = extract_statics(item.block.stmts);
- let (stmts, assigns) = if returns_late_resources {
- (stmts, vec![])
- } else {
- extract_assignments(stmts)
- };
-
- Ok(Init {
- args,
- attrs: item.attrs,
- unsafety: item.unsafety,
- statics: Static::parse(statics)?,
- stmts,
- assigns,
- returns_late_resources,
- })
- }
-}
-
-/// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs`
-pub struct Args {
- pub binds: Option<Ident>,
- pub capacity: Option<u8>,
- pub priority: u8,
- pub resources: Idents,
- pub schedule: Idents,
- pub spawn: Idents,
-}
-
-impl Default for Args {
- fn default() -> Self {
- Args {
- binds: None,
- capacity: None,
- priority: 1,
- resources: Idents::new(),
- schedule: Idents::new(),
- spawn: Idents::new(),
- }
- }
-}
-
-pub struct Exception {
- pub args: ExceptionArgs,
- pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-pub struct ExceptionArgs {
- binds: Option<Ident>,
- pub priority: u8,
- pub resources: Idents,
- pub schedule: Idents,
- pub spawn: Idents,
-}
-
-impl ExceptionArgs {
- /// Returns the name of the exception / interrupt this handler binds to
- pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident {
- self.binds.as_ref().unwrap_or(handler)
- }
-}
-
-impl Parse for ExceptionArgs {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- parse_args(input, /* binds */ true, /* capacity */ false).map(
- |Args {
- binds,
- priority,
- resources,
- schedule,
- spawn,
- ..
- }| {
- ExceptionArgs {
- binds,
- priority,
- resources,
- schedule,
- spawn,
- }
- },
- )
- }
-}
-
-impl Exception {
- fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result<Self> {
- let valid_signature = item.vis == Visibility::Inherited
- && item.constness.is_none()
- && item.asyncness.is_none()
- && item.abi.is_none()
- && item.decl.generics.params.is_empty()
- && item.decl.generics.where_clause.is_none()
- && item.decl.inputs.is_empty()
- && item.decl.variadic.is_none()
- && is_unit(&item.decl.output);
-
- if !valid_signature {
- return Err(parse::Error::new(
- item.span(),
- "`exception` handlers must have type signature `[unsafe] fn()`",
- ));
- }
-
- let span = item.ident.span();
- match &*args.binds.as_ref().unwrap_or(&item.ident).to_string() {
- "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
- | "DebugMonitor" | "PendSV" => {} // OK
- "SysTick" => {
- if cfg!(feature = "timer-queue") {
- return Err(parse::Error::new(
- span,
- "the `SysTick` exception can't be used because it's used by \
- the runtime when the `timer-queue` feature is enabled",
- ));
- }
- }
- _ => {
- return Err(parse::Error::new(
- span,
- "only exceptions with configurable priority can be used as hardware tasks",
- ));
- }
- }
-
- let (statics, stmts) = extract_statics(item.block.stmts);
-
- Ok(Exception {
- args,
- attrs: item.attrs,
- unsafety: item.unsafety,
- statics: Static::parse(statics)?,
- stmts,
- })
- }
-}
-
-pub struct Interrupt {
- pub args: InterruptArgs,
- pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-pub type InterruptArgs = ExceptionArgs;
-
-impl Interrupt {
- fn check(args: InterruptArgs, item: ItemFn) -> parse::Result<Self> {
- let valid_signature = item.vis == Visibility::Inherited
- && item.constness.is_none()
- && item.asyncness.is_none()
- && item.abi.is_none()
- && item.decl.generics.params.is_empty()
- && item.decl.generics.where_clause.is_none()
- && item.decl.inputs.is_empty()
- && item.decl.variadic.is_none()
- && is_unit(&item.decl.output);
-
- let span = item.span();
-
- if !valid_signature {
- return Err(parse::Error::new(
- span,
- "`interrupt` handlers must have type signature `[unsafe] fn()`",
- ));
- }
-
- match &*item.ident.to_string() {
- "init" | "idle" | "resources" => {
- return Err(parse::Error::new(
- span,
- "`interrupt` handlers can NOT be named `idle`, `init` or `resources`",
- ));
- }
- _ => {}
- }
-
- let (statics, stmts) = extract_statics(item.block.stmts);
-
- Ok(Interrupt {
- args,
- attrs: item.attrs,
- unsafety: item.unsafety,
- statics: Static::parse(statics)?,
- stmts,
- })
- }
-}
-
-pub struct Resource {
- pub singleton: bool,
- pub cfgs: Vec<Attribute>,
- pub attrs: Vec<Attribute>,
- pub mutability: Option<Token![mut]>,
- pub ty: Box<Type>,
- pub expr: Option<Box<Expr>>,
-}
-
-impl Resource {
- fn check(mut item: ItemStatic) -> parse::Result<Resource> {
- if item.vis != Visibility::Inherited {
- return Err(parse::Error::new(
- item.span(),
- "resources must have inherited / private visibility",
- ));
- }
-
- let uninitialized = match *item.expr {
- Expr::Tuple(ref tuple) => tuple.elems.is_empty(),
- _ => false,
- };
-
- let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton"));
-
- if let Some(pos) = pos {
- item.attrs[pos].path.segments.insert(
- 0,
- PathSegment::from(Ident::new("owned_singleton", Span::call_site())),
- );
- }
-
- let (cfgs, attrs) = extract_cfgs(item.attrs);
-
- Ok(Resource {
- singleton: pos.is_some(),
- cfgs,
- attrs,
- mutability: item.mutability,
- ty: item.ty,
- expr: if uninitialized { None } else { Some(item.expr) },
- })
- }
-}
-
-pub struct TaskArgs {
- pub capacity: Option<u8>,
- pub priority: u8,
- pub resources: Idents,
- pub spawn: Idents,
- pub schedule: Idents,
-}
-
-impl Parse for TaskArgs {
- fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
- parse_args(input, /* binds */ false, /* capacity */ true).map(
- |Args {
- capacity,
- priority,
- resources,
- schedule,
- spawn,
- ..
- }| {
- TaskArgs {
- capacity,
- priority,
- resources,
- schedule,
- spawn,
- }
- },
- )
- }
-}
-
-// Parser shared by ExceptionArgs, InterruptArgs and TaskArgs
-fn parse_args(
- input: ParseStream<'_>,
- accepts_binds: bool,
- accepts_capacity: bool,
-) -> parse::Result<Args> {
- if input.is_empty() {
- return Ok(Args::default());
- }
-
- let mut binds = None;
- let mut capacity = None;
- let mut priority = None;
- let mut resources = None;
- let mut schedule = None;
- let mut spawn = None;
-
- let content;
- parenthesized!(content in input);
- loop {
- if content.is_empty() {
- break;
- }
-
- // #ident = ..
- let ident: Ident = content.parse()?;
- let _: Token![=] = content.parse()?;
-
- let ident_s = ident.to_string();
- match &*ident_s {
- "binds" if accepts_binds => {
- if binds.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- // #ident
- let ident = content.parse()?;
-
- binds = Some(ident);
- }
- "capacity" if accepts_capacity => {
- if capacity.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- // #lit
- let lit: LitInt = content.parse()?;
-
- if lit.suffix() != IntSuffix::None {
- return Err(parse::Error::new(
- lit.span(),
- "this literal must be unsuffixed",
- ));
- }
-
- let value = lit.value();
- if value > u64::from(u8::MAX) || value == 0 {
- return Err(parse::Error::new(
- lit.span(),
- "this literal must be in the range 1...255",
- ));
- }
-
- capacity = Some(value as u8);
- }
- "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() != IntSuffix::None {
- return Err(parse::Error::new(
- lit.span(),
- "this literal must be unsuffixed",
- ));
- }
-
- let value = lit.value();
- if value > u64::from(u8::MAX) || value == 0 {
- return Err(parse::Error::new(
- lit.span(),
- "this literal must be in the range 1...255",
- ));
- }
-
- priority = Some(value as u8);
- }
- "schedule" if cfg!(not(feature = "timer-queue")) => {
- return Err(parse::Error::new(
- ident.span(),
- "The `schedule` API requires that the `timer-queue` feature is \
- enabled in the `cortex-m-rtfm` crate",
- ));
- }
- "resources" | "schedule" | "spawn" => {
- // .. [#(#idents)*]
- let inner;
- bracketed!(inner in content);
- let mut idents = Idents::new();
- for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? {
- if idents.contains(&ident) {
- return Err(parse::Error::new(
- ident.span(),
- "element appears more than once in list",
- ));
- }
-
- idents.insert(ident);
- }
-
- match &*ident_s {
- "resources" => {
- if resources.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- resources = Some(idents);
- }
- "schedule" => {
- if schedule.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- schedule = Some(idents);
- }
- "spawn" => {
- if spawn.is_some() {
- return Err(parse::Error::new(
- ident.span(),
- "argument appears more than once",
- ));
- }
-
- spawn = Some(idents);
- }
- _ => unreachable!(),
- }
- }
- _ => {
- return Err(parse::Error::new(
- ident.span(),
- format!(
- "expected one of: {}{}priority, resources, schedule or spawn",
- if accepts_binds { "binds, " } else { "" },
- if accepts_capacity { "capacity, " } else { "" },
- ),
- ));
- }
- }
-
- if content.is_empty() {
- break;
- }
-
- // ,
- let _: Token![,] = content.parse()?;
- }
-
- Ok(Args {
- binds,
- capacity,
- priority: priority.unwrap_or(1),
- resources: resources.unwrap_or(Idents::new()),
- schedule: schedule.unwrap_or(Idents::new()),
- spawn: spawn.unwrap_or(Idents::new()),
- })
-}
-
-pub struct Static {
- /// `#[cfg]` attributes
- pub cfgs: Vec<Attribute>,
- /// Attributes that are not `#[cfg]`
- pub attrs: Vec<Attribute>,
- pub ty: Box<Type>,
- pub expr: Box<Expr>,
-}
-
-impl Static {
- fn parse(items: Vec<ItemStatic>) -> parse::Result<BTreeMap<Ident, Static>> {
- let mut statics = BTreeMap::new();
-
- for item in items {
- if statics.contains_key(&item.ident) {
- return Err(parse::Error::new(
- item.ident.span(),
- "this `static` is listed twice",
- ));
- }
-
- let (cfgs, attrs) = extract_cfgs(item.attrs);
-
- statics.insert(
- item.ident,
- Static {
- cfgs,
- attrs,
- ty: item.ty,
- expr: item.expr,
- },
- );
- }
-
- Ok(statics)
- }
-}
-
-pub struct Task {
- pub args: TaskArgs,
- pub cfgs: Vec<Attribute>,
- pub attrs: Vec<Attribute>,
- pub unsafety: Option<Token![unsafe]>,
- pub inputs: Vec<ArgCaptured>,
- pub statics: BTreeMap<Ident, Static>,
- pub stmts: Vec<Stmt>,
-}
-
-impl Task {
- fn check(args: TaskArgs, item: ItemFn) -> parse::Result<Self> {
- let valid_signature = item.vis == Visibility::Inherited
- && item.constness.is_none()
- && item.asyncness.is_none()
- && item.abi.is_none()
- && item.decl.generics.params.is_empty()
- && item.decl.generics.where_clause.is_none()
- && item.decl.variadic.is_none()
- && is_unit(&item.decl.output);
-
- let span = item.span();
-
- if !valid_signature {
- return Err(parse::Error::new(
- span,
- "`task` handlers must have type signature `[unsafe] fn(..)`",
- ));
- }
-
- let (statics, stmts) = extract_statics(item.block.stmts);
-
- let mut inputs = vec![];
- for input in item.decl.inputs {
- if let FnArg::Captured(capture) = input {
- inputs.push(capture);
- } else {
- return Err(parse::Error::new(
- span,
- "inputs must be named arguments (e.f. `foo: u32`) and not include `self`",
- ));
- }
- }
-
- match &*item.ident.to_string() {
- "init" | "idle" | "resources" => {
- return Err(parse::Error::new(
- span,
- "`task` handlers can NOT be named `idle`, `init` or `resources`",
- ));
- }
- _ => {}
- }
-
- let (cfgs, attrs) = extract_cfgs(item.attrs);
- Ok(Task {
- args,
- cfgs,
- attrs,
- unsafety: item.unsafety,
- inputs,
- statics: Static::parse(statics)?,
- stmts,
- })
- }
-}
-
-pub struct FreeInterrupt {
- pub attrs: Vec<Attribute>,
-}
-
-impl FreeInterrupt {
- fn parse(mod_: ItemForeignMod) -> parse::Result<FreeInterrupts> {
- let mut free_interrupts = FreeInterrupts::new();
-
- for item in mod_.items {
- if let ForeignItem::Fn(f) = item {
- let valid_signature = f.vis == Visibility::Inherited
- && f.decl.generics.params.is_empty()
- && f.decl.generics.where_clause.is_none()
- && f.decl.inputs.is_empty()
- && f.decl.variadic.is_none()
- && is_unit(&f.decl.output);
-
- if !valid_signature {
- return Err(parse::Error::new(
- f.span(),
- "free interrupts must have type signature `fn()`",
- ));
- }
-
- if free_interrupts.contains_key(&f.ident) {
- return Err(parse::Error::new(
- f.ident.span(),
- "this interrupt appears twice",
- ));
- }
-
- free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs });
- } else {
- return Err(parse::Error::new(
- mod_.abi.extern_token.span(),
- "`extern` block should only contains functions",
- ));
- }
- }
-
- Ok(free_interrupts)
- }
-}
-
-fn eq(attr: &Attribute, name: &str) -> bool {
- attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && {
- let pair = attr.path.segments.first().unwrap();
- let segment = pair.value();
- segment.arguments == PathArguments::None && segment.ident.to_string() == name
- }
-}
-
-fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
- let mut cfgs = vec![];
- let mut not_cfgs = vec![];
-
- for attr in attrs {
- if eq(&attr, "cfg") {
- cfgs.push(attr);
- } else {
- not_cfgs.push(attr);
- }
- }
-
- (cfgs, not_cfgs)
-}
-
-/// Extracts `static mut` vars from the beginning of the given statements
-fn extract_statics(stmts: Vec<Stmt>) -> (Statics, Vec<Stmt>) {
- let mut istmts = stmts.into_iter();
-
- let mut statics = Statics::new();
- let mut stmts = vec![];
- while let Some(stmt) = istmts.next() {
- match stmt {
- Stmt::Item(Item::Static(var)) => {
- if var.mutability.is_some() {
- statics.push(var);
- } else {
- stmts.push(Stmt::Item(Item::Static(var)));
- break;
- }
- }
- _ => {
- stmts.push(stmt);
- break;
- }
- }
- }
-
- stmts.extend(istmts);
-
- (statics, stmts)
-}
-
-// TODO remove in v0.5.x
-fn extract_assignments(stmts: Vec<Stmt>) -> (Vec<Stmt>, Vec<Assign>) {
- let mut istmts = stmts.into_iter().rev();
-
- let mut assigns = vec![];
- let mut stmts = vec![];
- while let Some(stmt) = istmts.next() {
- match stmt {
- Stmt::Semi(Expr::Assign(assign), semi) => {
- if let Expr::Path(ref expr) = *assign.left {
- if expr.path.segments.len() == 1 {
- assigns.push(Assign {
- attrs: assign.attrs,
- left: expr.path.segments[0].ident.clone(),
- right: assign.right,
- });
- continue;
- }
- }
-
- stmts.push(Stmt::Semi(Expr::Assign(assign), semi));
- }
- _ => {
- stmts.push(stmt);
- break;
- }
- }
- }
-
- stmts.extend(istmts);
-
- (stmts.into_iter().rev().collect(), assigns)
-}
-
-fn is_bottom(ty: &ReturnType) -> bool {
- if let ReturnType::Type(_, ty) = ty {
- if let Type::Never(_) = **ty {
- true
- } else {
- false
- }
- } else {
- false
- }
-}
-
-fn 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
new file mode 100644
index 00000000..e9e3326e
--- /dev/null
+++ b/macros/src/tests.rs
@@ -0,0 +1,4 @@
+// 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
new file mode 100644
index 00000000..97cbbb3f
--- /dev/null
+++ b/macros/src/tests/single.rs
@@ -0,0 +1,34 @@
+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(
+ quote!(device = pac),
+ quote!(
+ mod app {
+ #[task(priority = 1)]
+ fn a(_: a::Context) {}
+
+ #[task(priority = 2)]
+ fn b(_: b::Context) {}
+
+ // First interrupt is assigned to the highest priority dispatcher
+ extern "C" {
+ fn B();
+ fn A();
+ }
+ }
+ ),
+ settings,
+ )
+ .unwrap();
+
+ let analysis = crate::analyze::app(analysis, &app);
+ let interrupts = &analysis.interrupts;
+ assert_eq!(interrupts.len(), 2);
+ assert_eq!(interrupts[&2].to_string(), "B");
+ assert_eq!(interrupts[&1].to_string(), "A");
+}
diff --git a/redirect.html b/redirect.html
index c42694b4..b832e0b1 100644
--- a/redirect.html
+++ b/redirect.html
@@ -3,14 +3,14 @@
<html lang="en-US">
<head>
<meta charset="UTF-8">
- <meta http-equiv="refresh" content="0; url=https://japaric.github.io/cortex-m-rtfm/book/en/">
+ <meta http-equiv="refresh" content="0; url=URL">
<script type="text/javascript">
- window.location.href = "https://japaric.github.io/cortex-m-rtfm/book/en/"
+ window.location.href = "URL"
</script>
<title>Page Redirection</title>
</head>
<body>
<!-- Note: don't tell people to `click` the link, just tell them that it is a link. -->
- If you are not redirected automatically, follow this <a href='https://japaric.github.io/cortex-m-rtfm/book/en/'>link to example</a>.
+ If you are not redirected automatically, follow this <a href='URL'>link</a>.
</body>
</html>
diff --git a/src/cyccnt.rs b/src/cyccnt.rs
new file mode 100644
index 00000000..8e07b001
--- /dev/null
+++ b/src/cyccnt.rs
@@ -0,0 +1,221 @@
+//! Data Watchpoint Trace (DWT) unit's CYCle CouNTer (CYCCNT)
+
+use core::{
+ cmp::Ordering,
+ convert::{Infallible, TryInto},
+ fmt, ops,
+};
+
+use cortex_m::peripheral::DWT;
+
+use crate::Fraction;
+
+/// A measurement of the CYCCNT. Opaque and useful only with `Duration`
+///
+/// This data type is only available on ARMv7-M
+///
+/// # Correctness
+///
+/// Adding or subtracting a `Duration` of more than `(1 << 31)` cycles to an `Instant` effectively
+/// makes it "wrap around" and creates an incorrect value. This is also true if the operation is
+/// done in steps, e.g. `(instant + dur) + dur` where `dur` is `(1 << 30)` ticks.
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct Instant {
+ inner: i32,
+}
+
+impl Instant {
+ /// Returns an instant corresponding to "now"
+ ///
+ /// *HEADS UP* this function can, and will, return nonsensical values if called within `init`.
+ /// Only use it in `idle` and tasks. In `init`, use the `init::Context.start` field, or the
+ /// `CYCCNT::zero` function, instead of this function
+ pub fn now() -> Self {
+ Instant {
+ inner: DWT::get_cycle_count() as i32,
+ }
+ }
+
+ /// Returns the amount of time elapsed since this instant was created.
+ pub fn elapsed(&self) -> Duration {
+ let diff = Instant::now().inner.wrapping_sub(self.inner);
+ assert!(diff >= 0, "instant now is earlier than self");
+ Duration { inner: diff as u32 }
+ }
+
+ /// Returns the amount of time elapsed from another instant to this one.
+ pub fn duration_since(&self, earlier: Instant) -> Duration {
+ let diff = self.inner.wrapping_sub(earlier.inner);
+ assert!(diff >= 0, "second instant is later than self");
+ Duration { inner: diff as u32 }
+ }
+}
+
+impl fmt::Debug for Instant {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("Instant")
+ .field(&(self.inner as u32))
+ .finish()
+ }
+}
+
+impl ops::AddAssign<Duration> for Instant {
+ fn add_assign(&mut self, dur: Duration) {
+ // NOTE this is a debug assertion because there's no foolproof way to detect a wrap around;
+ // the user may write `(instant + dur) + dur` where `dur` is `(1<<31)-1` ticks.
+ debug_assert!(dur.inner < (1 << 31));
+ self.inner = self.inner.wrapping_add(dur.inner as i32);
+ }
+}
+
+impl ops::Add<Duration> for Instant {
+ type Output = Self;
+
+ fn add(mut self, dur: Duration) -> Self {
+ self += dur;
+ self
+ }
+}
+
+impl ops::SubAssign<Duration> for Instant {
+ fn sub_assign(&mut self, dur: Duration) {
+ // NOTE see the NOTE in `<Instant as AddAssign<Duration>>::add_assign`
+ debug_assert!(dur.inner < (1 << 31));
+ self.inner = self.inner.wrapping_sub(dur.inner as i32);
+ }
+}
+
+impl ops::Sub<Duration> for Instant {
+ type Output = Self;
+
+ fn sub(mut self, dur: Duration) -> Self {
+ self -= dur;
+ self
+ }
+}
+
+impl ops::Sub<Instant> for Instant {
+ type Output = Duration;
+
+ fn sub(self, other: Instant) -> Duration {
+ self.duration_since(other)
+ }
+}
+
+impl Ord for Instant {
+ fn cmp(&self, rhs: &Self) -> Ordering {
+ self.inner.wrapping_sub(rhs.inner).cmp(&0)
+ }
+}
+
+impl PartialOrd for Instant {
+ fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
+ Some(self.cmp(rhs))
+ }
+}
+
+/// A `Duration` type to represent a span of time.
+///
+/// This data type is only available on ARMv7-M
+///
+/// # Correctness
+///
+/// This type is *not* appropriate for representing time spans in the order of, or larger than,
+/// seconds because it can hold a maximum of `(1 << 31)` "ticks" where each tick is the inverse of
+/// the CPU frequency, which usually is dozens of MHz.
+#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
+pub struct Duration {
+ inner: u32,
+}
+
+impl Duration {
+ /// Creates a new `Duration` from the specified number of clock cycles
+ pub fn from_cycles(cycles: u32) -> Self {
+ Duration { inner: cycles }
+ }
+
+ /// Returns the total number of clock cycles contained by this `Duration`
+ pub fn as_cycles(&self) -> u32 {
+ self.inner
+ }
+}
+
+impl TryInto<u32> for Duration {
+ type Error = Infallible;
+
+ fn try_into(self) -> Result<u32, Infallible> {
+ Ok(self.as_cycles())
+ }
+}
+
+impl ops::AddAssign for Duration {
+ fn add_assign(&mut self, dur: Duration) {
+ self.inner += dur.inner;
+ }
+}
+
+impl ops::Add<Duration> for Duration {
+ type Output = Self;
+
+ fn add(self, other: Self) -> Self {
+ Duration {
+ inner: self.inner + other.inner,
+ }
+ }
+}
+
+impl ops::SubAssign for Duration {
+ fn sub_assign(&mut self, rhs: Duration) {
+ self.inner -= rhs.inner;
+ }
+}
+
+impl ops::Sub<Duration> for Duration {
+ type Output = Self;
+
+ fn sub(self, rhs: Self) -> Self {
+ Duration {
+ inner: self.inner - rhs.inner,
+ }
+ }
+}
+
+/// Adds the `cycles` method to the `u32` type
+///
+/// This trait is only available on ARMv7-M
+pub trait U32Ext {
+ /// Converts the `u32` value into clock cycles
+ fn cycles(self) -> Duration;
+}
+
+impl U32Ext for u32 {
+ fn cycles(self) -> Duration {
+ Duration { inner: self }
+ }
+}
+
+/// Implementation of the `Monotonic` trait based on CYCle CouNTer
+pub struct CYCCNT;
+
+impl crate::Monotonic for CYCCNT {
+ type Instant = Instant;
+
+ fn ratio() -> Fraction {
+ Fraction {
+ numerator: 1,
+ denominator: 1,
+ }
+ }
+
+ unsafe fn reset() {
+ (0xE0001004 as *mut u32).write_volatile(0)
+ }
+
+ fn now() -> Instant {
+ Instant::now()
+ }
+
+ fn zero() -> Instant {
+ Instant { inner: 0 }
+ }
+}
diff --git a/src/export.rs b/src/export.rs
index cf7293b6..27f7f5fb 100644
--- a/src/export.rs
+++ b/src/export.rs
@@ -1,44 +1,71 @@
-//! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE
-
-#[cfg(not(feature = "nightly"))]
-use core::ptr;
-use core::{cell::Cell, u8};
+use core::{
+ cell::Cell,
+ sync::atomic::{AtomicBool, Ordering},
+};
+pub use crate::tq::{NotReady, TimerQueue};
+pub use bare_metal::CriticalSection;
#[cfg(armv7m)]
-use cortex_m::register::basepri;
+pub use cortex_m::register::basepri;
pub use cortex_m::{
- asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource,
- peripheral::Peripherals,
+ asm::wfi,
+ interrupt,
+ peripheral::{scb::SystemHandler, syst::SystClkSource, DWT, NVIC},
+ Peripherals,
};
-pub use heapless::consts;
-use heapless::spsc::{Queue, SingleCore};
+use heapless::spsc::SingleCore;
+pub use heapless::{consts, i::Queue as iQueue, spsc::Queue};
+pub use heapless::{i::BinaryHeap as iBinaryHeap, BinaryHeap};
-#[cfg(feature = "timer-queue")]
-pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue};
-
-pub type FreeQueue<N> = Queue<u8, N, usize, SingleCore>;
-pub type ReadyQueue<T, N> = Queue<(T, u8), N, usize, SingleCore>;
+pub type SCFQ<N> = Queue<u8, N, u8, SingleCore>;
+pub type SCRQ<T, N> = Queue<(T, u8), N, u8, SingleCore>;
#[cfg(armv7m)]
#[inline(always)]
-pub fn run<F>(f: F)
+pub fn run<F>(priority: u8, f: F)
where
F: FnOnce(),
{
- let initial = basepri::read();
- f();
- unsafe { basepri::write(initial) }
+ 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(armv7m))]
#[inline(always)]
-pub fn run<F>(f: F)
+pub fn run<F>(_priority: u8, f: F)
where
F: FnOnce(),
{
f();
}
+pub struct Barrier {
+ inner: AtomicBool,
+}
+
+impl Barrier {
+ pub const fn new() -> Self {
+ Barrier {
+ inner: AtomicBool::new(false),
+ }
+ }
+
+ pub fn release(&self) {
+ self.inner.store(true, Ordering::Release)
+ }
+
+ pub fn wait(&self) {
+ while !self.inner.load(Ordering::Acquire) {}
+ }
+}
+
// Newtype over `Cell` that forbids mutation through a shared reference
pub struct Priority {
inner: Cell<u8>,
@@ -52,7 +79,7 @@ impl Priority {
}
}
- // these two methods are used by claim (see below) but can't be used from the RTFM application
+ // These two methods are used by `lock` (see below) but can't be used from the RTIC application
#[inline(always)]
fn set(&self, value: u8) {
self.inner.set(value)
@@ -64,86 +91,6 @@ impl Priority {
}
}
-#[cfg(feature = "nightly")]
-pub struct MaybeUninit<T> {
- // we newtype so the end-user doesn't need `#![feature(maybe_uninit)]` in their code
- inner: core::mem::MaybeUninit<T>,
-}
-
-#[cfg(feature = "nightly")]
-impl<T> MaybeUninit<T> {
- pub const fn uninit() -> Self {
- MaybeUninit {
- inner: core::mem::MaybeUninit::uninit(),
- }
- }
-
- pub fn as_ptr(&self) -> *const T {
- self.inner.as_ptr()
- }
-
- pub fn as_mut_ptr(&mut self) -> *mut T {
- self.inner.as_mut_ptr()
- }
-
- pub fn write(&mut self, value: T) -> &mut T {
- self.inner.write(value)
- }
-}
-
-#[cfg(not(feature = "nightly"))]
-pub struct MaybeUninit<T> {
- value: Option<T>,
-}
-
-#[cfg(not(feature = "nightly"))]
-const MSG: &str =
- "you have hit a bug (UB) in RTFM implementation; try enabling this crate 'nightly' feature";
-
-#[cfg(not(feature = "nightly"))]
-impl<T> MaybeUninit<T> {
- pub const fn uninit() -> Self {
- MaybeUninit { value: None }
- }
-
- pub fn as_ptr(&self) -> *const T {
- if let Some(x) = self.value.as_ref() {
- x
- } else {
- unreachable!(MSG)
- }
- }
-
- pub fn as_mut_ptr(&mut self) -> *mut T {
- if let Some(x) = self.value.as_mut() {
- x
- } else {
- unreachable!(MSG)
- }
- }
-
- pub unsafe fn get_ref(&self) -> &T {
- if let Some(x) = self.value.as_ref() {
- x
- } else {
- unreachable!(MSG)
- }
- }
-
- pub unsafe fn get_mut(&mut self) -> &mut T {
- if let Some(x) = self.value.as_mut() {
- x
- } else {
- unreachable!(MSG)
- }
- }
-
- pub fn write(&mut self, val: T) {
- // NOTE(volatile) we have observed UB when this uses a plain `ptr::write`
- unsafe { ptr::write_volatile(&mut self.value, Some(val)) }
- }
-}
-
#[inline(always)]
pub fn assert_send<T>()
where
@@ -160,21 +107,18 @@ where
#[cfg(armv7m)]
#[inline(always)]
-pub unsafe fn claim<T, R, F>(
+pub unsafe fn lock<T, R>(
ptr: *mut T,
priority: &Priority,
ceiling: u8,
nvic_prio_bits: u8,
- f: F,
-) -> R
-where
- F: FnOnce(&mut T) -> R,
-{
+ f: impl FnOnce(&mut T) -> R,
+) -> R {
let current = priority.get();
- if priority.get() < ceiling {
+ if current < ceiling {
if ceiling == (1 << nvic_prio_bits) {
- priority.set(u8::MAX);
+ priority.set(u8::max_value());
let r = interrupt::free(|_| f(&mut *ptr));
priority.set(current);
r
@@ -193,20 +137,17 @@ where
#[cfg(not(armv7m))]
#[inline(always)]
-pub unsafe fn claim<T, R, F>(
+pub unsafe fn lock<T, R>(
ptr: *mut T,
priority: &Priority,
ceiling: u8,
_nvic_prio_bits: u8,
- f: F,
-) -> R
-where
- F: FnOnce(&mut T) -> R,
-{
+ f: impl FnOnce(&mut T) -> R,
+) -> R {
let current = priority.get();
- if priority.get() < ceiling {
- priority.set(u8::MAX);
+ if current < ceiling {
+ priority.set(u8::max_value());
let r = interrupt::free(|_| f(&mut *ptr));
priority.set(current);
r
@@ -215,8 +156,7 @@ where
}
}
-#[cfg(armv7m)]
#[inline]
-fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 {
+pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 {
((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)
}
diff --git a/src/lib.rs b/src/lib.rs
index b0bf6689..a7d399cd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,22 +1,23 @@
-//! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
+//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers
//!
-//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the
-//! library is `rtfm`.
+//! **HEADS UP** This is an **beta** pre-release; there may be breaking changes in the API and
+//! semantics before a proper release is made.
//!
-//! [`cortex-m-rtfm`]: https://crates.io/crates/cortex-m-rtfm
+//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the
+//! library is `rtic`.
//!
-//! The user level documentation can be found [here].
+//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic
//!
-//! [here]: https://japaric.github.io/cortex-m-rtfm/book/en/
+//! The user level documentation can be found [here].
//!
-//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component
-//! of the framework.
+//! [here]: https://rtic.rs
//!
-//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html
+//! 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 guaranteed to compile on stable Rust 1.31 (2018 edition) and up. It *might*
+//! This crate is guaranteed to compile on stable Rust 1.36 (2018 edition) and up. It *might*
//! compile on older versions but that may change in any new patch release.
//!
//! # Semantic Versioning
@@ -27,73 +28,43 @@
//! release.
//!
//! [SemVer]: https://semver.org/spec/v2.0.0.html
-//!
-//! # Cargo features
-//!
-//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule
-//! tasks to run in the future. Also see [`Instant`] and [`Duration`].
-//!
-//! [`Instant`]: struct.Instant.html
-//! [`Duration`]: struct.Duration.html
-//!
-//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable
-//! `core::mem::MaybeUninit` API and unstable `const_fn` language feature to reduce static memory
-//! usage, runtime overhead and initialization overhead. This feature requires a nightly compiler
-//! and may stop working at any time!
-#![cfg_attr(feature = "nightly", feature(maybe_uninit))]
#![deny(missing_docs)]
+#![deny(rust_2018_compatibility)]
+#![deny(rust_2018_idioms)]
#![deny(warnings)]
#![no_std]
-#[cfg(feature = "timer-queue")]
-use core::cmp::Ordering;
-use core::{fmt, ops};
+use core::ops::Sub;
-#[cfg(not(feature = "timer-queue"))]
-use cortex_m::peripheral::SYST;
use cortex_m::{
interrupt::Nr,
peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU},
};
-pub use cortex_m_rtfm_macros::app;
+use cortex_m_rt as _; // vector table
+pub use cortex_m_rtic_macros::app;
+pub use rtic_core::{Exclusive, Mutex};
+#[cfg(armv7m)]
+pub mod cyccnt;
#[doc(hidden)]
pub mod export;
#[doc(hidden)]
-#[cfg(feature = "timer-queue")]
mod tq;
-#[cfg(all(feature = "timer-queue", armv6m))]
-compile_error!(
- "The `timer-queue` feature is currently not supported on ARMv6-M (`thumbv6m-none-eabi`)"
-);
-
-/// Core peripherals
-///
-/// This is `cortex_m::Peripherals` minus the peripherals that the RTFM runtime uses
-///
-/// - The `NVIC` field is never present.
-/// - When the `timer-queue` feature is enabled the following fields are *not* present: `DWT` and
-/// `SYST`.
+/// `cortex_m::Peripherals` minus `SYST`
#[allow(non_snake_case)]
-pub struct Peripherals<'a> {
+pub struct Peripherals {
/// Cache and branch predictor maintenance operations (not present on Cortex-M0 variants)
pub CBP: CBP,
/// CPUID
pub CPUID: CPUID,
- /// Debug Control Block (by value if the `timer-queue` feature is disabled)
- #[cfg(feature = "timer-queue")]
- pub DCB: &'a mut DCB,
-
- /// Debug Control Block (borrowed if the `timer-queue` feature is enabled)
- #[cfg(not(feature = "timer-queue"))]
+ /// Debug Control Block
pub DCB: DCB,
- /// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled)
- #[cfg(not(feature = "timer-queue"))]
+ /// Data Watchpoint and Trace unit
pub DWT: DWT,
/// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants)
@@ -108,253 +79,78 @@ pub struct Peripherals<'a> {
/// Memory Protection Unit
pub MPU: MPU,
- // Nested Vector Interrupt Controller
- // pub NVIC: NVIC,
- /// System Control Block
- pub SCB: &'a mut SCB,
+ /// Nested Vector Interrupt Controller
+ pub NVIC: NVIC,
- /// SysTick: System Timer (not present if the `timer-queue` is enabled)
- #[cfg(not(feature = "timer-queue"))]
- pub SYST: SYST,
+ /// System Control Block
+ pub SCB: SCB,
+ // SysTick: System Timer
+ // pub SYST: SYST,
/// Trace Port Interface Unit (not present on Cortex-M0 variants)
pub TPIU: TPIU,
}
-/// A measurement of a monotonically nondecreasing clock. Opaque and useful only with `Duration`
-///
-/// This data type is only available when the `timer-queue` feature is enabled
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-#[cfg(feature = "timer-queue")]
-pub struct Instant(i32);
-
-#[cfg(feature = "timer-queue")]
-impl Instant {
- /// IMPLEMENTATION DETAIL. DO NOT USE
- #[doc(hidden)]
- pub fn artificial(timestamp: i32) -> Self {
- Instant(timestamp)
- }
-
- /// Returns an instant corresponding to "now"
- pub fn now() -> Self {
- Instant(DWT::get_cycle_count() as i32)
- }
-
- /// Returns the amount of time elapsed since this instant was created.
- pub fn elapsed(&self) -> Duration {
- Instant::now() - *self
- }
-
- /// Returns the amount of time elapsed from another instant to this one.
- pub fn duration_since(&self, earlier: Instant) -> Duration {
- let diff = self.0 - earlier.0;
- assert!(diff >= 0, "second instant is later than self");
- Duration(diff as u32)
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::AddAssign<Duration> for Instant {
- fn add_assign(&mut self, dur: Duration) {
- debug_assert!(dur.0 < (1 << 31));
- self.0 = self.0.wrapping_add(dur.0 as i32);
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Add<Duration> for Instant {
- type Output = Self;
-
- fn add(mut self, dur: Duration) -> Self {
- self += dur;
- self
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::SubAssign<Duration> for Instant {
- fn sub_assign(&mut self, dur: Duration) {
- // XXX should this be a non-debug assertion?
- debug_assert!(dur.0 < (1 << 31));
- self.0 = self.0.wrapping_sub(dur.0 as i32);
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Sub<Duration> for Instant {
- type Output = Self;
-
- fn sub(mut self, dur: Duration) -> Self {
- self -= dur;
- self
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Sub<Instant> for Instant {
- type Output = Duration;
-
- fn sub(self, other: Instant) -> Duration {
- self.duration_since(other)
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl Ord for Instant {
- fn cmp(&self, rhs: &Self) -> Ordering {
- self.0.wrapping_sub(rhs.0).cmp(&0)
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl PartialOrd for Instant {
- fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
- Some(self.cmp(rhs))
- }
-}
-
-/// A `Duration` type to represent a span of time.
-///
-/// This data type is only available when the `timer-queue` feature is enabled
-#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
-#[cfg(feature = "timer-queue")]
-pub struct Duration(u32);
-
-#[cfg(feature = "timer-queue")]
-impl Duration {
- /// Returns the total number of clock cycles contained by this `Duration`
- pub fn as_cycles(&self) -> u32 {
- self.0
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::AddAssign for Duration {
- fn add_assign(&mut self, dur: Duration) {
- self.0 += dur.0;
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Add<Duration> for Duration {
- type Output = Self;
-
- fn add(self, other: Self) -> Self {
- Duration(self.0 + other.0)
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::SubAssign for Duration {
- fn sub_assign(&mut self, rhs: Duration) {
- self.0 -= rhs.0;
- }
-}
-
-#[cfg(feature = "timer-queue")]
-impl ops::Sub<Duration> for Duration {
- type Output = Self;
-
- fn sub(self, rhs: Self) -> Self {
- Duration(self.0 - rhs.0)
- }
-}
-
-/// Adds the `cycles` method to the `u32` type
-///
-/// This trait is only available when the `timer-queue` feature is enabled
-#[cfg(feature = "timer-queue")]
-pub trait U32Ext {
- /// Converts the `u32` value into clock cycles
- fn cycles(self) -> Duration;
-}
-
-#[cfg(feature = "timer-queue")]
-impl U32Ext for u32 {
- fn cycles(self) -> Duration {
- Duration(self)
- }
-}
-
-/// Memory safe access to shared resources
-///
-/// In RTFM, locks are implemented as critical sections that prevent other tasks from *starting*.
-/// These critical sections are implemented by temporarily increasing the dynamic priority (see
-/// [BASEPRI]) of the current context. Entering and leaving these critical sections is always done
-/// in constant time (a few instructions).
-///
-/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers
-pub trait Mutex {
- /// Data protected by the mutex
- type T;
-
- /// Creates a critical section and grants temporary access to the protected data
- fn lock<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R;
-}
-
-impl<'a, M> Mutex for &'a mut M
-where
- M: Mutex,
-{
- type T = M::T;
-
- fn lock<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
- (**self).lock(f)
- }
-}
-
-/// Newtype over `&'a mut T` that implements the `Mutex` trait
-///
-/// The `Mutex` implementation for this type is a no-op, no critical section is created
-pub struct Exclusive<'a, T>(pub &'a mut T);
-
-impl<'a, T> Mutex for Exclusive<'a, T> {
- type T = T;
-
- fn lock<R, F>(&mut self, f: F) -> R
- where
- F: FnOnce(&mut Self::T) -> R,
- {
- f(self.0)
- }
-}
-
-impl<'a, T> fmt::Debug for Exclusive<'a, T>
-where
- T: fmt::Debug,
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- (**self).fmt(f)
- }
-}
-
-impl<'a, T> fmt::Display for Exclusive<'a, T>
-where
- T: fmt::Display,
-{
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- (**self).fmt(f)
- }
-}
-
-impl<'a, T> ops::Deref for Exclusive<'a, T> {
- type Target = T;
-
- fn deref(&self) -> &T {
- self.0
- }
-}
-
-impl<'a, T> ops::DerefMut for Exclusive<'a, T> {
- fn deref_mut(&mut self) -> &mut T {
- self.0
- }
+impl From<cortex_m::Peripherals> for Peripherals {
+ fn from(p: cortex_m::Peripherals) -> Self {
+ Self {
+ CBP: p.CBP,
+ CPUID: p.CPUID,
+ DCB: p.DCB,
+ DWT: p.DWT,
+ FPB: p.FPB,
+ FPU: p.FPU,
+ ITM: p.ITM,
+ MPU: p.MPU,
+ NVIC: p.NVIC,
+ SCB: p.SCB,
+ TPIU: p.TPIU,
+ }
+ }
+}
+
+/// A fraction
+pub struct Fraction {
+ /// The numerator
+ pub numerator: u32,
+
+ /// The denominator
+ pub denominator: u32,
+}
+
+/// A monotonic clock / counter
+pub trait Monotonic {
+ /// A measurement of this clock, use `CYCCNT` as a reference implementation for `Instant`.
+ /// Note that the Instant must be a signed value such as `i32`.
+ type Instant: Copy + Ord + Sub;
+
+ /// The ratio between the system timer (SysTick) frequency and this clock frequency, i.e.
+ /// `Monotonic clock * Fraction = System clock`
+ ///
+ /// The ratio must be expressed in *reduced* `Fraction` form to prevent overflows. That is
+ /// `2 / 3` instead of `4 / 6`
+ fn ratio() -> Fraction;
+
+ /// Returns the current time
+ ///
+ /// # Correctness
+ ///
+ /// This function is *allowed* to return nonsensical values if called before `reset` is invoked
+ /// by the runtime. Therefore application authors should *not* call this function during the
+ /// `#[init]` phase.
+ fn now() -> Self::Instant;
+
+ /// Resets the counter to *zero*
+ ///
+ /// # Safety
+ ///
+ /// This function will be called *exactly once* by the RTIC runtime after `#[init]` returns and
+ /// before tasks can start; this is also the case in multi-core applications. User code must
+ /// *never* call this function.
+ unsafe fn reset();
+
+ /// A `Self::Instant` that represents a count of *zero*
+ fn zero() -> Self::Instant;
}
/// Sets the given `interrupt` as pending
diff --git a/src/tq.rs b/src/tq.rs
index 8d520518..9300dbfc 100644
--- a/src/tq.rs
+++ b/src/tq.rs
@@ -1,36 +1,34 @@
-use core::cmp::{self, Ordering};
+use core::{
+ cmp::{self, Ordering},
+ convert::TryInto,
+ mem,
+ ops::Sub,
+};
use cortex_m::peripheral::{SCB, SYST};
use heapless::{binary_heap::Min, ArrayLength, BinaryHeap};
-use crate::{Instant, Mutex};
+use crate::Monotonic;
-pub struct TimerQueue<T, N>
+pub struct TimerQueue<M, T, N>(pub BinaryHeap<NotReady<M, T>, N, Min>)
where
- N: ArrayLength<NotReady<T>>,
- T: Copy,
-{
- pub syst: SYST,
- pub queue: BinaryHeap<NotReady<T>, N, Min>,
-}
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+ N: ArrayLength<NotReady<M, T>>,
+ T: Copy;
-impl<T, N> TimerQueue<T, N>
+impl<M, T, N> TimerQueue<M, T, N>
where
- N: ArrayLength<NotReady<T>>,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+ N: ArrayLength<NotReady<M, T>>,
T: Copy,
{
- pub fn new(syst: SYST) -> Self {
- TimerQueue {
- syst,
- queue: BinaryHeap::new(),
- }
- }
-
#[inline]
- pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<T>) {
+ pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<M, T>) {
let mut is_empty = true;
if self
- .queue
+ .0
.peek()
.map(|head| {
is_empty = false;
@@ -39,97 +37,111 @@ where
.unwrap_or(true)
{
if is_empty {
- self.syst.enable_interrupt();
+ mem::transmute::<_, SYST>(()).enable_interrupt();
}
- // set SysTick pending
- (*SCB::ptr()).icsr.write(1 << 26);
+ // Set SysTick pending
+ SCB::set_pendst();
}
- self.queue.push_unchecked(nr);
+ self.0.push_unchecked(nr);
+ }
+
+ #[inline]
+ pub fn dequeue(&mut self) -> Option<(T, u8)> {
+ unsafe {
+ if let Some(instant) = self.0.peek().map(|p| p.instant) {
+ let now = M::now();
+
+ if instant < now {
+ // task became ready
+ let nr = self.0.pop_unchecked();
+
+ Some((nr.task, nr.index))
+ } else {
+ // set a new timeout
+ const MAX: u32 = 0x00ffffff;
+
+ let ratio = M::ratio();
+ let dur = match (instant - now).try_into().ok().and_then(|x| {
+ x.checked_mul(ratio.numerator)
+ .map(|x| x / ratio.denominator)
+ }) {
+ None => MAX,
+
+ // ARM Architecture Reference Manual says:
+ // "Setting SYST_RVR to zero has the effect of
+ // disabling the SysTick counter independently
+ // of the counter enable bit."
+ Some(0) => 1,
+
+ Some(x) => cmp::min(MAX, x),
+ };
+ mem::transmute::<_, SYST>(()).set_reload(dur);
+
+ // Start counting down from the new reload
+ mem::transmute::<_, SYST>(()).clear_current();
+
+ None
+ }
+ } else {
+ // The queue is empty
+ mem::transmute::<_, SYST>(()).disable_interrupt();
+
+ None
+ }
+ }
}
}
-pub struct NotReady<T>
+pub struct NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
pub index: u8,
- pub instant: Instant,
+ pub instant: M::Instant,
pub task: T,
}
-impl<T> Eq for NotReady<T> where T: Copy {}
+impl<M, T> Eq for NotReady<M, T>
+where
+ T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
+{
+}
-impl<T> Ord for NotReady<T>
+impl<M, T> Ord for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn cmp(&self, other: &Self) -> Ordering {
self.instant.cmp(&other.instant)
}
}
-impl<T> PartialEq for NotReady<T>
+impl<M, T> PartialEq for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn eq(&self, other: &Self) -> bool {
self.instant == other.instant
}
}
-impl<T> PartialOrd for NotReady<T>
+impl<M, T> PartialOrd for NotReady<M, T>
where
T: Copy,
+ M: Monotonic,
+ <M::Instant as Sub>::Output: TryInto<u32>,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other))
}
}
-
-#[inline(always)]
-pub fn isr<TQ, T, N, F>(mut tq: TQ, mut f: F)
-where
- TQ: Mutex<T = TimerQueue<T, N>>,
- T: Copy + Send,
- N: ArrayLength<NotReady<T>>,
- F: FnMut(T, u8),
-{
- loop {
- // XXX does `#[inline(always)]` improve performance or not?
- let next = tq.lock(#[inline(always)]
- |tq| {
- if let Some(instant) = tq.queue.peek().map(|p| p.instant) {
- let diff = instant.0.wrapping_sub(Instant::now().0);
-
- if diff < 0 {
- // task became ready
- let m = unsafe { tq.queue.pop_unchecked() };
-
- Some((m.task, m.index))
- } else {
- // set a new timeout
- const MAX: u32 = 0x00ffffff;
-
- tq.syst.set_reload(cmp::min(MAX, diff as u32));
-
- // start counting down from the new reload
- tq.syst.clear_current();
-
- None
- }
- } else {
- // the queue is empty
- tq.syst.disable_interrupt();
- None
- }
- });
-
- if let Some((task, index)) = next {
- f(task, index)
- } else {
- return;
- }
- }
-}
diff --git a/tests/cfail/cfg-resources.rs b/tests/cfail/cfg-resources.rs
deleted file mode 100644
index dee1485b..00000000
--- a/tests/cfail/cfg-resources.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[cfg(never)]
- static mut O1: u32 = 0; // init
- #[cfg(never)]
- static mut O2: u32 = 0; // idle
- #[cfg(never)]
- static mut O3: u32 = 0; // EXTI0
- #[cfg(never)]
- static O4: u32 = 0; // idle
- #[cfg(never)]
- static O5: u32 = 0; // EXTI1
- #[cfg(never)]
- static O6: u32 = 0; // init
-
- #[cfg(never)]
- static mut S1: u32 = 0; // idle & EXTI0
- #[cfg(never)]
- static mut S2: u32 = 0; // EXTI0 & EXTI1
- #[cfg(never)]
- static S3: u32 = 0;
-
- #[init(resources = [O1, O4, O5, O6, S3])]
- fn init() {
- resources.O1; //~ ERROR no field `O1`
- resources.O4; //~ ERROR no field `O4`
- resources.O5; //~ ERROR no field `O5`
- resources.O6; //~ ERROR no field `O6`
- resources.S3; //~ ERROR no field `S3`
- }
-
- #[idle(resources = [O2, O4, S1, S3])]
- fn idle() -> ! {
- resources.O2; //~ ERROR no field `O2`
- resources.O4; //~ ERROR no field `O4`
- resources.S1; //~ ERROR no field `S1`
- resources.S3; //~ ERROR no field `S3`
-
- loop {}
- }
-
- #[interrupt(resources = [O3, S1, S2, S3])]
- fn UART0() {
- resources.O3; //~ ERROR no field `O3`
- resources.S1; //~ ERROR no field `S1`
- resources.S2; //~ ERROR no field `S2`
- resources.S3; //~ ERROR no field `S3`
- }
-
- #[interrupt(resources = [S2, O5])]
- fn UART1() {
- resources.S2; //~ ERROR no field `S2`
- resources.O5; //~ ERROR no field `O5`
- }
-};
diff --git a/tests/cfail/cfg-static.rs b/tests/cfail/cfg-static.rs
deleted file mode 100644
index 0d27e533..00000000
--- a/tests/cfail/cfg-static.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- FOO; //~ ERROR cannot find value `FOO` in this scope
- }
-
- #[idle]
- fn idle() -> ! {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- FOO; //~ ERROR cannot find value `FOO` in this scope
-
- loop {}
- }
-
- #[exception]
- fn SVCall() {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- FOO; //~ ERROR cannot find value `FOO` in this scope
- }
-
- #[interrupt]
- fn UART0() {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- FOO; //~ ERROR cannot find value `FOO` in this scope
- }
-
- #[task]
- fn foo() {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- FOO; //~ ERROR cannot find value `FOO` in this scope
- }
-
- extern "C" {
- fn UART1();
- }
-};
diff --git a/tests/cfail/duplicate-args-2.rs b/tests/cfail/duplicate-args-2.rs
deleted file mode 100644
index 1a196e99..00000000
--- a/tests/cfail/duplicate-args-2.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[task(
- priority = 1,
- priority = 2, //~ ERROR argument appears more than once
- )]
- fn foo() {}
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/duplicate-args.rs b/tests/cfail/duplicate-args.rs
deleted file mode 100644
index a946bae2..00000000
--- a/tests/cfail/duplicate-args.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[task(
- capacity = 1,
- capacity = 2, //~ ERROR argument appears more than once
- )]
- fn foo() {}
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/early-return-2.rs b/tests/cfail/early-return-2.rs
deleted file mode 100644
index bf867e07..00000000
--- a/tests/cfail/early-return-2.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut UNINITIALIZED: bool = ();
-
- #[init]
- fn init() {
- if false {
- return; //~ ERROR `init` is *not* allowed to early return
- }
-
- UNINITIALIZED = true;
- }
-
- #[interrupt(resources = [UNINITIALIZED])]
- fn UART0() {
- if resources.UNINITIALIZED {
- // UB
- }
- }
-};
diff --git a/tests/cfail/early-return.rs b/tests/cfail/early-return.rs
deleted file mode 100644
index fb695aac..00000000
--- a/tests/cfail/early-return.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut UNINITIALIZED: bool = ();
-
- #[init]
- fn init() {
- let x = || {
- // this is OK
- return 0;
- };
-
- return; //~ ERROR `init` is *not* allowed to early return
-
- UNINITIALIZED = true;
- }
-
- #[interrupt(resources = [UNINITIALIZED])]
- fn UART0() {
- if resources.UNINITIALIZED {
- // UB
- }
- }
-};
diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs
deleted file mode 100644
index 692a57c7..00000000
--- a/tests/cfail/exception-divergent.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception]
- fn SVCall() -> ! {
- //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()`
- loop {}
- }
-};
diff --git a/tests/cfail/exception-input.rs b/tests/cfail/exception-input.rs
deleted file mode 100644
index cb0711ce..00000000
--- a/tests/cfail/exception-input.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception]
- fn SVCall(undef: u32) {
- //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()`
- }
-};
diff --git a/tests/cfail/exception-invalid.rs b/tests/cfail/exception-invalid.rs
deleted file mode 100644
index 0a7fb520..00000000
--- a/tests/cfail/exception-invalid.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception]
- fn NonMaskableInt() {
- //~^ ERROR only exceptions with configurable priority can be used as hardware tasks
- }
-};
diff --git a/tests/cfail/exception-output.rs b/tests/cfail/exception-output.rs
deleted file mode 100644
index 758dbdd7..00000000
--- a/tests/cfail/exception-output.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception]
- fn SVCall() -> u32 {
- //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()`
- 0
- }
-};
diff --git a/tests/cfail/exception-sys-tick.rs b/tests/cfail/exception-sys-tick.rs
deleted file mode 100644
index 69d73dbc..00000000
--- a/tests/cfail/exception-sys-tick.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception]
- fn SysTick() {
- //~^ ERROR the `SysTick` exception can't be used because it's used by the runtime
- }
-};
diff --git a/tests/cfail/idle-input.rs b/tests/cfail/idle-input.rs
deleted file mode 100644
index 5095977e..00000000
--- a/tests/cfail/idle-input.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[idle]
- fn idle(undef: u32) {
- //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !`
- }
-};
diff --git a/tests/cfail/idle-not-divergent.rs b/tests/cfail/idle-not-divergent.rs
deleted file mode 100644
index e90eff08..00000000
--- a/tests/cfail/idle-not-divergent.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[idle]
- fn idle() {
- //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !`
- }
-};
diff --git a/tests/cfail/init-divergent.rs b/tests/cfail/init-divergent.rs
deleted file mode 100644
index 54813d47..00000000
--- a/tests/cfail/init-divergent.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() -> ! {
- //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
- loop {}
- }
-};
diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs
deleted file mode 100644
index 3bf0cadf..00000000
--- a/tests/cfail/init-input.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init(undef: u32) {
- //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
- }
-};
diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs
deleted file mode 100644
index 3ac495f5..00000000
--- a/tests/cfail/init-not-send.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-//! This is equivalent to the `late-not-send` cfail test
-
-#![feature(extern_crate_item_prelude)] // ???
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use core::marker::PhantomData;
-
-use rtfm::app;
-
-pub struct NotSend {
- _0: PhantomData<*const ()>,
-}
-
-#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely
-const APP: () = {
- static mut X: Option<NotSend> = None;
-
- #[init(resources = [X])]
- fn init() {
- *resources.X = Some(NotSend { _0: PhantomData })
- }
-
- #[interrupt(resources = [X])]
- fn UART0() {}
-};
diff --git a/tests/cfail/init-output.rs b/tests/cfail/init-output.rs
deleted file mode 100644
index 414a35a8..00000000
--- a/tests/cfail/init-output.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() -> u32 {
- //~^ ERROR `init` must have type signature `[unsafe] fn() [-> init::LateResources]`
- 0
- }
-};
diff --git a/tests/cfail/insufficient-free-interrupts.rs b/tests/cfail/insufficient-free-interrupts.rs
deleted file mode 100644
index baa2582b..00000000
--- a/tests/cfail/insufficient-free-interrupts.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required
-const APP: () = {
- #[init]
- fn init() {}
-
- #[task]
- fn foo() {}
-};
diff --git a/tests/cfail/interrupt-divergent.rs b/tests/cfail/interrupt-divergent.rs
deleted file mode 100644
index 4a015330..00000000
--- a/tests/cfail/interrupt-divergent.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[interrupt]
- fn UART0() -> ! {
- //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()`
- loop {}
- }
-};
diff --git a/tests/cfail/interrupt-input.rs b/tests/cfail/interrupt-input.rs
deleted file mode 100644
index d0240f4e..00000000
--- a/tests/cfail/interrupt-input.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[interrupt]
- fn UART0(undef: u32) {
- //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()`
- }
-};
diff --git a/tests/cfail/interrupt-output.rs b/tests/cfail/interrupt-output.rs
deleted file mode 100644
index 37cd7c21..00000000
--- a/tests/cfail/interrupt-output.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[interrupt]
- fn UART0() -> u32 {
- //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()`
- 0
- }
-};
diff --git a/tests/cfail/late-assigned-to-init.rs b/tests/cfail/late-assigned-to-init.rs
deleted file mode 100644
index 70a361c1..00000000
--- a/tests/cfail/late-assigned-to-init.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut X: u32 = ();
-
- #[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init`
- fn init() {}
-};
diff --git a/tests/cfail/late-not-send.rs b/tests/cfail/late-not-send.rs
deleted file mode 100644
index eb3048d9..00000000
--- a/tests/cfail/late-not-send.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-//! `init` has a static priority of `0`. Initializing resources from it is equivalent to sending a
-//! message to the task that will own the resource
-
-#![feature(extern_crate_item_prelude)] // ???
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use core::marker::PhantomData;
-
-use rtfm::app;
-
-struct NotSend {
- _0: PhantomData<*const ()>,
-}
-
-#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely
-const APP: () = {
- static mut X: NotSend = ();
-
- #[init]
- fn init() -> init::LateResources {
- init::LateResources {
- X: NotSend { _0: PhantomData },
- }
- }
-
- #[interrupt(resources = [X])]
- fn UART0() {}
-};
diff --git a/tests/cfail/late-uninit.rs b/tests/cfail/late-uninit.rs
deleted file mode 100644
index 55122ed7..00000000
--- a/tests/cfail/late-uninit.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-// TODO remove in v0.5.x
-
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut X: u32 = (); //~ ERROR late resources MUST be initialized at the end of `init`
-
- #[init]
- fn init() {}
-};
diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs
deleted file mode 100644
index 7e3ca306..00000000
--- a/tests/cfail/needs-send.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-#![feature(extern_crate_item_prelude)] // ???
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use core::marker::PhantomData;
-
-use rtfm::app;
-
-pub struct NotSend {
- _0: PhantomData<*const ()>,
-}
-
-unsafe impl Sync for NotSend {}
-
-#[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely
-const APP: () = {
- #[init(spawn = [foo])]
- fn init() {}
-
- #[task]
- fn foo(_x: NotSend) {}
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/needs-sync.rs b/tests/cfail/needs-sync.rs
deleted file mode 100644
index f25f91a2..00000000
--- a/tests/cfail/needs-sync.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-#![feature(extern_crate_item_prelude)] // ???
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use core::marker::PhantomData;
-
-use rtfm::app;
-
-pub struct NotSync {
- _0: PhantomData<*const ()>,
-}
-
-unsafe impl Send for NotSync {}
-
-#[app(device = lm3s6965)] //~ ERROR cannot be shared between threads safely
-const APP: () = {
- static X: NotSync = NotSync { _0: PhantomData };
-
- #[init(spawn = [foo])]
- fn init() {}
-
- #[task(priority = 1, resources = [X])]
- fn foo() {}
-
- #[task(priority = 2, resources = [X])]
- fn bar() {}
-
- extern "C" {
- fn UART0();
- fn UART1();
- }
-};
diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs
deleted file mode 100644
index ec324014..00000000
--- a/tests/cfail/priority-too-high.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)] //~ error evaluation of constant value failed
-const APP: () = {
- #[init]
- fn init() {}
-
- // OK, this is the maximum priority supported by the device
- #[interrupt(priority = 8)]
- fn UART0() {}
-
- // this value is too high!
- #[interrupt(priority = 9)]
- fn UART1() {}
-};
diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs
deleted file mode 100644
index 6dcbfd63..00000000
--- a/tests/cfail/priority-too-low.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- // OK, this is the minimum priority that tasks can have
- #[interrupt(priority = 1)]
- fn UART0() {}
-
- // this value is too low!
- #[interrupt(priority = 0)] //~ error this literal must be in the range 1...255
- fn UART1() {}
-};
diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs
deleted file mode 100644
index f6d08a65..00000000
--- a/tests/cfail/resource-not-declared.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init(resources = [X])] //~ ERROR this resource has NOT been declared
- fn init() {}
-};
diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs
deleted file mode 100644
index 970fc6cc..00000000
--- a/tests/cfail/resource-pub.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- pub static mut X: u32 = 0;
- //~^ ERROR resources must have inherited / private visibility
-
- #[init]
- fn init() {}
-};
diff --git a/tests/cfail/task-divergent.rs b/tests/cfail/task-divergent.rs
deleted file mode 100644
index 3822d754..00000000
--- a/tests/cfail/task-divergent.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[task]
- fn foo() -> ! {
- //~^ ERROR `task` handlers must have type signature `[unsafe] fn(..)`
- loop {}
- }
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/task-idle.rs b/tests/cfail/task-idle.rs
deleted file mode 100644
index 62d927b9..00000000
--- a/tests/cfail/task-idle.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[task]
- fn idle() {
- //~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources`
- }
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/task-not-declared.rs b/tests/cfail/task-not-declared.rs
deleted file mode 100644
index 3e6d87c4..00000000
--- a/tests/cfail/task-not-declared.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init(spawn = [X])] //~ ERROR this task has NOT been declared
- fn init() {}
-};
diff --git a/tests/cfail/used-free-interrupt-2.rs b/tests/cfail/used-free-interrupt-2.rs
deleted file mode 100644
index 616d308d..00000000
--- a/tests/cfail/used-free-interrupt-2.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[interrupt(binds = UART0)] //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers
- fn foo() {}
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs
deleted file mode 100644
index 78ae5407..00000000
--- a/tests/cfail/used-free-interrupt.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[interrupt]
- fn UART0() {} //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers
-
- extern "C" {
- fn UART0();
- }
-};
diff --git a/tests/compiletest.rs b/tests/compiletest.rs
deleted file mode 100644
index 58702eec..00000000
--- a/tests/compiletest.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use std::{fs, path::PathBuf, process::Command};
-
-use compiletest_rs::{common::Mode, Config};
-use tempdir::TempDir;
-
-#[test]
-fn cfail() {
- let mut config = Config::default();
-
- config.mode = Mode::CompileFail;
- config.src_base = PathBuf::from("tests/cfail");
- config.link_deps();
-
- // remove duplicate and trailing `-L` flags
- let mut s = String::new();
- if let Some(flags) = config.target_rustcflags.as_mut() {
- let mut iter = flags.split_whitespace().peekable();
-
- while let Some(flag) = iter.next() {
- if flag == "-L" && (iter.peek() == Some(&"-L") || iter.peek() == None) {
- iter.next();
- continue;
- }
-
- s += flag;
- s += " ";
- }
-
- // path to proc-macro crate
- s += "-L target/debug/deps ";
-
- // avoid "error: language item required, but not found: `eh_personality`"
- s += "-C panic=abort ";
- }
-
- let td = TempDir::new("rtfm").unwrap();
- for f in fs::read_dir("tests/cpass").unwrap() {
- let f = f.unwrap().path();
- let name = f.file_stem().unwrap().to_str().unwrap();
-
- assert!(Command::new("rustc")
- .args(s.split_whitespace())
- .arg(f.display().to_string())
- .arg("-o")
- .arg(td.path().join(name).display().to_string())
- .arg("-C")
- .arg("linker=true")
- .status()
- .unwrap()
- .success());
- }
-
- config.target_rustcflags = Some(s);
- config.clean_rmeta();
-
- compiletest_rs::run_tests(&config);
-}
diff --git a/tests/cpass/binds.rs b/tests/cpass/binds.rs
deleted file mode 100644
index 7cb91741..00000000
--- a/tests/cpass/binds.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-//! Check that `binds` works as advertised
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {}
-
- #[exception(binds = SVCall)]
- fn foo() {}
-
- #[interrupt(binds = UART0)]
- fn bar() {}
-};
-
-#[allow(dead_code)]
-fn foo_trampoline(_: foo::Context) {}
-
-#[allow(dead_code)]
-fn bar_trampoline(_: bar::Context) {}
diff --git a/tests/cpass/cfg.rs b/tests/cpass/cfg.rs
deleted file mode 100644
index c91ab604..00000000
--- a/tests/cpass/cfg.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-//! Compile-pass test that checks that `#[cfg]` attributes are respected
-
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_semihosting;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[cfg(never)]
- static mut FOO: u32 = 0;
-
- #[init]
- fn init() {
- #[cfg(never)]
- static mut BAR: u32 = 0;
- }
-
- #[idle]
- fn idle() -> ! {
- #[cfg(never)]
- static mut BAR: u32 = 0;
-
- loop {}
- }
-
- #[task(resources = [FOO], schedule = [quux], spawn = [quux])]
- fn foo() {
- #[cfg(never)]
- static mut BAR: u32 = 0;
- }
-
- #[task(priority = 3, resources = [FOO], schedule = [quux], spawn = [quux])]
- fn bar() {
- #[cfg(never)]
- static mut BAR: u32 = 0;
- }
-
- #[cfg(never)]
- #[task]
- fn quux() {}
-
- extern "C" {
- fn UART0();
- fn UART1();
- }
-};
diff --git a/tests/cpass/late-not-send.rs b/tests/cpass/late-not-send.rs
deleted file mode 100644
index 5b278ab5..00000000
--- a/tests/cpass/late-not-send.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use core::marker::PhantomData;
-
-use rtfm::app;
-
-pub struct NotSend {
- _0: PhantomData<*const ()>,
-}
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut X: NotSend = ();
- static mut Y: Option<NotSend> = None;
-
- #[init(resources = [Y])]
- fn init() -> init::LateResources {
- *resources.Y = Some(NotSend { _0: PhantomData });
-
- init::LateResources {
- X: NotSend { _0: PhantomData },
- }
- }
-
- #[idle(resources = [X, Y])]
- fn idle() -> ! {
- loop {}
- }
-};
diff --git a/tests/cpass/late-resource.rs b/tests/cpass/late-resource.rs
deleted file mode 100644
index 0dec4cbe..00000000
--- a/tests/cpass/late-resource.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-//! Runtime initialized resources
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut X: u32 = ();
- static Y: u32 = ();
-
- #[init]
- fn init() -> init::LateResources {
- init::LateResources { X: 0, Y: 1 }
- }
-};
diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs
deleted file mode 100644
index 509a6be1..00000000
--- a/tests/cpass/peripheral.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-//! Core and device peripherals
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- fn init() {
- let _: rtfm::Peripherals = core;
- let _: lm3s6965::Peripherals = device;
- }
-};
diff --git a/tests/cpass/resource.rs b/tests/cpass/resource.rs
deleted file mode 100644
index bb837393..00000000
--- a/tests/cpass/resource.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-//! Check code generation of resources
-
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::{app, Exclusive};
-
-#[app(device = lm3s6965)]
-const APP: () = {
- static mut O1: u32 = 0; // init
- static mut O2: u32 = 0; // idle
- static mut O3: u32 = 0; // EXTI0
- static O4: u32 = 0; // idle
- static O5: u32 = 0; // EXTI1
- static O6: u32 = 0; // init
-
- static mut S1: u32 = 0; // idle & EXTI0
- static mut S2: u32 = 0; // EXTI0 & EXTI1
- static S3: u32 = 0;
-
- #[init(resources = [O1, O4, O5, O6, S3])]
- fn init() {
- // owned by `init` == `&'static mut`
- let _: &'static mut u32 = resources.O1;
-
- // owned by `init` == `&'static` if read-only
- let _: &'static u32 = resources.O6;
-
- // `init` has exclusive access to all resources
- let _: &mut u32 = resources.O4;
- let _: &mut u32 = resources.O5;
- let _: &mut u32 = resources.S3;
- }
-
- #[idle(resources = [O2, O4, S1, S3])]
- fn idle() -> ! {
- // owned by `idle` == `&'static mut`
- let _: &'static mut u32 = resources.O2;
-
- // owned by `idle` == `&'static` if read-only
- let _: &'static u32 = resources.O4;
-
- // shared with `idle` == `Mutex`
- resources.S1.lock(|_| {});
-
- // `&` if read-only
- let _: &u32 = resources.S3;
-
- loop {}
- }
-
- #[interrupt(resources = [O3, S1, S2, S3])]
- fn UART0() {
- // owned by interrupt == `&mut`
- let _: &mut u32 = resources.O3;
-
- // no `Mutex` proxy when access from highest priority task
- let _: Exclusive<u32> = resources.S1;
-
- // no `Mutex` proxy when co-owned by cooperative (same priority) tasks
- let _: Exclusive<u32> = resources.S2;
-
- // `&` if read-only
- let _: &u32 = resources.S3;
- }
-
- #[interrupt(resources = [S2, O5])]
- fn UART1() {
- // owned by interrupt == `&` if read-only
- let _: &u32 = resources.O5;
-
- // no `Mutex` proxy when co-owned by cooperative (same priority) tasks
- let _: Exclusive<u32> = resources.S2;
- }
-};
diff --git a/tests/cpass/schedule.rs b/tests/cpass/schedule.rs
deleted file mode 100644
index 0728d8b3..00000000
--- a/tests/cpass/schedule.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::{app, Instant};
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init(schedule = [foo, bar, baz])]
- fn init() {
- let _: Result<(), ()> = schedule.foo(start + 10.cycles());
- let _: Result<(), u32> = schedule.bar(start + 20.cycles(), 0);
- let _: Result<(), (u32, u32)> = schedule.baz(start + 30.cycles(), 0, 1);
- }
-
- #[idle(schedule = [foo, bar, baz])]
- fn idle() -> ! {
- let _: Result<(), ()> = schedule.foo(Instant::now() + 40.cycles());
- let _: Result<(), u32> = schedule.bar(Instant::now() + 50.cycles(), 0);
- let _: Result<(), (u32, u32)> = schedule.baz(Instant::now() + 60.cycles(), 0, 1);
-
- loop {}
- }
-
- #[exception(schedule = [foo, bar, baz])]
- fn SVCall() {
- let _: Result<(), ()> = schedule.foo(start + 70.cycles());
- let _: Result<(), u32> = schedule.bar(start + 80.cycles(), 0);
- let _: Result<(), (u32, u32)> = schedule.baz(start + 90.cycles(), 0, 1);
- }
-
- #[interrupt(schedule = [foo, bar, baz])]
- fn UART0() {
- let _: Result<(), ()> = schedule.foo(start + 100.cycles());
- let _: Result<(), u32> = schedule.bar(start + 110.cycles(), 0);
- let _: Result<(), (u32, u32)> = schedule.baz(start + 120.cycles(), 0, 1);
- }
-
- #[task(schedule = [foo, bar, baz])]
- fn foo() {
- let _: Result<(), ()> = schedule.foo(scheduled + 130.cycles());
- let _: Result<(), u32> = schedule.bar(scheduled + 140.cycles(), 0);
- let _: Result<(), (u32, u32)> = schedule.baz(scheduled + 150.cycles(), 0, 1);
- }
-
- #[task]
- fn bar(_x: u32) {}
-
- #[task]
- fn baz(_x: u32, _y: u32) {}
-
- extern "C" {
- fn UART1();
- }
-};
diff --git a/tests/cpass/singleton.rs b/tests/cpass/singleton.rs
deleted file mode 100644
index d50f8523..00000000
--- a/tests/cpass/singleton.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate owned_singleton;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::{app, Exclusive};
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[Singleton]
- static mut O1: u32 = 0;
- #[Singleton]
- static mut O2: u32 = 0;
- #[Singleton]
- static mut O3: u32 = 0;
- #[Singleton]
- static O4: u32 = 0;
- #[Singleton]
- static O5: u32 = 0;
- #[Singleton]
- static O6: u32 = 0;
-
- #[Singleton]
- static mut S1: u32 = 0;
- #[Singleton]
- static S2: u32 = 0;
-
- #[init(resources = [O1, O2, O3, O4, O5, O6, S1, S2])]
- fn init() {
- let _: O1 = resources.O1;
- let _: &mut O2 = resources.O2;
- let _: &mut O3 = resources.O3;
- let _: O4 = resources.O4;
- let _: &mut O5 = resources.O5;
- let _: &mut O6 = resources.O6;
-
- let _: &mut S1 = resources.S1;
- let _: &mut S2 = resources.S2;
- }
-
- #[idle(resources = [O2, O5])]
- fn idle() -> ! {
- let _: O2 = resources.O2;
- let _: O5 = resources.O5;
-
- loop {}
- }
-
- #[interrupt(resources = [O3, O6, S1, S2])]
- fn UART0() {
- let _: &mut O3 = resources.O3;
- let _: &O6 = resources.O6;
-
- let _: Exclusive<S1> = resources.S1;
- let _: &S2 = resources.S2;
- }
-
- #[interrupt(resources = [S1, S2])]
- fn UART1() {
- let _: Exclusive<S1> = resources.S1;
- let _: &S2 = resources.S2;
- }
-};
diff --git a/tests/cpass/spawn.rs b/tests/cpass/spawn.rs
deleted file mode 100644
index 3df606a4..00000000
--- a/tests/cpass/spawn.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-//! Check code generation of `spawn`
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init(spawn = [foo, bar, baz])]
- fn init() {
- let _: Result<(), ()> = spawn.foo();
- let _: Result<(), u32> = spawn.bar(0);
- let _: Result<(), (u32, u32)> = spawn.baz(0, 1);
- }
-
- #[idle(spawn = [foo, bar, baz])]
- fn idle() -> ! {
- let _: Result<(), ()> = spawn.foo();
- let _: Result<(), u32> = spawn.bar(0);
- let _: Result<(), (u32, u32)> = spawn.baz(0, 1);
-
- loop {}
- }
-
- #[exception(spawn = [foo, bar, baz])]
- fn SVCall() {
- let _: Result<(), ()> = spawn.foo();
- let _: Result<(), u32> = spawn.bar(0);
- let _: Result<(), (u32, u32)> = spawn.baz(0, 1);
- }
-
- #[interrupt(spawn = [foo, bar, baz])]
- fn UART0() {
- let _: Result<(), ()> = spawn.foo();
- let _: Result<(), u32> = spawn.bar(0);
- let _: Result<(), (u32, u32)> = spawn.baz(0, 1);
- }
-
- #[task(spawn = [foo, bar, baz])]
- fn foo() {
- let _: Result<(), ()> = spawn.foo();
- let _: Result<(), u32> = spawn.bar(0);
- let _: Result<(), (u32, u32)> = spawn.baz(0, 1);
- }
-
- #[task]
- fn bar(_x: u32) {}
-
- #[task]
- fn baz(_x: u32, _y: u32) {}
-
- extern "C" {
- fn UART1();
- }
-};
diff --git a/tests/cpass/unsafe.rs b/tests/cpass/unsafe.rs
deleted file mode 100644
index b6996ad1..00000000
--- a/tests/cpass/unsafe.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-//! Check code generation of `unsafe` `init` / `idle` / `exception` / `interrupt` / `task`
-#![no_main]
-#![no_std]
-
-extern crate lm3s6965;
-extern crate panic_halt;
-extern crate rtfm;
-
-use rtfm::app;
-
-unsafe fn foo() {}
-
-#[app(device = lm3s6965)]
-const APP: () = {
- #[init]
- unsafe fn init() {
- foo();
- }
-
- #[idle]
- unsafe fn idle() -> ! {
- foo();
-
- loop {}
- }
-
- #[exception]
- unsafe fn SVCall() {
- foo();
- }
-
- #[interrupt]
- unsafe fn UART0() {
- foo();
- }
-
- #[task]
- unsafe fn bar() {
- foo();
- }
-
- extern "C" {
- fn UART1();
- }
-};
diff --git a/tests/single.rs b/tests/single.rs
new file mode 100644
index 00000000..b5d480c7
--- /dev/null
+++ b/tests/single.rs
@@ -0,0 +1,7 @@
+use trybuild::TestCases;
+
+#[test]
+fn ui() {
+ let t = TestCases::new();
+ t.compile_fail("ui/single/*.rs");
+}
diff --git a/ui/single/exception-invalid.rs b/ui/single/exception-invalid.rs
new file mode 100644
index 00000000..04d9bc75
--- /dev/null
+++ b/ui/single/exception-invalid.rs
@@ -0,0 +1,7 @@
+#![no_main]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[task(binds = NonMaskableInt)]
+ fn nmi(_: nmi::Context) {}
+}
diff --git a/ui/single/exception-invalid.stderr b/ui/single/exception-invalid.stderr
new file mode 100644
index 00000000..90213768
--- /dev/null
+++ b/ui/single/exception-invalid.stderr
@@ -0,0 +1,5 @@
+error: only exceptions with configurable priority can be used as hardware tasks
+ --> $DIR/exception-invalid.rs:6:8
+ |
+6 | fn nmi(_: nmi::Context) {}
+ | ^^^
diff --git a/ui/single/exception-systick-used.rs b/ui/single/exception-systick-used.rs
new file mode 100644
index 00000000..1c30b700
--- /dev/null
+++ b/ui/single/exception-systick-used.rs
@@ -0,0 +1,10 @@
+#![no_main]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[task(binds = SysTick)]
+ fn sys_tick(_: sys_tick::Context) {}
+
+ #[task(schedule = [foo])]
+ fn foo(_: foo::Context) {}
+}
diff --git a/ui/single/exception-systick-used.stderr b/ui/single/exception-systick-used.stderr
new file mode 100644
index 00000000..23b6dc4a
--- /dev/null
+++ b/ui/single/exception-systick-used.stderr
@@ -0,0 +1,5 @@
+error: this exception can't be used because it's being used by the runtime
+ --> $DIR/exception-systick-used.rs:6:8
+ |
+6 | fn sys_tick(_: sys_tick::Context) {}
+ | ^^^^^^^^
diff --git a/ui/single/extern-interrupt-not-enough.rs b/ui/single/extern-interrupt-not-enough.rs
new file mode 100644
index 00000000..f2624036
--- /dev/null
+++ b/ui/single/extern-interrupt-not-enough.rs
@@ -0,0 +1,7 @@
+#![no_main]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[task]
+ fn a(_: a::Context) {}
+}
diff --git a/ui/single/extern-interrupt-not-enough.stderr b/ui/single/extern-interrupt-not-enough.stderr
new file mode 100644
index 00000000..73ce7ad0
--- /dev/null
+++ b/ui/single/extern-interrupt-not-enough.stderr
@@ -0,0 +1,5 @@
+error: not enough `extern` interrupts to dispatch all software tasks (need: 1; given: 0)
+ --> $DIR/extern-interrupt-not-enough.rs:6:8
+ |
+6 | fn a(_: a::Context) {}
+ | ^
diff --git a/ui/single/extern-interrupt-used.rs b/ui/single/extern-interrupt-used.rs
new file mode 100644
index 00000000..89c23784
--- /dev/null
+++ b/ui/single/extern-interrupt-used.rs
@@ -0,0 +1,11 @@
+#![no_main]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[task(binds = UART0)]
+ fn a(_: a::Context) {}
+
+ extern "C" {
+ fn UART0();
+ }
+}
diff --git a/ui/single/extern-interrupt-used.stderr b/ui/single/extern-interrupt-used.stderr
new file mode 100644
index 00000000..fb0ff5bc
--- /dev/null
+++ b/ui/single/extern-interrupt-used.stderr
@@ -0,0 +1,5 @@
+error: `extern` interrupts can't be used as hardware tasks
+ --> $DIR/extern-interrupt-used.rs:5:20
+ |
+5 | #[task(binds = UART0)]
+ | ^^^^^
diff --git a/ui/single/locals-cfg.rs b/ui/single/locals-cfg.rs
new file mode 100644
index 00000000..45a7a911
--- /dev/null
+++ b/ui/single/locals-cfg.rs
@@ -0,0 +1,53 @@
+#![no_main]
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ #[cfg(never)]
+ static mut FOO: u32 = 0;
+
+ FOO;
+
+ init::LateResources {}
+ }
+
+ #[idle]
+ fn idle(_: idle::Context) -> ! {
+ #[cfg(never)]
+ static mut FOO: u32 = 0;
+
+ FOO;
+
+ loop {}
+ }
+
+ #[task(binds = SVCall)]
+ fn svcall(_: svcall::Context) {
+ #[cfg(never)]
+ static mut FOO: u32 = 0;
+
+ FOO;
+ }
+
+ #[task(binds = UART0)]
+ fn uart0(_: uart0::Context) {
+ #[cfg(never)]
+ static mut FOO: u32 = 0;
+
+ FOO;
+ }
+
+ #[task]
+ fn foo(_: foo::Context) {
+ #[cfg(never)]
+ static mut FOO: u32 = 0;
+
+ FOO;
+ }
+
+ extern "C" {
+ fn UART1();
+ }
+}
diff --git a/ui/single/locals-cfg.stderr b/ui/single/locals-cfg.stderr
new file mode 100644
index 00000000..e58bd935
--- /dev/null
+++ b/ui/single/locals-cfg.stderr
@@ -0,0 +1,41 @@
+error[E0425]: cannot find value `FOO` in this scope
+ --> $DIR/locals-cfg.rs:11:9
+ |
+11 | FOO;
+ | ^^^ not found in this scope
+
+error[E0425]: cannot find value `FOO` in this scope
+ --> $DIR/locals-cfg.rs:21:9
+ |
+21 | FOO;
+ | ^^^ not found in this scope
+
+error[E0425]: cannot find value `FOO` in this scope
+ --> $DIR/locals-cfg.rs:31:9
+ |
+31 | FOO;
+ | ^^^ not found in this scope
+
+error[E0425]: cannot find value `FOO` in this scope
+ --> $DIR/locals-cfg.rs:39:9
+ |
+39 | FOO;
+ | ^^^ not found in this scope
+
+error[E0425]: cannot find value `FOO` in this scope
+ --> $DIR/locals-cfg.rs:47:9
+ |
+47 | FOO;
+ | ^^^ not found in this scope
+
+error: duplicate lang item in crate `panic_halt` (which `$CRATE` depends on): `panic_impl`.
+ |
+ = note: the lang item is first defined in crate `std` (which `$CRATE` depends on)
+ = note: first definition in `std` loaded from /usr/share/rust/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-cf0f33af3a901778.rlib
+ = note: second definition in `panic_halt` loaded from $DIR/target/tests/target/x86_64-unknown-linux-gnu/debug/deps/libpanic_halt-ba6f0ab3439cbc7e.rmeta
+
+error: duplicate lang item in crate `panic_semihosting`: `panic_impl`.
+ |
+ = note: the lang item is first defined in crate `panic_halt` (which `$CRATE` depends on)
+ = note: first definition in `panic_halt` loaded from $DIR/target/tests/target/x86_64-unknown-linux-gnu/debug/deps/libpanic_halt-ba6f0ab3439cbc7e.rmeta
+ = note: second definition in `panic_semihosting` loaded from $DIR/target/tests/target/x86_64-unknown-linux-gnu/debug/deps/libpanic_semihosting-805015f4a2d05965.rmeta
diff --git a/ui/single/resources-cfg.rs b/ui/single/resources-cfg.rs
new file mode 100644
index 00000000..2ba65a04
--- /dev/null
+++ b/ui/single/resources-cfg.rs
@@ -0,0 +1,79 @@
+#![no_main]
+use panic_halt as _;
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[resources]
+ struct Resources {
+ #[cfg(never)]
+ #[init(0)]
+ o1: u32, // init
+
+ #[cfg(never)]
+ #[init(0)]
+ o2: u32, // idle
+
+ #[cfg(never)]
+ #[init(0)]
+ o3: u32, // EXTI0
+
+ #[cfg(never)]
+ #[init(0)]
+ o4: u32, // idle
+
+ #[cfg(never)]
+ #[init(0)]
+ o5: u32, // EXTI1
+
+ #[cfg(never)]
+ #[init(0)]
+ o6: u32, // init
+
+ #[cfg(never)]
+ #[init(0)]
+ s1: u32, // idle & EXTI0
+
+ #[cfg(never)]
+ #[init(0)]
+ s2: u32, // EXTI0 & EXTI1
+
+ #[cfg(never)]
+ #[init(0)]
+ s3: u32,
+ }
+
+ #[init(resources = [o1, o4, o5, o6, s3])]
+ fn init(c: init::Context) -> init::LateResources {
+ c.resources.o1;
+ c.resources.o4;
+ c.resources.o5;
+ c.resources.o6;
+ c.resources.s3;
+
+ init::LateResources {}
+ }
+
+ #[idle(resources = [o2, &o4, s1, &s3])]
+ fn idle(c: idle::Context) -> ! {
+ c.resources.o2;
+ c.resources.o4;
+ c.resources.s1;
+ c.resources.s3;
+
+ loop {}
+ }
+
+ #[task(binds = UART0, resources = [o3, s1, s2, &s3])]
+ fn uart0(c: uart0::Context) {
+ c.resources.o3;
+ c.resources.s1;
+ c.resources.s2;
+ c.resources.s3;
+ }
+
+ #[task(binds = UART1, resources = [s2, &o5])]
+ fn uart1(c: uart1::Context) {
+ c.resources.s2;
+ c.resources.o5;
+ }
+}
diff --git a/ui/single/resources-cfg.stderr b/ui/single/resources-cfg.stderr
new file mode 100644
index 00000000..17f08d81
--- /dev/null
+++ b/ui/single/resources-cfg.stderr
@@ -0,0 +1,125 @@
+error: duplicate lang item in crate `panic_halt` (which `$CRATE` depends on): `panic_impl`.
+ |
+ = note: the lang item is first defined in crate `std` (which `$CRATE` depends on)
+ = note: first definition in `std` loaded from /usr/share/rust/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-cf0f33af3a901778.rlib
+ = note: second definition in `panic_halt` loaded from $DIR/target/tests/target/x86_64-unknown-linux-gnu/debug/deps/libpanic_halt-ba6f0ab3439cbc7e.rmeta
+
+error[E0609]: no field `o1` on type `initResources<'_>`
+ --> $DIR/resources-cfg.rs:47:21
+ |
+47 | c.resources.o1;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o4` on type `initResources<'_>`
+ --> $DIR/resources-cfg.rs:48:21
+ |
+48 | c.resources.o4;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o5` on type `initResources<'_>`
+ --> $DIR/resources-cfg.rs:49:21
+ |
+49 | c.resources.o5;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o6` on type `initResources<'_>`
+ --> $DIR/resources-cfg.rs:50:21
+ |
+50 | c.resources.o6;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s3` on type `initResources<'_>`
+ --> $DIR/resources-cfg.rs:51:21
+ |
+51 | c.resources.s3;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o2` on type `idleResources<'_>`
+ --> $DIR/resources-cfg.rs:58:21
+ |
+58 | c.resources.o2;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o4` on type `idleResources<'_>`
+ --> $DIR/resources-cfg.rs:59:21
+ |
+59 | c.resources.o4;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s1` on type `idleResources<'_>`
+ --> $DIR/resources-cfg.rs:60:21
+ |
+60 | c.resources.s1;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s3` on type `idleResources<'_>`
+ --> $DIR/resources-cfg.rs:61:21
+ |
+61 | c.resources.s3;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o3` on type `uart0Resources<'_>`
+ --> $DIR/resources-cfg.rs:68:21
+ |
+68 | c.resources.o3;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s1` on type `uart0Resources<'_>`
+ --> $DIR/resources-cfg.rs:69:21
+ |
+69 | c.resources.s1;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s2` on type `uart0Resources<'_>`
+ --> $DIR/resources-cfg.rs:70:21
+ |
+70 | c.resources.s2;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s3` on type `uart0Resources<'_>`
+ --> $DIR/resources-cfg.rs:71:21
+ |
+71 | c.resources.s3;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `s2` on type `uart1Resources<'_>`
+ --> $DIR/resources-cfg.rs:76:21
+ |
+76 | c.resources.s2;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
+
+error[E0609]: no field `o5` on type `uart1Resources<'_>`
+ --> $DIR/resources-cfg.rs:77:21
+ |
+77 | c.resources.o5;
+ | ^^ unknown field
+ |
+ = note: available fields are: `__marker__`
diff --git a/ui/single/task-priority-too-high.rs b/ui/single/task-priority-too-high.rs
new file mode 100644
index 00000000..caa7b8ee
--- /dev/null
+++ b/ui/single/task-priority-too-high.rs
@@ -0,0 +1,38 @@
+#![no_main]
+
+#[rtic::app(device = lm3s6965)]
+mod app {
+ #[init]
+ fn init(_: init::Context) -> init::LateResources {
+ init::LateResources {}
+ }
+
+ #[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/single/task-priority-too-high.stderr b/ui/single/task-priority-too-high.stderr
new file mode 100644
index 00000000..e84ddd3c
--- /dev/null
+++ b/ui/single/task-priority-too-high.stderr
@@ -0,0 +1,7 @@
+error[E0080]: evaluation of constant value failed
+ --> $DIR/task-priority-too-high.rs:3:1
+ |
+3 | #[rtic::app(device = lm3s6965)]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to compute `8_usize - 9_usize` which would overflow
+ |
+ = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)