closes #32
closes #33
This commit is contained in:
Jorge Aparicio 2018-11-03 17:02:41 +01:00
parent 653338e799
commit c631049efc
154 changed files with 7538 additions and 3276 deletions

View file

@ -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'
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "linker=arm-none-eabi-ld",
"-Z", "linker-flavor=ld",
]
[target.thumbv7em-none-eabi]
runner = 'arm-none-eabi-gdb'
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"

View file

@ -1,6 +0,0 @@
target remote :3333
monitor arm semihosting enable
load
step

1
.github/bors.toml vendored
View file

@ -1,3 +1,4 @@
delete_merged_branches = true
status = [
"continuous-integration/travis-ci/push",
]

4
.gitignore vendored
View file

@ -1,6 +1,6 @@
**/*.rs.bk
*.org
.#*
.gdb_history
/book/book
/target
Cargo.lock
target/

View file

@ -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: beta
- env: TARGET=thumbv6m-none-eabi
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 = staging OR branch = trying) OR (type = pull_request AND branch = master)
- 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=thumbv7m-none-eabi
rust: nightly
if: branch != master
- env: TARGET=thumbv7em-none-eabi
rust: nightly
if: branch != master
- env: TARGET=thumbv7em-none-eabihf
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:

View file

@ -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"
[lib]
name = "rtfm"
[[example]]
name = "baseline"
required-features = ["timer-queue"]
[[example]]
name = "periodic"
required-features = ["timer-queue"]
[[example]]
name = "schedule"
required-features = ["timer-queue"]
[[example]]
name = "types"
required-features = ["timer-queue"]
[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"
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"
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
compiletest_rs = "0.3.5"
[dev-dependencies]
alloc-singleton = "0.1.0"
cortex-m-semihosting = "0.3.1"
lm3s6965 = "0.1.3"
panic-halt = "0.2.0"
[dev-dependencies.cortex-m-rt]
features = ["abort-on-panic"]
version = "0.3.9"
[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"]

428
LICENSE-CC-BY-SA Normal file
View file

@ -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.

View file

@ -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

101
README.md
View file

@ -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.

5
book/book.toml Normal file
View file

@ -0,0 +1,5 @@
[book]
authors = ["Jorge Aparicio"]
multilingual = false
src = "src"
title = "Real Time For the Masses"

16
book/src/SUMMARY.md Normal file
View file

@ -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)

16
book/src/by-example.md Normal file
View file

@ -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

105
book/src/by-example/app.md Normal file
View file

@ -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.

View file

@ -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}}```

View file

@ -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}}```

View file

@ -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}}```

View file

@ -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}}```

View file

@ -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}}
```

View file

@ -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}}```

View file

@ -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}}
```

6
book/src/internals.md Normal file
View file

@ -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.

View file

@ -0,0 +1,3 @@
# Ceiling analysis
**TODO**

View file

@ -0,0 +1,3 @@
# Task dispatcher
**TODO**

View file

@ -0,0 +1,3 @@
# Timer queue
**TODO**

12
book/src/preface.md Normal file
View file

@ -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:}}

View file

@ -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");

View file

@ -1,20 +1,27 @@
set -euxo pipefail
main() {
cargo doc
rm -f .cargo/config
cargo doc --features timer-queue
( cd book && mdbook build )
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

4
ci/expected/baseline.run Normal file
View file

@ -0,0 +1,4 @@
init(baseline = Instant(0))
foo(baseline = Instant(0))
UART0(baseline = Instant(904))
foo(baseline = Instant(904))

5
ci/expected/capacity.run Normal file
View file

@ -0,0 +1,5 @@
foo(0)
foo(1)
foo(2)
foo(3)
bar

2
ci/expected/idle.run Normal file
View file

@ -0,0 +1,2 @@
init
idle

1
ci/expected/init.run Normal file
View file

@ -0,0 +1 @@
init

View file

@ -0,0 +1,4 @@
init
UART0 called 1 time
idle
UART0 called 2 times

1
ci/expected/late.run Normal file
View file

@ -0,0 +1 @@
received message: 42

5
ci/expected/lock.run Normal file
View file

@ -0,0 +1,5 @@
A
B - SHARED = 1
C
D - SHARED = 2
E

6
ci/expected/message.run Normal file
View file

@ -0,0 +1,6 @@
foo
bar(0)
baz(1, 2)
foo
bar(1)
baz(2, 3)

0
ci/expected/not-send.run Normal file
View file

0
ci/expected/not-sync.run Normal file
View file

3
ci/expected/periodic.run Normal file
View file

@ -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))

View file

@ -0,0 +1,3 @@
20000100 B bar::FREE_QUEUE::lk14244m263eivix
200000dc B bar::INPUTS::mi89534s44r1mnj1
20000000 T bar::ns9009yhw2dc2y25

View file

@ -0,0 +1,3 @@
20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20
200000dc B foo::INPUTS::thvubs85b91dg365
000002c6 T foo::sidaht420cg1mcm8

1
ci/expected/ramfunc.run Normal file
View file

@ -0,0 +1 @@
foo

2
ci/expected/resource.run Normal file
View file

@ -0,0 +1,2 @@
UART0: SHARED = 1
UART1: SHARED = 2

3
ci/expected/schedule.run Normal file
View file

@ -0,0 +1,3 @@
init @ Instant(0)
bar @ Instant(4000236)
foo @ Instant(8000173)

View file

@ -0,0 +1,2 @@
bar(2)
foo(1)

2
ci/expected/static.run Normal file
View file

@ -0,0 +1,2 @@
UART1(KEY = 0xdeadbeef)
UART0(KEY = 0xdeadbeef)

3
ci/expected/task.run Normal file
View file

@ -0,0 +1,3 @@
foo
baz
bar

0
ci/expected/types.run Normal file
View file

View file

@ -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

View file

@ -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
cargo check --target $TARGET
cargo check --target $TARGET --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 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

61
examples/baseline.rs Normal file
View file

@ -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();
}
};

57
examples/capacity.rs Normal file
View file

@ -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();
}
};

View file

@ -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) {}

View file

@ -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;
}

View file

@ -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);
}

43
examples/idle.rs Normal file
View file

@ -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 {}
}
};

44
examples/init.rs Normal file
View file

@ -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);
}
};

61
examples/interrupt.rs Normal file
View file

@ -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 { "" }
);
}
};

View file

@ -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();
}
}

65
examples/late.rs Normal file
View file

@ -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();
}
};

71
examples/lock.rs Normal file
View file

@ -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");
}
};

61
examples/message.rs Normal file
View file

@ -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();
}
};

View file

@ -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();
}

58
examples/not-send.rs Normal file
View file

@ -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();
}
};

41
examples/not-sync.rs Normal file
View file

@ -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();
}
};

View file

@ -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());
}
}
}

43
examples/periodic.rs Normal file
View file

@ -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();
}
};

View file

@ -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;
});
// ..
}

53
examples/ramfunc.rs Normal file
View file

@ -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();
}
};

60
examples/resource.rs Normal file
View file

@ -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);
}
};

View file

@ -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();
}
}

51
examples/schedule.rs Normal file
View file

@ -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();
}
};

69
examples/singleton.rs Normal file
View file

@ -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();
}
};

17
examples/smallest.rs Normal file
View file

@ -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() {}
};

47
examples/static.rs Normal file
View file

@ -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);
}
};

61
examples/task.rs Normal file
View file

@ -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();
}
};

View file

@ -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;
// ..
}

54
examples/types.rs Normal file
View file

@ -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();
}
};

View file

@ -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();
}
}

View file

@ -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

View file

@ -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 = []

View file

@ -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) {
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 } => {
if priority == task.priority {
*ownership = Ownership::Owned { priority };
} else {
Ownership::Owned { priority: ceiling } | Ownership::Shared { ceiling } => {
if priority != ceiling {
*ownership = Ownership::Shared {
ceiling: cmp::max(priority, task.priority),
ceiling: cmp::max(ceiling, priority),
};
let res = &app.resources[res];
if res.mutability.is_none() {
assert_sync.insert(res.ty.clone());
}
}
Ownership::Shared { ceiling } => {
if task.priority > ceiling {
*ownership = Ownership::Shared {
ceiling: task.priority,
};
}
}
}
continue;
}
ownerships.insert(
resource.clone(),
Ownership::Owned {
priority: task.priority,
},
);
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;
}
}
ownerships
// Compute the size of the timer queue
// Compute the priority of the timer queue, which matches the priority of the highest
// `schedule`-able task
let mut tq_capacity = 0;
let mut tq_priority = 1;
let mut tq_tasks = Idents::new();
for (_, task) in app.schedule_calls() {
tq_capacity += capacities[task];
tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority);
tq_tasks.insert(task.clone());
}
// Compute dispatchers capacities
// Determine which tasks are dispatched by which dispatcher
// Compute the timer queue priority which matches the priority of the highest priority
// dispatcher
let mut dispatchers = Dispatchers::new();
let mut free_interrupts = app.free_interrupts.iter();
let mut tasks = app.tasks.iter().collect::<Vec<_>>();
tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority));
for (name, task) in tasks {
let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| {
let (name, fi) = free_interrupts
.next()
.expect("BUG: not enough free_interrupts");
Dispatcher {
attrs: fi.attrs.clone(),
capacity: 0,
interrupt: name.clone(),
tasks: vec![],
}
});
dispatcher.capacity += capacities[name];
dispatcher.tasks.push(name.clone());
}
// All messages sent from `init` need to be `Send`
for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) {
tasks_assert_send.insert(task.clone());
}
// All late resources need to be `Send`, unless they are owned by `idle`
for (name, res) in &app.resources {
let owned_by_idle = Ownership::Owned { priority: 0 };
if res.expr.is_none()
&& ownerships
.get(name)
.map(|ship| *ship != owned_by_idle)
.unwrap_or(false)
{
resources_assert_send.insert(res.ty.clone());
}
}
// All resources shared with init need to be `Send`, unless they are owned by `idle`
// This is equivalent to late initialization (e.g. `static mut LATE: Option<T> = None`)
for name in &app.init.args.resources {
let owned_by_idle = Ownership::Owned { priority: 0 };
if ownerships
.get(name)
.map(|ship| *ship != owned_by_idle)
.unwrap_or(false)
{
resources_assert_send.insert(app.resources[name].ty.clone());
}
}
// Ceiling analysis of free queues (consumer end point) -- first pass
// Ceiling analysis of ready queues (producer end point)
// Also compute more Send-ness requirements
let mut free_queues: HashMap<_, _> = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
let mut ready_queues: HashMap<_, _> = dispatchers.keys().map(|level| (*level, 0)).collect();
for (priority, task) in app.spawn_calls() {
if let Some(priority) = priority {
// Users of `spawn` contend for the to-be-spawned task FREE_QUEUE
let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut");
*c = cmp::max(*c, priority);
let c = ready_queues
.get_mut(&app.tasks[task].args.priority)
.expect("BUG: ready_queues.get_mut");
*c = cmp::max(*c, priority);
// Send is required when sending messages from a task whose priority doesn't match the
// priority of the receiving task
if app.tasks[task].args.priority != priority {
tasks_assert_send.insert(task.clone());
}
} else {
// spawns from `init` are excluded from the ceiling analysis
}
}
// Ceiling analysis of free queues (consumer end point) -- second pass
// Ceiling analysis of the timer queue
let mut tq_ceiling = tq_priority;
for (priority, task) in app.schedule_calls() {
if let Some(priority) = priority {
// Users of `schedule` contend for the to-be-spawned task FREE_QUEUE (consumer end point)
let c = free_queues.get_mut(task).expect("BUG: free_queue.get_mut");
*c = cmp::max(*c, priority);
// Users of `schedule` contend for the timer queu
tq_ceiling = cmp::max(tq_ceiling, priority);
} else {
// spawns from `init` are excluded from the ceiling analysis
}
}
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,
}

View file

@ -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,
}
use syntax::App;
pub type Tasks = HashMap<Ident, Task>;
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",
));
}
}
#[allow(non_camel_case_types)]
pub enum Exception {
PENDSV,
SVCALL,
SYS_TICK,
}
// 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`",
));
}
}
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,
// 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`",
));
}
}
// 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))
})
}
pub fn nr(&self) -> usize {
match *self {
Exception::PENDSV => 14,
Exception::SVCALL => 11,
Exception::SYS_TICK => 15,
.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",
));
}
}
}
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<_>>()?,
};
Ok(app)
}
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"
);
Kind::Exception(e)
// 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" },
),
));
}
None => Kind::Interrupt {
enabled: task.enabled.unwrap_or(true),
},
};
Ok(Task {
kind,
path: task.path,
priority: task.priority.unwrap_or(1),
resources: task.resources,
})
// 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",
));
}
}
Ok(())
}

1815
macros/src/codegen.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -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,
};
// Check the specification
if let Err(e) = check::app(&app) {
return e.to_compile_error().into();
}
}
fn run(ts: TokenStream) -> Result<TokenStream> {
let app = App::parse(ts)?.check()?;
let app = check::app(app)?;
let ownerships = analyze::app(&app);
let tokens = trans::app(&app, &ownerships);
Ok(tokens.into())
// Ceiling analysis
let analysis = analyze::app(&app);
// Code generation
codegen::app(&app, &analysis)
}

1235
macros/src/syntax.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -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;));
}
}

View file

@ -1,6 +0,0 @@
/* STM32F103C8V6 */
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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;

