aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cargo/config28
-rw-r--r--.gdbinit6
-rw-r--r--.github/bors.toml1
-rw-r--r--.gitignore4
-rw-r--r--.travis.yml30
-rw-r--r--Cargo.toml63
-rw-r--r--LICENSE-CC-BY-SA428
-rw-r--r--LICENSE-MIT2
-rw-r--r--README.md101
-rw-r--r--book/book.toml5
-rw-r--r--book/src/SUMMARY.md16
-rw-r--r--book/src/by-example.md16
-rw-r--r--book/src/by-example/app.md105
-rw-r--r--book/src/by-example/new.md67
-rw-r--r--book/src/by-example/resources.md119
-rw-r--r--book/src/by-example/singletons.md26
-rw-r--r--book/src/by-example/tasks.md63
-rw-r--r--book/src/by-example/timer-queue.md89
-rw-r--r--book/src/by-example/tips.md43
-rw-r--r--book/src/by-example/types-send-sync.md60
-rw-r--r--book/src/internals.md6
-rw-r--r--book/src/internals/ceilings.md3
-rw-r--r--book/src/internals/tasks.md3
-rw-r--r--book/src/internals/timer-queue.md3
-rw-r--r--book/src/preface.md12
-rw-r--r--build.rs4
-rw-r--r--ci/after-success.sh17
-rw-r--r--ci/expected/baseline.run4
-rw-r--r--ci/expected/capacity.run5
-rw-r--r--ci/expected/idle.run2
-rw-r--r--ci/expected/init.run1
-rw-r--r--ci/expected/interrupt.run4
-rw-r--r--ci/expected/late.run1
-rw-r--r--ci/expected/lock.run5
-rw-r--r--ci/expected/message.run6
-rw-r--r--ci/expected/not-send.run0
-rw-r--r--ci/expected/not-sync.run0
-rw-r--r--ci/expected/periodic.run3
-rw-r--r--ci/expected/ramfunc.grep.bar3
-rw-r--r--ci/expected/ramfunc.grep.foo3
-rw-r--r--ci/expected/ramfunc.run1
-rw-r--r--ci/expected/resource.run2
-rw-r--r--ci/expected/schedule.run3
-rw-r--r--ci/expected/singleton.run2
-rw-r--r--ci/expected/static.run2
-rw-r--r--ci/expected/task.run3
-rw-r--r--ci/expected/types.run0
-rw-r--r--ci/install.sh10
-rw-r--r--ci/script.sh96
-rw-r--r--examples/baseline.rs61
-rw-r--r--examples/capacity.rs57
-rw-r--r--examples/custom-type.rs49
-rw-r--r--examples/full-syntax.rs83
-rw-r--r--examples/generics.rs73
-rw-r--r--examples/idle.rs43
-rw-r--r--examples/init.rs44
-rw-r--r--examples/interrupt.rs61
-rw-r--r--examples/late-resources.rs86
-rw-r--r--examples/late.rs65
-rw-r--r--examples/lock.rs71
-rw-r--r--examples/message.rs61
-rw-r--r--examples/nested.rs128
-rw-r--r--examples/not-send.rs58
-rw-r--r--examples/not-sync.rs41
-rw-r--r--examples/one-task.rs96
-rw-r--r--examples/periodic.rs43
-rw-r--r--examples/preemption.rs67
-rw-r--r--examples/ramfunc.rs53
-rw-r--r--examples/resource.rs60
-rw-r--r--examples/safe-static-mut-ref.rs31
-rw-r--r--examples/schedule.rs51
-rw-r--r--examples/singleton.rs69
-rw-r--r--examples/smallest.rs17
-rw-r--r--examples/static.rs47
-rw-r--r--examples/task.rs61
-rw-r--r--examples/two-tasks.rs58
-rw-r--r--examples/types.rs54
-rw-r--r--examples/zero-tasks.rs43
-rw-r--r--gen-examples.sh57
-rw-r--r--macros/Cargo.toml30
-rw-r--r--macros/src/analyze.rs262
-rw-r--r--macros/src/check.rs182
-rw-r--r--macros/src/codegen.rs1815
-rw-r--r--macros/src/lib.rs367
-rw-r--r--macros/src/syntax.rs1235
-rw-r--r--macros/src/trans.rs631
-rw-r--r--memory.x6
-rw-r--r--src/examples/_0_zero_tasks.rs47
-rw-r--r--src/examples/_1_one_task.rs100
-rw-r--r--src/examples/_2_two_tasks.rs62
-rw-r--r--src/examples/_3_preemption.rs71
-rw-r--r--src/examples/_4_nested.rs132
-rw-r--r--src/examples/_5_late_resources.rs90
-rw-r--r--src/examples/_6_safe_static_mut_ref.rs35
-rw-r--r--src/examples/_7_generics.rs77
-rw-r--r--src/examples/_8_full_syntax.rs87
-rw-r--r--src/examples/mod.rs11
-rw-r--r--src/export.rs84
-rw-r--r--src/lib.rs436
-rw-r--r--src/tq.rs135
-rw-r--r--tests/cfail.rs17
-rw-r--r--tests/cfail/critical-section.rs47
-rw-r--r--tests/cfail/duplicated-task.rs27
-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/exception.rs26
-rw-r--r--tests/cfail/idle-input.rs19
-rw-r--r--tests/cfail/idle-not-divergent.rs19
-rw-r--r--tests/cfail/idle.rs17
-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/init-resource-share-idle.rs30
-rw-r--r--tests/cfail/init-resource-share-task.rs35
-rw-r--r--tests/cfail/init.rs19
-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/interrupt.rs26
-rw-r--r--tests/cfail/late-assigned-to-init.rs16
-rw-r--r--tests/cfail/late-not-send.rs31
-rw-r--r--tests/cfail/late-resource-init.rs49
-rw-r--r--tests/cfail/late-uninit.rs16
-rw-r--r--tests/cfail/lock.rs67
-rw-r--r--tests/cfail/needs-send.rs30
-rw-r--r--tests/cfail/needs-sync.rs36
-rw-r--r--tests/cfail/peripheral-alias.rs27
-rw-r--r--tests/cfail/priority-too-high.rs30
-rw-r--r--tests/cfail/priority-too-low.rs30
-rw-r--r--tests/cfail/resource-alias.rs31
-rw-r--r--tests/cfail/resource-not-declared.rs14
-rw-r--r--tests/cfail/resource-not-send-sync.rs53
-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/token-outlive.rs45
-rw-r--r--tests/cfail/token-transfer.rs36
-rw-r--r--tests/cfail/used-free-interrupt.rs21
-rw-r--r--tests/cfail/wrong-threshold.rs47
-rw-r--r--tests/compiletest.rs59
-rw-r--r--tests/cpass/late-not-send.rs33
-rw-r--r--tests/cpass/late-resource.rs22
-rw-r--r--tests/cpass/peripheral.rs19
-rw-r--r--tests/cpass/resource.rs80
-rw-r--r--tests/cpass/schedule.rs59
-rw-r--r--tests/cpass/singleton.rs67
-rw-r--r--tests/cpass/spawn.rs60
-rw-r--r--tests/cpass/unsafe.rs46
154 files changed, 7529 insertions, 3267 deletions
diff --git a/.cargo/config b/.cargo/config
index 36061ee4..2683610d 100644
--- a/.cargo/config
+++ b/.cargo/config
@@ -1,31 +1,13 @@
[target.thumbv6m-none-eabi]
-runner = 'arm-none-eabi-gdb'
-rustflags = [
- "-C", "link-arg=-Tlink.x",
- "-C", "linker=true",
- "-Z", "linker-flavor=ld",
-]
+runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
[target.thumbv7m-none-eabi]
-runner = 'arm-none-eabi-gdb'
-rustflags = [
- "-C", "link-arg=-Tlink.x",
- "-C", "linker=arm-none-eabi-ld",
- "-Z", "linker-flavor=ld",
-]
+runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
-[target.thumbv7em-none-eabi]
-runner = 'arm-none-eabi-gdb'
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
"-C", "link-arg=-Tlink.x",
- "-C", "linker=arm-none-eabi-ld",
- "-Z", "linker-flavor=ld",
]
-[target.thumbv7em-none-eabihf]
-runner = 'arm-none-eabi-gdb'
-rustflags = [
- "-C", "link-arg=-Tlink.x",
- "-C", "linker=arm-none-eabi-ld",
- "-Z", "linker-flavor=ld",
-]
+[build]
+target = "thumbv7m-none-eabi" \ No newline at end of file
diff --git a/.gdbinit b/.gdbinit
deleted file mode 100644
index 7c72d4fb..00000000
--- a/.gdbinit
+++ /dev/null
@@ -1,6 +0,0 @@
-target remote :3333
-
-monitor arm semihosting enable
-
-load
-step
diff --git a/.github/bors.toml b/.github/bors.toml
index 5ccee21e..39ad6042 100644
--- a/.github/bors.toml
+++ b/.github/bors.toml
@@ -1,3 +1,4 @@
+delete_merged_branches = true
status = [
"continuous-integration/travis-ci/push",
] \ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 29204d65..6d34a8d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
**/*.rs.bk
-*.org
.#*
.gdb_history
+/book/book
+/target
Cargo.lock
-target/
diff --git a/.travis.yml b/.travis.yml
index 1d6f3abb..09edbfe4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,38 +2,48 @@ language: rust
matrix:
include:
+ # NOTE used to build docs on successful merges to master
- env: TARGET=x86_64-unknown-linux-gnu
- rust: nightly
+ rust: beta
- env: TARGET=thumbv6m-none-eabi
- rust: nightly
- if: branch != master
+ rust: beta
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
- env: TARGET=thumbv7m-none-eabi
+ rust: beta
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
+
+ - env: TARGET=x86_64-unknown-linux-gnu
rust: nightly
- if: branch != master
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
- - env: TARGET=thumbv7em-none-eabi
+ - env: TARGET=thumbv6m-none-eabi
rust: nightly
- if: branch != master
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
- - env: TARGET=thumbv7em-none-eabihf
+ - env: TARGET=thumbv7m-none-eabi
rust: nightly
- if: branch != master
+ if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master)
before_install: set -e
install:
- bash ci/install.sh
- - export PATH="$PATH:$PWD/gcc/bin"
+ - export PATH="$PATH:$PWD/qemu"
script:
- bash ci/script.sh
+after_script: set +e
+
after_success:
- bash ci/after-success.sh
-after_script: set +e
+cache: cache
+
+before_cache:
+ - chmod -R a+r $HOME/.cargo;
branches:
only:
diff --git a/Cargo.toml b/Cargo.toml
index d505f209..765fef1c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,33 +4,62 @@ authors = [
"Per Lindgren <per.lindgren@ltu.se>",
]
categories = ["concurrency", "embedded", "no-std"]
-description = "Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers"
-documentation = "https://japaric.github.io/cortex-m-rtfm/cortex_m_rtfm/"
+description = "Real Time For the Masses (RTFM): a concurrency framework for building real time systems"
+documentation = "https://japaric.github.io/cortex-m-rtfm/book/"
+edition = "2018"
keywords = ["arm", "cortex-m"]
license = "MIT OR Apache-2.0"
name = "cortex-m-rtfm"
+readme = "README.md"
repository = "https://github.com/japaric/cortex-m-rtfm"
-version = "0.3.4"
+version = "0.4.0-beta.1"
-[dependencies]
-cortex-m = "0.4.0"
-cortex-m-rtfm-macros = { path = "macros", version = "0.3.2" }
-rtfm-core = "0.2.0"
-untagged-option = "0.1.1"
+[lib]
+name = "rtfm"
+
+[[example]]
+name = "baseline"
+required-features = ["timer-queue"]
+
+[[example]]
+name = "periodic"
+required-features = ["timer-queue"]
+
+[[example]]
+name = "schedule"
+required-features = ["timer-queue"]
-[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
-compiletest_rs = "0.3.5"
+[[example]]
+name = "types"
+required-features = ["timer-queue"]
-[dev-dependencies.cortex-m-rt]
-features = ["abort-on-panic"]
-version = "0.3.9"
+[dependencies]
+cortex-m = "0.5.8"
+cortex-m-rt = "0.6.5"
+cortex-m-rtfm-macros = { path = "macros", version = "0.4.0-beta.1" }
+heapless = "0.4.0"
+owned-singleton = "0.1.0"
+
+[dev-dependencies]
+alloc-singleton = "0.1.0"
+cortex-m-semihosting = "0.3.1"
+lm3s6965 = "0.1.3"
+panic-halt = "0.2.0"
-[dev-dependencies.stm32f103xx]
-features = ["rt"]
-version = "0.8.0"
+[dev-dependencies.panic-semihosting]
+features = ["exit"]
+version = "0.5.1"
[features]
-cm7-r0p1 = ["cortex-m/cm7-r0p1"]
+timer-queue = ["cortex-m-rtfm-macros/timer-queue"]
+
+[target.x86_64-unknown-linux-gnu.dev-dependencies]
+compiletest_rs = "0.3.16"
+tempdir = "0.3.7"
[profile.release]
+codegen-units = 1
lto = true
+
+[workspace]
+members = ["macros"] \ No newline at end of file
diff --git a/LICENSE-CC-BY-SA b/LICENSE-CC-BY-SA
new file mode 100644
index 00000000..a73481c4
--- /dev/null
+++ b/LICENSE-CC-BY-SA
@@ -0,0 +1,428 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+
+ including for purposes of Section 3(b); and
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/LICENSE-MIT b/LICENSE-MIT
index a128ba40..52cb453f 100644
--- a/LICENSE-MIT
+++ b/LICENSE-MIT
@@ -1,4 +1,4 @@
-Copyright (c) 2017 Jorge Aparicio
+Copyright (c) 2017-2018 Jorge Aparicio
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 335440b2..c04be8bb 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,104 @@
-[![crates.io](https://img.shields.io/crates/v/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm)
-[![crates.io](https://img.shields.io/crates/d/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm)
+# Real Time For the Masses
-# `cortex-m-rtfm`
+A concurrency framework for building real time systems.
-> Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
+**IMPORTANT** This crate is currently in pre-release (beta) state . We reserve
+the right to make breaking changes in the syntax or to patch memory safety holes
+before the v0.4.0 release, which is planned for 2018-12-07. When v0.4.0 is
+released *all the pre-releases will be yanked*. If you run into a panic message
+or an unhelpful error message (e.g. misleading span), or if something doesn't
+behave the way you expect please open [an issue]!
-# [Documentation](https://japaric.github.io/cortex-m-rtfm/cortex_m_rtfm/)
+[an issue]: https://github.com/japaric/cortex-m-rtfm/issues
-# License
+## Features
-Licensed under either of
+- **Tasks** as the unit of concurrency [^1]. Tasks can be *event triggered*
+ (fired in response to asynchronous stimuli) or spawned by the application on
+ demand.
+
+- **Message passing** between tasks. Specifically, messages can be passed to
+ software tasks at spawn time.
+
+- **A timer queue** [^2]. Software tasks can be scheduled to run at some time
+ in the future. This feature can be used to implement periodic tasks.
+
+- Support for prioritization of tasks and, thus, **preemptive multitasking**.
+
+- **Efficient and data race free memory sharing** through fine grained *priority
+ based* critical sections [^1].
+
+- **Deadlock free execution** guaranteed at compile time. This is an stronger
+ guarantee than what's provided by [the standard `Mutex`
+ abstraction][std-mutex].
+
+[std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
+
+- **Minimal scheduling overhead**. The task scheduler has minimal software
+ footprint; the hardware does the bulk of the scheduling.
+
+- **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 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
+ friendly tooling for that.)
+
+## Requirements
+
+- Rust 1.31.0+
+
+- Applications must be written using the 2018 edition.
+
+## [User documentation](https://japaric.github.io/cortex-m-rtfm/book/index.html)
+
+## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html)
+
+## Acknowledgments
+
+This crate is based on [the RTFM language][rtfm-lang] created by the Embedded
+Systems group at [Luleå University of Technology][ltu], led by [Prof. Per
+Lindgren][per].
+
+[rtfm-lang]: http://www.rtfm-lang.org/
+[ltu]: https://www.ltu.se/?l=en
+[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
+
+## References
+
+[^1]: Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
+ (2013, June). Real-time for the masses, step 1: Programming API and static
+ priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013
+ 8th IEEE International Symposium on (pp. 110-113). IEEE.
+
+[^2]: Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho,
+ L. M. (2016). Abstract timers and their implementation onto the arm cortex-m
+ family of mcus. ACM SIGBED Review, 13(1), 48-53.
+
+## License
+
+All source code (including code snippets) is licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
- http://www.apache.org/licenses/LICENSE-2.0)
+ [https://www.apache.org/licenses/LICENSE-2.0][L1])
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or
+ [https://opensource.org/licenses/MIT][L2])
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+[L1]: https://www.apache.org/licenses/LICENSE-2.0
+[L2]: https://opensource.org/licenses/MIT
at your option.
-## Contribution
+The written prose contained within the book is licensed under the terms of the
+Creative Commons CC-BY-SA v4.0 license ([LICENSE-CC-BY-SA](LICENSE-CC-BY-SA) or
+[https://creativecommons.org/licenses/by-sa/4.0/legalcode][L3]).
+
+[L3]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
+
+### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
-dual licensed as above, without any additional terms or conditions.
+licensed as above, without any additional terms or conditions.
diff --git a/book/book.toml b/book/book.toml
new file mode 100644
index 00000000..c611ce07
--- /dev/null
+++ b/book/book.toml
@@ -0,0 +1,5 @@
+[book]
+authors = ["Jorge Aparicio"]
+multilingual = false
+src = "src"
+title = "Real Time For the Masses"
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
new file mode 100644
index 00000000..051d1acc
--- /dev/null
+++ b/book/src/SUMMARY.md
@@ -0,0 +1,16 @@
+# Summary
+
+[Preface](./preface.md)
+- [RTFM by example](./by-example.md)
+ - [The `app` attribute](./by-example/app.md)
+ - [Resources](./by-example/resources.md)
+ - [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)
+- [Under the hood](./internals.md)
+ - [Ceiling analysis](./internals/ceilings.md)
+ - [Task dispatcher](./internals/tasks.md)
+ - [Timer queue](./internals/timer-queue.md)
diff --git a/book/src/by-example.md b/book/src/by-example.md
new file mode 100644
index 00000000..0e09b03f
--- /dev/null
+++ b/book/src/by-example.md
@@ -0,0 +1,16 @@
+# RTFM by example
+
+This part of the book introduces the Real Time For the Masses (RTFM) 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
+
+To run the examples on your laptop / PC 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.
+
+[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html
diff --git a/book/src/by-example/app.md b/book/src/by-example/app.md
new file mode 100644
index 00000000..ae0f4b87
--- /dev/null
+++ b/book/src/by-example/app.md
@@ -0,0 +1,105 @@
+# The `app` attribute
+
+This is the smallest possible RTFM 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
+a mandatory `device` argument that takes a *path* as a value. This path must
+point to a *device* crate 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.
+
+[`app`]: ../../api/cortex_m_rtfm_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]`).
+
+## `init`
+
+Within the pseudo-module the `app` attribute expects to find an initialization
+function marked with the `init` attribute. This function must have signature
+`[unsafe] fn()`.
+
+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.
+
+`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
+
+The example below shows the types of the `core` and `device` variables and
+showcases safe access to a `static mut` variable.
+
+``` rust
+{{#include ../../../examples/init.rs}}
+```
+
+Running the example will print `init` to the console and then exit the QEMU
+process.
+
+``` console
+$ cargo run --example init
+{{#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() - > !`.
+
+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.
+
+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
+
+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`.
+
+``` rust
+{{#include ../../../examples/idle.rs}}
+```
+
+``` console
+$ cargo run --example idle
+{{#include ../../../ci/expected/idle.run}}```
+
+## `interrupt` / `exception`
+
+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.
+
+``` rust
+{{#include ../../../examples/interrupt.rs}}
+```
+
+``` console
+$ cargo run --example interrupt
+{{#include ../../../ci/expected/interrupt.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.
diff --git a/book/src/by-example/new.md b/book/src/by-example/new.md
new file mode 100644
index 00000000..b6c4643c
--- /dev/null
+++ b/book/src/by-example/new.md
@@ -0,0 +1,67 @@
+# Starting a new project
+
+Now that you have learned about the main features of the RTFM framework you can
+try it out on your hardware by following these instructions.
+
+1. Instantiate the [`cortex-m-quickstart`] template.
+
+[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart#cortex-m-quickstart
+
+``` console
+$ # for example using `cargo-generate`
+$ cargo generate \
+ --git https://github.com/rust-embedded/cortex-m-quickstart \
+ --name app
+
+$ # follow the rest of the instructions
+```
+
+2. Add a device crate that was generated using [`svd2rust`] **v0.14.x**, or a
+ board support crate that depends on one such device crate as a dependency.
+ Make sure that the `rt` feature of the crate is enabled.
+
+[`svd2rust`]: https://crates.io/crates/svd2rust
+
+In this example, I'll use the [`lm3s6965`] device crate. This device crate
+doesn't have an `rt` Cargo feature; that feature is always enabled.
+
+[`lm3s6965`]: https://crates.io/crates/lm3s6965
+
+This device crate provides a linker script with the memory layout of the target
+device so `memory.x` and `build.rs` need to be removed.
+
+``` console
+$ 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.
+
+``` console
+$ cargo add cortex-m-rtfm --allow-prerelease --upgrade=none
+```
+
+4. Write your RTFM application.
+
+Here I'll use the `init` example from the `cortex-m-rtfm` crate.
+
+``` console
+$ curl \
+ -L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0-beta.1/examples/init.rs \
+ > src/main.rs
+```
+
+That example depends on the `panic-semihosting` crate:
+
+``` console
+$ cargo add panic-semihosting
+```
+
+5. Build it, flash it and run it.
+
+``` console
+$ # NOTE: I have uncommented the `runner` option in `.cargo/config`
+$ cargo run
+{{#include ../../../ci/expected/init.run}}```
diff --git a/book/src/by-example/resources.md b/book/src/by-example/resources.md
new file mode 100644
index 00000000..93332f02
--- /dev/null
+++ b/book/src/by-example/resources.md
@@ -0,0 +1,119 @@
+## 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.
+
+``` rust
+{{#include ../../../examples/resource.rs}}
+```
+
+``` console
+$ cargo run --example resource
+{{#include ../../../ci/expected/resource.run}}```
+
+## Priorities
+
+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.
+
+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.
+
+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.
+
+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.
+
+[`Mutex`]: ../../api/rtfm/trait.Mutex.html
+
+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
+
+``` rust
+{{#include ../../../examples/lock.rs}}
+```
+
+``` console
+$ cargo run --example lock
+{{#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 declared like normal resources but that are given an initial
+value of `()` (the unit value). Late resources must be initialized at the end of
+the `init` function using plain assignments (e.g. `FOO = 1`).
+
+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
+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
+
+``` rust
+{{#include ../../../examples/late.rs}}
+```
+
+``` console
+$ cargo run --example late
+{{#include ../../../ci/expected/late.run}}```
+
+## `static` resources
+
+`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.
+
+In the example below a key is loaded (or created) at runtime and then used from
+two tasks that run at different priorities.
+
+``` rust
+{{#include ../../../examples/static.rs}}
+```
+
+``` console
+$ cargo run --example static
+{{#include ../../../ci/expected/static.run}}```
diff --git a/book/src/by-example/singletons.md b/book/src/by-example/singletons.md
new file mode 100644
index 00000000..d83cf1c2
--- /dev/null
+++ b/book/src/by-example/singletons.md
@@ -0,0 +1,26 @@
+# 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/src/by-example/tasks.md b/book/src/by-example/tasks.md
new file mode 100644
index 00000000..0b51d8b9
--- /dev/null
+++ b/book/src/by-example/tasks.md
@@ -0,0 +1,63 @@
+# 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.).
+
+The example below showcases three software tasks that run at 2 different
+priorities. The three tasks map to 2 interrupts handlers.
+
+``` rust
+{{#include ../../../examples/task.rs}}
+```
+
+``` console
+$ cargo run --example task
+{{#include ../../../ci/expected/task.run}}```
+
+## Message passing
+
+The other advantage of software tasks is that messages can be passed to these
+tasks when spawning them. The type of the message payload must be specified in
+the signature of the task handler.
+
+The example below showcases three tasks, two of them expect a message.
+
+``` rust
+{{#include ../../../examples/message.rs}}
+```
+
+``` console
+$ cargo run --example message
+{{#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 `capacicy` argument of the `task` attribute.
+
+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.
+
+``` rust
+{{#include ../../../examples/capacity.rs}}
+```
+
+``` console
+$ cargo run --example capacity
+{{#include ../../../ci/expected/capacity.run}}```
diff --git a/book/src/by-example/timer-queue.md b/book/src/by-example/timer-queue.md
new file mode 100644
index 00000000..a9c3622d
--- /dev/null
+++ b/book/src/by-example/timer-queue.md
@@ -0,0 +1,89 @@
+# 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 some
+time in the future.
+
+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
+
+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.
+
+> **IMPORTANT**: The examples that use the `schedule` API or the `Instant`
+> abstraction will **not** properly work on QEMU because the Cortex-M cycle
+> counter functionality has not been implemented in `qemu-system-arm`.
+
+``` rust
+{{#include ../../../examples/schedule.rs}}
+```
+
+Running the program on real hardware produces the following output in the console:
+
+``` text
+{{#include ../../../ci/expected/schedule.run}}
+```
+
+## Periodic tasks
+
+Software tasks have access to the `Instant` at which they were scheduled to run
+through the `scheduled` variable. This information and the `schedule` API can be
+used to implement periodic tasks as shown in the example below.
+
+``` rust
+{{#include ../../../examples/periodic.rs}}
+```
+
+This is the output produced by the example. Note that there is zero drift /
+jitter even though `schedule.foo` was invoked at the *end* of `foo`. Using
+`Instant::now` instead of `scheduled` would have resulted in drift / jitter.
+
+``` text
+{{#include ../../../ci/expected/periodic.run}}
+```
+
+## Baseline
+
+For the tasks scheduled from `init` we have exact information about their
+`scheduled` time. For hardware tasks there's no `scheduled` time because these
+tasks are asynchronous in nature. For hardware task the runtime provides a
+`start` time, which indicates the time at which the task handler started
+executing.
+
+Note that `start` is **not** equal to the arrival time of the event that fired
+the task. Depending on the priority of the task and the load of the system the
+`start` time could be very far off from the event arrival time.
+
+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.
+
+The example below showcases the different meanings of the *baseline*.
+
+``` rust
+{{#include ../../../examples/baseline.rs}}
+```
+
+Running the program on real hardware produces the following output in the console:
+
+``` text
+{{#include ../../../ci/expected/baseline.run}}
+```
diff --git a/book/src/by-example/tips.md b/book/src/by-example/tips.md
new file mode 100644
index 00000000..0e3d47b7
--- /dev/null
+++ b/book/src/by-example/tips.md
@@ -0,0 +1,43 @@
+# Tips & tricks
+
+## 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
+`link_section` attribute can be applied to tasks to place them in RAM; this can
+improve performance in some cases.
+
+> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle`
+> attributes are very powerful but also easy to misuse. Incorrectly using any of
+> these attributes can cause undefined behavior; you should always prefer to use
+> safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and
+> `exception` attributes.
+>
+> In the particular case of RAM functions there's no
+> safe abstraction for it in `cortex-m-rt` v0.6.5 but there's an [RFC] for
+> adding a `ramfunc` attribute in a future release.
+
+[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100
+
+The example below shows how to place the higher priority task, `bar`, in RAM.
+
+``` rust
+{{#include ../../../examples/ramfunc.rs}}
+```
+
+Running this program produces the expected output.
+
+``` console
+$ cargo run --example ramfunc
+{{#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}}```
+
+``` console
+$ cargo nm --example ramfunc --release | grep ' bar::'
+{{#include ../../../ci/expected/ramfunc.grep.bar}}```
diff --git a/book/src/by-example/types-send-sync.md b/book/src/by-example/types-send-sync.md
new file mode 100644
index 00000000..6433060a
--- /dev/null
+++ b/book/src/by-example/types-send-sync.md
@@ -0,0 +1,60 @@
+# 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.
+
+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.
+
+The example below shows the different types generates by the `app` attribute.
+
+``` rust
+{{#include ../../../examples/types.rs}}
+```
+
+## `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
+`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
+resources.
+
+[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
+
+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.
+
+The example below shows where a type that doesn't implement `Send` can be used.
+
+``` rust
+{{#include ../../../examples/not-send.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.
+
+[`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.
+
+The example below shows where a type that doesn't implement `Sync` can be used.
+
+``` rust
+{{#include ../../../examples/not-sync.rs}}
+```
diff --git a/book/src/internals.md b/book/src/internals.md
new file mode 100644
index 00000000..0ef55e62
--- /dev/null
+++ b/book/src/internals.md
@@ -0,0 +1,6 @@
+# Under the hood
+
+This section describes the internals of the RTFM 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.
diff --git a/book/src/internals/ceilings.md b/book/src/internals/ceilings.md
new file mode 100644
index 00000000..2c645a4d
--- /dev/null
+++ b/book/src/internals/ceilings.md
@@ -0,0 +1,3 @@
+# Ceiling analysis
+
+**TODO**
diff --git a/book/src/internals/tasks.md b/book/src/internals/tasks.md
new file mode 100644
index 00000000..85f783fb
--- /dev/null
+++ b/book/src/internals/tasks.md
@@ -0,0 +1,3 @@
+# Task dispatcher
+
+**TODO**
diff --git a/book/src/internals/timer-queue.md b/book/src/internals/timer-queue.md
new file mode 100644
index 00000000..70592852
--- /dev/null
+++ b/book/src/internals/timer-queue.md
@@ -0,0 +1,3 @@
+# Timer queue
+
+**TODO**
diff --git a/book/src/preface.md b/book/src/preface.md
new file mode 100644
index 00000000..c041ef5f
--- /dev/null
+++ b/book/src/preface.md
@@ -0,0 +1,12 @@
+<h1 align="center">Real Time For the Masses</h1>
+
+<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).
+
+{{#include ../../README.md:5:53}}
+
+{{#include ../../README.md:59:}}
diff --git a/build.rs b/build.rs
index ebfee06c..b29f0bd6 100644
--- a/build.rs
+++ b/build.rs
@@ -3,8 +3,8 @@ use std::env;
fn main() {
let target = env::var("TARGET").unwrap();
- if target.starts_with("thumbv6m-") {
- println!("cargo:rustc-cfg=armv6m");
+ if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") {
+ println!("cargo:rustc-cfg=armv7m")
}
println!("cargo:rerun-if-changed=build.rs");
diff --git a/ci/after-success.sh b/ci/after-success.sh
index 43772674..f9f25226 100644
--- a/ci/after-success.sh
+++ b/ci/after-success.sh
@@ -1,20 +1,27 @@
set -euxo pipefail
main() {
- cargo doc
+ rm -f .cargo/config
+ cargo doc --features timer-queue
+ ( cd book && mdbook build )
- mkdir ghp-import
+ local td=$(mktemp -d)
+ cp -r target/doc $td/api
+ cp -r book/book $td/
+ cp LICENSE-* $td/book/
+ mkdir ghp-import
curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz |
tar --strip-components 1 -C ghp-import -xz
- ./ghp-import/ghp_import.py target/doc
+ ./ghp-import/ghp_import.py $td
set +x
git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK
+
+ rm -rf $td
}
-# only publish on successful merges to master
-if [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && [ $TARGET = x86_64-unknown-linux-gnu ]; then
+if [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ]; then
main
fi
diff --git a/ci/expected/baseline.run b/ci/expected/baseline.run
new file mode 100644
index 00000000..fa3822f6
--- /dev/null
+++ b/ci/expected/baseline.run
@@ -0,0 +1,4 @@
+init(baseline = Instant(0))
+foo(baseline = Instant(0))
+UART0(baseline = Instant(904))
+foo(baseline = Instant(904)) \ No newline at end of file
diff --git a/ci/expected/capacity.run b/ci/expected/capacity.run
new file mode 100644
index 00000000..f96815de
--- /dev/null
+++ b/ci/expected/capacity.run
@@ -0,0 +1,5 @@
+foo(0)
+foo(1)
+foo(2)
+foo(3)
+bar
diff --git a/ci/expected/idle.run b/ci/expected/idle.run
new file mode 100644
index 00000000..43077763
--- /dev/null
+++ b/ci/expected/idle.run
@@ -0,0 +1,2 @@
+init
+idle
diff --git a/ci/expected/init.run b/ci/expected/init.run
new file mode 100644
index 00000000..b1b71610
--- /dev/null
+++ b/ci/expected/init.run
@@ -0,0 +1 @@
+init
diff --git a/ci/expected/interrupt.run b/ci/expected/interrupt.run
new file mode 100644
index 00000000..ef00864b
--- /dev/null
+++ b/ci/expected/interrupt.run
@@ -0,0 +1,4 @@
+init
+UART0 called 1 time
+idle
+UART0 called 2 times
diff --git a/ci/expected/late.run b/ci/expected/late.run
new file mode 100644
index 00000000..6d3d3e43
--- /dev/null
+++ b/ci/expected/late.run
@@ -0,0 +1 @@
+received message: 42
diff --git a/ci/expected/lock.run b/ci/expected/lock.run
new file mode 100644
index 00000000..156ac222
--- /dev/null
+++ b/ci/expected/lock.run
@@ -0,0 +1,5 @@
+A
+B - SHARED = 1
+C
+D - SHARED = 2
+E
diff --git a/ci/expected/message.run b/ci/expected/message.run
new file mode 100644
index 00000000..11814db2
--- /dev/null
+++ b/ci/expected/message.run
@@ -0,0 +1,6 @@
+foo
+bar(0)
+baz(1, 2)
+foo
+bar(1)
+baz(2, 3)
diff --git a/ci/expected/not-send.run b/ci/expected/not-send.run
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ci/expected/not-send.run
diff --git a/ci/expected/not-sync.run b/ci/expected/not-sync.run
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ci/expected/not-sync.run
diff --git a/ci/expected/periodic.run b/ci/expected/periodic.run
new file mode 100644
index 00000000..11414c53
--- /dev/null
+++ b/ci/expected/periodic.run
@@ -0,0 +1,3 @@
+foo(scheduled = Instant(8000000), now = Instant(8000196))
+foo(scheduled = Instant(16000000), now = Instant(16000196))
+foo(scheduled = Instant(24000000), now = Instant(24000196)) \ No newline at end of file
diff --git a/ci/expected/ramfunc.grep.bar b/ci/expected/ramfunc.grep.bar
new file mode 100644
index 00000000..7f69d25c
--- /dev/null
+++ b/ci/expected/ramfunc.grep.bar
@@ -0,0 +1,3 @@
+20000100 B bar::FREE_QUEUE::lk14244m263eivix
+200000dc B bar::INPUTS::mi89534s44r1mnj1
+20000000 T bar::ns9009yhw2dc2y25
diff --git a/ci/expected/ramfunc.grep.foo b/ci/expected/ramfunc.grep.foo
new file mode 100644
index 00000000..a076ac0a
--- /dev/null
+++ b/ci/expected/ramfunc.grep.foo
@@ -0,0 +1,3 @@
+20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20
+200000dc B foo::INPUTS::thvubs85b91dg365
+000002c6 T foo::sidaht420cg1mcm8
diff --git a/ci/expected/ramfunc.run b/ci/expected/ramfunc.run
new file mode 100644
index 00000000..257cc564
--- /dev/null
+++ b/ci/expected/ramfunc.run
@@ -0,0 +1 @@
+foo
diff --git a/ci/expected/resource.run b/ci/expected/resource.run
new file mode 100644
index 00000000..9c70856a
--- /dev/null
+++ b/ci/expected/resource.run
@@ -0,0 +1,2 @@
+UART0: SHARED = 1
+UART1: SHARED = 2
diff --git a/ci/expected/schedule.run b/ci/expected/schedule.run
new file mode 100644
index 00000000..9facc71a
--- /dev/null
+++ b/ci/expected/schedule.run
@@ -0,0 +1,3 @@
+init @ Instant(0)
+bar @ Instant(4000236)
+foo @ Instant(8000173) \ No newline at end of file
diff --git a/ci/expected/singleton.run b/ci/expected/singleton.run
new file mode 100644
index 00000000..c55dc1ac
--- /dev/null
+++ b/ci/expected/singleton.run
@@ -0,0 +1,2 @@
+bar(2)
+foo(1)
diff --git a/ci/expected/static.run b/ci/expected/static.run
new file mode 100644
index 00000000..2c295c99
--- /dev/null
+++ b/ci/expected/static.run
@@ -0,0 +1,2 @@
+UART1(KEY = 0xdeadbeef)
+UART0(KEY = 0xdeadbeef)
diff --git a/ci/expected/task.run b/ci/expected/task.run
new file mode 100644
index 00000000..309fdb99
--- /dev/null
+++ b/ci/expected/task.run
@@ -0,0 +1,3 @@
+foo
+baz
+bar
diff --git a/ci/expected/types.run b/ci/expected/types.run
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ci/expected/types.run
diff --git a/ci/install.sh b/ci/install.sh
index e63e8054..9a896b90 100644
--- a/ci/install.sh
+++ b/ci/install.sh
@@ -5,9 +5,11 @@ main() {
rustup target add $TARGET
fi
- mkdir gcc
-
- curl -L https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2?revision=bc2c96c0-14b5-4bb4-9f18-bceb4050fee7?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,7-2018-q2-update | tar --strip-components=1 -C gcc -xj
+ 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
}
-main
+if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST = true ]; then
+ main
+fi
diff --git a/ci/script.sh b/ci/script.sh
index 0d42ff3a..7502b850 100644
--- a/ci/script.sh
+++ b/ci/script.sh
@@ -1,23 +1,95 @@
set -euxo pipefail
main() {
- if [ $TARGET = x86_64-unknown-linux-gnu ]; then
- cargo build
- cargo test --test cfail
+ local T=$TARGET
+
+ if [ $T = x86_64-unknown-linux-gnu ]; then
+ # compile-fail and compile-pass tests
+ if [ $TRAVIS_RUST_VERSION = nightly ]; then
+ # TODO how to run a subset of these tests when timer-queue is disabled?
+ cargo test --features timer-queue --test compiletest --target $T
+ fi
+
+ cargo check --target $T
+ cargo check --features timer-queue --target $T
return
fi
- case $TARGET in
- thumbv7em-none-eabi*)
- cargo check --target $TARGET --features cm7-r0p1
- cargo check --target $TARGET --features cm7-r0p1 --examples
- ;;
- esac
+ cargo check --target $T --examples
+ cargo check --features timer-queue --target $T --examples
+
+ # run-pass tests
+ case $T in
+ thumbv6m-none-eabi | thumbv7m-none-eabi)
+ local exs=(
+ idle
+ init
+ interrupt
+
+ resource
+ lock
+ late
+ static
+
+ task
+ message
+ capacity
+
+ singleton
+
+ types
+ not-send
+ not-sync
+
+ ramfunc
+ )
+
+ 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 != types ]; then
+ cargo run --example $ex --target $T | \
+ diff -u ci/expected/$ex.run -
- cargo check --target $TARGET
- cargo check --target $TARGET --examples
+ cargo run --example $ex --target $T --release | \
+ diff -u ci/expected/$ex.run -
+ fi
+
+ cargo run --features timer-queue --example $ex --target $T | \
+ diff -u ci/expected/$ex.run -
+
+ cargo run --features timer-queue --example $ex --target $T --release | \
+ diff -u ci/expected/$ex.run -
+ done
+ esac
}
-if [ $TRAVIS_BRANCH != master ]; then
+# fake Travis variables to be able to run this on a local machine
+if [ -z ${TRAVIS_BRANCH-} ]; then
+ TRAVIS_BRANCH=auto
+fi
+
+if [ -z ${TRAVIS_PULL_REQUEST-} ]; then
+ TRAVIS_PULL_REQUEST=false
+fi
+
+if [ -z ${TRAVIS_RUST_VERSION-} ]; then
+ case $(rustc -V) in
+ *nightly*)
+ TRAVIS_RUST_VERSION=nightly
+ ;;
+ *beta*)
+ TRAVIS_RUST_VERSION=beta
+ ;;
+ *)
+ TRAVIS_RUST_VERSION=stable
+ ;;
+ esac
+fi
+
+if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST = true ]; then
main
fi
diff --git a/examples/baseline.rs b/examples/baseline.rs
new file mode 100644
index 00000000..73ef4c9a
--- /dev/null
+++ b/examples/baseline.rs
@@ -0,0 +1,61 @@
+//! examples/baseline.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+// NOTE: does NOT properly work on QEMU
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(spawn = [foo])]
+ fn init() {
+ println!("init(baseline = {:?})", start);
+
+ // `foo` inherits the baseline of `init`: `Instant(0)`
+ spawn.foo().unwrap();
+ }
+
+ #[task(schedule = [foo])]
+ fn foo() {
+ static mut ONCE: bool = true;
+
+ println!("foo(baseline = {:?})", scheduled);
+
+ if *ONCE {
+ *ONCE = false;
+
+ rtfm::pend(Interrupt::UART0);
+ } else {
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+ }
+
+ #[interrupt(spawn = [foo])]
+ fn UART0() {
+ println!("UART0(baseline = {:?})", start);
+
+ // `foo` inherits the baseline of `UART0`: its `start` time
+ spawn.foo().unwrap();
+ }
+
+ extern "C" {
+ fn UART1();
+ }
+};
diff --git a/examples/capacity.rs b/examples/capacity.rs
new file mode 100644
index 00000000..2dea2c30
--- /dev/null
+++ b/examples/capacity.rs
@@ -0,0 +1,57 @@
+//! examples/capacity.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(spawn = [foo])]
+ fn init() {
+ rtfm::pend(Interrupt::UART0);
+ }
+
+ #[interrupt(spawn = [foo, bar])]
+ fn UART0() {
+ spawn.foo(0).unwrap();
+ spawn.foo(1).unwrap();
+ spawn.foo(2).unwrap();
+ spawn.foo(3).unwrap();
+
+ spawn.bar().unwrap();
+ }
+
+ #[task(capacity = 4)]
+ fn foo(x: u32) {
+ println!("foo({})", x);
+ }
+
+ #[task]
+ fn bar() {
+ println!("bar");
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ // Interrupt handlers used to dispatch software tasks
+ extern "C" {
+ fn UART1();
+ }
+};
diff --git a/examples/custom-type.rs b/examples/custom-type.rs
deleted file mode 100644
index 826e9dd1..00000000
--- a/examples/custom-type.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-pub struct Foo;
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static CO_OWNED: Foo = Foo;
- static ON: Foo = Foo;
- static OWNED: Foo = Foo;
- static SHARED: Foo = Foo;
- },
-
- idle: {
- resources: [OWNED, SHARED],
- },
-
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- resources: [CO_OWNED, ON, SHARED],
- },
-
- TIM2: {
- enabled: false,
- path: tim2,
- priority: 1,
- resources: [CO_OWNED],
- },
- },
-}
-
-fn init(_p: ::init::Peripherals, _r: ::init::Resources) {}
-
-fn idle(_t: &mut Threshold, _r: ::idle::Resources) -> ! {
- loop {}
-}
-
-fn sys_tick(_t: &mut Threshold, _r: SYS_TICK::Resources) {}
-
-fn tim2(_t: &mut Threshold, _r: TIM2::Resources) {}
diff --git a/examples/full-syntax.rs b/examples/full-syntax.rs
deleted file mode 100644
index 9bdcd7b4..00000000
--- a/examples/full-syntax.rs
+++ /dev/null
@@ -1,83 +0,0 @@
-//! A showcase of the `app!` macro syntax
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static CO_OWNED: u32 = 0;
- static ON: bool = false;
- static OWNED: bool = false;
- static SHARED: bool = false;
- },
-
- init: {
- // This is the path to the `init` function
- //
- // `init` doesn't necessarily has to be in the root of the crate
- path: main::init,
- },
-
- idle: {
- // This is a path to the `idle` function
- //
- // `idle` doesn't necessarily has to be in the root of the crate
- path: main::idle,
- resources: [OWNED, SHARED],
- },
-
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- // If omitted priority is assumed to be 1
- // priority: 1,
- resources: [CO_OWNED, ON, SHARED],
- },
-
- TIM2: {
- // Tasks are enabled, between `init` and `idle`, by default but they
- // can start disabled if `false` is specified here
- enabled: false,
- path: tim2,
- priority: 1,
- resources: [CO_OWNED],
- },
- },
-}
-
-mod main {
- use rtfm::{self, Resource, Threshold};
-
- pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {}
-
- pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! {
- loop {
- *r.OWNED = !*r.OWNED;
-
- if *r.OWNED {
- if r.SHARED.claim(t, |shared, _| *shared) {
- rtfm::wfi();
- }
- } else {
- r.SHARED.claim_mut(t, |shared, _| *shared = !*shared);
- }
- }
- }
-}
-
-fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
- *r.ON = !*r.ON;
-
- *r.CO_OWNED += 1;
-}
-
-fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) {
- *r.CO_OWNED += 1;
-}
diff --git a/examples/generics.rs b/examples/generics.rs
deleted file mode 100644
index aceba1a9..00000000
--- a/examples/generics.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-//! Working with resources in a generic fashion
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-use stm32f103xx::{GPIOA, SPI1};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static GPIOA: GPIOA;
- static SPI1: SPI1;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [GPIOA, SPI1],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [GPIOA, SPI1],
- },
- },
-}
-
-fn init(p: init::Peripherals) -> init::LateResources {
- init::LateResources {
- GPIOA: p.device.GPIOA,
- SPI1: p.device.SPI1,
- }
-}
-
-fn idle() -> ! {
- loop {
- rtfm::wfi();
- }
-}
-
-// A generic function that uses some resources
-fn work<G, S>(t: &mut Threshold, gpioa: &G, spi1: &S)
-where
- G: Resource<Data = GPIOA>,
- S: Resource<Data = SPI1>,
-{
- gpioa.claim(t, |_gpioa, t| {
- // drive NSS low
-
- spi1.claim(t, |_spi1, _| {
- // transfer data
- });
-
- // drive NSS high
- });
-}
-
-// This task needs critical sections to access the resources
-fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
- work(t, &r.GPIOA, &r.SPI1);
-}
-
-// This task has direct access to the resources
-fn exti1(t: &mut Threshold, r: EXTI1::Resources) {
- work(t, &r.GPIOA, &r.SPI1);
-}
diff --git a/examples/idle.rs b/examples/idle.rs
new file mode 100644
index 00000000..013cccea
--- /dev/null
+++ b/examples/idle.rs
@@ -0,0 +1,43 @@
+//! examples/idle.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init]
+ fn init() {
+ println!("init");
+ }
+
+ #[idle]
+ fn idle() -> ! {
+ static mut X: u32 = 0;
+
+ // Safe access to local `static mut` variable
+ let _x: &'static mut u32 = X;
+
+ println!("idle");
+
+ debug::exit(debug::EXIT_SUCCESS);
+
+ loop {}
+ }
+};
diff --git a/examples/init.rs b/examples/init.rs
new file mode 100644
index 00000000..d6caa609
--- /dev/null
+++ b/examples/init.rs
@@ -0,0 +1,44 @@
+//! examples/init.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+// NOTE: This convenience macro will appear in all the other examples and
+// will always look the same
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init]
+ fn init() {
+ static mut X: u32 = 0;
+
+ // Cortex-M peripherals
+ let _core: rtfm::Peripherals = core;
+
+ // Device specific peripherals
+ let _device: lm3s6965::Peripherals = device;
+
+ // Safe access to local `static mut` variable
+ let _x: &'static mut u32 = X;
+
+ println!("init");
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+};
diff --git a/examples/interrupt.rs b/examples/interrupt.rs
new file mode 100644
index 00000000..19b1fed0
--- /dev/null
+++ b/examples/interrupt.rs
@@ -0,0 +1,61 @@
+//! examples/interrupt.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init]
+ fn init() {
+ // Pends the UART0 interrupt but its handler won't run until *after*
+ // `init` returns because interrupts are disabled
+ rtfm::pend(Interrupt::UART0);
+
+ println!("init");
+ }
+
+ #[idle]
+ fn idle() -> ! {
+ // interrupts are enabled again; the `UART0` handler runs at this point
+
+ println!("idle");
+
+ rtfm::pend(Interrupt::UART0);
+
+ debug::exit(debug::EXIT_SUCCESS);
+
+ loop {}
+ }
+
+ #[interrupt]
+ fn UART0() {
+ static mut TIMES: u32 = 0;
+
+ // Safe access to local `static mut` variable
+ *TIMES += 1;
+
+ println!(
+ "UART0 called {} time{}",
+ *TIMES,
+ if *TIMES > 1 { "s" } else { "" }
+ );
+ }
+};
diff --git a/examples/late-resources.rs b/examples/late-resources.rs
deleted file mode 100644
index 3bfc3884..00000000
--- a/examples/late-resources.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-//! Demonstrates initialization of resources in `init`.
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- // Usually, resources are initialized with a constant initializer:
- static ON: bool = false;
-
- // However, there are cases where this is not possible or not desired.
- // For example, there may not be a sensible value to use, or the type may
- // not be constructible in a constant (like `Vec`).
- //
- // While it is possible to use an `Option` in some cases, that requires
- // you to properly initialize it and `.unwrap()` it at every use. It
- // also consumes more memory.
- //
- // To solve this, it is possible to defer initialization of resources to
- // `init` by omitting the initializer. Doing that will require `init` to
- // return the values of all "late" resources.
- static IP_ADDRESS: u32;
-
- // PORT is used by 2 tasks, making it a shared resource. This just tests
- // another internal code path and is not important for the example.
- static PORT: u16;
- },
-
- idle: {
- // Test that late resources can be used in idle
- resources: [IP_ADDRESS],
- },
-
- tasks: {
- SYS_TICK: {
- priority: 1,
- path: sys_tick,
- resources: [IP_ADDRESS, PORT, ON],
- },
-
- EXTI0: {
- priority: 2,
- path: exti0,
- resources: [PORT],
- }
- }
-}
-
-// The signature of `init` is now required to have a specific return type.
-fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources {
- // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet
- // initialized.
- //_r.IP_ADDRESS; // doesn't compile
-
- // ...obtain value for IP_ADDRESS from EEPROM/DHCP...
- let ip_address = 0x7f000001;
-
- init::LateResources {
- // This struct will contain fields for all resources with omitted
- // initializers.
- IP_ADDRESS: ip_address,
- PORT: 0,
- }
-}
-
-fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
- // Other tasks can access late resources like any other, since they are
- // guaranteed to be initialized when tasks are run.
-
- r.IP_ADDRESS;
-}
-
-fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {}
-
-fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! {
- loop {
- rtfm::wfi();
- }
-}
diff --git a/examples/late.rs b/examples/late.rs
new file mode 100644
index 00000000..6d76c58a
--- /dev/null
+++ b/examples/late.rs
@@ -0,0 +1,65 @@
+//! examples/late.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use heapless::{
+ consts::*,
+ spsc::{Consumer, Producer, Queue},
+};
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ // Late resources
+ static mut P: Producer<'static, u32, U4> = ();
+ static mut C: Consumer<'static, u32, U4> = ();
+
+ #[init]
+ fn init() {
+ // NOTE: we use `Option` here to work around the lack of
+ // a stable `const` constructor
+ static mut Q: Option<Queue<u32, U4>> = None;
+
+ *Q = Some(Queue::new());
+ let (p, c) = Q.as_mut().unwrap().split();
+
+ // Initialization of late resources
+ P = p;
+ C = c;
+ }
+
+ #[idle(resources = [C])]
+ fn idle() -> ! {
+ loop {
+ if let Some(byte) = resources.C.dequeue() {
+ println!("received message: {}", byte);
+
+ debug::exit(debug::EXIT_SUCCESS);
+ } else {
+ rtfm::pend(Interrupt::UART0);
+ }
+ }
+ }
+
+ #[interrupt(resources = [P])]
+ fn UART0() {
+ resources.P.enqueue(42).unwrap();
+ }
+};
diff --git a/examples/lock.rs b/examples/lock.rs
new file mode 100644
index 00000000..097bd5c3
--- /dev/null
+++ b/examples/lock.rs
@@ -0,0 +1,71 @@
+//! examples/lock.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ static mut SHARED: u32 = 0;
+
+ #[init]
+ fn init() {
+ rtfm::pend(Interrupt::GPIOA);
+ }
+
+ // when omitted priority is assumed to be `1`
+ #[interrupt(resources = [SHARED])]
+ fn GPIOA() {
+ println!("A");
+
+ // the lower priority task requires a critical section to access the data
+ 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);
+
+ println!("B - SHARED = {}", *shared);
+
+ // GPIOC does not contend for `SHARED` so it's allowed to run now
+ rtfm::pend(Interrupt::GPIOC);
+ });
+
+ // critical section is over: GPIOB can now start
+
+ println!("E");
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[interrupt(priority = 2, resources = [SHARED])]
+ fn GPIOB() {
+ // the higher priority task does *not* need a critical section
+ *resources.SHARED += 1;
+
+ println!("D - SHARED = {}", *resources.SHARED);
+ }
+
+ #[interrupt(priority = 3)]
+ fn GPIOC() {
+ println!("C");
+ }
+};
diff --git a/examples/message.rs b/examples/message.rs
new file mode 100644
index 00000000..1ff08b22
--- /dev/null
+++ b/examples/message.rs
@@ -0,0 +1,61 @@
+//! examples/message.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(spawn = [foo])]
+ fn init() {
+ spawn.foo(/* no message */).unwrap();
+ }
+
+ #[task(spawn = [bar])]
+ fn foo() {
+ static mut COUNT: u32 = 0;
+
+ println!("foo");
+
+ spawn.bar(*COUNT).unwrap();
+ *COUNT += 1;
+ }
+
+ #[task(spawn = [baz])]
+ fn bar(x: u32) {
+ println!("bar({})", x);
+
+ spawn.baz(x + 1, x + 2).unwrap();
+ }
+
+ #[task(spawn = [foo])]
+ fn baz(x: u32, y: u32) {
+ println!("baz({}, {})", x, y);
+
+ if x + y > 4 {
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ spawn.foo().unwrap();
+ }
+
+ extern "C" {
+ fn UART0();
+ }
+};
diff --git a/examples/nested.rs b/examples/nested.rs
deleted file mode 100644
index 46c00b2b..00000000
--- a/examples/nested.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-//! Nesting claims and how the preemption threshold works
-//!
-//! If you run this program you'll hit the breakpoints as indicated by the
-//! letters in the comments: A, then B, then C, etc.
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-use stm32f103xx::Interrupt;
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static LOW: u64 = 0;
- static HIGH: u64 = 0;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [LOW, HIGH],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [LOW],
- },
-
- EXTI2: {
- path: exti2,
- priority: 3,
- resources: [HIGH],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle() -> ! {
- // A
- rtfm::bkpt();
-
- // Sets task `exti0` as pending
- //
- // Because `exti0` has higher priority than `idle` it will be executed
- // immediately
- rtfm::set_pending(Interrupt::EXTI0); // ~> exti0
-
- loop {
- rtfm::wfi();
- }
-}
-
-#[allow(non_snake_case)]
-fn exti0(
- t: &mut Threshold,
- EXTI0::Resources {
- LOW: mut low,
- HIGH: mut high,
- }: EXTI0::Resources,
-) {
- // Because this task has a priority of 1 the preemption threshold `t` also
- // starts at 1
-
- // B
- rtfm::bkpt();
-
- // Because `exti1` has higher priority than `exti0` it can preempt it
- rtfm::set_pending(Interrupt::EXTI1); // ~> exti1
-
- // A claim creates a critical section
- low.claim_mut(t, |_low, t| {
- // This claim increases the preemption threshold to 2
- //
- // 2 is just high enough to not race with task `exti1` for access to the
- // `LOW` resource
-
- // D
- rtfm::bkpt();
-
- // Now `exti1` can't preempt this task because its priority is equal to
- // the current preemption threshold
- rtfm::set_pending(Interrupt::EXTI1);
-
- // But `exti2` can, because its priority is higher than the current
- // preemption threshold
- rtfm::set_pending(Interrupt::EXTI2); // ~> exti2
-
- // F
- rtfm::bkpt();
-
- // Claims can be nested
- high.claim_mut(t, |_high, _| {
- // This claim increases the preemption threshold to 3
-
- // Now `exti2` can't preempt this task
- rtfm::set_pending(Interrupt::EXTI2);
-
- // G
- rtfm::bkpt();
- });
-
- // Upon leaving the critical section the preemption threshold drops back
- // to 2 and `exti2` immediately preempts this task
- // ~> exti2
- });
-
- // Once again the preemption threshold drops but this time to 1. Now the
- // pending `exti1` task can preempt this task
- // ~> exti1
-}
-
-fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {
- // C, I
- rtfm::bkpt();
-}
-
-fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {
- // E, H
- rtfm::bkpt();
-}
diff --git a/examples/not-send.rs b/examples/not-send.rs
new file mode 100644
index 00000000..be78c332
--- /dev/null
+++ b/examples/not-send.rs
@@ -0,0 +1,58 @@
+//! `examples/not-send.rs`
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_halt;
+
+use core::marker::PhantomData;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+pub struct NotSend {
+ _0: PhantomData<*const ()>,
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ static mut SHARED: Option<NotSend> = None;
+
+ #[init(spawn = [baz, quux])]
+ fn init() {
+ spawn.baz().unwrap();
+ spawn.quux().unwrap();
+ }
+
+ #[task(spawn = [bar])]
+ fn foo() {
+ // scenario 1: message passed to task that runs at the same priority
+ spawn.bar(NotSend { _0: PhantomData }).ok();
+ }
+
+ #[task]
+ fn bar(_x: NotSend) {
+ // scenario 1
+ }
+
+ #[task(priority = 2, resources = [SHARED])]
+ fn baz() {
+ // scenario 2: resource shared between tasks that run at the same priority
+ *resources.SHARED = Some(NotSend { _0: PhantomData });
+ }
+
+ #[task(priority = 2, resources = [SHARED])]
+ fn quux() {
+ // scenario 2
+ let _not_send = resources.SHARED.take().unwrap();
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ extern "C" {
+ fn UART0();
+ fn UART1();
+ }
+};
diff --git a/examples/not-sync.rs b/examples/not-sync.rs
new file mode 100644
index 00000000..d94e0a04
--- /dev/null
+++ b/examples/not-sync.rs
@@ -0,0 +1,41 @@
+//! `examples/not-sync.rs`
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_halt;
+
+use core::marker::PhantomData;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+pub struct NotSync {
+ _0: PhantomData<*const ()>,
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ static SHARED: NotSync = NotSync { _0: PhantomData };
+
+ #[init]
+ fn init() {
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(resources = [SHARED])]
+ fn foo() {
+ let _: &NotSync = resources.SHARED;
+ }
+
+ #[task(resources = [SHARED])]
+ fn bar() {
+ let _: &NotSync = resources.SHARED;
+ }
+
+ extern "C" {
+ fn UART0();
+ }
+};
diff --git a/examples/one-task.rs b/examples/one-task.rs
deleted file mode 100644
index dc2bfd29..00000000
--- a/examples/one-task.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-//! An application with one task
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m;
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use cortex_m::peripheral::syst::SystClkSource;
-use rtfm::{app, Threshold};
-use stm32f103xx::GPIOC;
-
-app! {
- device: stm32f103xx,
-
- // Here data resources are declared
- //
- // Data resources are static variables that are safe to share across tasks
- resources: {
- // Declaration of resources looks exactly like declaration of static
- // variables
- static ON: bool = false;
- },
-
- // Here tasks are declared
- //
- // Each task corresponds to an interrupt or an exception. Every time the
- // interrupt or exception becomes *pending* the corresponding task handler
- // will be executed.
- tasks: {
- // Here we declare that we'll use the SYS_TICK exception as a task
- SYS_TICK: {
- // Path to the task handler
- path: sys_tick,
-
- // These are the resources this task has access to.
- //
- // The resources listed here must also appear in `app.resources`
- resources: [ON],
- },
- }
-}
-
-fn init(mut p: init::Peripherals, r: init::Resources) {
- // `init` can modify all the `resources` declared in `app!`
- r.ON;
-
- // power on GPIOC
- p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled());
-
- // configure PC13 as output
- p.device.GPIOC.bsrr.write(|w| w.bs13().set());
- p.device
- .GPIOC
- .crh
- .modify(|_, w| w.mode13().output().cnf13().push());
-
- // configure the system timer to generate one interrupt every second
- p.core.SYST.set_clock_source(SystClkSource::Core);
- p.core.SYST.set_reload(8_000_000); // 1s
- p.core.SYST.enable_interrupt();
- p.core.SYST.enable_counter();
-}
-
-fn idle() -> ! {
- loop {
- rtfm::wfi();
- }
-}
-
-// This is the task handler of the SYS_TICK exception
-//
-// `_t` is the preemption threshold token. We won't use it in this program.
-//
-// `r` is the set of resources this task has access to. `SYS_TICK::Resources`
-// has one field per resource declared in `app!`.
-#[allow(unsafe_code)]
-fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
- // toggle state
- *r.ON = !*r.ON;
-
- if *r.ON {
- // set the pin PC13 high
- // NOTE(unsafe) atomic write to a stateless register
- unsafe {
- (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set());
- }
- } else {
- // set the pin PC13 low
- // NOTE(unsafe) atomic write to a stateless register
- unsafe {
- (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset());
- }
- }
-}
diff --git a/examples/periodic.rs b/examples/periodic.rs
new file mode 100644
index 00000000..0fb8bdf4
--- /dev/null
+++ b/examples/periodic.rs
@@ -0,0 +1,43 @@
+//! examples/periodic.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use rtfm::{app, Instant};
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+const PERIOD: u32 = 8_000_000;
+
+// NOTE: does NOT work on QEMU!
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(schedule = [foo])]
+ fn init() {
+ schedule.foo(Instant::now() + PERIOD.cycles()).unwrap();
+ }
+
+ #[task(schedule = [foo])]
+ fn foo() {
+ let now = Instant::now();
+ println!("foo(scheduled = {:?}, now = {:?})", scheduled, now);
+
+ schedule.foo(scheduled + PERIOD.cycles()).unwrap();
+ }
+
+ extern "C" {
+ fn UART0();
+ }
+};
diff --git a/examples/preemption.rs b/examples/preemption.rs
deleted file mode 100644
index 340b9766..00000000
--- a/examples/preemption.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-//! Two tasks running at *different* priorities with access to the same resource
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static COUNTER: u64 = 0;
- },
-
- tasks: {
- // The `SYS_TICK` task has higher priority than `TIM2`
- SYS_TICK: {
- path: sys_tick,
- priority: 2,
- resources: [COUNTER],
- },
-
- TIM2: {
- path: tim2,
- priority: 1,
- resources: [COUNTER],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {
- // ..
-}
-
-fn idle() -> ! {
- loop {
- rtfm::wfi();
- }
-}
-
-fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
- // ..
-
- // This task can't be preempted by `tim2` so it has direct access to the
- // resource data
- *r.COUNTER += 1;
-
- // ..
-}
-
-fn tim2(t: &mut Threshold, mut r: TIM2::Resources) {
- // ..
-
- // As this task runs at lower priority it needs a critical section to
- // prevent `sys_tick` from preempting it while it modifies this resource
- // data. The critical section is required to prevent data races which can
- // lead to undefined behavior.
- r.COUNTER.claim_mut(t, |counter, _t| {
- // `claim_mut` creates a critical section
- *counter += 1;
- });
-
- // ..
-}
diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs
new file mode 100644
index 00000000..b7fe2523
--- /dev/null
+++ b/examples/ramfunc.rs
@@ -0,0 +1,53 @@
+//! examples/ramfunc.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(spawn = [bar])]
+ fn init() {
+ spawn.bar().unwrap();
+ }
+
+ #[inline(never)]
+ #[task]
+ fn foo() {
+ println!("foo");
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ // run this task from RAM
+ #[inline(never)]
+ #[link_section = ".data.bar"]
+ #[task(priority = 2, spawn = [foo])]
+ fn bar() {
+ spawn.foo().unwrap();
+ }
+
+ extern "C" {
+ fn UART0();
+
+ // run the task dispatcher from RAM
+ #[link_section = ".data.UART1"]
+ fn UART1();
+ }
+};
diff --git a/examples/resource.rs b/examples/resource.rs
new file mode 100644
index 00000000..2777da17
--- /dev/null
+++ b/examples/resource.rs
@@ -0,0 +1,60 @@
+//! examples/resource.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ // A resource
+ static mut SHARED: u32 = 0;
+
+ #[init]
+ fn init() {
+ rtfm::pend(Interrupt::UART0);
+ rtfm::pend(Interrupt::UART1);
+ }
+
+ #[idle]
+ fn idle() -> ! {
+ debug::exit(debug::EXIT_SUCCESS);
+
+ // error: `SHARED` can't be accessed from this context
+ // SHARED += 1;
+
+ loop {}
+ }
+
+ // `SHARED` can be access from this context
+ #[interrupt(resources = [SHARED])]
+ fn UART0() {
+ *resources.SHARED += 1;
+
+ println!("UART0: SHARED = {}", resources.SHARED);
+ }
+
+ // `SHARED` can be access from this context
+ #[interrupt(resources = [SHARED])]
+ fn UART1() {
+ *resources.SHARED += 1;
+
+ println!("UART1: SHARED = {}", resources.SHARED);
+ }
+};
diff --git a/examples/safe-static-mut-ref.rs b/examples/safe-static-mut-ref.rs
deleted file mode 100644
index 9579f523..00000000
--- a/examples/safe-static-mut-ref.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-//! Safe creation of `&'static mut` references
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static BUFFER: [u8; 16] = [0; 16];
- },
-
- init: {
- resources: [BUFFER],
- },
-}
-
-fn init(_p: init::Peripherals, r: init::Resources) {
- let _buf: &'static mut [u8; 16] = r.BUFFER;
-}
-
-fn idle() -> ! {
- loop {
- rtfm::wfi();
- }
-}
diff --git a/examples/schedule.rs b/examples/schedule.rs
new file mode 100644
index 00000000..9fb2796d
--- /dev/null
+++ b/examples/schedule.rs
@@ -0,0 +1,51 @@
+//! examples/schedule.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use rtfm::{app, Instant};
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+// NOTE: does NOT work on QEMU!
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(schedule = [foo, bar])]
+ fn init() {
+ let now = Instant::now();
+
+ println!("init @ {:?}", now);
+
+ // Schedule `foo` to run 8e6 cycles (clock cycles) in the future
+ 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();
+ }
+
+ #[task]
+ fn foo() {
+ println!("foo @ {:?}", Instant::now());
+ }
+
+ #[task]
+ fn bar() {
+ println!("bar @ {:?}", Instant::now());
+ }
+
+ extern "C" {
+ fn UART0();
+ }
+};
diff --git a/examples/singleton.rs b/examples/singleton.rs
new file mode 100644
index 00000000..888a5a67
--- /dev/null
+++ b/examples/singleton.rs
@@ -0,0 +1,69 @@
+//! 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;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[Singleton(Send)]
+ static mut M: [u32; 2] = [0; 2];
+
+ static mut P: Pool<M> = ();
+
+ #[init(resources = [M])]
+ fn init() {
+ rtfm::pend(Interrupt::I2C0);
+
+ 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>) {
+ println!("foo({})", x);
+
+ resources.P.lock(|p| p.dealloc(x));
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(priority = 2, resources = [P])]
+ fn bar(x: Box<M>) {
+ println!("bar({})", x);
+
+ resources.P.dealloc(x);
+ }
+
+ extern "C" {
+ fn UART0();
+ fn UART1();
+ }
+};
diff --git a/examples/smallest.rs b/examples/smallest.rs
new file mode 100644
index 00000000..e4d86be9
--- /dev/null
+++ b/examples/smallest.rs
@@ -0,0 +1,17 @@
+//! examples/smallest.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+// panic-handler crate
+extern crate panic_semihosting;
+
+use rtfm::app;
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init]
+ fn init() {}
+};
diff --git a/examples/static.rs b/examples/static.rs
new file mode 100644
index 00000000..3dc0e89c
--- /dev/null
+++ b/examples/static.rs
@@ -0,0 +1,47 @@
+//! examples/static.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use lm3s6965::Interrupt;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ static KEY: u32 = ();
+
+ #[init]
+ fn init() {
+ rtfm::pend(Interrupt::UART0);
+ rtfm::pend(Interrupt::UART1);
+
+ KEY = 0xdeadbeef;
+ }
+
+ #[interrupt(resources = [KEY])]
+ fn UART0() {
+ println!("UART0(KEY = {:#x})", resources.KEY);
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[interrupt(priority = 2, resources = [KEY])]
+ fn UART1() {
+ println!("UART1(KEY = {:#x})", resources.KEY);
+ }
+};
diff --git a/examples/task.rs b/examples/task.rs
new file mode 100644
index 00000000..b1cd7ae1
--- /dev/null
+++ b/examples/task.rs
@@ -0,0 +1,61 @@
+//! examples/task.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::app;
+
+macro_rules! println {
+ ($($tt:tt)*) => {
+ if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() {
+ use core::fmt::Write;
+
+ writeln!(stdout, $($tt)*).ok();
+ }
+ };
+}
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ #[init(spawn = [foo])]
+ fn init() {
+ spawn.foo().unwrap();
+ }
+
+ #[task(spawn = [bar, baz])]
+ fn foo() {
+ println!("foo");
+
+ // 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();
+
+ // spawns `baz` onto the task scheduler
+ // `baz` has higher priority than `foo` so it immediately preempts `foo`
+ spawn.baz().unwrap();
+ }
+
+ #[task]
+ fn bar() {
+ println!("bar");
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[task(priority = 2)]
+ fn baz() {
+ println!("baz");
+ }
+
+ // Interrupt handlers used to dispatch software tasks
+ extern "C" {
+ fn UART0();
+ fn UART1();
+ }
+};
diff --git a/examples/two-tasks.rs b/examples/two-tasks.rs
deleted file mode 100644
index 23489151..00000000
--- a/examples/two-tasks.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-//! Two tasks running at the *same* priority with access to the same resource
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static COUNTER: u64 = 0;
- },
-
- // Both SYS_TICK and TIM2 have access to the `COUNTER` data
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- resources: [COUNTER],
- },
-
- TIM2: {
- path: tim2,
- resources: [COUNTER],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {
- // ..
-}
-
-fn idle() -> ! {
- loop {
- rtfm::wfi();
- }
-}
-
-// As both tasks are running at the same priority one can't preempt the other.
-// Thus both tasks have direct access to the resource
-fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
- // ..
-
- *r.COUNTER += 1;
-
- // ..
-}
-
-fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) {
- // ..
-
- *r.COUNTER += 1;
-
- // ..
-}
diff --git a/examples/types.rs b/examples/types.rs
new file mode 100644
index 00000000..66062085
--- /dev/null
+++ b/examples/types.rs
@@ -0,0 +1,54 @@
+//! examples/types.rs
+
+#![deny(unsafe_code)]
+#![deny(warnings)]
+#![no_main]
+#![no_std]
+
+extern crate panic_semihosting;
+
+use cortex_m_semihosting::debug;
+use rtfm::{app, Instant};
+
+#[app(device = lm3s6965)]
+const APP: () = {
+ static mut SHARED: u32 = 0;
+
+ #[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;
+
+ debug::exit(debug::EXIT_SUCCESS);
+ }
+
+ #[exception(schedule = [foo], spawn = [foo])]
+ fn SVCall() {
+ let _: Instant = start;
+ let _: SVCall::Schedule = schedule;
+ let _: SVCall::Spawn = spawn;
+ }
+
+ #[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(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])]
+ fn foo() {
+ let _: Instant = scheduled;
+ let _: foo::Resources = resources;
+ let _: foo::Schedule = schedule;
+ let _: foo::Spawn = spawn;
+ }
+
+ extern "C" {
+ fn UART1();
+ }
+};
diff --git a/examples/zero-tasks.rs b/examples/zero-tasks.rs
deleted file mode 100644
index abd1c4cd..00000000
--- a/examples/zero-tasks.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-//! Minimal example with zero tasks
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename
-extern crate stm32f103xx; // the device crate
-
-// import the procedural macro
-use rtfm::app;
-
-// This macro call indicates that this is a RTFM application
-//
-// This macro will expand to a `main` function so you don't need to supply
-// `main` yourself.
-app! {
- // this is the path to the device crate
- device: stm32f103xx,
-}
-
-// The initialization phase.
-//
-// This runs first and within a *global* critical section. Nothing can preempt
-// this function.
-fn init(p: init::Peripherals) {
- // This function has access to all the peripherals of the device
- p.core.SYST;
- p.device.GPIOA;
- p.device.RCC;
- // ..
-}
-
-// The idle loop.
-//
-// This runs after `init` and has a priority of 0. All tasks can preempt this
-// function. This function can never return so it must contain some sort of
-// endless loop.
-fn idle() -> ! {
- loop {
- // This puts the processor to sleep until there's a task to service
- rtfm::wfi();
- }
-}
diff --git a/gen-examples.sh b/gen-examples.sh
deleted file mode 100644
index 20c9d7c7..00000000
--- a/gen-examples.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-# Converts the examples in the `examples` directory into documentation in the
-# `examples` module (`src/examples/*.rs`)
-
-set -ex
-
-main() {
- local examples=(
- zero-tasks
- one-task
- two-tasks
- preemption
- nested
- late-resources
- safe-static-mut-ref
- generics
- full-syntax
- )
-
- rm -rf src/examples
-
- mkdir src/examples
-
- cat >src/examples/mod.rs <<'EOF'
-//! Examples
-// Auto-generated. Do not modify.
-EOF
-
- local i=0 out=
- for ex in ${examples[@]}; do
- name=_${i}_${ex//-/_}
- out=src/examples/${name}.rs
-
- echo "pub mod $name;" >> src/examples/mod.rs
-
- grep '//!' examples/$ex.rs > $out
- echo '//!' >> $out
- echo '//! ```' >> $out
- grep -v '//!' examples/$ex.rs | (
- IFS=''
-
- while read line; do
- echo "//! $line" >> $out;
- done
- )
- echo '//! ```' >> $out
- echo '// Auto-generated. Do not modify.' >> $out
-
-
- chmod -x $out
-
- i=$(( i + 1 ))
- done
-
- chmod -x src/examples/mod.rs
-}
-
-main
diff --git a/macros/Cargo.toml b/macros/Cargo.toml
index 410be2fc..684a7e12 100644
--- a/macros/Cargo.toml
+++ b/macros/Cargo.toml
@@ -1,20 +1,22 @@
[package]
authors = ["Jorge Aparicio <jorge@japaric.io>"]
-categories = ["concurrency", "embedded", "no-std"]
-description = "Procedural macros of the cortex-m-rtfm crate"
-documentation = "https://docs.rs/cortex-m-rtfm-macros"
-keywords = ["arm", "cortex-m"]
-license = "MIT OR Apache-2.0"
name = "cortex-m-rtfm-macros"
-repository = "https://github.com/japaric/cortex-m-rtfm"
-version = "0.3.2"
-
-[dependencies]
-failure = "0.1.1"
-proc-macro2 = "0.4.6"
-quote = "0.6.3"
-rtfm-syntax = "0.3.4"
-syn = "0.14.2"
+version = "0.4.0-beta.1"
[lib]
proc-macro = true
+
+[dependencies]
+quote = "0.6.8"
+proc-macro2 = "0.4.20"
+
+[dependencies.syn]
+features = ["extra-traits", "full"]
+version = "0.15.6"
+
+[dependencies.rand]
+default-features = false
+version = "0.5.5"
+
+[features]
+timer-queue = [] \ No newline at end of file
diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs
index 666dc306..04b462fa 100644
--- a/macros/src/analyze.rs
+++ b/macros/src/analyze.rs
@@ -1,77 +1,243 @@
-use std::cmp;
-use std::collections::HashMap;
+use std::{
+ cmp,
+ collections::{HashMap, HashSet},
+};
-use syn::Ident;
+use syn::{Attribute, Ident, Type};
-use check::App;
+use syntax::{App, Idents};
pub type Ownerships = HashMap<Ident, Ownership>;
+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 {
- /// Owned or co-owned by tasks that run at the same priority
+ // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority)
Owned { priority: u8 },
- /// Shared by tasks that run at different priorities.
- ///
- /// `ceiling` is the maximum value across all the task priorities
Shared { ceiling: u8 },
}
impl Ownership {
- pub fn ceiling(&self) -> u8 {
+ pub fn needs_lock(&self, priority: u8) -> bool {
match *self {
- Ownership::Owned { priority } => priority,
- Ownership::Shared { ceiling } => ceiling,
- }
- }
+ Ownership::Owned { .. } => false,
+ Ownership::Shared { ceiling } => {
+ debug_assert!(ceiling >= priority);
- pub fn is_owned(&self) -> bool {
- match *self {
- Ownership::Owned { .. } => true,
- _ => false,
+ priority < ceiling
+ }
}
}
}
-pub fn app(app: &App) -> Ownerships {
- let mut ownerships = HashMap::new();
+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,
+}
- for resource in &app.idle.resources {
- ownerships.insert(resource.clone(), Ownership::Owned { priority: 0 });
- }
+/// Priority -> Dispatcher
+pub type Dispatchers = HashMap<u8, Dispatcher>;
- for task in app.tasks.values() {
- for resource in task.resources.iter() {
- if let Some(ownership) = ownerships.get_mut(resource) {
- match *ownership {
- Ownership::Owned { priority } => {
- if priority == task.priority {
- *ownership = Ownership::Owned { priority };
- } else {
- *ownership = Ownership::Shared {
- ceiling: cmp::max(priority, task.priority),
- };
- }
- }
- Ownership::Shared { ceiling } => {
- if task.priority > ceiling {
- *ownership = Ownership::Shared {
- ceiling: task.priority,
- };
+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::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());
}
}
}
+ }
+
+ 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;
+ }
+ }
- continue;
+ // 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());
+ }
+ }
- ownerships.insert(
- resource.clone(),
- Ownership::Owned {
- priority: task.priority,
- },
- );
+ // 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());
}
}
- ownerships
+ // 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
+ }
+ }
+
+ 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,
+ },
+ }
+}
+
+pub struct TimerQueue {
+ pub capacity: u8,
+ pub ceiling: u8,
+ pub priority: u8,
+ pub tasks: Idents,
}
diff --git a/macros/src/check.rs b/macros/src/check.rs
index b81fc4d4..f2832207 100644
--- a/macros/src/check.rs
+++ b/macros/src/check.rs
@@ -1,95 +1,115 @@
-use std::collections::HashMap;
+use std::{collections::HashSet, iter};
-use syn::{Ident, Path};
-use syntax::check::{self, Idents, Idle, Init, Statics};
-use syntax::{self, Result};
+use proc_macro2::Span;
+use syn::parse;
-pub struct App {
- pub device: Path,
- pub idle: Idle,
- pub init: Init,
- pub resources: Statics,
- pub tasks: Tasks,
-}
-
-pub type Tasks = HashMap<Ident, Task>;
+use syntax::App;
-#[allow(non_camel_case_types)]
-pub enum Exception {
- PENDSV,
- SVCALL,
- SYS_TICK,
-}
-
-impl Exception {
- pub fn from(s: &str) -> Option<Self> {
- Some(match s {
- "PENDSV" => Exception::PENDSV,
- "SVCALL" => Exception::SVCALL,
- "SYS_TICK" => Exception::SYS_TICK,
- _ => return None,
- })
+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<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",
+ ));
+ }
}
- pub fn nr(&self) -> usize {
- match *self {
- Exception::PENDSV => 14,
- Exception::SVCALL => 11,
- Exception::SYS_TICK => 15,
+ // 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 enum Kind {
- Exception(Exception),
- Interrupt { enabled: bool },
-}
-
-pub struct Task {
- pub kind: Kind,
- pub path: Path,
- pub priority: u8,
- pub resources: Idents,
-}
-
-pub fn app(app: check::App) -> Result<App> {
- let app = App {
- device: app.device,
- idle: app.idle,
- init: app.init,
- resources: app.resources,
- tasks: app.tasks
- .into_iter()
- .map(|(k, v)| {
- let v = ::check::task(&k.to_string(), v)?;
- Ok((k, v))
- })
- .collect::<Result<_>>()?,
- };
+ // Check that all late resources have been initialized in `#[init]`
+ 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) {
+ return Err(parse::Error::new(
+ res.span(),
+ "late resources MUST be initialized at the end of `init`",
+ ));
+ }
+ }
- Ok(app)
-}
+ // Check that all referenced tasks have been declared
+ for task in app
+ .idle
+ .as_ref()
+ .map(|idle| -> Box<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",
+ ));
+ }
+ }
-fn task(name: &str, task: syntax::check::Task) -> Result<Task> {
- let kind = match Exception::from(name) {
- Some(e) => {
- ensure!(
- task.enabled.is_none(),
- "`enabled` field is not valid for exceptions"
- );
+ // 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" },
+ ),
+ ));
+ }
- Kind::Exception(e)
+ // Check that free interrupts are not being used
+ for int in app.interrupts.keys() {
+ if app.free_interrupts.contains_key(int) {
+ return Err(parse::Error::new(
+ int.span(),
+ "free interrupts (`extern { .. }`) can't be used as interrupt handlers",
+ ));
}
- None => Kind::Interrupt {
- enabled: task.enabled.unwrap_or(true),
- },
- };
+ }
- Ok(Task {
- kind,
- path: task.path,
- priority: task.priority.unwrap_or(1),
- resources: task.resources,
- })
+ Ok(())
}
diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs
new file mode 100644
index 00000000..ff1062ae
--- /dev/null
+++ b/macros/src/codegen.rs
@@ -0,0 +1,1815 @@
+use proc_macro::TokenStream;
+use std::{
+ collections::HashMap,
+ sync::atomic::{AtomicUsize, Ordering},
+ time::{SystemTime, UNIX_EPOCH},
+};
+
+use proc_macro2::Span;
+use quote::quote;
+use rand::{Rng, SeedableRng};
+use syn::{ArgCaptured, Ident, IntSuffix, LitInt};
+
+use analyze::{Analysis, Ownership};
+use 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 = HashMap<Ident, Ident>;
+
+struct Context {
+ // Alias
+ #[cfg(feature = "timer-queue")]
+ baseline: Ident,
+ // Dispatcher -> Alias (`enum`)
+ enums: HashMap<u8, Ident>,
+ // Task -> Alias (`static` / resource)
+ free_queues: Aliases,
+ // Alias (`fn`)
+ idle: Ident,
+ // Alias (`fn`)
+ init: Ident,
+ // Task -> Alias (`static`)
+ inputs: Aliases,
+ // Alias
+ priority: Ident,
+ // Dispatcher -> Alias (`static` / resource)
+ ready_queues: HashMap<u8, Ident>,
+ // For non-singletons this maps the resource name to its `static mut` variable name
+ statics: Aliases,
+ /// Task -> Alias (`struct`)
+ resources: HashMap<Kind, Resources>,
+ // Task -> Alias (`static`)
+ #[cfg(feature = "timer-queue")]
+ scheduleds: Aliases,
+ // Task -> Alias (`fn`)
+ spawn_fn: Aliases,
+ // Alias (`enum`)
+ schedule_enum: Ident,
+ // Task -> Alias (`fn`)
+ schedule_fn: Aliases,
+ tasks: Aliases,
+ // Alias (`struct` / `static mut`)
+ timer_queue: Ident,
+}
+
+impl Default for Context {
+ fn default() -> Self {
+ Context {
+ #[cfg(feature = "timer-queue")]
+ baseline: mk_ident(),
+ enums: HashMap::new(),
+ free_queues: Aliases::new(),
+ idle: mk_ident(),
+ init: mk_ident(),
+ inputs: Aliases::new(),
+ priority: mk_ident(),
+ ready_queues: HashMap::new(),
+ statics: Aliases::new(),
+ resources: HashMap::new(),
+ #[cfg(feature = "timer-queue")]
+ scheduleds: Aliases::new(),
+ spawn_fn: Aliases::new(),
+ schedule_enum: mk_ident(),
+ schedule_fn: Aliases::new(),
+ tasks: Aliases::new(),
+ timer_queue: mk_ident(),
+ }
+ }
+}
+
+struct Resources {
+ alias: Ident,
+ decl: proc_macro2::TokenStream,
+}
+
+pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
+ let mut ctxt = Context::default();
+
+ let device = &app.args.device;
+
+ 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 = 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 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(&ctxt, app, analysis);
+
+ let pre_init = pre_init(&ctxt, analysis);
+
+ let assertions = assertions(app, analysis);
+
+ let init = &ctxt.init;
+ quote!(
+ #resources
+
+ #spawn
+
+ #timer_queue
+
+ #schedule
+
+ #dispatchers_data
+
+ #(#exceptions)*
+
+ #root_interrupts
+
+ // We put these items into a pseudo-module to avoid a collision between the `interrupt`
+ // import and user code
+ const APP: () = {
+ use #device::interrupt;
+
+ #scoped_interrupts
+
+ #(#dispatchers)*
+ };
+
+ #(#tasks)*
+
+ #init_fn
+
+ #idle_fn
+
+ #[allow(unsafe_code)]
+ #[rtfm::export::entry]
+ #[doc(hidden)]
+ unsafe fn main() -> ! {
+ #assertions
+
+ rtfm::export::interrupt::disable();
+
+ #pre_init
+
+ #init(#init_arg);
+
+ #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 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 = mk_ident();
+ if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ items.push(mk_resource(
+ ctxt,
+ 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 = mk_ident();
+ let symbol = format!("{}::{}", name, alias);
+
+ items.push(
+ expr.as_ref()
+ .map(|expr| {
+ quote!(
+ #(#attrs)*
+ #[export_name = #symbol]
+ static mut #alias: #ty = #expr;
+ )
+ })
+ .unwrap_or_else(|| {
+ quote!(
+ #(#attrs)*
+ #[export_name = #symbol]
+ static mut #alias: rtfm::export::MaybeUninit<#ty> =
+ rtfm::export::MaybeUninit::uninitialized();
+ )
+ }),
+ );
+
+ if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
+ if res.mutability.is_some() {
+ let ptr = if res.expr.is_none() {
+ quote!(unsafe { #alias.get_mut() })
+ } else {
+ quote!(unsafe { &mut #alias })
+ };
+
+ items.push(mk_resource(
+ ctxt,
+ 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 {
+ let attrs = &app.init.attrs;
+ let locals = mk_locals(&app.init.statics, true);
+ let stmts = &app.init.stmts;
+ let assigns = app
+ .init
+ .assigns
+ .iter()
+ .map(|assign| {
+ 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!(unsafe { #alias.set(#expr); })
+ } else {
+ let left = &assign.left;
+ let right = &assign.right;
+ quote!(#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 module = module(
+ ctxt,
+ Kind::Init,
+ !app.init.args.schedule.is_empty(),
+ !app.init.args.spawn.is_empty(),
+ app,
+ );
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #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;
+ let name = format!("init::{}", init);
+ quote!(
+ #module
+
+ #(#attrs)*
+ #[export_name = #name]
+ #unsafety fn #init(mut core: rtfm::Peripherals) {
+ #(#locals)*
+
+ #baseline_let
+
+ #prelude
+
+ let mut device = unsafe { #device::Peripherals::steal() };
+
+ #start_let
+
+ #(#stmts)*
+
+ #(#assigns)*
+ }
+ )
+}
+
+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 (name, interrupt) in &app.interrupts {
+ let priority = interrupt.args.priority;
+ exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.NVIC.set_priority(
+ #device::Interrupt::#name,
+ ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
+ )));
+ }
+
+ for (name, exception) in &app.exceptions {
+ 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),
+ )));
+ }
+
+ for (priority, dispatcher) in &analysis.dispatchers {
+ let name = &dispatcher.interrupt;
+ exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
+ exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
+ exprs.push(quote!(p.NVIC.set_priority(
+ #device::Interrupt::#name,
+ ((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.get_mut().syst.set_clock_source(rtfm::export::SystClkSource::Core)));
+ exprs.push(quote!(#tq.get_mut().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,
+) -> 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 core::cell::Cell<u8>,
+ }
+ ));
+ }
+
+ 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 core::cell::Cell<u8>,
+ }
+ ));
+ } else {
+ let baseline_field = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(
+ #[doc(hidden)]
+ pub #baseline: 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 core::cell::Cell<u8>,
+ }
+ ));
+ }
+ }
+
+ 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",
+ };
+
+ quote!(
+ #root
+
+ #[doc = #doc]
+ 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)] #priority: &'a core::cell::Cell<u8>));
+ exprs.push(quote!(#priority));
+
+ let mut may_call_lock = false;
+ let mut needs_unsafe = false;
+ for name in resources {
+ let res = &app.resources[name];
+ 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!(pub #name: #name));
+ exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ defs.push(quote!(pub #name: &'static #mut_ #ty));
+ }
+ } else {
+ // owned by someone else
+ if singleton {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a mut #name));
+ exprs
+ .push(quote!(#name: &mut <#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ force_mut = true;
+ defs.push(quote!(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!(#name: &mut #alias));
+ } else {
+ exprs.push(quote!(#name: &#mut_ #alias));
+ }
+ } else {
+ let ownership = &analysis.ownerships[name];
+
+ if ownership.needs_lock(logical_prio) {
+ may_call_lock = true;
+ if singleton {
+ if mut_.is_none() {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a #name));
+ exprs
+ .push(quote!(#name: &<#name as owned_singleton::Singleton>::new()));
+ continue;
+ } else {
+ // Generate a resource proxy
+ defs.push(quote!(pub #name: resources::#name<'a>));
+ exprs.push(quote!(#name: resources::#name { #priority }));
+ continue;
+ }
+ } else {
+ if mut_.is_none() {
+ defs.push(quote!(pub #name: &'a #ty));
+ } else {
+ // Generate a resource proxy
+ defs.push(quote!(pub #name: resources::#name<'a>));
+ exprs.push(quote!(#name: resources::#name { #priority }));
+ continue;
+ }
+ }
+ } else {
+ if singleton {
+ if kind.runs_once() {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: #name));
+ exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
+ } else {
+ needs_unsafe = true;
+ defs.push(quote!(pub #name: &'a mut #name));
+ exprs.push(
+ quote!(#name: &mut <#name as owned_singleton::Singleton>::new()),
+ );
+ }
+ continue;
+ } else {
+ defs.push(quote!(pub #name: &#lt #mut_ #ty));
+ }
+ }
+
+ let alias = &ctxt.statics[name];
+ needs_unsafe = true;
+ if initialized {
+ exprs.push(quote!(#name: &#mut_ #alias));
+ } else {
+ let method = if mut_.is_some() {
+ quote!(get_mut)
+ } else {
+ quote!(get_ref)
+ };
+ exprs.push(quote!(#name: #alias.#method() ));
+ }
+ }
+ }
+
+ let alias = mk_ident();
+ let unsafety = if needs_unsafe {
+ Some(quote!(unsafe))
+ } else {
+ None
+ };
+
+ 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() {
+ // Populate `spawn_fn`
+ for task in spawn {
+ if ctxt.spawn_fn.contains_key(task) {
+ continue;
+ }
+
+ ctxt.spawn_fn.insert(task.clone(), mk_ident());
+ }
+
+ 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(), mk_ident());
+ }
+
+ 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 = core::cell::Cell::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,
+ );
+
+ let unsafety = &idle.unsafety;
+ let idle = &ctxt.idle;
+
+ let name = format!("idle::{}", idle);
+ (
+ quote!(
+ #module
+
+ #(#attrs)*
+ #[export_name = #name]
+ #unsafety fn #idle() -> ! {
+ #(#locals)*
+
+ #prelude
+
+ #(#stmts)*
+ }),
+ 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 statics = &exception.statics;
+ let stmts = &exception.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Exception(ident.clone()),
+ &exception.args.resources,
+ &exception.args.spawn,
+ &exception.args.schedule,
+ app,
+ exception.args.priority,
+ analysis,
+ );
+
+ let module = module(
+ ctxt,
+ Kind::Exception(ident.clone()),
+ !exception.args.schedule.is_empty(),
+ !exception.args.spawn.is_empty(),
+ app,
+ );
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #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 unsafety = &exception.unsafety;
+ quote!(
+ #module
+
+ #[rtfm::export::exception]
+ #[doc(hidden)]
+ #(#attrs)*
+ #unsafety fn #ident() {
+ #(#statics)*
+
+ #baseline_let
+
+ #prelude
+
+ #start_let
+
+ rtfm::export::run(move || {
+ #(#stmts)*
+ })
+ })
+ })
+ .collect()
+}
+
+fn interrupts(
+ ctxt: &mut Context,
+ app: &App,
+ analysis: &Analysis,
+) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
+ let mut root = vec![];
+ let mut scoped = vec![];
+
+ for (ident, interrupt) in &app.interrupts {
+ let attrs = &interrupt.attrs;
+ let statics = &interrupt.statics;
+ let stmts = &interrupt.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Interrupt(ident.clone()),
+ &interrupt.args.resources,
+ &interrupt.args.spawn,
+ &interrupt.args.schedule,
+ app,
+ interrupt.args.priority,
+ analysis,
+ );
+
+ root.push(module(
+ ctxt,
+ Kind::Interrupt(ident.clone()),
+ !interrupt.args.schedule.is_empty(),
+ !interrupt.args.spawn.is_empty(),
+ app,
+ ));
+
+ #[cfg(feature = "timer-queue")]
+ let baseline = &ctxt.baseline;
+ let baseline_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => quote!(let #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 unsafety = &interrupt.unsafety;
+ scoped.push(quote!(
+ #[interrupt]
+ #(#attrs)*
+ #unsafety fn #ident() {
+ #(#statics)*
+
+ #baseline_let
+
+ #prelude
+
+ #start_let
+
+ rtfm::export::run(move || {
+ #(#stmts)*
+ })
+ }));
+ }
+
+ (quote!(#(#root)*), quote!(#(#scoped)*))
+}
+
+fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut items = vec![];
+ for (name, task) in &app.tasks {
+ #[cfg(feature = "timer-queue")]
+ let scheduleds_alias = mk_ident();
+ let free_alias = mk_ident();
+ let inputs_alias = mk_ident();
+ let task_alias = mk_ident();
+
+ let attrs = &task.attrs;
+ let inputs = &task.inputs;
+ let locals = mk_locals(&task.statics, false);
+ let stmts = &task.stmts;
+
+ let prelude = prelude(
+ ctxt,
+ Kind::Task(name.clone()),
+ &task.args.resources,
+ &task.args.spawn,
+ &task.args.schedule,
+ app,
+ task.args.priority,
+ analysis,
+ );
+
+ let ty = tuple_ty(inputs);
+
+ let capacity_lit = mk_capacity_literal(analysis.capacities[name]);
+ let capacity_ty = mk_typenum_capacity(analysis.capacities[name], true);
+
+ let resource = mk_resource(
+ ctxt,
+ &free_alias,
+ quote!(rtfm::export::FreeQueue<#capacity_ty>),
+ *analysis.free_queues.get(name).unwrap_or(&0),
+ quote!(#free_alias.get_mut()),
+ app,
+ None,
+ );
+
+ let scheduleds_static = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias);
+
+ quote!(
+ #[export_name = #scheduleds_symbol]
+ static mut #scheduleds_alias:
+ rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> =
+ rtfm::export::MaybeUninit::uninitialized();
+ )
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let scheduled_let = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(let scheduled = #baseline;)
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+
+ let baseline_arg = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let baseline = &ctxt.baseline;
+ quote!(#baseline: rtfm::Instant,)
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => quote!(),
+ };
+ let task_symbol = format!("{}::{}", name, task_alias);
+ let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias);
+ let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias);
+ let unsafety = &task.unsafety;
+ items.push(quote!(
+ // FIXME(MaybeUninit) MaybeUninit won't be necessary when core::mem::MaybeUninit
+ // stabilizes because heapless constructors will work in const context
+ #[export_name = #free_symbol]
+ static mut #free_alias: rtfm::export::MaybeUninit<
+ rtfm::export::FreeQueue<#capacity_ty>
+ > = rtfm::export::MaybeUninit::uninitialized();
+
+ #resource
+
+ #[export_name = #inputs_symbol]
+ static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> =
+ rtfm::export::MaybeUninit::uninitialized();
+
+ #scheduleds_static
+
+ #(#attrs)*
+ #[export_name = #task_symbol]
+ #unsafety fn #task_alias(#baseline_arg #(#inputs,)*) {
+ #(#locals)*
+
+ #prelude
+
+ #scheduled_let
+
+ #(#stmts)*
+ }
+ ));
+
+ items.push(module(
+ ctxt,
+ Kind::Task(name.clone()),
+ !task.args.schedule.is_empty(),
+ !task.args.spawn.is_empty(),
+ app,
+ ));
+
+ #[cfg(feature = "timer-queue")]
+ ctxt.scheduleds.insert(name.clone(), scheduleds_alias);
+ ctxt.free_queues.insert(name.clone(), free_alias);
+ ctxt.inputs.insert(name.clone(), inputs_alias);
+ ctxt.tasks.insert(name.clone(), task_alias);
+ }
+
+ 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![];
+
+ for (level, dispatcher) in &analysis.dispatchers {
+ let ready_alias = mk_ident();
+ let enum_alias = mk_ident();
+ let tasks = &dispatcher.tasks;
+ let capacity = mk_typenum_capacity(dispatcher.capacity, true);
+
+ 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,
+ quote!(#ready_alias.get_mut()),
+ app,
+ None,
+ );
+ data.push(quote!(
+ #[allow(dead_code)]
+ #[allow(non_camel_case_types)]
+ enum #enum_alias { #(#tasks,)* }
+
+ #[export_name = #symbol]
+ static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninitialized();
+
+ #resource
+ ));
+
+ let interrupt = &dispatcher.interrupt;
+
+ let arms = dispatcher
+ .tasks
+ .iter()
+ .map(|task| {
+ let inputs = &ctxt.inputs[task];
+ let free = &ctxt.free_queues[task];
+ let pats = tuple_pat(&app.tasks[task].inputs);
+ let alias = &ctxt.tasks[task];
+
+ let baseline_let;
+ let call;
+ match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds = &ctxt.scheduleds[task];
+ baseline_let = quote!(
+ let baseline =
+ ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index)));
+ );
+ call = quote!(#alias(baseline, #pats));
+ }
+ #[cfg(not(feature = "timer-queue"))]
+ () => {
+ baseline_let = quote!();
+ call = quote!(#alias(#pats));
+ }
+ };
+
+ quote!(#enum_alias::#task => {
+ #baseline_let
+ let input = ptr::read(#inputs.get_ref().get_unchecked(usize::from(index)));
+ #free.get_mut().split().0.enqueue_unchecked(index);
+ let (#pats) = input;
+ #call
+ })
+ })
+ .collect::<Vec<_>>();
+
+ let attrs = &dispatcher.attrs;
+ dispatchers.push(quote!(
+ #(#attrs)*
+ #[interrupt]
+ unsafe fn #interrupt() {
+ use core::ptr;
+
+ rtfm::export::run(|| {
+ while let Some((task, index)) = #ready_alias.get_mut().split().1.dequeue() {
+ match task {
+ #(#arms)*
+ }
+ }
+ });
+ }
+ ));
+
+ ctxt.ready_queues.insert(*level, ready_alias);
+ ctxt.enums.insert(*level, 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 (task, alias) in &ctxt.spawn_fn {
+ let free = &ctxt.free_queues[task];
+ let level = app.tasks[task].args.priority;
+ let ready = &ctxt.ready_queues[&level];
+ let enum_ = &ctxt.enums[&level];
+ let dispatcher = &analysis.dispatchers[&level].interrupt;
+ let inputs = &ctxt.inputs[task];
+ let args = &app.tasks[task].inputs;
+ let ty = tuple_ty(args);
+ let pats = tuple_pat(args);
+
+ let scheduleds_write = match () {
+ #[cfg(feature = "timer-queue")]
+ () => {
+ let scheduleds = &ctxt.scheduleds[task];
+ 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!(),
+ };
+
+ items.push(quote!(
+ #[inline(always)]
+ unsafe fn #alias(
+ #baseline_arg
+ #priority: &core::cell::Cell<u8>,
+ #(#args,)*
+ ) -> Result<(), #ty> {
+ use core::ptr;
+
+ use rtfm::Mutex;
+
+ if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
+ ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
+ #scheduleds_write
+
+ #ready { #priority }.lock(|rq| {
+ rq.split().0.enqueue_unchecked((#enum_::#task, index))
+ });
+
+ rtfm::pend(#device::Interrupt::#dispatcher);
+
+ Ok(())
+ } else {
+ Err((#pats))
+ }
+ }
+ ))
+ }
+
+ // 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 alias = &ctxt.spawn_fn[task];
+ let inputs = &app.tasks[task].inputs;
+ 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]
+ 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 free = &ctxt.free_queues[task];
+ let enum_ = &ctxt.schedule_enum;
+ let inputs = &ctxt.inputs[task];
+ let scheduleds = &ctxt.scheduleds[task];
+ let args = &app.tasks[task].inputs;
+ let ty = tuple_ty(args);
+ let pats = tuple_pat(args);
+
+ items.push(quote!(
+ #[inline(always)]
+ unsafe fn #alias(
+ #priority: &core::cell::Cell<u8>,
+ 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(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
+ ptr::write(
+ #scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
+ instant,
+ );
+
+ let nr = rtfm::export::NotReady {
+ instant,
+ index,
+ task: #enum_::#task,
+ };
+
+ ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr));
+
+ Ok(())
+ } else {
+ Err((#pats))
+ }
+ }
+ ))
+ }
+
+ // 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 inputs = &app.tasks[task].inputs;
+ let ty = tuple_ty(inputs);
+ let pats = tuple_pat(inputs);
+
+ methods.push(quote!(
+ #[inline]
+ 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: &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 enum_ = &ctxt.schedule_enum;
+ items.push(quote!(
+ #[allow(dead_code)]
+ #[allow(non_camel_case_types)]
+ #[derive(Clone, Copy)]
+ enum #enum_ { #(#tasks,)* }
+ ));
+
+ let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false);
+ let tq = &ctxt.timer_queue;
+ let symbol = format!("TIMER_QUEUE::{}", tq);
+ items.push(quote!(
+ #[export_name = #symbol]
+ static mut #tq:
+ rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
+ rtfm::export::MaybeUninit::uninitialized();
+ ));
+
+ items.push(mk_resource(
+ ctxt,
+ tq,
+ quote!(rtfm::export::TimerQueue<#enum_, #cap>),
+ analysis.timer_queue.ceiling,
+ quote!(#tq.get_mut()),
+ app,
+ None,
+ ));
+
+ let priority = &ctxt.priority;
+ let device = &app.args.device;
+ let arms = tasks
+ .iter()
+ .map(|task| {
+ let level = app.tasks[task].args.priority;
+ let tenum = &ctxt.enums[&level];
+ let ready = &ctxt.ready_queues[&level];
+ let dispatcher = &analysis.dispatchers[&level].interrupt;
+
+ quote!(
+ #enum_::#task => {
+ (#ready { #priority }).lock(|rq| {
+ rq.split().0.enqueue_unchecked((#tenum::#task, index))
+ });
+
+ rtfm::pend(#device::Interrupt::#dispatcher);
+ }
+ )
+ })
+ .collect::<Vec<_>>();
+
+ let logical_prio = analysis.timer_queue.priority;
+ items.push(quote!(
+ #[rtfm::export::exception]
+ #[doc(hidden)]
+ unsafe fn SysTick() {
+ use rtfm::Mutex;
+
+ let ref #priority = core::cell::Cell::new(#logical_prio);
+
+ rtfm::export::run(|| {
+ rtfm::export::sys_tick(#tq { #priority }, |task, index| {
+ match task {
+ #(#arms)*
+ }
+ });
+ })
+ }
+ ));
+
+ quote!(#(#items)*)
+}
+
+fn pre_init(ctxt: &Context, analysis: &Analysis) -> proc_macro2::TokenStream {
+ let mut exprs = vec![];
+
+ // FIXME(MaybeUninit) Because we are using a fake MaybeUninit we need to set the Option tag to
+ // Some; otherwise the get_ref and get_mut could result in UB. Also heapless collections can't
+ // be constructed in const context; we have to initialize them at runtime (i.e. here).
+
+ // these are `MaybeUninit` arrays
+ for inputs in ctxt.inputs.values() {
+ exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
+ }
+
+ #[cfg(feature = "timer-queue")]
+ for inputs in ctxt.scheduleds.values() {
+ exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
+ }
+
+ // these are `MaybeUninit` `ReadyQueue`s
+ for queue in ctxt.ready_queues.values() {
+ exprs.push(quote!(#queue.set(rtfm::export::ReadyQueue::new());))
+ }
+
+ // these are `MaybeUninit` `FreeQueue`s
+ for free in ctxt.free_queues.values() {
+ exprs.push(quote!(#free.set(rtfm::export::FreeQueue::new());))
+ }
+
+ // end-of-FIXME
+
+ // Initialize the timer queue
+ if !analysis.timer_queue.tasks.is_empty() {
+ let tq = &ctxt.timer_queue;
+ exprs.push(quote!(#tq.set(rtfm::export::TimerQueue::new(p.SYST));));
+ }
+
+ // Populate the `FreeQueue`s
+ for (task, alias) in &ctxt.free_queues {
+ let capacity = analysis.capacities[task];
+ exprs.push(quote!(
+ for i in 0..#capacity {
+ #alias.get_mut().enqueue_unchecked(i);
+ }
+ ))
+ }
+
+ // 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,
+ 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;
+
+ let mut items = vec![];
+
+ let path = if let Some(module) = module {
+ let doc = format!("`{}`", ty);
+ module.push(quote!(
+ #[doc = #doc]
+ pub struct #struct_<'a> {
+ #[doc(hidden)]
+ pub #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+
+ quote!(resources::#struct_)
+ } else {
+ items.push(quote!(
+ struct #struct_<'a> {
+ #priority: &'a core::cell::Cell<u8>,
+ }
+ ));
+
+ quote!(#struct_)
+ };
+
+ items.push(quote!(
+ unsafe impl<'a> rtfm::Mutex for #path<'a> {
+ const CEILING: u8 = #ceiling;
+ const NVIC_PRIO_BITS: u8 = #device::NVIC_PRIO_BITS;
+ type Data = #ty;
+
+ #[inline(always)]
+ unsafe fn priority(&self) -> &core::cell::Cell<u8> {
+ &self.#priority
+ }
+
+ #[inline(always)]
+ fn ptr(&self) -> *mut Self::Data {
+ unsafe { #ptr }
+ }
+ }
+ ));
+
+ quote!(#(#items)*)
+}
+
+fn mk_capacity_literal(capacity: u8) -> LitInt {
+ LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
+}
+
+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 ident = Ident::new(&format!("U{}", capacity), Span::call_site());
+
+ quote!(rtfm::export::consts::#ident)
+}
+
+fn mk_ident() -> Ident {
+ static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+ let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+
+ let secs = elapsed.as_secs();
+ let nanos = elapsed.subsec_nanos();
+
+ let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32;
+ let mut seed: [u8; 16] = [0; 16];
+
+ for (i, v) in seed.iter_mut().take(8).enumerate() {
+ *v = ((secs >> (i * 8)) & 0xFF) as u8
+ }
+
+ for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() {
+ *v = ((nanos >> (i * 8)) & 0xFF) as u8
+ }
+
+ for (i, v) in seed.iter_mut().skip(12).enumerate() {
+ *v = ((count >> (i * 8)) & 0xFF) as u8
+ }
+
+ let mut rng = rand::rngs::SmallRng::from_seed(seed);
+ Ident::new(
+ &(0..16)
+ .map(|i| {
+ if i == 0 || rng.gen() {
+ ('a' as u8 + rng.gen::<u8>() % 25) as char
+ } else {
+ ('0' as u8 + rng.gen::<u8>() % 10) as char
+ }
+ })
+ .collect::<String>(),
+ Span::call_site(),
+ )
+}
+
+// `once = true` means that these locals will be called from a function that will run *once*
+fn mk_locals(locals: &HashMap<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 expr = &static_.expr;
+ let ident = name;
+ let ty = &static_.ty;
+
+ quote!(
+ #[allow(non_snake_case)]
+ let #ident: &#lt mut #ty = {
+ #(#attrs)*
+ static mut #ident: #ty = #expr;
+
+ unsafe { &mut #ident }
+ };
+ )
+ })
+ .collect::<Vec<_>>();
+
+ quote!(#(#locals)*)
+}
+
+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<_>>();
+
+ quote!(#(#pats,)*)
+ }
+}
+
+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<_>>();
+
+ quote!((#(#tys,)*))
+ }
+}
+
+#[derive(Clone, Eq, Hash, PartialEq)]
+enum Kind {
+ Exception(Ident),
+ Idle,
+ Init,
+ Interrupt(Ident),
+ Task(Ident),
+}
+
+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(),
+ }
+ }
+
+ fn is_idle(&self) -> bool {
+ *self == Kind::Idle
+ }
+
+ fn is_init(&self) -> bool {
+ *self == Kind::Init
+ }
+
+ fn runs_once(&self) -> bool {
+ match *self {
+ Kind::Init | Kind::Idle => true,
+ _ => false,
+ }
+ }
+}
diff --git a/macros/src/lib.rs b/macros/src/lib.rs
index 65d5ad89..e382b410 100644
--- a/macros/src/lib.rs
+++ b/macros/src/lib.rs
@@ -1,185 +1,312 @@
-//! Procedural macros of the `cortex-m-rtfm` crate
// #![deny(warnings)]
#![recursion_limit = "128"]
-#[macro_use]
-extern crate failure;
extern crate proc_macro;
extern crate proc_macro2;
-extern crate syn;
-#[macro_use]
extern crate quote;
-extern crate rtfm_syntax as syntax;
+extern crate rand;
+extern crate syn;
use proc_macro::TokenStream;
-use syntax::{App, Result};
+use syn::parse_macro_input;
mod analyze;
mod check;
-mod trans;
+mod codegen;
+mod syntax;
-/// The `app!` macro, a macro used to specify the tasks and resources of a RTFM application.
+/// Attribute used to declare a RTFM application
///
-/// The contents of this macro uses a `key: value` syntax. All the possible keys are shown below:
+/// 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,
+/// like functions and `static` variables.
///
-/// ``` text
-/// app! {
-/// device: ..,
+/// The `app` attribute has one mandatory argument:
///
-/// resources: { .. },
+/// - `device = <path>`. The path must point to a device crate generated using [`svd2rust`]
+/// **v0.14.x**.
///
-/// init: { .. },
+/// [`svd2rust`]: https://crates.io/crates/svd2rust
///
-/// idle: { .. },
+/// The items allowed in the block value of the `const` item are specified below:
///
-/// tasks: { .. },
-/// }
-/// ```
+/// # 1. `static [mut]` variables
///
-/// # `device`
+/// 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.
///
-/// The value of this key is a Rust path, like `foo::bar::baz`, that must point to a *device crate*,
-/// a crate generated using `svd2rust`.
+/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock
///
-/// # `resources`
+/// `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.
///
-/// This key is optional. Its value is a list of `static` variables. These variables are the data
-/// that can be safely accessed, modified and shared by tasks.
+/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
+/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
///
-/// ``` text
-/// resources: {
-/// static A: bool = false;
-/// static B: i32 = 0;
-/// static C: [u8; 16] = [0; 16];
-/// static D: Thing = Thing::new(..);
-/// static E: Thing;
-/// }
-/// ```
+/// 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 initial value of a resource can be omitted. This means that the resource will be runtime
-/// initialized; these runtime initialized resources are also known as *late resources*.
+/// 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.
///
-/// If this key is omitted its value defaults to an empty list.
+/// [`Mutex`]: ../rtfm/trait.Mutex.html
///
-/// # `init`
+/// # 2. `fn`
///
-/// This key is optional. Its value is a set of key values. All the possible keys are shown below:
+/// 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.
///
-/// ``` text
-/// init: {
-/// path: ..,
-/// }
-/// ```
+/// ## a. `#[init]`
///
-/// ## `init.path`
+/// 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 ()`.
///
-/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the
-/// initialization function.
+/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled.
+/// Interrupts are re-enabled after `init` returns.
///
-/// If the key is omitted its value defaults to `init`.
+/// The `init` attribute accepts the following optional arguments:
///
-/// ## `init.resources`
+/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has
+/// access to.
///
-/// This key is optional. Its value is a set of resources the `init` function *owns*. The resources
-/// in this list must be a subset of the resources listed in the top `resources` key. Note that some
-/// restrictions apply:
+/// - `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.
///
-/// - The resources in this list can't be late resources.
-/// - The resources that appear in this list can't appear in other list like `idle.resources` or
-/// `tasks.$TASK.resources`
+/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can
+/// immediately spawn.
///
-/// If this key is omitted its value is assumed to be an empty list.
+/// The `app` attribute will injected a *context* into this function that comprises the following
+/// variables:
///
-/// # `idle`
+/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for
+/// more details.
///
-/// This key is optional. Its value is a set of key values. All the possible keys are shown below:
+/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html
///
-/// ``` text
-/// idle: {
-/// path: ..,
-/// resources: [..],
-/// }
-/// ```
+/// - `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.
///
-/// ## `idle.path`
+/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**:
+/// only present if the `timer-queue` feature is enabled.
///
-/// This key is optional. Its value is a Rust path, like `foo::bar::baz`, that points to the idle
-/// loop function.
+/// - `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`).
///
-/// If the key is omitted its value defaults to `idle`.
+/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks.
+/// **NOTE**: only present if the `timer-queue` feature is enabled.
///
-/// ## `idle.resources`
+/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks.
///
-/// This key is optional. Its value is a list of resources the `idle` loop has access to. The
-/// resources in this list must be a subset of the resources listed in the top `resources` key.
+/// Other properties / constraints:
///
-/// If omitted its value defaults to an empty list.
+/// - The `init` function can **not** be called from software.
///
-/// # `tasks`
+/// - 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`.
///
-/// This key is optional. Its value is a list of tasks. Each task itself is a set of key value pair.
-/// The full syntax is shown below:
+/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late*
+/// resources.
///
-/// ``` text
-/// tasks: {
-/// $TASK: {
-/// enabled: ..,
-/// path: ..,
-/// priority: ..,
-/// resources: [..],
-/// },
-/// }
-/// ```
+/// ## b. `#[idle]`
///
-/// If this key is omitted its value is assumed to be an empty list.
+/// 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() -> !`.
///
-/// ## `tasks.$TASK`
+/// 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
+/// [SLEEPONEXIT] bit after executing `init`.
///
-/// The key must be either a Cortex-M exception or a device specific interrupt. `PENDSV`, `SVCALL`,
-/// `SYS_TICK` are considered as exceptions. All other names are assumed to be interrupts.
+/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
///
-/// ## `tasks.$TASK.enabled`
+/// The `idle` attribute accepts the following optional arguments:
///
-/// This key is optional for interrupts and forbidden for exceptions. Its value must be a boolean
-/// and indicates whether the interrupt will be enabled (`true`) or disabled (`false`) after `init`
-/// ends and before `idle` starts.
+/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
///
-/// If this key is omitted its value defaults to `true`.
+/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
///
-/// ## `tasks.$TASK.path`
+/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
-/// The value of this key is a Rust path, like `foo::bar::baz`, that points to the handler of this
-/// task.
+/// The `app` attribute will injected a *context* into this function that comprises the following
+/// variables:
///
-/// ## `tasks.$TASK.priority`
+/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
///
-/// This key is optional. Its value is an integer with type `u8` that specifies the priority of this
-/// task. The minimum valid priority is 1. The maximum valid priority depends on the number of the
-/// NVIC priority bits the device has; if the device has 4 priority bits the maximum allowed value
-/// would be 16.
+/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
///
-/// If this key is omitted its value defaults to `1`.
+/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
///
-/// ## `tasks.$TASK.resources`
+/// Other properties / constraints:
///
-/// This key is optional. Its value is a list of resources this task has access to. The resources in
-/// this list must be a subset of the resources listed in the top `resources` key.
+/// - The `idle` function can **not** be called from software.
///
-/// If omitted its value defaults to an empty list.
-#[proc_macro]
-pub fn app(ts: TokenStream) -> TokenStream {
- match run(ts) {
- Err(e) => panic!("error: {}", e),
- Ok(ts) => ts,
- }
-}
+/// - 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()`.
+///
+/// The name of the function must match one of the Cortex-M exceptions that has [configurable
+/// priority][system-handler].
+///
+/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html
+///
+/// The `exception` attribute accepts the following optional arguments.
+///
+/// - `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
+/// is assumed to be 1.
+///
+/// - `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`. The time at which this handler started executing. **NOTE**: only
+/// present if the `timer-queue` feature is enabled.
+///
+/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
+///
+/// - `schedule: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
+///
+/// - `spawn: <exception-name>::Spawn`. Same meaning / function as [`init.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`.
+///
+/// # 3. `extern` block
+///
+/// This `extern` block contains a list of interrupts which are *not* used by the application as
+/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be
+/// used to dispatch *multiple* software tasks *at the same priority level*.
+///
+/// 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.
+#[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 app = match syntax::App::parse(items, args) {
+ Err(e) => return e.to_compile_error().into(),
+ Ok(app) => app,
+ };
-fn run(ts: TokenStream) -> Result<TokenStream> {
- let app = App::parse(ts)?.check()?;
- let app = check::app(app)?;
+ // Check the specification
+ if let Err(e) = check::app(&app) {
+ return e.to_compile_error().into();
+ }
- let ownerships = analyze::app(&app);
- let tokens = trans::app(&app, &ownerships);
+ // Ceiling analysis
+ let analysis = analyze::app(&app);
- Ok(tokens.into())
+ // Code generation
+ codegen::app(&app, &analysis)
}
diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs
new file mode 100644
index 00000000..24586dcf
--- /dev/null
+++ b/macros/src/syntax.rs
@@ -0,0 +1,1235 @@
+use std::{
+ collections::{HashMap, HashSet},
+ 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 = HashMap::new();
+ let mut interrupts = HashMap::new();
+ let mut resources = HashMap::new();
+ let mut tasks = HashMap::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<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<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<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<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<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 = HashSet<Ident>;
+
+pub type Exceptions = HashMap<Ident, Exception>;
+
+pub type Interrupts = HashMap<Ident, Interrupt>;
+
+pub type Resources = HashMap<Ident, Resource>;
+
+pub type Statics = Vec<ItemStatic>;
+
+pub type Tasks = HashMap<Ident, Task>;
+
+pub type FreeInterrupts = HashMap<Ident, FreeInterrupt>;
+
+pub struct Idle {
+ pub args: IdleArgs,
+ pub attrs: Vec<Attribute>,
+ pub unsafety: Option<Token![unsafe]>,
+ pub statics: HashMap<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()),
+ })
+ }
+}
+
+pub struct Assign {
+ pub left: Ident,
+ pub right: Box<Expr>,
+}
+
+pub struct Init {
+ pub args: InitArgs,
+ pub attrs: Vec<Attribute>,
+ pub unsafety: Option<Token![unsafe]>,
+ pub statics: HashMap<Ident, Static>,
+ pub stmts: Vec<Stmt>,
+ pub assigns: Vec<Assign>,
+}
+
+impl Init {
+ fn check(args: InitArgs, 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,
+ "`init` must have type signature `[unsafe] fn()`",
+ ));
+ }
+
+ let (statics, stmts) = extract_statics(item.block.stmts);
+ let (stmts, assigns) = extract_assignments(stmts);
+
+ Ok(Init {
+ args,
+ attrs: item.attrs,
+ unsafety: item.unsafety,
+ statics: Static::parse(statics)?,
+ stmts,
+ assigns,
+ })
+ }
+}
+
+pub struct Exception {
+ pub args: ExceptionArgs,
+ pub attrs: Vec<Attribute>,
+ pub unsafety: Option<Token![unsafe]>,
+ pub statics: Statics,
+ pub stmts: Vec<Stmt>,
+}
+
+pub struct ExceptionArgs {
+ pub priority: u8,
+ pub resources: Idents,
+ pub schedule: Idents,
+ pub spawn: Idents,
+}
+
+impl Parse for ExceptionArgs {
+ fn parse(input: ParseStream) -> parse::Result<Self> {
+ parse_args(input, false).map(
+ |TaskArgs {
+ priority,
+ resources,
+ schedule,
+ spawn,
+ ..
+ }| {
+ ExceptionArgs {
+ 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 &*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,
+ stmts,
+ })
+ }
+}
+
+pub struct Interrupt {
+ pub args: InterruptArgs,
+ pub attrs: Vec<Attribute>,
+ pub unsafety: Option<Token![unsafe]>,
+ pub statics: Statics,
+ 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,
+ stmts,
+ })
+ }
+}
+
+pub struct Resource {
+ pub singleton: bool,
+ 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())),
+ );
+ }
+
+ Ok(Resource {
+ singleton: pos.is_some(),
+ attrs: item.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 Default for TaskArgs {
+ fn default() -> Self {
+ TaskArgs {
+ capacity: None,
+ priority: 1,
+ resources: Idents::new(),
+ schedule: Idents::new(),
+ spawn: Idents::new(),
+ }
+ }
+}
+
+impl Parse for TaskArgs {
+ fn parse(input: ParseStream) -> parse::Result<Self> {
+ parse_args(input, true)
+ }
+}
+
+// Parser shared by TaskArgs and ExceptionArgs / InterruptArgs
+fn parse_args(input: ParseStream, accept_capacity: bool) -> parse::Result<TaskArgs> {
+ if input.is_empty() {
+ return Ok(TaskArgs::default());
+ }
+
+ 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 {
+ "capacity" if accept_capacity => {
+ // #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" => {
+ // #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) {
+ return Err(parse::Error::new(
+ lit.span(),
+ "this literal must be in the range 0...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(),
+ "expected one of: priority, resources, schedule or spawn",
+ ))
+ }
+ }
+
+ if content.is_empty() {
+ break;
+ }
+
+ // ,
+ let _: Token![,] = content.parse()?;
+ }
+
+ Ok(TaskArgs {
+ 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 {
+ pub attrs: Vec<Attribute>,
+ pub ty: Box<Type>,
+ pub expr: Box<Expr>,
+}
+
+impl Static {
+ fn parse(items: Vec<ItemStatic>) -> parse::Result<HashMap<Ident, Static>> {
+ let mut statics = HashMap::new();
+
+ for item in items {
+ if statics.contains_key(&item.ident) {
+ return Err(parse::Error::new(
+ item.ident.span(),
+ "this `static` is listed twice",
+ ));
+ }
+
+ statics.insert(
+ item.ident,
+ Static {
+ attrs: item.attrs,
+ ty: item.ty,
+ expr: item.expr,
+ },
+ );
+ }
+
+ Ok(statics)
+ }
+}
+
+pub struct Task {
+ pub args: TaskArgs,
+ pub attrs: Vec<Attribute>,
+ pub unsafety: Option<Token![unsafe]>,
+ pub inputs: Vec<ArgCaptured>,
+ pub statics: HashMap<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`",
+ ));
+ }
+ _ => {}
+ }
+
+ Ok(Task {
+ args,
+ attrs: item.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
+ }
+}
+
+/// 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)
+}
+
+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 {
+ 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/trans.rs b/macros/src/trans.rs
deleted file mode 100644
index dcd6cfb6..00000000
--- a/macros/src/trans.rs
+++ /dev/null
@@ -1,631 +0,0 @@
-use proc_macro2::{TokenStream, Span};
-use syn::{Ident, LitStr};
-
-use analyze::Ownerships;
-use check::{App, Kind};
-
-fn krate() -> Ident {
- Ident::new("rtfm", Span::call_site())
-}
-
-pub fn app(app: &App, ownerships: &Ownerships) -> TokenStream {
- let mut root = vec![];
- let mut main = vec![quote!(#![allow(path_statements)])];
-
- ::trans::tasks(app, ownerships, &mut root, &mut main);
- ::trans::init(app, &mut main, &mut root);
- ::trans::idle(app, ownerships, &mut main, &mut root);
- ::trans::resources(app, ownerships, &mut root);
-
- root.push(quote! {
- #[allow(unsafe_code)]
- fn main() {
- #(#main)*
- }
- });
-
- quote!(#(#root)*)
-}
-
-fn idle(app: &App, ownerships: &Ownerships, main: &mut Vec<TokenStream>, root: &mut Vec<TokenStream>) {
- let krate = krate();
-
- let mut mod_items = vec![];
- let mut tys = vec![];
- let mut exprs = vec![];
-
- if !app.idle.resources.is_empty() {
- tys.push(quote!(&mut #krate::Threshold));
- exprs.push(quote!(unsafe { &mut #krate::Threshold::new(0) }));
- }
-
- if !app.idle.resources.is_empty() {
- let mut needs_reexport = false;
- for name in &app.idle.resources {
- if ownerships[name].is_owned() {
- if app.resources.get(name).is_some() {
- needs_reexport = true;
- break;
- }
- }
- }
-
- let super_ = if needs_reexport {
- None
- } else {
- Some(Ident::new("super", Span::call_site()))
- };
- let mut rexprs = vec![];
- let mut rfields = vec![];
- for name in &app.idle.resources {
- if ownerships[name].is_owned() {
- let resource = app.resources.get(name).expect(&format!(
- "BUG: resource {} assigned to `idle` has no definition",
- name
- ));
- let ty = &resource.ty;
-
- rfields.push(quote! {
- pub #name: &'static mut #ty,
- });
-
- let _name = Ident::new(&name.to_string(), Span::call_site());
- rexprs.push(if resource.expr.is_some() {
- quote! {
- #name: &mut #super_::#_name,
- }
- } else {
- quote! {
- #name: #super_::#_name.as_mut(),
- }
- });
- } else {
- rfields.push(quote! {
- pub #name: ::idle::#name,
- });
-
- rexprs.push(quote! {
- #name: ::idle::#name { _0: ::core::marker::PhantomData },
- });
- }
- }
-
- if needs_reexport {
- root.push(quote! {
- #[allow(non_camel_case_types)]
- #[allow(non_snake_case)]
- pub struct _idleResources {
- #(#rfields)*
- }
- });
-
- mod_items.push(quote! {
- pub use ::_idleResources as Resources;
- });
- } else {
- mod_items.push(quote! {
- #[allow(non_snake_case)]
- pub struct Resources {
- #(#rfields)*
- }
- });
- }
-
- mod_items.push(quote! {
- #[allow(unsafe_code)]
- impl Resources {
- pub unsafe fn new() -> Self {
- Resources {
- #(#rexprs)*
- }
- }
- }
- });
-
- tys.push(quote!(idle::Resources));
- exprs.push(quote!(unsafe { idle::Resources::new() }));
- }
-
- let device = &app.device;
- for name in &app.idle.resources {
- let ceiling = ownerships[name].ceiling();
-
- // owned resource
- if ceiling == 0 {
- continue;
- }
-
- let _name = Ident::new(&name.to_string(), Span::call_site());
- let resource = app.resources
- .get(name)
- .expect(&format!("BUG: resource {} has no definition", name));
-
- let ty = &resource.ty;
- let _static = if resource.expr.is_some() {
- quote!(#_name)
- } else {
- quote!(#_name.some)
- };
-
- mod_items.push(quote! {
- #[allow(non_camel_case_types)]
- pub struct #name { _0: ::core::marker::PhantomData<*const ()> }
- });
-
- root.push(quote! {
- #[allow(unsafe_code)]
- unsafe impl #krate::Resource for idle::#name {
- type Data = #ty;
-
- fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data {
- assert!(t.value() >= #ceiling);
-
- unsafe { &#_static }
- }
-
- fn borrow_mut<'cs>(
- &'cs mut self,
- t: &'cs Threshold,
- ) -> &'cs mut Self::Data {
- assert!(t.value() >= #ceiling);
-
- unsafe { &mut #_static }
- }
-
- fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R
- where
- F: FnOnce(&Self::Data, &mut Threshold) -> R
- {
- unsafe {
- #krate::claim(
- &#_static,
- #ceiling,
- #device::NVIC_PRIO_BITS,
- t,
- f,
- )
- }
- }
-
- fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R
- where
- F: FnOnce(&mut Self::Data, &mut Threshold) -> R
- {
- unsafe {
- #krate::claim(
- &mut #_static,
- #ceiling,
- #device::NVIC_PRIO_BITS,
- t,
- f,
- )
- }
- }
- }
- });
- }
-
- if !mod_items.is_empty() {
- root.push(quote! {
- #[allow(unsafe_code)]
- mod idle {
- #(#mod_items)*
- }
- });
- }
-
- let idle = &app.idle.path;
- main.push(quote! {
- // type check
- let idle: fn(#(#tys),*) -> ! = #idle;
-
- idle(#(#exprs),*);
- });
-}
-
-fn init(app: &App, main: &mut Vec<TokenStream>, root: &mut Vec<TokenStream>) {
- let device = &app.device;
- let krate = krate();
-
- let mut tys = vec![quote!(init::Peripherals)];
- let mut exprs = vec![
- quote!{
- init::Peripherals {
- core: ::#device::CorePeripherals::steal(),
- device: ::#device::Peripherals::steal(),
- }
- },
- ];
- let mut ret = None;
- let mut mod_items = vec![];
-
- let (init_resources, late_resources): (Vec<_>, Vec<_>) = app.resources
- .iter()
- .partition(|&(_, res)| res.expr.is_some());
-
- if !init_resources.is_empty() {
- let mut fields = vec![];
- let mut lifetime = None;
- let mut rexprs = vec![];
-
- for (name, resource) in init_resources {
- let ty = &resource.ty;
-
- if app.init.resources.contains(name) {
- fields.push(quote! {
- pub #name: &'static mut #ty,
- });
-
- let expr = &resource.expr;
- rexprs.push(quote!(#name: {
- static mut #name: #ty = #expr;
- &mut #name
- },));
- } else {
- let _name = Ident::new(&name.to_string(), Span::call_site());
- lifetime = Some(quote!('a));
-
- fields.push(quote! {
- pub #name: &'a mut #ty,
- });
-
- rexprs.push(quote! {
- #name: &mut ::#_name,
- });
- }
- }
-
- root.push(quote! {
- #[allow(non_camel_case_types)]
- #[allow(non_snake_case)]
- pub struct _initResources<#lifetime> {
- #(#fields)*
- }
- });
-
- mod_items.push(quote! {
- pub use ::_initResources as Resources;
-
- #[allow(unsafe_code)]
- impl<#lifetime> Resources<#lifetime> {
- pub unsafe fn new() -> Self {
- Resources {
- #(#rexprs)*
- }
- }
- }
- });
-
- tys.push(quote!(init::Resources));
- exprs.push(quote!(init::Resources::new()));
- }
-
- // Initialization statements for late resources
- let mut late_resource_init = vec![];
-
- if !late_resources.is_empty() {
- // `init` must initialize and return resources
-
- let mut fields = vec![];
-
- for (name, resource) in late_resources {
- let _name = Ident::new(&name.to_string(), Span::call_site());
-
- let ty = &resource.ty;
-
- fields.push(quote! {
- pub #name: #ty,
- });
-
- late_resource_init.push(quote! {
- #_name = #krate::UntaggedOption { some: _late_resources.#name };
- });
- }
-
- root.push(quote! {
- #[allow(non_camel_case_types)]
- #[allow(non_snake_case)]
- pub struct _initLateResources {
- #(#fields)*
- }
- });
-
- mod_items.push(quote! {
- pub use ::_initLateResources as LateResources;
- });
-
- // `init` must return the initialized resources
- ret = Some(quote!( -> ::init::LateResources));
- }
-
- root.push(quote! {
- #[allow(unsafe_code)]
- mod init {
- pub struct Peripherals {
- pub core: ::#device::CorePeripherals,
- pub device: ::#device::Peripherals,
- }
-
- #(#mod_items)*
- }
- });
-
- let mut exceptions = vec![];
- let mut interrupts = vec![];
- for (name, task) in &app.tasks {
- match task.kind {
- Kind::Exception(ref e) => {
- if exceptions.is_empty() {
- exceptions.push(quote! {
- let scb = &*#device::SCB::ptr();
- });
- }
-
- let nr = e.nr();
- let priority = task.priority;
- exceptions.push(quote! {
- let prio_bits = #device::NVIC_PRIO_BITS;
- let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits);
- scb.shpr[#nr - 4].write(hw);
- });
- }
- Kind::Interrupt { enabled } => {
- // Interrupt. These are enabled / disabled through the NVIC
- if interrupts.is_empty() {
- interrupts.push(quote! {
- use #device::Interrupt;
-
- let mut nvic: #device::NVIC = core::mem::transmute(());
- });
- }
-
- let priority = task.priority;
- interrupts.push(quote! {
- let prio_bits = #device::NVIC_PRIO_BITS;
- let hw = ((1 << prio_bits) - #priority) << (8 - prio_bits);
- nvic.set_priority(Interrupt::#name, hw);
- });
-
- if enabled {
- interrupts.push(quote! {
- nvic.enable(Interrupt::#name);
- });
- } else {
- interrupts.push(quote! {
- nvic.disable(Interrupt::#name);
- });
- }
- }
- }
- }
-
- let init = &app.init.path;
- main.push(quote! {
- // type check
- let init: fn(#(#tys,)*) #ret = #init;
-
- #krate::atomic(unsafe { &mut #krate::Threshold::new(0) }, |_t| unsafe {
- let _late_resources = init(#(#exprs,)*);
- #(#late_resource_init)*
-
- #(#exceptions)*
- #(#interrupts)*
- });
- });
-}
-
-fn resources(app: &App, ownerships: &Ownerships, root: &mut Vec<TokenStream>) {
- let krate = krate();
-
- for name in ownerships.keys() {
- let _name = Ident::new(&name.to_string(), Span::call_site());
-
- // Declare the static that holds the resource
- let resource = app.resources
- .get(name)
- .expect(&format!("BUG: resource {} has no definition", name));
-
- let expr = &resource.expr;
- let ty = &resource.ty;
-
- root.push(match *expr {
- Some(ref expr) => quote! {
- static mut #_name: #ty = #expr;
- },
- None => quote! {
- // Resource initialized in `init`
- static mut #_name: #krate::UntaggedOption<#ty> =
- #krate::UntaggedOption { none: () };
- },
- });
- }
-}
-
-fn tasks(app: &App, ownerships: &Ownerships, root: &mut Vec<TokenStream>, main: &mut Vec<TokenStream>) {
- let device = &app.device;
- let krate = krate();
-
- for (tname, task) in &app.tasks {
- let mut exprs = vec![];
- let mut fields = vec![];
- let mut items = vec![];
-
- let has_resources = !task.resources.is_empty();
-
- if has_resources {
- for rname in &task.resources {
- let ceiling = ownerships[rname].ceiling();
- let _rname = Ident::new(&rname.to_string(), Span::call_site());
- let resource = app.resources
- .get(rname)
- .expect(&format!("BUG: resource {} has no definition", rname));
-
- let ty = &resource.ty;
- let _static = if resource.expr.is_some() {
- quote!(#_rname)
- } else {
- quote!(#_rname.some)
- };
-
- items.push(quote! {
- #[allow(non_camel_case_types)]
- pub struct #rname { _0: PhantomData<*const ()> }
- });
-
- root.push(quote! {
- #[allow(unsafe_code)]
- unsafe impl #krate::Resource for #tname::#rname {
- type Data = #ty;
-
- fn borrow<'cs>(&'cs self, t: &'cs Threshold) -> &'cs Self::Data {
- assert!(t.value() >= #ceiling);
-
- unsafe { &#_static }
- }
-
- fn borrow_mut<'cs>(
- &'cs mut self,
- t: &'cs Threshold,
- ) -> &'cs mut Self::Data {
- assert!(t.value() >= #ceiling);
-
- unsafe { &mut #_static }
- }
-
- fn claim<R, F>(&self, t: &mut Threshold, f: F) -> R
- where
- F: FnOnce(&Self::Data, &mut Threshold) -> R
- {
- unsafe {
- #krate::claim(
- &#_static,
- #ceiling,
- #device::NVIC_PRIO_BITS,
- t,
- f,
- )
- }
- }
-
- fn claim_mut<R, F>(&mut self, t: &mut Threshold, f: F) -> R
- where
- F: FnOnce(&mut Self::Data, &mut Threshold) -> R
- {
- unsafe {
- #krate::claim(
- &mut #_static,
- #ceiling,
- #device::NVIC_PRIO_BITS,
- t,
- f,
- )
- }
- }
- }
- });
-
- if ceiling <= task.priority {
- root.push(quote! {
- #[allow(unsafe_code)]
- impl core::ops::Deref for #tname::#rname {
- type Target = #ty;
-
- fn deref(&self) -> &Self::Target {
- unsafe { &#_static }
- }
- }
-
- #[allow(unsafe_code)]
- impl core::ops::DerefMut for #tname::#rname {
- fn deref_mut(&mut self) -> &mut Self::Target {
- unsafe { &mut #_static }
- }
- }
- })
- }
-
- fields.push(quote! {
- pub #rname: #rname,
- });
-
- exprs.push(quote! {
- #rname: #rname { _0: PhantomData },
- });
- }
-
- items.push(quote! {
- #[allow(non_snake_case)]
- pub struct Resources {
- #(#fields)*
- }
- });
-
- items.push(quote! {
- #[allow(unsafe_code)]
- impl Resources {
- pub unsafe fn new() -> Self {
- Resources {
- #(#exprs)*
- }
- }
- }
- });
- }
-
- let mut tys = vec![];
- let mut exprs = vec![];
-
- let priority = task.priority;
- if has_resources {
- tys.push(quote!(&mut #krate::Threshold));
- exprs.push(quote! {
- &mut if #priority == 1 << #device::NVIC_PRIO_BITS {
- #krate::Threshold::new(::core::u8::MAX)
- } else {
- #krate::Threshold::new(#priority)
- }
- });
- }
-
- if has_resources {
- tys.push(quote!(#tname::Resources));
- exprs.push(quote!(#tname::Resources::new()));
- }
-
- let path = &task.path;
- let _tname = Ident::new(&tname.to_string(), Span::call_site());
- let export_name = LitStr::new(&tname.to_string(), Span::call_site());
- root.push(quote! {
- #[allow(non_snake_case)]
- #[allow(unsafe_code)]
- #[export_name = #export_name]
- pub unsafe extern "C" fn #_tname() {
- let f: fn(#(#tys,)*) = #path;
-
- f(#(#exprs,)*)
- }
- });
-
- root.push(quote!{
- #[allow(non_snake_case)]
- #[allow(unsafe_code)]
- mod #tname {
- #[allow(unused_imports)]
- use core::marker::PhantomData;
-
- #[allow(dead_code)]
- #[deny(const_err)]
- pub const CHECK_PRIORITY: (u8, u8) = (
- #priority - 1,
- (1 << ::#device::NVIC_PRIO_BITS) - #priority,
- );
-
- #(#items)*
- }
- });
-
- // after miri landed (?) rustc won't analyze `const` items unless they are used so we force
- // evaluation with this path statement
- main.push(quote!(#tname::CHECK_PRIORITY;));
- }
-}
diff --git a/memory.x b/memory.x
deleted file mode 100644
index 534d4786..00000000
--- a/memory.x
+++ /dev/null
@@ -1,6 +0,0 @@
-/* STM32F103C8V6 */
-MEMORY
-{
- FLASH : ORIGIN = 0x08000000, LENGTH = 64K
- RAM : ORIGIN = 0x20000000, LENGTH = 20K
-}
diff --git a/src/examples/_0_zero_tasks.rs b/src/examples/_0_zero_tasks.rs
deleted file mode 100644
index 0484bb9d..00000000
--- a/src/examples/_0_zero_tasks.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-//! Minimal example with zero tasks
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename
-//! extern crate stm32f103xx; // the device crate
-//!
-//! // import the procedural macro
-//! use rtfm::app;
-//!
-//! // This macro call indicates that this is a RTFM application
-//! //
-//! // This macro will expand to a `main` function so you don't need to supply
-//! // `main` yourself.
-//! app! {
-//! // this is the path to the device crate
-//! device: stm32f103xx,
-//! }
-//!
-//! // The initialization phase.
-//! //
-//! // This runs first and within a *global* critical section. Nothing can preempt
-//! // this function.
-//! fn init(p: init::Peripherals) {
-//! // This function has access to all the peripherals of the device
-//! p.core.SYST;
-//! p.device.GPIOA;
-//! p.device.RCC;
-//! // ..
-//! }
-//!
-//! // The idle loop.
-//! //
-//! // This runs after `init` and has a priority of 0. All tasks can preempt this
-//! // function. This function can never return so it must contain some sort of
-//! // endless loop.
-//! fn idle() -> ! {
-//! loop {
-//! // This puts the processor to sleep until there's a task to service
-//! rtfm::wfi();
-//! }
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_1_one_task.rs b/src/examples/_1_one_task.rs
deleted file mode 100644
index b9075a59..00000000
--- a/src/examples/_1_one_task.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-//! An application with one task
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m;
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use cortex_m::peripheral::syst::SystClkSource;
-//! use rtfm::{app, Threshold};
-//! use stm32f103xx::GPIOC;
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! // Here data resources are declared
-//! //
-//! // Data resources are static variables that are safe to share across tasks
-//! resources: {
-//! // Declaration of resources looks exactly like declaration of static
-//! // variables
-//! static ON: bool = false;
-//! },
-//!
-//! // Here tasks are declared
-//! //
-//! // Each task corresponds to an interrupt or an exception. Every time the
-//! // interrupt or exception becomes *pending* the corresponding task handler
-//! // will be executed.
-//! tasks: {
-//! // Here we declare that we'll use the SYS_TICK exception as a task
-//! SYS_TICK: {
-//! // Path to the task handler
-//! path: sys_tick,
-//!
-//! // These are the resources this task has access to.
-//! //
-//! // The resources listed here must also appear in `app.resources`
-//! resources: [ON],
-//! },
-//! }
-//! }
-//!
-//! fn init(mut p: init::Peripherals, r: init::Resources) {
-//! // `init` can modify all the `resources` declared in `app!`
-//! r.ON;
-//!
-//! // power on GPIOC
-//! p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled());
-//!
-//! // configure PC13 as output
-//! p.device.GPIOC.bsrr.write(|w| w.bs13().set());
-//! p.device
-//! .GPIOC
-//! .crh
-//! .modify(|_, w| w.mode13().output().cnf13().push());
-//!
-//! // configure the system timer to generate one interrupt every second
-//! p.core.SYST.set_clock_source(SystClkSource::Core);
-//! p.core.SYST.set_reload(8_000_000); // 1s
-//! p.core.SYST.enable_interrupt();
-//! p.core.SYST.enable_counter();
-//! }
-//!
-//! fn idle() -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//!
-//! // This is the task handler of the SYS_TICK exception
-//! //
-//! // `_t` is the preemption threshold token. We won't use it in this program.
-//! //
-//! // `r` is the set of resources this task has access to. `SYS_TICK::Resources`
-//! // has one field per resource declared in `app!`.
-//! #[allow(unsafe_code)]
-//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
-//! // toggle state
-//! *r.ON = !*r.ON;
-//!
-//! if *r.ON {
-//! // set the pin PC13 high
-//! // NOTE(unsafe) atomic write to a stateless register
-//! unsafe {
-//! (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set());
-//! }
-//! } else {
-//! // set the pin PC13 low
-//! // NOTE(unsafe) atomic write to a stateless register
-//! unsafe {
-//! (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset());
-//! }
-//! }
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_2_two_tasks.rs b/src/examples/_2_two_tasks.rs
deleted file mode 100644
index 516ff0c9..00000000
--- a/src/examples/_2_two_tasks.rs
+++ /dev/null
@@ -1,62 +0,0 @@
-//! Two tasks running at the *same* priority with access to the same resource
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Threshold};
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static COUNTER: u64 = 0;
-//! },
-//!
-//! // Both SYS_TICK and TIM2 have access to the `COUNTER` data
-//! tasks: {
-//! SYS_TICK: {
-//! path: sys_tick,
-//! resources: [COUNTER],
-//! },
-//!
-//! TIM2: {
-//! path: tim2,
-//! resources: [COUNTER],
-//! },
-//! },
-//! }
-//!
-//! fn init(_p: init::Peripherals, _r: init::Resources) {
-//! // ..
-//! }
-//!
-//! fn idle() -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//!
-//! // As both tasks are running at the same priority one can't preempt the other.
-//! // Thus both tasks have direct access to the resource
-//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
-//! // ..
-//!
-//! *r.COUNTER += 1;
-//!
-//! // ..
-//! }
-//!
-//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) {
-//! // ..
-//!
-//! *r.COUNTER += 1;
-//!
-//! // ..
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_3_preemption.rs b/src/examples/_3_preemption.rs
deleted file mode 100644
index 14c9d925..00000000
--- a/src/examples/_3_preemption.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-//! Two tasks running at *different* priorities with access to the same resource
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Resource, Threshold};
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static COUNTER: u64 = 0;
-//! },
-//!
-//! tasks: {
-//! // The `SYS_TICK` task has higher priority than `TIM2`
-//! SYS_TICK: {
-//! path: sys_tick,
-//! priority: 2,
-//! resources: [COUNTER],
-//! },
-//!
-//! TIM2: {
-//! path: tim2,
-//! priority: 1,
-//! resources: [COUNTER],
-//! },
-//! },
-//! }
-//!
-//! fn init(_p: init::Peripherals, _r: init::Resources) {
-//! // ..
-//! }
-//!
-//! fn idle() -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//!
-//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
-//! // ..
-//!
-//! // This task can't be preempted by `tim2` so it has direct access to the
-//! // resource data
-//! *r.COUNTER += 1;
-//!
-//! // ..
-//! }
-//!
-//! fn tim2(t: &mut Threshold, mut r: TIM2::Resources) {
-//! // ..
-//!
-//! // As this task runs at lower priority it needs a critical section to
-//! // prevent `sys_tick` from preempting it while it modifies this resource
-//! // data. The critical section is required to prevent data races which can
-//! // lead to undefined behavior.
-//! r.COUNTER.claim_mut(t, |counter, _t| {
-//! // `claim_mut` creates a critical section
-//! *counter += 1;
-//! });
-//!
-//! // ..
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_4_nested.rs b/src/examples/_4_nested.rs
deleted file mode 100644
index 26f8fd84..00000000
--- a/src/examples/_4_nested.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-//! Nesting claims and how the preemption threshold works
-//!
-//! If you run this program you'll hit the breakpoints as indicated by the
-//! letters in the comments: A, then B, then C, etc.
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Resource, Threshold};
-//! use stm32f103xx::Interrupt;
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static LOW: u64 = 0;
-//! static HIGH: u64 = 0;
-//! },
-//!
-//! tasks: {
-//! EXTI0: {
-//! path: exti0,
-//! priority: 1,
-//! resources: [LOW, HIGH],
-//! },
-//!
-//! EXTI1: {
-//! path: exti1,
-//! priority: 2,
-//! resources: [LOW],
-//! },
-//!
-//! EXTI2: {
-//! path: exti2,
-//! priority: 3,
-//! resources: [HIGH],
-//! },
-//! },
-//! }
-//!
-//! fn init(_p: init::Peripherals, _r: init::Resources) {}
-//!
-//! fn idle() -> ! {
-//! // A
-//! rtfm::bkpt();
-//!
-//! // Sets task `exti0` as pending
-//! //
-//! // Because `exti0` has higher priority than `idle` it will be executed
-//! // immediately
-//! rtfm::set_pending(Interrupt::EXTI0); // ~> exti0
-//!
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//!
-//! #[allow(non_snake_case)]
-//! fn exti0(
-//! t: &mut Threshold,
-//! EXTI0::Resources {
-//! LOW: mut low,
-//! HIGH: mut high,
-//! }: EXTI0::Resources,
-//! ) {
-//! // Because this task has a priority of 1 the preemption threshold `t` also
-//! // starts at 1
-//!
-//! // B
-//! rtfm::bkpt();
-//!
-//! // Because `exti1` has higher priority than `exti0` it can preempt it
-//! rtfm::set_pending(Interrupt::EXTI1); // ~> exti1
-//!
-//! // A claim creates a critical section
-//! low.claim_mut(t, |_low, t| {
-//! // This claim increases the preemption threshold to 2
-//! //
-//! // 2 is just high enough to not race with task `exti1` for access to the
-//! // `LOW` resource
-//!
-//! // D
-//! rtfm::bkpt();
-//!
-//! // Now `exti1` can't preempt this task because its priority is equal to
-//! // the current preemption threshold
-//! rtfm::set_pending(Interrupt::EXTI1);
-//!
-//! // But `exti2` can, because its priority is higher than the current
-//! // preemption threshold
-//! rtfm::set_pending(Interrupt::EXTI2); // ~> exti2
-//!
-//! // F
-//! rtfm::bkpt();
-//!
-//! // Claims can be nested
-//! high.claim_mut(t, |_high, _| {
-//! // This claim increases the preemption threshold to 3
-//!
-//! // Now `exti2` can't preempt this task
-//! rtfm::set_pending(Interrupt::EXTI2);
-//!
-//! // G
-//! rtfm::bkpt();
-//! });
-//!
-//! // Upon leaving the critical section the preemption threshold drops back
-//! // to 2 and `exti2` immediately preempts this task
-//! // ~> exti2
-//! });
-//!
-//! // Once again the preemption threshold drops but this time to 1. Now the
-//! // pending `exti1` task can preempt this task
-//! // ~> exti1
-//! }
-//!
-//! fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {
-//! // C, I
-//! rtfm::bkpt();
-//! }
-//!
-//! fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {
-//! // E, H
-//! rtfm::bkpt();
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_5_late_resources.rs b/src/examples/_5_late_resources.rs
deleted file mode 100644
index 7ab90a4e..00000000
--- a/src/examples/_5_late_resources.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-//! Demonstrates initialization of resources in `init`.
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Threshold};
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! // Usually, resources are initialized with a constant initializer:
-//! static ON: bool = false;
-//!
-//! // However, there are cases where this is not possible or not desired.
-//! // For example, there may not be a sensible value to use, or the type may
-//! // not be constructible in a constant (like `Vec`).
-//! //
-//! // While it is possible to use an `Option` in some cases, that requires
-//! // you to properly initialize it and `.unwrap()` it at every use. It
-//! // also consumes more memory.
-//! //
-//! // To solve this, it is possible to defer initialization of resources to
-//! // `init` by omitting the initializer. Doing that will require `init` to
-//! // return the values of all "late" resources.
-//! static IP_ADDRESS: u32;
-//!
-//! // PORT is used by 2 tasks, making it a shared resource. This just tests
-//! // another internal code path and is not important for the example.
-//! static PORT: u16;
-//! },
-//!
-//! idle: {
-//! // Test that late resources can be used in idle
-//! resources: [IP_ADDRESS],
-//! },
-//!
-//! tasks: {
-//! SYS_TICK: {
-//! priority: 1,
-//! path: sys_tick,
-//! resources: [IP_ADDRESS, PORT, ON],
-//! },
-//!
-//! EXTI0: {
-//! priority: 2,
-//! path: exti0,
-//! resources: [PORT],
-//! }
-//! }
-//! }
-//!
-//! // The signature of `init` is now required to have a specific return type.
-//! fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources {
-//! // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet
-//! // initialized.
-//! //_r.IP_ADDRESS; // doesn't compile
-//!
-//! // ...obtain value for IP_ADDRESS from EEPROM/DHCP...
-//! let ip_address = 0x7f000001;
-//!
-//! init::LateResources {
-//! // This struct will contain fields for all resources with omitted
-//! // initializers.
-//! IP_ADDRESS: ip_address,
-//! PORT: 0,
-//! }
-//! }
-//!
-//! fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) {
-//! // Other tasks can access late resources like any other, since they are
-//! // guaranteed to be initialized when tasks are run.
-//!
-//! r.IP_ADDRESS;
-//! }
-//!
-//! fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {}
-//!
-//! fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_6_safe_static_mut_ref.rs b/src/examples/_6_safe_static_mut_ref.rs
deleted file mode 100644
index 8f7267f5..00000000
--- a/src/examples/_6_safe_static_mut_ref.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-//! Safe creation of `&'static mut` references
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::app;
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static BUFFER: [u8; 16] = [0; 16];
-//! },
-//!
-//! init: {
-//! resources: [BUFFER],
-//! },
-//! }
-//!
-//! fn init(_p: init::Peripherals, r: init::Resources) {
-//! let _buf: &'static mut [u8; 16] = r.BUFFER;
-//! }
-//!
-//! fn idle() -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_7_generics.rs b/src/examples/_7_generics.rs
deleted file mode 100644
index 5dafdbf2..00000000
--- a/src/examples/_7_generics.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-//! Working with resources in a generic fashion
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Resource, Threshold};
-//! use stm32f103xx::{GPIOA, SPI1};
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static GPIOA: GPIOA;
-//! static SPI1: SPI1;
-//! },
-//!
-//! tasks: {
-//! EXTI0: {
-//! path: exti0,
-//! priority: 1,
-//! resources: [GPIOA, SPI1],
-//! },
-//!
-//! EXTI1: {
-//! path: exti1,
-//! priority: 2,
-//! resources: [GPIOA, SPI1],
-//! },
-//! },
-//! }
-//!
-//! fn init(p: init::Peripherals) -> init::LateResources {
-//! init::LateResources {
-//! GPIOA: p.device.GPIOA,
-//! SPI1: p.device.SPI1,
-//! }
-//! }
-//!
-//! fn idle() -> ! {
-//! loop {
-//! rtfm::wfi();
-//! }
-//! }
-//!
-//! // A generic function that uses some resources
-//! fn work<G, S>(t: &mut Threshold, gpioa: &G, spi1: &S)
-//! where
-//! G: Resource<Data = GPIOA>,
-//! S: Resource<Data = SPI1>,
-//! {
-//! gpioa.claim(t, |_gpioa, t| {
-//! // drive NSS low
-//!
-//! spi1.claim(t, |_spi1, _| {
-//! // transfer data
-//! });
-//!
-//! // drive NSS high
-//! });
-//! }
-//!
-//! // This task needs critical sections to access the resources
-//! fn exti0(t: &mut Threshold, r: EXTI0::Resources) {
-//! work(t, &r.GPIOA, &r.SPI1);
-//! }
-//!
-//! // This task has direct access to the resources
-//! fn exti1(t: &mut Threshold, r: EXTI1::Resources) {
-//! work(t, &r.GPIOA, &r.SPI1);
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/_8_full_syntax.rs b/src/examples/_8_full_syntax.rs
deleted file mode 100644
index cc7fbc22..00000000
--- a/src/examples/_8_full_syntax.rs
+++ /dev/null
@@ -1,87 +0,0 @@
-//! A showcase of the `app!` macro syntax
-//!
-//! ```
-//! #![deny(unsafe_code)]
-//! #![deny(warnings)]
-//! #![no_std]
-//!
-//! extern crate cortex_m_rtfm as rtfm;
-//! extern crate stm32f103xx;
-//!
-//! use rtfm::{app, Threshold};
-//!
-//! app! {
-//! device: stm32f103xx,
-//!
-//! resources: {
-//! static CO_OWNED: u32 = 0;
-//! static ON: bool = false;
-//! static OWNED: bool = false;
-//! static SHARED: bool = false;
-//! },
-//!
-//! init: {
-//! // This is the path to the `init` function
-//! //
-//! // `init` doesn't necessarily has to be in the root of the crate
-//! path: main::init,
-//! },
-//!
-//! idle: {
-//! // This is a path to the `idle` function
-//! //
-//! // `idle` doesn't necessarily has to be in the root of the crate
-//! path: main::idle,
-//! resources: [OWNED, SHARED],
-//! },
-//!
-//! tasks: {
-//! SYS_TICK: {
-//! path: sys_tick,
-//! // If omitted priority is assumed to be 1
-//! // priority: 1,
-//! resources: [CO_OWNED, ON, SHARED],
-//! },
-//!
-//! TIM2: {
-//! // Tasks are enabled, between `init` and `idle`, by default but they
-//! // can start disabled if `false` is specified here
-//! enabled: false,
-//! path: tim2,
-//! priority: 1,
-//! resources: [CO_OWNED],
-//! },
-//! },
-//! }
-//!
-//! mod main {
-//! use rtfm::{self, Resource, Threshold};
-//!
-//! pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {}
-//!
-//! pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! {
-//! loop {
-//! *r.OWNED = !*r.OWNED;
-//!
-//! if *r.OWNED {
-//! if r.SHARED.claim(t, |shared, _| *shared) {
-//! rtfm::wfi();
-//! }
-//! } else {
-//! r.SHARED.claim_mut(t, |shared, _| *shared = !*shared);
-//! }
-//! }
-//! }
-//! }
-//!
-//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) {
-//! *r.ON = !*r.ON;
-//!
-//! *r.CO_OWNED += 1;
-//! }
-//!
-//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) {
-//! *r.CO_OWNED += 1;
-//! }
-//! ```
-// Auto-generated. Do not modify.
diff --git a/src/examples/mod.rs b/src/examples/mod.rs
deleted file mode 100644
index 64d1e2ec..00000000
--- a/src/examples/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! Examples
-// Auto-generated. Do not modify.
-pub mod _0_zero_tasks;
-pub mod _1_one_task;
-pub mod _2_two_tasks;
-pub mod _3_preemption;
-pub mod _4_nested;
-pub mod _5_late_resources;
-pub mod _6_safe_static_mut_ref;
-pub mod _7_generics;
-pub mod _8_full_syntax;
diff --git a/src/export.rs b/src/export.rs
new file mode 100644
index 00000000..cb63e0ce
--- /dev/null
+++ b/src/export.rs
@@ -0,0 +1,84 @@
+/// IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE
+use core::{hint, ptr};
+
+#[cfg(armv7m)]
+use cortex_m::register::basepri;
+pub use cortex_m::{
+ asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource,
+ peripheral::Peripherals,
+};
+pub use cortex_m_rt::{entry, exception};
+pub use heapless::consts;
+use heapless::spsc::Queue;
+
+#[cfg(feature = "timer-queue")]
+pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue};
+
+pub type FreeQueue<N> = Queue<u8, N>;
+pub type ReadyQueue<T, N> = Queue<(T, u8), N>;
+
+#[cfg(armv7m)]
+#[inline(always)]
+pub fn run<F>(f: F)
+where
+ F: FnOnce(),
+{
+ let initial = basepri::read();
+ f();
+ unsafe { basepri::write(initial) }
+}
+
+#[cfg(not(armv7m))]
+#[inline(always)]
+pub fn run<F>(f: F)
+where
+ F: FnOnce(),
+{
+ f();
+}
+
+// TODO(MaybeUninit) Until core::mem::MaybeUninit is stabilized we use our own (inefficient)
+// implementation
+pub struct MaybeUninit<T> {
+ value: Option<T>,
+}
+
+impl<T> MaybeUninit<T> {
+ pub const fn uninitialized() -> Self {
+ MaybeUninit { value: None }
+ }
+
+ pub unsafe fn get_ref(&self) -> &T {
+ if let Some(x) = self.value.as_ref() {
+ x
+ } else {
+ hint::unreachable_unchecked()
+ }
+ }
+
+ pub unsafe fn get_mut(&mut self) -> &mut T {
+ if let Some(x) = self.value.as_mut() {
+ x
+ } else {
+ hint::unreachable_unchecked()
+ }
+ }
+
+ pub fn set(&mut self, val: T) {
+ unsafe { ptr::write(&mut self.value, Some(val)) }
+ }
+}
+
+#[inline(always)]
+pub fn assert_send<T>()
+where
+ T: Send,
+{
+}
+
+#[inline(always)]
+pub fn assert_sync<T>()
+where
+ T: Sync,
+{
+}
diff --git a/src/lib.rs b/src/lib.rs
index 9d558875..74cf96ac 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,170 +1,342 @@
//! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
//!
-//! This crate is based on [the RTFM framework] created by the Embedded Systems
-//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren,
-//! and uses a simplified version of the Stack Resource Policy as scheduling
-//! policy (check the [references] for details).
+//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the
+//! library is `rtfm`.
//!
-//! [the RTFM framework]: http://www.rtfm-lang.org/
-//! [ltu]: https://www.ltu.se/?l=en
-//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
-//! [references]: ./index.html#references
+//! [`cortex-m-rtfm`]: https://crates.io/crates/cortex-m-rtfm
//!
-//! # Features
+//! The user level documentation can be found [here].
//!
-//! - **Event triggered tasks** as the unit of concurrency.
-//! - Support for prioritization of tasks and, thus, **preemptive
-//! multitasking**.
-//! - **Efficient and data race free memory sharing** through fine grained *non
-//! global* critical sections.
-//! - **Deadlock free execution** guaranteed at compile time.
-//! - **Minimal scheduling overhead** as the scheduler has no "software
-//! component": the hardware does all the scheduling.
-//! - **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 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 friendly tooling for that.)
+//! [here]: ../../book/index.html
//!
-//! # Constraints
+//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component
+//! of the framework.
//!
-//! - Tasks must run to completion. That's it, tasks can't contain endless
-//! loops. However, you can run an endless event loop in the `idle` *loop*.
+//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html
//!
-//! - Task priorities must remain constant at runtime.
+//! # Cargo features
//!
-//! # Dependencies
+//! - `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`].
//!
-//! The application crate must depend on a device crate generated using
-//! [`svd2rust`] v0.12.x and the "rt" feature of that crate must be enabled. The
-//! SVD file used to generate the device crate *must* contain [`<cpu>`]
-//! information.
-//!
-//! [`svd2rust`]: https://docs.rs/svd2rust/0.12.0/svd2rust/
-//! [`<cpu>`]: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_cpu.html
-//!
-//! # `app!`
-//!
-//! The `app!` macro is documented [here].
-//!
-//! [here]: https://docs.rs/cortex-m-rtfm-macros/0.3.0/cortex_m_rtfm_macros/fn.app.html
-//!
-//! # Important: Cortex-M7 devices
-//!
-//! If targeting a Cortex-M7 device with revision r0p1 then you MUST enable the `cm7-r0p1` Cargo
-//! feature of this crate or the `Resource.claim` and `Resource.claim_mut` methods WILL misbehave.
-//!
-//! # Examples
-//!
-//! In increasing grade of complexity. See the [examples](./examples/index.html)
-//! module.
-//!
-//! # References
-//!
-//! - Baker, T. P. (1991). Stack-based scheduling of realtime processes.
-//! *Real-Time Systems*, 3(1), 67-99.
-//!
-//! > The original Stack Resource Policy paper. [PDF][srp].
-//!
-//! [srp]: http://www.cs.fsu.edu/~baker/papers/mstacks3.pdf
-//!
-//! - Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
-//! (2013, June). Real-time for the masses, step 1: Programming API and static
-//! priority SRP kernel primitives. In Industrial Embedded Systems (SIES),
-//! 2013 8th IEEE International Symposium on (pp. 110-113). IEEE.
-//!
-//! > A description of the RTFM task and resource model. [PDF][rtfm]
-//!
-//! [rtfm]: http://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf
+//! [`Instant`]: struct.Instant.html
+//! [`Duration`]: struct.Duration.html
+
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
-extern crate cortex_m;
-extern crate cortex_m_rtfm_macros;
-extern crate rtfm_core;
-extern crate untagged_option;
+use core::{cell::Cell, u8};
+#[cfg(feature = "timer-queue")]
+use core::{cmp::Ordering, ops};
-use core::{mem, u8};
-
-pub use cortex_m::asm::{bkpt, wfi};
+#[cfg(not(feature = "timer-queue"))]
+use cortex_m::peripheral::SYST;
+#[cfg(armv7m)]
+use cortex_m::register::basepri;
+use cortex_m::{
+ interrupt::{self, Nr},
+ peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU},
+};
pub use cortex_m_rtfm_macros::app;
-pub use rtfm_core::{Resource, Threshold};
+
+#[doc(hidden)]
+pub mod export;
#[doc(hidden)]
-pub use untagged_option::UntaggedOption;
+#[cfg(feature = "timer-queue")]
+mod tq;
-use cortex_m::interrupt::{self, Nr};
-use cortex_m::peripheral::NVIC;
-#[cfg(not(armv6m))]
-use cortex_m::register::basepri;
+/// 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`.
+#[allow(non_snake_case)]
+pub struct Peripherals<'a> {
+ /// 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"))]
+ pub DCB: DCB,
+
+ /// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled)
+ #[cfg(not(feature = "timer-queue"))]
+ pub DWT: DWT,
-pub mod examples;
+ /// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants)
+ pub FPB: FPB,
-/// Executes the closure `f` in a preemption free context
+ /// Floating Point Unit (only present on `thumbv7em-none-eabihf`)
+ pub FPU: FPU,
+
+ /// Instrumentation Trace Macrocell (not present on Cortex-M0 variants)
+ pub ITM: ITM,
+
+ /// Memory Protection Unit
+ pub MPU: MPU,
+
+ // Nested Vector Interrupt Controller
+ // pub NVIC: NVIC,
+ /// System Control Block
+ pub SCB: &'a mut SCB,
+
+ /// SysTick: System Timer (not present if the `timer-queue` is enabled)
+ #[cfg(not(feature = "timer-queue"))]
+ 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`
///
-/// During the execution of the closure no task can preempt the current task.
-pub fn atomic<R, F>(t: &mut Threshold, f: F) -> R
-where
- F: FnOnce(&mut Threshold) -> R,
-{
- if t.value() == u8::MAX {
- f(t)
- } else {
- interrupt::disable();
- let r = f(&mut unsafe { Threshold::max() });
- unsafe { interrupt::enable() };
- r
+/// 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)
}
}
-#[inline]
-#[doc(hidden)]
-pub unsafe fn claim<T, R, F>(
- data: T,
- ceiling: u8,
- _nvic_prio_bits: u8,
- t: &mut Threshold,
- f: F,
-) -> R
-where
- F: FnOnce(T, &mut Threshold) -> R,
-{
- if ceiling > t.value() {
- match () {
- #[cfg(armv6m)]
- () => atomic(t, |t| f(data, t)),
+#[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;
- #[cfg(not(armv6m))]
- () => {
- let max_priority = 1 << _nvic_prio_bits;
+ fn add(mut self, dur: Duration) -> Self {
+ self += dur;
+ self
+ }
+}
- if ceiling == max_priority {
- atomic(t, |t| f(data, t))
+#[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, Eq, Ord, PartialEq, PartialOrd)]
+#[cfg(feature = "timer-queue")]
+pub struct Duration(u32);
+
+#[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.
+///
+/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers
+pub unsafe trait Mutex {
+ /// IMPLEMENTATION DETAIL. DO NOT USE THIS CONSTANT
+ #[doc(hidden)]
+ const CEILING: u8;
+
+ /// IMPLEMENTATION DETAIL. DO NOT USE THIS CONSTANT
+ #[doc(hidden)]
+ const NVIC_PRIO_BITS: u8;
+
+ /// Data protected by the mutex
+ type Data: Send;
+
+ /// IMPLEMENTATION DETAIL. DO NOT USE THIS METHOD
+ #[doc(hidden)]
+ unsafe fn priority(&self) -> &Cell<u8>;
+
+ /// IMPLEMENTATION DETAIL. DO NOT USE THIS METHOD
+ #[doc(hidden)]
+ fn ptr(&self) -> *mut Self::Data;
+
+ /// Creates a critical section and grants temporary access to the protected data
+ #[inline(always)]
+ #[cfg(armv7m)]
+ fn lock<R, F>(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut Self::Data) -> R,
+ {
+ unsafe {
+ let current = self.priority().get();
+
+ if self.priority().get() < Self::CEILING {
+ if Self::CEILING == (1 << Self::NVIC_PRIO_BITS) {
+ self.priority().set(u8::MAX);
+ let r = interrupt::free(|_| f(&mut *self.ptr()));
+ self.priority().set(current);
+ r
} else {
- let old = basepri::read();
- let hw = (max_priority - ceiling) << (8 - _nvic_prio_bits);
- basepri::write(hw);
- let ret = f(data, &mut Threshold::new(ceiling));
- basepri::write(old);
- ret
+ self.priority().set(Self::CEILING);
+ basepri::write(logical2hw(Self::CEILING, Self::NVIC_PRIO_BITS));
+ let r = f(&mut *self.ptr());
+ basepri::write(logical2hw(current, Self::NVIC_PRIO_BITS));
+ self.priority().set(current);
+ r
}
+ } else {
+ f(&mut *self.ptr())
}
}
- } else {
- f(data, t)
}
+
+ /// Creates a critical section and grants temporary access to the protected data
+ #[cfg(not(armv7m))]
+ fn lock<R, F>(&mut self, f: F) -> R
+ where
+ F: FnOnce(&mut Self::Data) -> R,
+ {
+ unsafe {
+ let current = self.priority().get();
+
+ if self.priority().get() < Self::CEILING {
+ self.priority().set(u8::MAX);
+ let r = interrupt::free(|_| f(&mut *self.ptr()));
+ self.priority().set(current);
+ r
+ } else {
+ f(&mut *self.ptr())
+ }
+ }
+ }
+}
+
+#[cfg(armv7m)]
+#[inline]
+fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 {
+ ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)
}
-/// Sets an interrupt, that is a task, as pending
+/// Sets the given `interrupt` as pending
///
-/// If the task priority is high enough the task will be serviced immediately,
-/// otherwise it will be serviced at some point after the current task ends.
-pub fn set_pending<I>(interrupt: I)
+/// This is a convenience function around
+/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend)
+pub fn pend<I>(interrupt: I)
where
I: Nr,
{
- // NOTE(safe) atomic write
- let mut nvic: NVIC = unsafe { mem::transmute(()) };
- nvic.set_pending(interrupt);
+ NVIC::pend(interrupt)
}
diff --git a/src/tq.rs b/src/tq.rs
new file mode 100644
index 00000000..88db2f84
--- /dev/null
+++ b/src/tq.rs
@@ -0,0 +1,135 @@
+use core::cmp::{self, Ordering};
+
+use cortex_m::peripheral::{SCB, SYST};
+use heapless::{binary_heap::Min, ArrayLength, BinaryHeap};
+
+use crate::{Instant, Mutex};
+
+pub struct TimerQueue<T, N>
+where
+ N: ArrayLength<NotReady<T>>,
+ T: Copy,
+{
+ pub syst: SYST,
+ pub queue: BinaryHeap<NotReady<T>, N, Min>,
+}
+
+impl<T, N> TimerQueue<T, N>
+where
+ N: ArrayLength<NotReady<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>) {
+ let mut is_empty = true;
+ if self
+ .queue
+ .peek()
+ .map(|head| {
+ is_empty = false;
+ nr.instant < head.instant
+ })
+ .unwrap_or(true)
+ {
+ if is_empty {
+ self.syst.enable_interrupt();
+ }
+
+ // set SysTick pending
+ (*SCB::ptr()).icsr.write(1 << 26);
+ }
+
+ self.queue.push_unchecked(nr);
+ }
+}
+
+pub struct NotReady<T>
+where
+ T: Copy,
+{
+ pub index: u8,
+ pub instant: Instant,
+ pub task: T,
+}
+
+impl<T> Eq for NotReady<T> where T: Copy {}
+
+impl<T> Ord for NotReady<T>
+where
+ T: Copy,
+{
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.instant.cmp(&other.instant)
+ }
+}
+
+impl<T> PartialEq for NotReady<T>
+where
+ T: Copy,
+{
+ fn eq(&self, other: &Self) -> bool {
+ self.instant == other.instant
+ }
+}
+
+impl<T> PartialOrd for NotReady<T>
+where
+ T: Copy,
+{
+ 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<Data = 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.rs b/tests/cfail.rs
deleted file mode 100644
index 6572d657..00000000
--- a/tests/cfail.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-extern crate compiletest_rs as compiletest;
-
-use std::path::PathBuf;
-
-use compiletest::common::Mode;
-use compiletest::Config;
-
-#[test]
-fn cfail() {
- let mut config = Config::default();
- config.mode = Mode::CompileFail;
- config.src_base = PathBuf::from(format!("tests/cfail"));
- config.target_rustcflags =
- Some("-C panic=abort -L target/debug -L target/debug/deps ".to_string());
-
- compiletest::run_tests(&config);
-}
diff --git a/tests/cfail/critical-section.rs b/tests/cfail/critical-section.rs
deleted file mode 100644
index c0f475c4..00000000
--- a/tests/cfail/critical-section.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![feature(const_fn)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static ON: bool = false;
- },
-
- idle: {
- resources: [ON],
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [ON],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle(t: &mut Threshold, r: idle::Resources) -> ! {
- let state = rtfm::atomic(t, |t| {
- // ERROR borrow can't escape this *global* critical section
- r.ON.borrow(t) //~ error cannot infer an appropriate lifetime
- });
-
- let state = r.ON.claim(t, |state, _t| {
- // ERROR borrow can't escape this critical section
- state //~ error cannot infer an appropriate lifetime
- });
-
- loop {}
-}
-
-fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {}
diff --git a/tests/cfail/duplicated-task.rs b/tests/cfail/duplicated-task.rs
deleted file mode 100644
index 885c961c..00000000
--- a/tests/cfail/duplicated-task.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! {
- //~^ error proc macro panicked
- device: stm32f103xx,
-
- tasks: {
- SYS_TICK: {
- priority: 1,
- },
-
- SYS_TICK: {
- priority: 2,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {}
diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs
new file mode 100644
index 00000000..692a57c7
--- /dev/null
+++ b/tests/cfail/exception-divergent.rs
@@ -0,0 +1,20 @@
+#![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
new file mode 100644
index 00000000..cb0711ce
--- /dev/null
+++ b/tests/cfail/exception-input.rs
@@ -0,0 +1,19 @@
+#![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
new file mode 100644
index 00000000..0a7fb520
--- /dev/null
+++ b/tests/cfail/exception-invalid.rs
@@ -0,0 +1,19 @@
+#![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
new file mode 100644
index 00000000..758dbdd7
--- /dev/null
+++ b/tests/cfail/exception-output.rs
@@ -0,0 +1,20 @@
+#![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
new file mode 100644
index 00000000..69d73dbc
--- /dev/null
+++ b/tests/cfail/exception-sys-tick.rs
@@ -0,0 +1,19 @@
+#![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/exception.rs b/tests/cfail/exception.rs
deleted file mode 100644
index b4e025fc..00000000
--- a/tests/cfail/exception.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error proc macro panicked
- device: stm32f103xx,
-
- tasks: {
- // ERROR exceptions can't be enabled / disabled here
- SYS_TICK: {
- enabled: true,
- priority: 1,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
diff --git a/tests/cfail/idle-input.rs b/tests/cfail/idle-input.rs
new file mode 100644
index 00000000..5095977e
--- /dev/null
+++ b/tests/cfail/idle-input.rs
@@ -0,0 +1,19 @@
+#![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
new file mode 100644
index 00000000..e90eff08
--- /dev/null
+++ b/tests/cfail/idle-not-divergent.rs
@@ -0,0 +1,19 @@
+#![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/idle.rs b/tests/cfail/idle.rs
deleted file mode 100644
index ef20e1a8..00000000
--- a/tests/cfail/idle.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error mismatched types
- device: stm32f103xx,
-}
-
-fn init(_p: init::Peripherals) {}
-
-// ERROR `idle` must be a diverging function
-fn idle() {}
diff --git a/tests/cfail/init-divergent.rs b/tests/cfail/init-divergent.rs
new file mode 100644
index 00000000..400c805e
--- /dev/null
+++ b/tests/cfail/init-divergent.rs
@@ -0,0 +1,17 @@
+#![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()`
+ loop {}
+ }
+};
diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs
new file mode 100644
index 00000000..fa79099c
--- /dev/null
+++ b/tests/cfail/init-input.rs
@@ -0,0 +1,16 @@
+#![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()`
+ }
+};
diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs
new file mode 100644
index 00000000..3ac495f5
--- /dev/null
+++ b/tests/cfail/init-not-send.rs
@@ -0,0 +1,30 @@
+//! 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
new file mode 100644
index 00000000..1200aca7
--- /dev/null
+++ b/tests/cfail/init-output.rs
@@ -0,0 +1,17 @@
+#![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()`
+ 0
+ }
+};
diff --git a/tests/cfail/init-resource-share-idle.rs b/tests/cfail/init-resource-share-idle.rs
deleted file mode 100644
index 5b29f302..00000000
--- a/tests/cfail/init-resource-share-idle.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ proc macro panicked
- device: stm32f103xx,
-
- resources: {
- static BUFFER: [u8; 16] = [0; 16];
- },
-
- init: {
- resources: [BUFFER],
- },
-
- idle: {
- resources: [BUFFER],
- //~^ error: this resource is owned by `init` and can't be shared
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle(_r: init::Resources) -> ! {
- loop {}
-}
diff --git a/tests/cfail/init-resource-share-task.rs b/tests/cfail/init-resource-share-task.rs
deleted file mode 100644
index a93e840e..00000000
--- a/tests/cfail/init-resource-share-task.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ proc macro panicked
- device: stm32f103xx,
-
- resources: {
- static BUFFER: [u8; 16] = [0; 16];
- },
-
- init: {
- resources: [BUFFER],
- },
-
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- resources: [BUFFER],
- //~^ error: this resource is owned by `init` and can't be shared
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn sys_tick() {}
diff --git a/tests/cfail/init.rs b/tests/cfail/init.rs
deleted file mode 100644
index 057a2ee2..00000000
--- a/tests/cfail/init.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error mismatched types
- device: stm32f103xx,
-}
-
-// ERROR `init` must have signature `fn (init::Peripherals)`
-fn init() {}
-
-fn idle() -> ! {
- loop {}
-}
diff --git a/tests/cfail/insufficient-free-interrupts.rs b/tests/cfail/insufficient-free-interrupts.rs
new file mode 100644
index 00000000..baa2582b
--- /dev/null
+++ b/tests/cfail/insufficient-free-interrupts.rs
@@ -0,0 +1,17 @@
+#![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
new file mode 100644
index 00000000..4a015330
--- /dev/null
+++ b/tests/cfail/interrupt-divergent.rs
@@ -0,0 +1,20 @@
+#![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
new file mode 100644
index 00000000..d0240f4e
--- /dev/null
+++ b/tests/cfail/interrupt-input.rs
@@ -0,0 +1,19 @@
+#![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
new file mode 100644
index 00000000..37cd7c21
--- /dev/null
+++ b/tests/cfail/interrupt-output.rs
@@ -0,0 +1,20 @@
+#![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/interrupt.rs b/tests/cfail/interrupt.rs
deleted file mode 100644
index 522763a4..00000000
--- a/tests/cfail/interrupt.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error no variant named `EXTI33` found for type
- device: stm32f103xx,
-
- tasks: {
- EXTI33: {
- path: exti33,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn exti33() {}
diff --git a/tests/cfail/late-assigned-to-init.rs b/tests/cfail/late-assigned-to-init.rs
new file mode 100644
index 00000000..70a361c1
--- /dev/null
+++ b/tests/cfail/late-assigned-to-init.rs
@@ -0,0 +1,16 @@
+#![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
new file mode 100644
index 00000000..b9180fed
--- /dev/null
+++ b/tests/cfail/late-not-send.rs
@@ -0,0 +1,31 @@
+//! `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() {
+ X = NotSend { _0: PhantomData };
+ }
+
+ #[interrupt(resources = [X])]
+ fn UART0() {}
+};
diff --git a/tests/cfail/late-resource-init.rs b/tests/cfail/late-resource-init.rs
deleted file mode 100644
index 5235d930..00000000
--- a/tests/cfail/late-resource-init.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static A: u8 = 0;
- static LATE: u8;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [A, LATE],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [A, LATE],
- },
- },
-}
-
-fn init(_p: init::Peripherals, r: init::Resources) -> init::LateResources {
- // Try to use a resource that's not yet initialized:
- r.LATE;
- //~^ error: no field `LATE`
-
- init::LateResources {
- LATE: 0,
- }
-}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {}
-
-fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {}
diff --git a/tests/cfail/late-uninit.rs b/tests/cfail/late-uninit.rs
new file mode 100644
index 00000000..eeb9bd41
--- /dev/null
+++ b/tests/cfail/late-uninit.rs
@@ -0,0 +1,16 @@
+#![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/lock.rs b/tests/cfail/lock.rs
deleted file mode 100644
index 9cb0f3e6..00000000
--- a/tests/cfail/lock.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![feature(const_fn)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static ON: bool = false;
- static MAX: u8 = 0;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [MAX, ON],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [ON],
- },
-
- EXTI2: {
- path: exti2,
- priority: 16,
- resources: [MAX],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn exti0(mut t: &mut Threshold, mut r: EXTI0::Resources) {
- // ERROR need to lock to access the resource because priority < ceiling
- if *r.ON {
- //~^ error type `EXTI0::ON` cannot be dereferenced
- }
-
- // OK need to lock to access the resource
- if r.ON.claim(&mut t, |on, _| *on) {}
-
- // OK can claim a resource with maximum ceiling
- r.MAX.claim_mut(&mut t, |max, _| *max += 1);
-}
-
-fn exti1(mut t: &mut Threshold, r: EXTI1::Resources) {
- // OK to directly access the resource because priority == ceiling
- if *r.ON {}
-
- // though the resource can still be claimed -- the claim is a no-op
- if r.ON.claim(&mut t, |on, _| *on) {}
-}
-
-fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {}
diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs
new file mode 100644
index 00000000..7e3ca306
--- /dev/null
+++ b/tests/cfail/needs-send.rs
@@ -0,0 +1,30 @@
+#![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
new file mode 100644
index 00000000..f25f91a2
--- /dev/null
+++ b/tests/cfail/needs-sync.rs
@@ -0,0 +1,36 @@
+#![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/peripheral-alias.rs b/tests/cfail/peripheral-alias.rs
deleted file mode 100644
index 7f3790a5..00000000
--- a/tests/cfail/peripheral-alias.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error proc macro panicked
- device: stm32f103xx,
-
- tasks: {
- EXTI0: {
- enabled: true,
- priority: 1,
- // ERROR peripheral appears twice in this list
- resources: [GPIOA, GPIOA],
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs
deleted file mode 100644
index d63b9d05..00000000
--- a/tests/cfail/priority-too-high.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error referenced constant has errors
- //~^ error could not evaluate constant
- //~| error constant evaluation error
- device: stm32f103xx,
-
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- // ERROR priority must be in the range [1, 16]
- priority: 17,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn sys_tick() {}
diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs
deleted file mode 100644
index 476b7a07..00000000
--- a/tests/cfail/priority-too-low.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error referenced constant has errors
- //~^ error could not evaluate constant
- //~| error constant evaluation error
- device: stm32f103xx,
-
- tasks: {
- SYS_TICK: {
- path: sys_tick,
- // ERROR priority must be in the range [1, 16]
- priority: 0,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn sys_tick() {}
diff --git a/tests/cfail/resource-alias.rs b/tests/cfail/resource-alias.rs
deleted file mode 100644
index 81eeea07..00000000
--- a/tests/cfail/resource-alias.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::app;
-
-app! { //~ error proc macro panicked
- device: stm32f103xx,
-
- resources: {
- // resource `MAX` listed twice
- MAX: u8 = 0;
- MAX: u16 = 0;
- },
-
- tasks: {
- EXTI0: {
- enabled: true,
- priority: 1,
- },
- },
-}
-
-fn init(_p: init::Peripherals) {}
-
-fn idle() -> ! {
- loop {}
-}
diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs
new file mode 100644
index 00000000..f6d08a65
--- /dev/null
+++ b/tests/cfail/resource-not-declared.rs
@@ -0,0 +1,14 @@
+#![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-not-send-sync.rs b/tests/cfail/resource-not-send-sync.rs
deleted file mode 100644
index 27e5cb05..00000000
--- a/tests/cfail/resource-not-send-sync.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![feature(const_fn)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static SHARED: bool = false;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [SHARED],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [SHARED],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn is_send<T>(_: &T) where T: Send {}
-fn is_sync<T>(_: &T) where T: Sync {}
-
-fn exti0(_t: &mut Threshold, r: EXTI0::Resources) {
- // ERROR resource proxies can't be shared between tasks
- is_sync(&r.SHARED);
- //~^ error `*const ()` cannot be shared between threads safely
-
- // ERROR resource proxies are not `Send`able across tasks
- is_send(&r.SHARED);
- //~^ error `*const ()` cannot be sent between threads safely
-}
-
-fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {
-}
diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs
new file mode 100644
index 00000000..970fc6cc
--- /dev/null
+++ b/tests/cfail/resource-pub.rs
@@ -0,0 +1,17 @@
+#![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
new file mode 100644
index 00000000..3822d754
--- /dev/null
+++ b/tests/cfail/task-divergent.rs
@@ -0,0 +1,24 @@
+#![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
new file mode 100644
index 00000000..62d927b9
--- /dev/null
+++ b/tests/cfail/task-idle.rs
@@ -0,0 +1,23 @@
+#![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
new file mode 100644
index 00000000..3e6d87c4
--- /dev/null
+++ b/tests/cfail/task-not-declared.rs
@@ -0,0 +1,14 @@
+#![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/token-outlive.rs b/tests/cfail/token-outlive.rs
deleted file mode 100644
index 41ee827b..00000000
--- a/tests/cfail/token-outlive.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![feature(const_fn)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static STATE: bool = false;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [STATE],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [STATE],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn exti0(mut t: &mut Threshold, r: EXTI0::Resources) {
- // ERROR token should not outlive the critical section
- let t = r.STATE.claim(&mut t, |_state, t| t);
- //~^ error cannot infer an appropriate lifetime
-}
-
-fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {}
diff --git a/tests/cfail/token-transfer.rs b/tests/cfail/token-transfer.rs
deleted file mode 100644
index 5c6a22b1..00000000
--- a/tests/cfail/token-transfer.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![feature(const_fn)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Threshold};
-
-app! { //~ error `*const ()` cannot be sent between threads safely
- device: stm32f103xx,
-
- resources: {
- static TOKEN: Option<Threshold> = None;
- },
-
- idle: {
- resources: [TOKEN],
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- resources: [TOKEN],
- },
- }
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! {
- loop {}
-}
-
-fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {}
diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs
new file mode 100644
index 00000000..78ae5407
--- /dev/null
+++ b/tests/cfail/used-free-interrupt.rs
@@ -0,0 +1,21 @@
+#![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/cfail/wrong-threshold.rs b/tests/cfail/wrong-threshold.rs
deleted file mode 100644
index 86d8e262..00000000
--- a/tests/cfail/wrong-threshold.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-#![deny(unsafe_code)]
-#![deny(warnings)]
-#![no_std]
-
-extern crate cortex_m_rtfm as rtfm;
-extern crate stm32f103xx;
-
-use rtfm::{app, Resource, Threshold};
-
-app! {
- device: stm32f103xx,
-
- resources: {
- static A: u8 = 0;
- static B: u8 = 0;
- },
-
- tasks: {
- EXTI0: {
- path: exti0,
- priority: 1,
- resources: [A, B],
- },
-
- EXTI1: {
- path: exti1,
- priority: 2,
- resources: [A, B],
- },
- },
-}
-
-fn init(_p: init::Peripherals, _r: init::Resources) {}
-
-fn idle() -> ! {
- loop {}
-}
-
-fn exti0(mut ot: &mut Threshold, r: EXTI0::Resources) {
- r.A.claim(&mut ot, |_a, mut _it| {
- //~^ error cannot borrow `ot` as mutable more than once at a time
- // ERROR must use inner token `it` instead of the outer one (`ot`)
- r.B.claim(&mut ot, |_b, _| {})
- });
-}
-
-fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {}
diff --git a/tests/compiletest.rs b/tests/compiletest.rs
new file mode 100644
index 00000000..acc89546
--- /dev/null
+++ b/tests/compiletest.rs
@@ -0,0 +1,59 @@
+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/late-not-send.rs b/tests/cpass/late-not-send.rs
new file mode 100644
index 00000000..b7d2d60a
--- /dev/null
+++ b/tests/cpass/late-not-send.rs
@@ -0,0 +1,33 @@
+#![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)]
+const APP: () = {
+ static mut X: NotSend = ();
+ static mut Y: Option<NotSend> = None;
+
+ #[init(resources = [Y])]
+ fn init() {
+ *resources.Y = Some(NotSend { _0: PhantomData });
+
+ 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
new file mode 100644
index 00000000..43f51708
--- /dev/null
+++ b/tests/cpass/late-resource.rs
@@ -0,0 +1,22 @@
+//! Runtime initialized resources
+#![feature(extern_crate_item_prelude)] // ???
+#![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() {
+ X = 0;
+ Y = 1;
+ }
+};
diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs
new file mode 100644
index 00000000..0ca720cb
--- /dev/null
+++ b/tests/cpass/peripheral.rs
@@ -0,0 +1,19 @@
+//! Core and device peripherals
+#![feature(extern_crate_item_prelude)] // ???
+#![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
new file mode 100644
index 00000000..6a7a873c
--- /dev/null
+++ b/tests/cpass/resource.rs
@@ -0,0 +1,80 @@
+//! Check code generation of resources
+
+#![feature(extern_crate_item_prelude)] // ???
+#![no_main]
+#![no_std]
+
+extern crate lm3s6965;
+extern crate panic_halt;
+extern crate rtfm;
+
+use rtfm::app;
+
+#[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` when access from highest priority task
+ let _: &mut u32 = resources.S1;
+
+ // no `Mutex` when co-owned by cooperative (same priority) tasks
+ let _: &mut 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` when co-owned by cooperative (same priority) tasks
+ let _: &mut u32 = resources.S2;
+ }
+};
diff --git a/tests/cpass/schedule.rs b/tests/cpass/schedule.rs
new file mode 100644
index 00000000..bc7fe342
--- /dev/null
+++ b/tests/cpass/schedule.rs
@@ -0,0 +1,59 @@
+#![feature(extern_crate_item_prelude)] // ???
+#![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
new file mode 100644
index 00000000..77159f3f
--- /dev/null
+++ b/tests/cpass/singleton.rs
@@ -0,0 +1,67 @@
+#![feature(extern_crate_item_prelude)] // ???
+#![no_main]
+#![no_std]
+
+extern crate lm3s6965;
+extern crate owned_singleton;
+extern crate panic_halt;
+extern crate rtfm;
+
+use rtfm::app;
+
+#[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 mut 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 _: &mut S1 = resources.S1;
+ let _: &S2 = resources.S2;
+ }
+
+ #[interrupt(resources = [S1, S2])]
+ fn UART1() {
+ let _: &mut S1 = resources.S1;
+ let _: &S2 = resources.S2;
+ }
+};
diff --git a/tests/cpass/spawn.rs b/tests/cpass/spawn.rs
new file mode 100644
index 00000000..ba5a855f
--- /dev/null
+++ b/tests/cpass/spawn.rs
@@ -0,0 +1,60 @@
+//! Check code generation of `spawn`
+#![feature(extern_crate_item_prelude)] // ???
+#![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
new file mode 100644
index 00000000..86f461f1
--- /dev/null
+++ b/tests/cpass/unsafe.rs
@@ -0,0 +1,46 @@
+//! Check code generation of `unsafe` `init` / `idle` / `exception` / `interrupt` / `task`
+#![feature(extern_crate_item_prelude)] // ???
+#![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();
+ }
+};