mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-25 19:39:32 +01:00
Merge #97
97: v0.4.0 r=japaric a=japaric closes #32 closes #33 Co-authored-by: Jorge Aparicio <jorge@japaric.io>
This commit is contained in:
commit
777765e522
154 changed files with 7540 additions and 3271 deletions
|
@ -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"
|
6
.gdbinit
6
.gdbinit
|
@ -1,6 +0,0 @@
|
|||
target remote :3333
|
||||
|
||||
monitor arm semihosting enable
|
||||
|
||||
load
|
||||
step
|
1
.github/bors.toml
vendored
1
.github/bors.toml
vendored
|
@ -1,3 +1,4 @@
|
|||
delete_merged_branches = true
|
||||
status = [
|
||||
"continuous-integration/travis-ci/push",
|
||||
]
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
|||
**/*.rs.bk
|
||||
*.org
|
||||
.#*
|
||||
.gdb_history
|
||||
/book/book
|
||||
/target
|
||||
Cargo.lock
|
||||
target/
|
||||
|
|
34
.travis.yml
34
.travis.yml
|
@ -2,38 +2,48 @@ language: rust
|
|||
|
||||
matrix:
|
||||
include:
|
||||
# NOTE used to build docs on successful merges to master
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: 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:
|
||||
|
|
63
Cargo.toml
63
Cargo.toml
|
@ -4,33 +4,62 @@ authors = [
|
|||
"Per Lindgren <per.lindgren@ltu.se>",
|
||||
]
|
||||
categories = ["concurrency", "embedded", "no-std"]
|
||||
description = "Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers"
|
||||
documentation = "https://japaric.github.io/cortex-m-rtfm/cortex_m_rtfm/"
|
||||
description = "Real Time For the Masses (RTFM): a concurrency framework for building real time systems"
|
||||
documentation = "https://japaric.github.io/cortex-m-rtfm/book/"
|
||||
edition = "2018"
|
||||
keywords = ["arm", "cortex-m"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "cortex-m-rtfm"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/japaric/cortex-m-rtfm"
|
||||
version = "0.3.4"
|
||||
version = "0.4.0-beta.1"
|
||||
|
||||
[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
428
LICENSE-CC-BY-SA
Normal 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.
|
||||
|
|
@ -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
101
README.md
|
@ -1,25 +1,104 @@
|
|||
[![crates.io](https://img.shields.io/crates/v/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm)
|
||||
[![crates.io](https://img.shields.io/crates/d/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm)
|
||||
# Real Time For the Masses
|
||||
|
||||
# `cortex-m-rtfm`
|
||||
A concurrency framework for building real time systems.
|
||||
|
||||
> Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
|
||||
**IMPORTANT** This crate is currently in pre-release (beta) state . We reserve
|
||||
the right to make breaking changes in the syntax or to patch memory safety holes
|
||||
before the v0.4.0 release, which is planned for 2018-12-07. When v0.4.0 is
|
||||
released *all the pre-releases will be yanked*. If you run into a panic message
|
||||
or an unhelpful error message (e.g. misleading span), or if something doesn't
|
||||
behave the way you expect please open [an issue]!
|
||||
|
||||
# [Documentation](https://japaric.github.io/cortex-m-rtfm/cortex_m_rtfm/)
|
||||
[an issue]: https://github.com/japaric/cortex-m-rtfm/issues
|
||||
|
||||
# License
|
||||
## Features
|
||||
|
||||
Licensed under either of
|
||||
- **Tasks** as the unit of concurrency [^1]. Tasks can be *event triggered*
|
||||
(fired in response to asynchronous stimuli) or spawned by the application on
|
||||
demand.
|
||||
|
||||
- **Message passing** between tasks. Specifically, messages can be passed to
|
||||
software tasks at spawn time.
|
||||
|
||||
- **A timer queue** [^2]. Software tasks can be scheduled to run at some time
|
||||
in the future. This feature can be used to implement periodic tasks.
|
||||
|
||||
- Support for prioritization of tasks and, thus, **preemptive multitasking**.
|
||||
|
||||
- **Efficient and data race free memory sharing** through fine grained *priority
|
||||
based* critical sections [^1].
|
||||
|
||||
- **Deadlock free execution** guaranteed at compile time. This is an stronger
|
||||
guarantee than what's provided by [the standard `Mutex`
|
||||
abstraction][std-mutex].
|
||||
|
||||
[std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
|
||||
|
||||
- **Minimal scheduling overhead**. The task scheduler has minimal software
|
||||
footprint; the hardware does the bulk of the scheduling.
|
||||
|
||||
- **Highly efficient memory usage**: All the tasks share a single call stack and
|
||||
there's no hard dependency on a dynamic memory allocator.
|
||||
|
||||
- **All Cortex-M devices are fully supported**.
|
||||
|
||||
- This task model is amenable to known WCET (Worst Case Execution Time) analysis
|
||||
and scheduling analysis techniques. (Though we haven't yet developed Rust
|
||||
friendly tooling for that.)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rust 1.31.0+
|
||||
|
||||
- Applications must be written using the 2018 edition.
|
||||
|
||||
## [User documentation](https://japaric.github.io/cortex-m-rtfm/book/index.html)
|
||||
|
||||
## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This crate is based on [the RTFM language][rtfm-lang] created by the Embedded
|
||||
Systems group at [Luleå University of Technology][ltu], led by [Prof. Per
|
||||
Lindgren][per].
|
||||
|
||||
[rtfm-lang]: http://www.rtfm-lang.org/
|
||||
[ltu]: https://www.ltu.se/?l=en
|
||||
[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
|
||||
|
||||
## References
|
||||
|
||||
[^1]: Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
|
||||
(2013, June). Real-time for the masses, step 1: Programming API and static
|
||||
priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013
|
||||
8th IEEE International Symposium on (pp. 110-113). IEEE.
|
||||
|
||||
[^2]: Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho,
|
||||
L. M. (2016). Abstract timers and their implementation onto the arm cortex-m
|
||||
family of mcus. ACM SIGBED Review, 13(1), 48-53.
|
||||
|
||||
## License
|
||||
|
||||
All source code (including code snippets) is licensed under either of
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[https://www.apache.org/licenses/LICENSE-2.0][L1])
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||
[https://opensource.org/licenses/MIT][L2])
|
||||
|
||||
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
[L1]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
[L2]: https://opensource.org/licenses/MIT
|
||||
|
||||
at your option.
|
||||
|
||||
## Contribution
|
||||
The written prose contained within the book is licensed under the terms of the
|
||||
Creative Commons CC-BY-SA v4.0 license ([LICENSE-CC-BY-SA](LICENSE-CC-BY-SA) or
|
||||
[https://creativecommons.org/licenses/by-sa/4.0/legalcode][L3]).
|
||||
|
||||
[L3]: https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
||||
licensed as above, without any additional terms or conditions.
|
||||
|
|
5
book/book.toml
Normal file
5
book/book.toml
Normal 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
16
book/src/SUMMARY.md
Normal 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
16
book/src/by-example.md
Normal 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
105
book/src/by-example/app.md
Normal 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.
|
67
book/src/by-example/new.md
Normal file
67
book/src/by-example/new.md
Normal 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}}```
|
119
book/src/by-example/resources.md
Normal file
119
book/src/by-example/resources.md
Normal 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}}```
|
26
book/src/by-example/singletons.md
Normal file
26
book/src/by-example/singletons.md
Normal 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}}```
|
63
book/src/by-example/tasks.md
Normal file
63
book/src/by-example/tasks.md
Normal 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}}```
|
89
book/src/by-example/timer-queue.md
Normal file
89
book/src/by-example/timer-queue.md
Normal 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}}
|
||||
```
|
43
book/src/by-example/tips.md
Normal file
43
book/src/by-example/tips.md
Normal 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}}```
|
60
book/src/by-example/types-send-sync.md
Normal file
60
book/src/by-example/types-send-sync.md
Normal 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
6
book/src/internals.md
Normal 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.
|
3
book/src/internals/ceilings.md
Normal file
3
book/src/internals/ceilings.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Ceiling analysis
|
||||
|
||||
**TODO**
|
3
book/src/internals/tasks.md
Normal file
3
book/src/internals/tasks.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Task dispatcher
|
||||
|
||||
**TODO**
|
3
book/src/internals/timer-queue.md
Normal file
3
book/src/internals/timer-queue.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Timer queue
|
||||
|
||||
**TODO**
|
12
book/src/preface.md
Normal file
12
book/src/preface.md
Normal 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:}}
|
4
build.rs
4
build.rs
|
@ -3,8 +3,8 @@ use std::env;
|
|||
fn main() {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
|
||||
if target.starts_with("thumbv6m-") {
|
||||
println!("cargo:rustc-cfg=armv6m");
|
||||
if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") {
|
||||
println!("cargo:rustc-cfg=armv7m")
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
|
|
@ -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
4
ci/expected/baseline.run
Normal 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
5
ci/expected/capacity.run
Normal file
|
@ -0,0 +1,5 @@
|
|||
foo(0)
|
||||
foo(1)
|
||||
foo(2)
|
||||
foo(3)
|
||||
bar
|
2
ci/expected/idle.run
Normal file
2
ci/expected/idle.run
Normal file
|
@ -0,0 +1,2 @@
|
|||
init
|
||||
idle
|
1
ci/expected/init.run
Normal file
1
ci/expected/init.run
Normal file
|
@ -0,0 +1 @@
|
|||
init
|
4
ci/expected/interrupt.run
Normal file
4
ci/expected/interrupt.run
Normal file
|
@ -0,0 +1,4 @@
|
|||
init
|
||||
UART0 called 1 time
|
||||
idle
|
||||
UART0 called 2 times
|
1
ci/expected/late.run
Normal file
1
ci/expected/late.run
Normal file
|
@ -0,0 +1 @@
|
|||
received message: 42
|
5
ci/expected/lock.run
Normal file
5
ci/expected/lock.run
Normal file
|
@ -0,0 +1,5 @@
|
|||
A
|
||||
B - SHARED = 1
|
||||
C
|
||||
D - SHARED = 2
|
||||
E
|
6
ci/expected/message.run
Normal file
6
ci/expected/message.run
Normal 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
0
ci/expected/not-send.run
Normal file
0
ci/expected/not-sync.run
Normal file
0
ci/expected/not-sync.run
Normal file
3
ci/expected/periodic.run
Normal file
3
ci/expected/periodic.run
Normal 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))
|
3
ci/expected/ramfunc.grep.bar
Normal file
3
ci/expected/ramfunc.grep.bar
Normal file
|
@ -0,0 +1,3 @@
|
|||
20000100 B bar::FREE_QUEUE::lk14244m263eivix
|
||||
200000dc B bar::INPUTS::mi89534s44r1mnj1
|
||||
20000000 T bar::ns9009yhw2dc2y25
|
3
ci/expected/ramfunc.grep.foo
Normal file
3
ci/expected/ramfunc.grep.foo
Normal 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
1
ci/expected/ramfunc.run
Normal file
|
@ -0,0 +1 @@
|
|||
foo
|
2
ci/expected/resource.run
Normal file
2
ci/expected/resource.run
Normal file
|
@ -0,0 +1,2 @@
|
|||
UART0: SHARED = 1
|
||||
UART1: SHARED = 2
|
3
ci/expected/schedule.run
Normal file
3
ci/expected/schedule.run
Normal file
|
@ -0,0 +1,3 @@
|
|||
init @ Instant(0)
|
||||
bar @ Instant(4000236)
|
||||
foo @ Instant(8000173)
|
2
ci/expected/singleton.run
Normal file
2
ci/expected/singleton.run
Normal file
|
@ -0,0 +1,2 @@
|
|||
bar(2)
|
||||
foo(1)
|
2
ci/expected/static.run
Normal file
2
ci/expected/static.run
Normal file
|
@ -0,0 +1,2 @@
|
|||
UART1(KEY = 0xdeadbeef)
|
||||
UART0(KEY = 0xdeadbeef)
|
3
ci/expected/task.run
Normal file
3
ci/expected/task.run
Normal file
|
@ -0,0 +1,3 @@
|
|||
foo
|
||||
baz
|
||||
bar
|
0
ci/expected/types.run
Normal file
0
ci/expected/types.run
Normal 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 != false ]; then
|
||||
main
|
||||
fi
|
||||
|
|
96
ci/script.sh
96
ci/script.sh
|
@ -1,23 +1,95 @@
|
|||
set -euxo pipefail
|
||||
|
||||
main() {
|
||||
if [ $TARGET = x86_64-unknown-linux-gnu ]; then
|
||||
cargo build
|
||||
cargo test --test cfail
|
||||
local T=$TARGET
|
||||
|
||||
if [ $T = x86_64-unknown-linux-gnu ]; then
|
||||
# compile-fail and compile-pass tests
|
||||
if [ $TRAVIS_RUST_VERSION = nightly ]; then
|
||||
# TODO how to run a subset of these tests when timer-queue is disabled?
|
||||
cargo test --features timer-queue --test compiletest --target $T
|
||||
fi
|
||||
|
||||
cargo check --target $T
|
||||
cargo check --features timer-queue --target $T
|
||||
return
|
||||
fi
|
||||
|
||||
case $TARGET in
|
||||
thumbv7em-none-eabi*)
|
||||
cargo check --target $TARGET --features cm7-r0p1
|
||||
cargo check --target $TARGET --features cm7-r0p1 --examples
|
||||
;;
|
||||
esac
|
||||
cargo check --target $T --examples
|
||||
cargo check --features timer-queue --target $T --examples
|
||||
|
||||
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 != false ]; then
|
||||
main
|
||||
fi
|
||||
|
|
61
examples/baseline.rs
Normal file
61
examples/baseline.rs
Normal 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
57
examples/capacity.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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) {}
|
|
@ -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;
|
||||
}
|
|
@ -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
43
examples/idle.rs
Normal 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
44
examples/init.rs
Normal 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
61
examples/interrupt.rs
Normal 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 { "" }
|
||||
);
|
||||
}
|
||||
};
|
|
@ -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
65
examples/late.rs
Normal 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
71
examples/lock.rs
Normal 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
61
examples/message.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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
58
examples/not-send.rs
Normal 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
41
examples/not-sync.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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
43
examples/periodic.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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
53
examples/ramfunc.rs
Normal 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
60
examples/resource.rs
Normal 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);
|
||||
}
|
||||
};
|
|
@ -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
51
examples/schedule.rs
Normal 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
69
examples/singleton.rs
Normal 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
17
examples/smallest.rs
Normal 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
47
examples/static.rs
Normal 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
61
examples/task.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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
54
examples/types.rs
Normal 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();
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -2,19 +2,28 @@
|
|||
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"
|
||||
documentation = "https://japaric.github.io/cortex-m-rtfm/api/cortex_m_rtfm"
|
||||
keywords = ["arm", "cortex-m"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "cortex-m-rtfm-macros"
|
||||
readme = "../README.md"
|
||||
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 = []
|
|
@ -1,77 +1,243 @@
|
|||
use std::cmp;
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::{HashMap, HashSet},
|
||||
};
|
||||
|
||||
use syn::Ident;
|
||||
use syn::{Attribute, Ident, Type};
|
||||
|
||||
use check::App;
|
||||
use syntax::{App, Idents};
|
||||
|
||||
pub type Ownerships = HashMap<Ident, Ownership>;
|
||||
|
||||
pub struct Analysis {
|
||||
/// Capacities of free queues
|
||||
pub capacities: Capacities,
|
||||
pub dispatchers: Dispatchers,
|
||||
// Ceilings of free queues
|
||||
pub free_queues: HashMap<Ident, u8>,
|
||||
pub resources_assert_send: HashSet<Box<Type>>,
|
||||
pub tasks_assert_send: HashSet<Ident>,
|
||||
/// Types of RO resources that need to be Sync
|
||||
pub assert_sync: HashSet<Box<Type>>,
|
||||
// Resource ownership
|
||||
pub ownerships: Ownerships,
|
||||
// Ceilings of ready queues
|
||||
pub ready_queues: HashMap<u8, u8>,
|
||||
pub timer_queue: TimerQueue,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Ownership {
|
||||
/// Owned or co-owned by tasks that run at the same priority
|
||||
// NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority)
|
||||
Owned { priority: u8 },
|
||||
/// Shared by tasks that run at different priorities.
|
||||
///
|
||||
/// `ceiling` is the maximum value across all the task priorities
|
||||
Shared { ceiling: u8 },
|
||||
}
|
||||
|
||||
impl Ownership {
|
||||
pub fn ceiling(&self) -> u8 {
|
||||
pub fn needs_lock(&self, priority: u8) -> bool {
|
||||
match *self {
|
||||
Ownership::Owned { priority } => priority,
|
||||
Ownership::Shared { ceiling } => ceiling,
|
||||
}
|
||||
}
|
||||
Ownership::Owned { .. } => false,
|
||||
Ownership::Shared { ceiling } => {
|
||||
debug_assert!(ceiling >= priority);
|
||||
|
||||
pub fn is_owned(&self) -> bool {
|
||||
match *self {
|
||||
Ownership::Owned { .. } => true,
|
||||
_ => false,
|
||||
priority < ceiling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app(app: &App) -> Ownerships {
|
||||
let mut ownerships = HashMap::new();
|
||||
pub struct Dispatcher {
|
||||
/// Attributes to apply to the dispatcher
|
||||
pub attrs: Vec<Attribute>,
|
||||
pub interrupt: Ident,
|
||||
/// Tasks dispatched at this priority level
|
||||
pub tasks: Vec<Ident>,
|
||||
// Queue capacity
|
||||
pub capacity: u8,
|
||||
}
|
||||
|
||||
for resource in &app.idle.resources {
|
||||
ownerships.insert(resource.clone(), Ownership::Owned { priority: 0 });
|
||||
}
|
||||
/// Priority -> Dispatcher
|
||||
pub type Dispatchers = HashMap<u8, Dispatcher>;
|
||||
|
||||
for task in app.tasks.values() {
|
||||
for resource in task.resources.iter() {
|
||||
if let Some(ownership) = ownerships.get_mut(resource) {
|
||||
match *ownership {
|
||||
Ownership::Owned { priority } => {
|
||||
if priority == task.priority {
|
||||
*ownership = Ownership::Owned { priority };
|
||||
} else {
|
||||
*ownership = Ownership::Shared {
|
||||
ceiling: cmp::max(priority, task.priority),
|
||||
};
|
||||
}
|
||||
}
|
||||
Ownership::Shared { ceiling } => {
|
||||
if task.priority > ceiling {
|
||||
*ownership = Ownership::Shared {
|
||||
ceiling: task.priority,
|
||||
};
|
||||
pub type Capacities = HashMap<Ident, u8>;
|
||||
|
||||
pub fn app(app: &App) -> Analysis {
|
||||
// Ceiling analysis of R/W resource and Sync analysis of RO resources
|
||||
// (Resource shared by tasks that run at different priorities need to be `Sync`)
|
||||
let mut ownerships = Ownerships::new();
|
||||
let mut resources_assert_send = HashSet::new();
|
||||
let mut tasks_assert_send = HashSet::new();
|
||||
let mut assert_sync = HashSet::new();
|
||||
|
||||
for (priority, res) in app.resource_accesses() {
|
||||
if let Some(ownership) = ownerships.get_mut(res) {
|
||||
match *ownership {
|
||||
Ownership::Owned { priority: ceiling } | Ownership::Shared { ceiling } => {
|
||||
if priority != ceiling {
|
||||
*ownership = Ownership::Shared {
|
||||
ceiling: cmp::max(ceiling, priority),
|
||||
};
|
||||
|
||||
let res = &app.resources[res];
|
||||
if res.mutability.is_none() {
|
||||
assert_sync.insert(res.ty.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
ownerships.insert(
|
||||
resource.clone(),
|
||||
Ownership::Owned {
|
||||
priority: task.priority,
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
ownerships.insert(res.clone(), Ownership::Owned { priority });
|
||||
}
|
||||
|
||||
// Compute sizes of free queues
|
||||
// We assume at most one message per `spawn` / `schedule`
|
||||
let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
|
||||
for (_, task) in app.spawn_calls().chain(app.schedule_calls()) {
|
||||
*capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1;
|
||||
}
|
||||
|
||||
// Override computed capacities if user specified a capacity in `#[task]`
|
||||
for (name, task) in &app.tasks {
|
||||
if let Some(cap) = task.args.capacity {
|
||||
*capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
}
|
||||
// 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" },
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
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 free interrupts are not being used
|
||||
for int in app.interrupts.keys() {
|
||||
if app.free_interrupts.contains_key(int) {
|
||||
return Err(parse::Error::new(
|
||||
int.span(),
|
||||
"free interrupts (`extern { .. }`) can't be used as interrupt handlers",
|
||||
));
|
||||
}
|
||||
None => Kind::Interrupt {
|
||||
enabled: task.enabled.unwrap_or(true),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Task {
|
||||
kind,
|
||||
path: task.path,
|
||||
priority: task.priority.unwrap_or(1),
|
||||
resources: task.resources,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
|
1815
macros/src/codegen.rs
Normal file
1815
macros/src/codegen.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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
1235
macros/src/syntax.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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;));
|
||||
}
|
||||
}
|
6
memory.x
6
memory.x
|
@ -1,6 +0,0 @@
|
|||
/* STM32F103C8V6 */
|
||||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 20K
|
||||
}
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
84
src/export.rs
Normal 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,
|
||||
{
|
||||
}
|
440
src/lib.rs
440
src/lib.rs
|
@ -1,170 +1,342 @@
|
|||
//! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers
|
||||
//!
|
||||
//! This crate is based on [the RTFM framework] created by the Embedded Systems
|
||||
//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren,
|
||||
//! and uses a simplified version of the Stack Resource Policy as scheduling
|
||||
//! policy (check the [references] for details).
|
||||
//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the
|
||||
//! library is `rtfm`.
|
||||
//!
|
||||
//! [the RTFM framework]: http://www.rtfm-lang.org/
|
||||
//! [ltu]: https://www.ltu.se/?l=en
|
||||
//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en
|
||||
//! [references]: ./index.html#references
|
||||
//! [`cortex-m-rtfm`]: https://crates.io/crates/cortex-m-rtfm
|
||||
//!
|
||||
//! # Features
|
||||
//! The user level documentation can be found [here].
|
||||
//!
|
||||
//! - **Event triggered tasks** as the unit of concurrency.
|
||||
//! - Support for prioritization of tasks and, thus, **preemptive
|
||||
//! multitasking**.
|
||||
//! - **Efficient and data race free memory sharing** through fine grained *non
|
||||
//! global* critical sections.
|
||||
//! - **Deadlock free execution** guaranteed at compile time.
|
||||
//! - **Minimal scheduling overhead** as the scheduler has no "software
|
||||
//! component": the hardware does all the scheduling.
|
||||
//! - **Highly efficient memory usage**: All the tasks share a single call stack
|
||||
//! and there's no hard dependency on a dynamic memory allocator.
|
||||
//! - **All Cortex M devices are fully supported**.
|
||||
//! - This task model is amenable to known WCET (Worst Case Execution Time)
|
||||
//! analysis and scheduling analysis techniques. (Though we haven't yet
|
||||
//! developed Rust friendly tooling for that.)
|
||||
//! [here]: ../../book/index.html
|
||||
//!
|
||||
//! # Constraints
|
||||
//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component
|
||||
//! of the framework.
|
||||
//!
|
||||
//! - Tasks must run to completion. That's it, tasks can't contain endless
|
||||
//! loops. However, you can run an endless event loop in the `idle` *loop*.
|
||||
//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html
|
||||
//!
|
||||
//! - Task priorities must remain constant at runtime.
|
||||
//! # Cargo features
|
||||
//!
|
||||
//! # Dependencies
|
||||
//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule
|
||||
//! tasks to run in the future. Also see [`Instant`] and [`Duration`].
|
||||
//!
|
||||
//! The application crate must depend on a device crate generated using
|
||||
//! [`svd2rust`] v0.12.x and the "rt" feature of that crate must be enabled. The
|
||||
//! SVD file used to generate the device crate *must* contain [`<cpu>`]
|
||||
//! information.
|
||||
//!
|
||||
//! [`svd2rust`]: https://docs.rs/svd2rust/0.12.0/svd2rust/
|
||||
//! [`<cpu>`]: https://www.keil.com/pack/doc/CMSIS/SVD/html/elem_cpu.html
|
||||
//!
|
||||
//! # `app!`
|
||||
//!
|
||||
//! The `app!` macro is documented [here].
|
||||
//!
|
||||
//! [here]: https://docs.rs/cortex-m-rtfm-macros/0.3.0/cortex_m_rtfm_macros/fn.app.html
|
||||
//!
|
||||
//! # Important: Cortex-M7 devices
|
||||
//!
|
||||
//! If targeting a Cortex-M7 device with revision r0p1 then you MUST enable the `cm7-r0p1` Cargo
|
||||
//! feature of this crate or the `Resource.claim` and `Resource.claim_mut` methods WILL misbehave.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! In increasing grade of complexity. See the [examples](./examples/index.html)
|
||||
//! module.
|
||||
//!
|
||||
//! # References
|
||||
//!
|
||||
//! - Baker, T. P. (1991). Stack-based scheduling of realtime processes.
|
||||
//! *Real-Time Systems*, 3(1), 67-99.
|
||||
//!
|
||||
//! > The original Stack Resource Policy paper. [PDF][srp].
|
||||
//!
|
||||
//! [srp]: http://www.cs.fsu.edu/~baker/papers/mstacks3.pdf
|
||||
//!
|
||||
//! - Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P.
|
||||
//! (2013, June). Real-time for the masses, step 1: Programming API and static
|
||||
//! priority SRP kernel primitives. In Industrial Embedded Systems (SIES),
|
||||
//! 2013 8th IEEE International Symposium on (pp. 110-113). IEEE.
|
||||
//!
|
||||
//! > A description of the RTFM task and resource model. [PDF][rtfm]
|
||||
//!
|
||||
//! [rtfm]: http://www.diva-portal.org/smash/get/diva2:1005680/FULLTEXT01.pdf
|
||||
//! [`Instant`]: struct.Instant.html
|
||||
//! [`Duration`]: struct.Duration.html
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(warnings)]
|
||||
#![no_std]
|
||||
|
||||
extern crate cortex_m;
|
||||
extern crate cortex_m_rtfm_macros;
|
||||
extern crate rtfm_core;
|
||||
extern crate untagged_option;
|
||||
use core::{cell::Cell, u8};
|
||||
#[cfg(feature = "timer-queue")]
|
||||
use core::{cmp::Ordering, ops};
|
||||
|
||||
use core::{mem, u8};
|
||||
|
||||
pub use cortex_m::asm::{bkpt, wfi};
|
||||
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)
|
||||
} else {
|
||||
interrupt::disable();
|
||||
let r = f(&mut unsafe { Threshold::max() });
|
||||
unsafe { interrupt::enable() };
|
||||
r
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
pub unsafe fn claim<T, R, F>(
|
||||
data: T,
|
||||
ceiling: u8,
|
||||
_nvic_prio_bits: u8,
|
||||
t: &mut Threshold,
|
||||
f: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(T, &mut Threshold) -> R,
|
||||
{
|
||||
if ceiling > t.value() {
|
||||
match () {
|
||||
#[cfg(armv6m)]
|
||||
() => atomic(t, |t| f(data, t)),
|
||||
#[cfg(feature = "timer-queue")]
|
||||
impl ops::AddAssign<Duration> for Instant {
|
||||
fn add_assign(&mut self, dur: Duration) {
|
||||
debug_assert!(dur.0 < (1 << 31));
|
||||
self.0 = self.0.wrapping_add(dur.0 as i32);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(armv6m))]
|
||||
() => {
|
||||
let max_priority = 1 << _nvic_prio_bits;
|
||||
#[cfg(feature = "timer-queue")]
|
||||
impl ops::Add<Duration> for Instant {
|
||||
type Output = Self;
|
||||
|
||||
if ceiling == max_priority {
|
||||
atomic(t, |t| f(data, t))
|
||||
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 {
|
||||
let old = basepri::read();
|
||||
let hw = (max_priority - ceiling) << (8 - _nvic_prio_bits);
|
||||
basepri::write(hw);
|
||||
let ret = f(data, &mut Threshold::new(ceiling));
|
||||
basepri::write(old);
|
||||
ret
|
||||
self.priority().set(Self::CEILING);
|
||||
basepri::write(logical2hw(Self::CEILING, Self::NVIC_PRIO_BITS));
|
||||
let r = f(&mut *self.ptr());
|
||||
basepri::write(logical2hw(current, Self::NVIC_PRIO_BITS));
|
||||
self.priority().set(current);
|
||||
r
|
||||
}
|
||||
} else {
|
||||
f(&mut *self.ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
f(data, t)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an interrupt, that is a task, as pending
|
||||
#[cfg(armv7m)]
|
||||
#[inline]
|
||||
fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 {
|
||||
((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)
|
||||
}
|
||||
|
||||
/// 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
135
src/tq.rs
Normal 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
Loading…
Reference in a new issue