84
src/export.rs Normal file
View file

@ -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,
{
}

View file

@ -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};
pub use cortex_m_rtfm_macros::app;
pub use rtfm_core::{Resource, Threshold};
#[doc(hidden)]
pub use untagged_option::UntaggedOption;
use cortex_m::interrupt::{self, Nr};
use cortex_m::peripheral::NVIC;
#[cfg(not(armv6m))]
#[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 mod examples;
#[doc(hidden)]
pub mod export;
#[doc(hidden)]
#[cfg(feature = "timer-queue")]
mod tq;
/// Executes the closure `f` in a preemption free context
/// Core peripherals
///
/// 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)
/// 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,
/// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants)
pub FPB: FPB,
/// 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`
///
/// This data type is only available when the `timer-queue` feature is enabled
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg(feature = "timer-queue")]
pub struct Instant(i32);
#[cfg(feature = "timer-queue")]
impl Instant {
/// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)]
pub fn artificial(timestamp: i32) -> Self {
Instant(timestamp)
}
/// Returns an instant corresponding to "now"
pub fn now() -> Self {
Instant(DWT::get_cycle_count() as i32)
}
/// Returns the amount of time elapsed since this instant was created.
pub fn elapsed(&self) -> Duration {
Instant::now() - *self
}
/// Returns the amount of time elapsed from another instant to this one.
pub fn duration_since(&self, earlier: Instant) -> Duration {
let diff = self.0 - earlier.0;
assert!(diff >= 0, "second instant is later than self");
Duration(diff as u32)
}
}
#[cfg(feature = "timer-queue")]
impl ops::AddAssign<Duration> for Instant {
fn add_assign(&mut self, dur: Duration) {
debug_assert!(dur.0 < (1 << 31));
self.0 = self.0.wrapping_add(dur.0 as i32);
}
}
#[cfg(feature = "timer-queue")]
impl ops::Add<Duration> for Instant {
type Output = Self;
fn add(mut self, dur: Duration) -> Self {
self += dur;
self
}
}
#[cfg(feature = "timer-queue")]
impl ops::SubAssign<Duration> for Instant {
fn sub_assign(&mut self, dur: Duration) {
// XXX should this be a non-debug assertion?
debug_assert!(dur.0 < (1 << 31));
self.0 = self.0.wrapping_sub(dur.0 as i32);
}
}
#[cfg(feature = "timer-queue")]
impl ops::Sub<Duration> for Instant {
type Output = Self;
fn sub(mut self, dur: Duration) -> Self {
self -= dur;
self
}
}
#[cfg(feature = "timer-queue")]
impl ops::Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}
#[cfg(feature = "timer-queue")]
impl Ord for Instant {
fn cmp(&self, rhs: &Self) -> Ordering {
self.0.wrapping_sub(rhs.0).cmp(&0)
}
}
#[cfg(feature = "timer-queue")]
impl PartialOrd for Instant {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
/// A `Duration` type to represent a span of time.
///
/// This data type is only available when the `timer-queue` feature is enabled
#[derive(Clone, Copy, 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 {
interrupt::disable();
let r = f(&mut unsafe { Threshold::max() });
unsafe { interrupt::enable() };
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())
}
}
}
/// 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]
#[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(not(armv6m))]
() => {
let max_priority = 1 << _nvic_prio_bits;
if ceiling == max_priority {
atomic(t, |t| f(data, t))
} 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
}
}
}
} else {
f(data, t)
}
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)
}

135
src/tq.rs Normal file
View file

@ -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;
}
}
}

Some files were not shown because too many files have changed in this diff Show more