diff --git a/.cargo/config b/.cargo/config index 36061ee450..2683610de4 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,31 +1,13 @@ [target.thumbv6m-none-eabi] -runner = 'arm-none-eabi-gdb' -rustflags = [ - "-C", "link-arg=-Tlink.x", - "-C", "linker=true", - "-Z", "linker-flavor=ld", -] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" [target.thumbv7m-none-eabi] -runner = 'arm-none-eabi-gdb' +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" \ No newline at end of file diff --git a/.gdbinit b/.gdbinit deleted file mode 100644 index 7c72d4fb1f..0000000000 --- a/.gdbinit +++ /dev/null @@ -1,6 +0,0 @@ -target remote :3333 - -monitor arm semihosting enable - -load -step diff --git a/.github/bors.toml b/.github/bors.toml index 5ccee21e01..39ad604272 100644 --- a/.github/bors.toml +++ b/.github/bors.toml @@ -1,3 +1,4 @@ +delete_merged_branches = true status = [ "continuous-integration/travis-ci/push", ] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29204d6567..6d34a8d821 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ **/*.rs.bk -*.org .#* .gdb_history +/book/book +/target Cargo.lock -target/ diff --git a/.travis.yml b/.travis.yml index 1d6f3abbf8..09edbfe4ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,38 +2,48 @@ language: rust matrix: include: + # NOTE used to build docs on successful merges to master + - env: TARGET=x86_64-unknown-linux-gnu + rust: 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: diff --git a/Cargo.toml b/Cargo.toml index d505f20906..765fef1c41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,33 +4,62 @@ authors = [ "Per Lindgren ", ] 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"] \ No newline at end of file diff --git a/LICENSE-CC-BY-SA b/LICENSE-CC-BY-SA new file mode 100644 index 0000000000..a73481c4be --- /dev/null +++ b/LICENSE-CC-BY-SA @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/LICENSE-MIT b/LICENSE-MIT index a128ba4022..52cb453f23 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2017 Jorge Aparicio +Copyright (c) 2017-2018 Jorge Aparicio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/README.md b/README.md index 335440b249..c04be8bb92 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,104 @@ -[![crates.io](https://img.shields.io/crates/v/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm) -[![crates.io](https://img.shields.io/crates/d/cortex-m-rtfm.svg)](https://crates.io/crates/cortex-m-rtfm) +# Real Time For the Masses -# `cortex-m-rtfm` +A concurrency framework for building real time systems. -> Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers +**IMPORTANT** This crate is currently in pre-release (beta) state . We reserve +the right to make breaking changes in the syntax or to patch memory safety holes +before the v0.4.0 release, which is planned for 2018-12-07. When v0.4.0 is +released *all the pre-releases will be yanked*. If you run into a panic message +or an unhelpful error message (e.g. misleading span), or if something doesn't +behave the way you expect please open [an issue]! -# [Documentation](https://japaric.github.io/cortex-m-rtfm/cortex_m_rtfm/) +[an issue]: https://github.com/japaric/cortex-m-rtfm/issues -# License +## Features -Licensed under either of +- **Tasks** as the unit of concurrency [^1]. Tasks can be *event triggered* + (fired in response to asynchronous stimuli) or spawned by the application on + demand. + +- **Message passing** between tasks. Specifically, messages can be passed to + software tasks at spawn time. + +- **A timer queue** [^2]. Software tasks can be scheduled to run at some time + in the future. This feature can be used to implement periodic tasks. + +- Support for prioritization of tasks and, thus, **preemptive multitasking**. + +- **Efficient and data race free memory sharing** through fine grained *priority + based* critical sections [^1]. + +- **Deadlock free execution** guaranteed at compile time. This is an stronger + guarantee than what's provided by [the standard `Mutex` + abstraction][std-mutex]. + +[std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html + +- **Minimal scheduling overhead**. The task scheduler has minimal software + footprint; the hardware does the bulk of the scheduling. + +- **Highly efficient memory usage**: All the tasks share a single call stack and + there's no hard dependency on a dynamic memory allocator. + +- **All Cortex-M devices are fully supported**. + +- This task model is amenable to known WCET (Worst Case Execution Time) analysis + and scheduling analysis techniques. (Though we haven't yet developed Rust + friendly tooling for that.) + +## Requirements + +- Rust 1.31.0+ + +- Applications must be written using the 2018 edition. + +## [User documentation](https://japaric.github.io/cortex-m-rtfm/book/index.html) + +## [API reference](https://japaric.github.io/cortex-m-rtfm/api/rtfm/index.html) + +## Acknowledgments + +This crate is based on [the RTFM language][rtfm-lang] created by the Embedded +Systems group at [Luleå University of Technology][ltu], led by [Prof. Per +Lindgren][per]. + +[rtfm-lang]: http://www.rtfm-lang.org/ +[ltu]: https://www.ltu.se/?l=en +[per]: https://www.ltu.se/staff/p/pln-1.11258?l=en + +## References + +[^1]: Eriksson, J., Häggström, F., Aittamaa, S., Kruglyak, A., & Lindgren, P. + (2013, June). Real-time for the masses, step 1: Programming API and static + priority SRP kernel primitives. In Industrial Embedded Systems (SIES), 2013 + 8th IEEE International Symposium on (pp. 110-113). IEEE. + +[^2]: Lindgren, P., Fresk, E., Lindner, M., Lindner, A., Pereira, D., & Pinho, + L. M. (2016). Abstract timers and their implementation onto the arm cortex-m + family of mcus. ACM SIGBED Review, 13(1), 48-53. + +## License + +All source code (including code snippets) is licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) + [https://www.apache.org/licenses/LICENSE-2.0][L1]) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or + [https://opensource.org/licenses/MIT][L2]) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +[L1]: https://www.apache.org/licenses/LICENSE-2.0 +[L2]: https://opensource.org/licenses/MIT at your option. -## Contribution +The written prose contained within the book is licensed under the terms of the +Creative Commons CC-BY-SA v4.0 license ([LICENSE-CC-BY-SA](LICENSE-CC-BY-SA) or +[https://creativecommons.org/licenses/by-sa/4.0/legalcode][L3]). + +[L3]: https://creativecommons.org/licenses/by-sa/4.0/legalcode + +### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +licensed as above, without any additional terms or conditions. diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000000..c611ce079a --- /dev/null +++ b/book/book.toml @@ -0,0 +1,5 @@ +[book] +authors = ["Jorge Aparicio"] +multilingual = false +src = "src" +title = "Real Time For the Masses" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 0000000000..051d1acc4e --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,16 @@ +# Summary + +[Preface](./preface.md) +- [RTFM by example](./by-example.md) + - [The `app` attribute](./by-example/app.md) + - [Resources](./by-example/resources.md) + - [Tasks](./by-example/tasks.md) + - [Timer queue](./by-example/timer-queue.md) + - [Singletons](./by-example/singletons.md) + - [Types, Send and Sync](./by-example/types-send-sync.md) + - [Starting a new project](./by-example/new.md) + - [Tips & tricks](./by-example/tips.md) +- [Under the hood](./internals.md) + - [Ceiling analysis](./internals/ceilings.md) + - [Task dispatcher](./internals/tasks.md) + - [Timer queue](./internals/timer-queue.md) diff --git a/book/src/by-example.md b/book/src/by-example.md new file mode 100644 index 0000000000..0e09b03fb1 --- /dev/null +++ b/book/src/by-example.md @@ -0,0 +1,16 @@ +# RTFM by example + +This part of the book introduces the Real Time For the Masses (RTFM) framework +to new users by walking them through examples of increasing complexity. + +All examples in this part of the book can be found in the GitHub [repository] of +the project, and most of the examples can be run on QEMU so no special hardware +is required to follow along. + +[repository]: https://github.com/japaric/cortex-m-rtfm + +To run the examples on your laptop / PC you'll need the `qemu-system-arm` +program. Check [the embedded Rust book] for instructions on how to set up an +embedded development environment that includes QEMU. + +[the embedded Rust book]: https://rust-embedded.github.io/book/intro/install.html diff --git a/book/src/by-example/app.md b/book/src/by-example/app.md new file mode 100644 index 0000000000..ae0f4b8788 --- /dev/null +++ b/book/src/by-example/app.md @@ -0,0 +1,105 @@ +# The `app` attribute + +This is the smallest possible RTFM application: + +``` rust +{{#include ../../../examples/smallest.rs}} +``` + +All RTFM applications use the [`app`] attribute (`#[app(..)]`). This attribute +must be applied to a `const` item that contains items. The `app` attribute has +a mandatory `device` argument that takes a *path* as a value. This path must +point to a *device* crate generated using [`svd2rust`] **v0.14.x**. The `app` +attribute will expand into a suitable entry point so it's not required to use +the [`cortex_m_rt::entry`] attribute. + +[`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html +[`svd2rust`]: https://crates.io/crates/svd2rust +[`cortex_m_rt::entry`]: ../../api/cortex_m_rt_macros/attr.entry.html + +> **ASIDE**: Some of you may be wondering why we are using a `const` item as a +> module and not a proper `mod` item. The reason is that using attributes on +> modules requires a feature gate, which requires a nightly toolchain. To make +> RTFM work on stable we use the `const` item instead. When more parts of macros +> 1.2 are stabilized we'll move from a `const` item to a `mod` item and +> eventually to a crate level attribute (`#![app]`). + +## `init` + +Within the pseudo-module the `app` attribute expects to find an initialization +function marked with the `init` attribute. This function must have signature +`[unsafe] fn()`. + +This initialization function will be the first part of the application to run. +The `init` function will run *with interrupts disabled* and has exclusive access +to Cortex-M and device specific peripherals through the `core` and `device` +variables, which are injected in the scope of `init` by the `app` attribute. Not +all Cortex-M peripherals are available in `core` because the RTFM runtime takes +ownership of some of them -- for more details see the [`rtfm::Peripherals`] +struct. + +`static mut` variables declared at the beginning of `init` will be transformed +into `&'static mut` references that are safe to access. + +[`rtfm::Peripherals`]: ../../api/rtfm/struct.Peripherals.html + +The example below shows the types of the `core` and `device` variables and +showcases safe access to a `static mut` variable. + +``` rust +{{#include ../../../examples/init.rs}} +``` + +Running the example will print `init` to the console and then exit the QEMU +process. + +``` console +$ cargo run --example init +{{#include ../../../ci/expected/init.run}}``` + +## `idle` + +A function marked with the `idle` attribute can optionally appear in the +pseudo-module. This function is used as the special *idle task* and must have +signature `[unsafe] fn() - > !`. + +When present, the runtime will execute the `idle` task after `init`. Unlike +`init`, `idle` will run *with interrupts enabled* and it's not allowed to return +so it runs forever. + +When no `idle` function is declared, the runtime sets the [SLEEPONEXIT] bit and +then sends the microcontroller to sleep after running `init`. + +[SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit + +Like in `init`, `static mut` variables will be transformed into `&'static mut` +references that are safe to access. + +The example below shows that `idle` runs after `init`. + +``` rust +{{#include ../../../examples/idle.rs}} +``` + +``` console +$ cargo run --example idle +{{#include ../../../ci/expected/idle.run}}``` + +## `interrupt` / `exception` + +Just like you would do with the `cortex-m-rt` crate you can use the `interrupt` +and `exception` attributes within the `app` pseudo-module to declare interrupt +and exception handlers. In RTFM, we refer to interrupt and exception handlers as +*hardware* tasks. + +``` rust +{{#include ../../../examples/interrupt.rs}} +``` + +``` console +$ cargo run --example interrupt +{{#include ../../../ci/expected/interrupt.run}}``` + +So far all the RTFM applications we have seen look no different that the +applications one can write using only the `cortex-m-rt` crate. In the next +section we start introducing features unique to RTFM. diff --git a/book/src/by-example/new.md b/book/src/by-example/new.md new file mode 100644 index 0000000000..b6c4643cfc --- /dev/null +++ b/book/src/by-example/new.md @@ -0,0 +1,67 @@ +# Starting a new project + +Now that you have learned about the main features of the RTFM framework you can +try it out on your hardware by following these instructions. + +1. Instantiate the [`cortex-m-quickstart`] template. + +[`cortex-m-quickstart`]: https://github.com/rust-embedded/cortex-m-quickstart#cortex-m-quickstart + +``` console +$ # for example using `cargo-generate` +$ cargo generate \ + --git https://github.com/rust-embedded/cortex-m-quickstart \ + --name app + +$ # follow the rest of the instructions +``` + +2. Add a device crate that was generated using [`svd2rust`] **v0.14.x**, or a + board support crate that depends on one such device crate as a dependency. + Make sure that the `rt` feature of the crate is enabled. + +[`svd2rust`]: https://crates.io/crates/svd2rust + +In this example, I'll use the [`lm3s6965`] device crate. This device crate +doesn't have an `rt` Cargo feature; that feature is always enabled. + +[`lm3s6965`]: https://crates.io/crates/lm3s6965 + +This device crate provides a linker script with the memory layout of the target +device so `memory.x` and `build.rs` need to be removed. + +``` console +$ cargo add lm3s6965 --vers 0.1.3 + +$ rm memory.x build.rs +``` + +3. Add the `cortex-m-rtfm` crate as a dependency and, if you need it, enable the + `timer-queue` feature. + +``` console +$ cargo add cortex-m-rtfm --allow-prerelease --upgrade=none +``` + +4. Write your RTFM application. + +Here I'll use the `init` example from the `cortex-m-rtfm` crate. + +``` console +$ curl \ + -L https://github.com/japaric/cortex-m-rtfm/raw/v0.4.0-beta.1/examples/init.rs \ + > src/main.rs +``` + +That example depends on the `panic-semihosting` crate: + +``` console +$ cargo add panic-semihosting +``` + +5. Build it, flash it and run it. + +``` console +$ # NOTE: I have uncommented the `runner` option in `.cargo/config` +$ cargo run +{{#include ../../../ci/expected/init.run}}``` diff --git a/book/src/by-example/resources.md b/book/src/by-example/resources.md new file mode 100644 index 0000000000..93332f02aa --- /dev/null +++ b/book/src/by-example/resources.md @@ -0,0 +1,119 @@ +## Resources + +One of the limitations of the attributes provided by the `cortex-m-rt` crate is +that sharing data (or peripherals) between interrupts, or between an interrupt +and the `entry` function, requires a `cortex_m::interrupt::Mutex`, which +*always* requires disabling *all* interrupts to access the data. Disabling all +the interrupts is not always required for memory safety but the compiler doesn't +have enough information to optimize the access to the shared data. + +The `app` attribute has a full view of the application thus it can optimize +access to `static` variables. In RTFM we refer to the `static` variables +declared inside the `app` pseudo-module as *resources*. To access a resource the +context (`init`, `idle`, `interrupt` or `exception`) must first declare the +resource in the `resources` argument of its attribute. + +In the example below two interrupt handlers access the same resource. No `Mutex` +is required in this case because the two handlers run at the same priority and +no preemption is possible. The `SHARED` resource can only be accessed by these +two handlers. + +``` rust +{{#include ../../../examples/resource.rs}} +``` + +``` console +$ cargo run --example resource +{{#include ../../../ci/expected/resource.run}}``` + +## Priorities + +The priority of each handler can be declared in the `interrupt` and `exception` +attributes. It's not possible to set the priority in any other way because the +runtime takes ownership of the `NVIC` peripheral; it's also not possible to +change the priority of a handler / task at runtime. Thanks to this restriction +the framework has knowledge about the *static* priorities of all interrupt and +exception handlers. + +Interrupts and exceptions can have priorities in the range `1..=(1 << +NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device` +crate. The `idle` task has a priority of `0`, the lowest priority. + +Resources that are shared between handlers that run at different priorities +require critical sections for memory safety. The framework ensures that critical +sections are used but *only where required*: for example, no critical section is +required by the highest priority handler that has access to the resource. + +The critical section API provided by the RTFM framework (see [`Mutex`]) is +based on dynamic priorities rather than on disabling interrupts. The consequence +is that these critical sections will prevent *some* handlers, including all the +ones that contend for the resource, from *starting* but will let higher priority +handlers, that don't contend for the resource, run. + +[`Mutex`]: ../../api/rtfm/trait.Mutex.html + +In the example below we have three interrupt handlers with priorities ranging +from one to three. The two handlers with the lower priorities contend for the +`SHARED` resource. The lowest priority handler needs to [`lock`] the +`SHARED` resource to access its data, whereas the mid priority handler can +directly access its data. The highest priority handler is free to preempt +the critical section created by the lowest priority handler. + +[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock + +``` rust +{{#include ../../../examples/lock.rs}} +``` + +``` console +$ cargo run --example lock +{{#include ../../../ci/expected/lock.run}}``` + +## Late resources + +Unlike normal `static` variables, which need to be assigned an initial value +when declared, resources can be initialized at runtime. We refer to these +runtime initialized resources as *late resources*. Late resources are useful for +*moving* (as in transferring ownership) peripherals initialized in `init` into +interrupt and exception handlers. + +Late resources are declared like normal resources but that are given an initial +value of `()` (the unit value). Late resources must be initialized at the end of +the `init` function using plain assignments (e.g. `FOO = 1`). + +The example below uses late resources to stablish a lockless, one-way channel +between the `UART0` interrupt handler and the `idle` function. A single producer +single consumer [`Queue`] is used as the channel. The queue is split into +consumer and producer end points in `init` and then each end point is stored +in a different resource; `UART0` owns the producer resource and `idle` owns +the consumer resource. + +[`Queue`]: ../../api/heapless/spsc/struct.Queue.html + +``` rust +{{#include ../../../examples/late.rs}} +``` + +``` console +$ cargo run --example late +{{#include ../../../ci/expected/late.run}}``` + +## `static` resources + +`static` variables can also be used as resources. Tasks can only get `&` +(shared) references to these resources but locks are never required to access +their data. You can think of `static` resources as plain `static` variables that +can be initialized at runtime and have better scoping rules: you can control +which tasks can access the variable, instead of the variable being visible to +all the functions in the scope it was declared in. + +In the example below a key is loaded (or created) at runtime and then used from +two tasks that run at different priorities. + +``` rust +{{#include ../../../examples/static.rs}} +``` + +``` console +$ cargo run --example static +{{#include ../../../ci/expected/static.run}}``` diff --git a/book/src/by-example/singletons.md b/book/src/by-example/singletons.md new file mode 100644 index 0000000000..d83cf1c203 --- /dev/null +++ b/book/src/by-example/singletons.md @@ -0,0 +1,26 @@ +# Singletons + +The `app` attribute is aware of [`owned-singleton`] crate and its [`Singleton`] +attribute. When this attribute is applied to one of the resources the runtime +will perform the `unsafe` initialization of the singleton for you, ensuring that +only a single instance of the singleton is ever created. + +[`owned-singleton`]: ../../api/owned_singleton/index.html +[`Singleton`]: ../../api/owned_singleton_macros/attr.Singleton.html + +Note that when using the `Singleton` attribute you'll need to have the +`owned_singleton` in your dependencies. + +Below is an example that uses the `Singleton` attribute on a chunk of memory +and then uses the singleton instance as a fixed-size memory pool using one of +the [`alloc-singleton`] abstractions. + +[`alloc-singleton`]: https://crates.io/crates/alloc-singleton + +``` rust +{{#include ../../../examples/singleton.rs}} +``` + +``` console +$ cargo run --example singleton +{{#include ../../../ci/expected/singleton.run}}``` diff --git a/book/src/by-example/tasks.md b/book/src/by-example/tasks.md new file mode 100644 index 0000000000..0b51d8b93d --- /dev/null +++ b/book/src/by-example/tasks.md @@ -0,0 +1,63 @@ +# Software tasks + +RTFM treats interrupt and exception handlers as *hardware* tasks. Hardware tasks +are invoked by the hardware in response to events, like pressing a button. RTFM +also supports *software* tasks which can be spawned by the software from any +execution context. + +Software tasks can also be assigned priorities and are dispatched from interrupt +handlers. RTFM requires that free interrupts are declared in an `extern` block +when using software tasks; these free interrupts will be used to dispatch the +software tasks. An advantage of software tasks over hardware tasks is that many +tasks can be mapped to a single interrupt handler. + +Software tasks are declared by applying the `task` attribute to functions. To be +able to spawn a software task the name of the task must appear in the `spawn` +argument of the context attribute (`init`, `idle`, `interrupt`, etc.). + +The example below showcases three software tasks that run at 2 different +priorities. The three tasks map to 2 interrupts handlers. + +``` rust +{{#include ../../../examples/task.rs}} +``` + +``` console +$ cargo run --example task +{{#include ../../../ci/expected/task.run}}``` + +## Message passing + +The other advantage of software tasks is that messages can be passed to these +tasks when spawning them. The type of the message payload must be specified in +the signature of the task handler. + +The example below showcases three tasks, two of them expect a message. + +``` rust +{{#include ../../../examples/message.rs}} +``` + +``` console +$ cargo run --example message +{{#include ../../../ci/expected/message.run}}``` + +## Capacity + +Task dispatchers do *not* use any dynamic memory allocation. The memory required +to store messages is statically reserved. The framework will reserve enough +space for every context to be able to spawn each task at most once. This is a +sensible default but the "inbox" capacity of each task can be controlled using +the `capacicy` argument of the `task` attribute. + +The example below sets the capacity of the software task `foo` to 4. If the +capacity is not specified then the second `spawn.foo` call in `UART0` would +fail. + +``` rust +{{#include ../../../examples/capacity.rs}} +``` + +``` console +$ cargo run --example capacity +{{#include ../../../ci/expected/capacity.run}}``` diff --git a/book/src/by-example/timer-queue.md b/book/src/by-example/timer-queue.md new file mode 100644 index 0000000000..a9c3622dec --- /dev/null +++ b/book/src/by-example/timer-queue.md @@ -0,0 +1,89 @@ +# Timer queue + +When the `timer-queue` feature is enabled the RTFM framework includes a *global +timer queue* that applications can use to *schedule* software tasks to run some +time in the future. + +To be able to schedule a software task the name of the task must appear in the +`schedule` argument of the context attribute. When scheduling a task the +[`Instant`] at which the task should be executed must be passed as the first +argument of the `schedule` invocation. + +[`Instant`]: ../../api/rtfm/struct.Instant.html + +The RTFM runtime includes a monotonic, non-decreasing, 32-bit timer which can be +queried using the `Instant::now` constructor. A [`Duration`] can be added to +`Instant::now()` to obtain an `Instant` into the future. The monotonic timer is +disabled while `init` runs so `Instant::now()` always returns the value +`Instant(0 /* clock cycles */)`; the timer is enabled right before the +interrupts are re-enabled and `idle` is executed. + +[`Duration`]: ../../api/rtfm/struct.Duration.html + +The example below schedules two tasks from `init`: `foo` and `bar`. `foo` is +scheduled to run 8 million clock cycles in the future. Next, `bar` is scheduled +to run 4 million clock cycles in the future. `bar` runs before `foo` since it +was scheduled to run first. + +> **IMPORTANT**: The examples that use the `schedule` API or the `Instant` +> abstraction will **not** properly work on QEMU because the Cortex-M cycle +> counter functionality has not been implemented in `qemu-system-arm`. + +``` rust +{{#include ../../../examples/schedule.rs}} +``` + +Running the program on real hardware produces the following output in the console: + +``` text +{{#include ../../../ci/expected/schedule.run}} +``` + +## Periodic tasks + +Software tasks have access to the `Instant` at which they were scheduled to run +through the `scheduled` variable. This information and the `schedule` API can be +used to implement periodic tasks as shown in the example below. + +``` rust +{{#include ../../../examples/periodic.rs}} +``` + +This is the output produced by the example. Note that there is zero drift / +jitter even though `schedule.foo` was invoked at the *end* of `foo`. Using +`Instant::now` instead of `scheduled` would have resulted in drift / jitter. + +``` text +{{#include ../../../ci/expected/periodic.run}} +``` + +## Baseline + +For the tasks scheduled from `init` we have exact information about their +`scheduled` time. For hardware tasks there's no `scheduled` time because these +tasks are asynchronous in nature. For hardware task the runtime provides a +`start` time, which indicates the time at which the task handler started +executing. + +Note that `start` is **not** equal to the arrival time of the event that fired +the task. Depending on the priority of the task and the load of the system the +`start` time could be very far off from the event arrival time. + +What do you think will be the value of `scheduled` for software tasks that are +*spawned* instead of scheduled? The answer is that spawned tasks inherit the +*baseline* time of the context that spawned it. The baseline of hardware tasks +is `start`, the baseline of software tasks is `scheduled` and the baseline of +`init` is `start = Instant(0)`. `idle` doesn't really have a baseline but tasks +spawned from it will use `Instant::now()` as their baseline time. + +The example below showcases the different meanings of the *baseline*. + +``` rust +{{#include ../../../examples/baseline.rs}} +``` + +Running the program on real hardware produces the following output in the console: + +``` text +{{#include ../../../ci/expected/baseline.run}} +``` diff --git a/book/src/by-example/tips.md b/book/src/by-example/tips.md new file mode 100644 index 0000000000..0e3d47b7ad --- /dev/null +++ b/book/src/by-example/tips.md @@ -0,0 +1,43 @@ +# Tips & tricks + +## Running tasks from RAM + +The main goal of moving the specification of RTFM applications to attributes in +RTFM v0.4.x was to allow inter-operation with other attributes. For example, the +`link_section` attribute can be applied to tasks to place them in RAM; this can +improve performance in some cases. + +> **IMPORTANT**: In general, the `link_section`, `export_name` and `no_mangle` +> attributes are very powerful but also easy to misuse. Incorrectly using any of +> these attributes can cause undefined behavior; you should always prefer to use +> safe, higher level attributes around them like `cortex-m-rt`'s `interrupt` and +> `exception` attributes. +> +> In the particular case of RAM functions there's no +> safe abstraction for it in `cortex-m-rt` v0.6.5 but there's an [RFC] for +> adding a `ramfunc` attribute in a future release. + +[RFC]: https://github.com/rust-embedded/cortex-m-rt/pull/100 + +The example below shows how to place the higher priority task, `bar`, in RAM. + +``` rust +{{#include ../../../examples/ramfunc.rs}} +``` + +Running this program produces the expected output. + +``` console +$ cargo run --example ramfunc +{{#include ../../../ci/expected/ramfunc.run}}``` + +One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM +(`0x2000_0000`), whereas `foo` ended in Flash (`0x0000_0000`). + +``` console +$ cargo nm --example ramfunc --release | grep ' foo::' +{{#include ../../../ci/expected/ramfunc.grep.foo}}``` + +``` console +$ cargo nm --example ramfunc --release | grep ' bar::' +{{#include ../../../ci/expected/ramfunc.grep.bar}}``` diff --git a/book/src/by-example/types-send-sync.md b/book/src/by-example/types-send-sync.md new file mode 100644 index 0000000000..6433060a0a --- /dev/null +++ b/book/src/by-example/types-send-sync.md @@ -0,0 +1,60 @@ +# Types, Send and Sync + +The `app` attribute injects a context, a collection of variables, into every +function. All these variables have predictable, non-anonymous types so you can +write plain functions that take them as arguments. + +The API reference specifies how these types are generated from the input. You +can also generate documentation for you binary crate (`cargo doc --bin `); +in the documentation you'll find `Context` structs (e.g. `init::Context` and +`idle::Context`) whose fields represent the variables injected into each +function. + +The example below shows the different types generates by the `app` attribute. + +``` rust +{{#include ../../../examples/types.rs}} +``` + +## `Send` + +[`Send`] is a marker trait for "types that can be transferred across thread +boundaries", according to its definition in `core`. In the context of RTFM the +`Send` trait is only required where it's possible to transfer a value between +tasks that run at *different* priorities. This occurs in a few places: in message +passing, in shared `static mut` resources and in the initialization of late +resources. + +[`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html + +The `app` attribute will enforce that `Send` is implemented where required so +you don't need to worry much about it. It's more important to know where you do +*not* need the `Send` trait: on types that are transferred between tasks that +run at the *same* priority. This occurs in two places: in message passing and in +shared `static mut` resources. + +The example below shows where a type that doesn't implement `Send` can be used. + +``` rust +{{#include ../../../examples/not-send.rs}} +``` + +## `Sync` + +Similarly, [`Sync`] is a marker trait for "types for which it is safe to share +references between threads", according to its definition in `core`. In the +context of RTFM the `Sync` trait is only required where it's possible for two, +or more, tasks that run at different priority to hold a shared reference to a +resource. This only occurs with shared `static` resources. + +[`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html + +The `app` attribute will enforce that `Sync` is implemented where required but +it's important to know where the `Sync` bound is not required: in `static` +resources shared between tasks that run at the *same* priority. + +The example below shows where a type that doesn't implement `Sync` can be used. + +``` rust +{{#include ../../../examples/not-sync.rs}} +``` diff --git a/book/src/internals.md b/book/src/internals.md new file mode 100644 index 0000000000..0ef55e62e6 --- /dev/null +++ b/book/src/internals.md @@ -0,0 +1,6 @@ +# Under the hood + +This section describes the internals of the RTFM framework at a *high level*. +Low level details like the parsing and code generation done by the procedural +macro (`#[app]`) will not be explained here. The focus will be the analysis of +the user specification and the data structures used by the runtime. diff --git a/book/src/internals/ceilings.md b/book/src/internals/ceilings.md new file mode 100644 index 0000000000..2c645a4d43 --- /dev/null +++ b/book/src/internals/ceilings.md @@ -0,0 +1,3 @@ +# Ceiling analysis + +**TODO** diff --git a/book/src/internals/tasks.md b/book/src/internals/tasks.md new file mode 100644 index 0000000000..85f783fbc3 --- /dev/null +++ b/book/src/internals/tasks.md @@ -0,0 +1,3 @@ +# Task dispatcher + +**TODO** diff --git a/book/src/internals/timer-queue.md b/book/src/internals/timer-queue.md new file mode 100644 index 0000000000..7059285244 --- /dev/null +++ b/book/src/internals/timer-queue.md @@ -0,0 +1,3 @@ +# Timer queue + +**TODO** diff --git a/book/src/preface.md b/book/src/preface.md new file mode 100644 index 0000000000..c041ef5fa9 --- /dev/null +++ b/book/src/preface.md @@ -0,0 +1,12 @@ +

Real Time For the Masses

+ +

A concurrency framework for building real time systems

+ +# Preface + +This book contains user level documentation for the Real Time For the Masses +(RTFM) framework. The API reference can be found [here](../api/rtfm/index.html). + +{{#include ../../README.md:5:53}} + +{{#include ../../README.md:59:}} diff --git a/build.rs b/build.rs index ebfee06ce3..b29f0bd681 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,8 @@ use std::env; fn main() { let target = env::var("TARGET").unwrap(); - if target.starts_with("thumbv6m-") { - println!("cargo:rustc-cfg=armv6m"); + if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") { + println!("cargo:rustc-cfg=armv7m") } println!("cargo:rerun-if-changed=build.rs"); diff --git a/ci/after-success.sh b/ci/after-success.sh index 437726742e..f9f2522632 100644 --- a/ci/after-success.sh +++ b/ci/after-success.sh @@ -1,20 +1,27 @@ set -euxo pipefail main() { - cargo doc + rm -f .cargo/config + cargo doc --features timer-queue + ( cd book && mdbook build ) + + local td=$(mktemp -d) + cp -r target/doc $td/api + cp -r book/book $td/ + cp LICENSE-* $td/book/ mkdir ghp-import - curl -Ls https://github.com/davisp/ghp-import/archive/master.tar.gz | tar --strip-components 1 -C ghp-import -xz - ./ghp-import/ghp_import.py target/doc + ./ghp-import/ghp_import.py $td set +x git push -fq https://$GH_TOKEN@github.com/$TRAVIS_REPO_SLUG.git gh-pages && echo OK + + rm -rf $td } -# only publish on successful merges to master -if [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && [ $TARGET = x86_64-unknown-linux-gnu ]; then +if [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ]; then main fi diff --git a/ci/expected/baseline.run b/ci/expected/baseline.run new file mode 100644 index 0000000000..fa3822f696 --- /dev/null +++ b/ci/expected/baseline.run @@ -0,0 +1,4 @@ +init(baseline = Instant(0)) +foo(baseline = Instant(0)) +UART0(baseline = Instant(904)) +foo(baseline = Instant(904)) \ No newline at end of file diff --git a/ci/expected/capacity.run b/ci/expected/capacity.run new file mode 100644 index 0000000000..f96815dec6 --- /dev/null +++ b/ci/expected/capacity.run @@ -0,0 +1,5 @@ +foo(0) +foo(1) +foo(2) +foo(3) +bar diff --git a/ci/expected/idle.run b/ci/expected/idle.run new file mode 100644 index 0000000000..4307776304 --- /dev/null +++ b/ci/expected/idle.run @@ -0,0 +1,2 @@ +init +idle diff --git a/ci/expected/init.run b/ci/expected/init.run new file mode 100644 index 0000000000..b1b7161055 --- /dev/null +++ b/ci/expected/init.run @@ -0,0 +1 @@ +init diff --git a/ci/expected/interrupt.run b/ci/expected/interrupt.run new file mode 100644 index 0000000000..ef00864bc4 --- /dev/null +++ b/ci/expected/interrupt.run @@ -0,0 +1,4 @@ +init +UART0 called 1 time +idle +UART0 called 2 times diff --git a/ci/expected/late.run b/ci/expected/late.run new file mode 100644 index 0000000000..6d3d3e43e5 --- /dev/null +++ b/ci/expected/late.run @@ -0,0 +1 @@ +received message: 42 diff --git a/ci/expected/lock.run b/ci/expected/lock.run new file mode 100644 index 0000000000..156ac22286 --- /dev/null +++ b/ci/expected/lock.run @@ -0,0 +1,5 @@ +A +B - SHARED = 1 +C +D - SHARED = 2 +E diff --git a/ci/expected/message.run b/ci/expected/message.run new file mode 100644 index 0000000000..11814db20c --- /dev/null +++ b/ci/expected/message.run @@ -0,0 +1,6 @@ +foo +bar(0) +baz(1, 2) +foo +bar(1) +baz(2, 3) diff --git a/ci/expected/not-send.run b/ci/expected/not-send.run new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ci/expected/not-sync.run b/ci/expected/not-sync.run new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ci/expected/periodic.run b/ci/expected/periodic.run new file mode 100644 index 0000000000..11414c5354 --- /dev/null +++ b/ci/expected/periodic.run @@ -0,0 +1,3 @@ +foo(scheduled = Instant(8000000), now = Instant(8000196)) +foo(scheduled = Instant(16000000), now = Instant(16000196)) +foo(scheduled = Instant(24000000), now = Instant(24000196)) \ No newline at end of file diff --git a/ci/expected/ramfunc.grep.bar b/ci/expected/ramfunc.grep.bar new file mode 100644 index 0000000000..7f69d25cbf --- /dev/null +++ b/ci/expected/ramfunc.grep.bar @@ -0,0 +1,3 @@ +20000100 B bar::FREE_QUEUE::lk14244m263eivix +200000dc B bar::INPUTS::mi89534s44r1mnj1 +20000000 T bar::ns9009yhw2dc2y25 diff --git a/ci/expected/ramfunc.grep.foo b/ci/expected/ramfunc.grep.foo new file mode 100644 index 0000000000..a076ac0ad6 --- /dev/null +++ b/ci/expected/ramfunc.grep.foo @@ -0,0 +1,3 @@ +20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20 +200000dc B foo::INPUTS::thvubs85b91dg365 +000002c6 T foo::sidaht420cg1mcm8 diff --git a/ci/expected/ramfunc.run b/ci/expected/ramfunc.run new file mode 100644 index 0000000000..257cc5642c --- /dev/null +++ b/ci/expected/ramfunc.run @@ -0,0 +1 @@ +foo diff --git a/ci/expected/resource.run b/ci/expected/resource.run new file mode 100644 index 0000000000..9c70856ab2 --- /dev/null +++ b/ci/expected/resource.run @@ -0,0 +1,2 @@ +UART0: SHARED = 1 +UART1: SHARED = 2 diff --git a/ci/expected/schedule.run b/ci/expected/schedule.run new file mode 100644 index 0000000000..9facc71ada --- /dev/null +++ b/ci/expected/schedule.run @@ -0,0 +1,3 @@ +init @ Instant(0) +bar @ Instant(4000236) +foo @ Instant(8000173) \ No newline at end of file diff --git a/ci/expected/singleton.run b/ci/expected/singleton.run new file mode 100644 index 0000000000..c55dc1ac76 --- /dev/null +++ b/ci/expected/singleton.run @@ -0,0 +1,2 @@ +bar(2) +foo(1) diff --git a/ci/expected/static.run b/ci/expected/static.run new file mode 100644 index 0000000000..2c295c998d --- /dev/null +++ b/ci/expected/static.run @@ -0,0 +1,2 @@ +UART1(KEY = 0xdeadbeef) +UART0(KEY = 0xdeadbeef) diff --git a/ci/expected/task.run b/ci/expected/task.run new file mode 100644 index 0000000000..309fdb99e8 --- /dev/null +++ b/ci/expected/task.run @@ -0,0 +1,3 @@ +foo +baz +bar diff --git a/ci/expected/types.run b/ci/expected/types.run new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ci/install.sh b/ci/install.sh index e63e805481..9a896b90da 100644 --- a/ci/install.sh +++ b/ci/install.sh @@ -5,9 +5,11 @@ main() { rustup target add $TARGET fi - mkdir gcc - - curl -L https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2?revision=bc2c96c0-14b5-4bb4-9f18-bceb4050fee7?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,7-2018-q2-update | tar --strip-components=1 -C gcc -xj + mkdir qemu + curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 > qemu/qemu-system-arm + chmod +x qemu/qemu-system-arm } -main +if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST = true ]; then + main +fi diff --git a/ci/script.sh b/ci/script.sh index 0d42ff3a45..7502b85028 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -1,23 +1,95 @@ set -euxo pipefail main() { - if [ $TARGET = x86_64-unknown-linux-gnu ]; then - cargo build - cargo test --test cfail + local T=$TARGET + + if [ $T = x86_64-unknown-linux-gnu ]; then + # compile-fail and compile-pass tests + if [ $TRAVIS_RUST_VERSION = nightly ]; then + # TODO how to run a subset of these tests when timer-queue is disabled? + cargo test --features timer-queue --test compiletest --target $T + fi + + cargo check --target $T + cargo check --features timer-queue --target $T return fi - case $TARGET in - thumbv7em-none-eabi*) - cargo check --target $TARGET --features cm7-r0p1 - cargo check --target $TARGET --features cm7-r0p1 --examples - ;; - esac + cargo check --target $T --examples + cargo check --features timer-queue --target $T --examples - cargo check --target $TARGET - cargo check --target $TARGET --examples + # run-pass tests + case $T in + thumbv6m-none-eabi | thumbv7m-none-eabi) + local exs=( + idle + init + interrupt + + resource + lock + late + static + + task + message + capacity + + singleton + + types + not-send + not-sync + + ramfunc + ) + + for ex in ${exs[@]}; do + if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then + # LLD doesn't support this at the moment + continue + fi + + if [ $ex != types ]; then + cargo run --example $ex --target $T | \ + diff -u ci/expected/$ex.run - + + cargo run --example $ex --target $T --release | \ + diff -u ci/expected/$ex.run - + fi + + cargo run --features timer-queue --example $ex --target $T | \ + diff -u ci/expected/$ex.run - + + cargo run --features timer-queue --example $ex --target $T --release | \ + diff -u ci/expected/$ex.run - + done + esac } -if [ $TRAVIS_BRANCH != master ]; then +# fake Travis variables to be able to run this on a local machine +if [ -z ${TRAVIS_BRANCH-} ]; then + TRAVIS_BRANCH=auto +fi + +if [ -z ${TRAVIS_PULL_REQUEST-} ]; then + TRAVIS_PULL_REQUEST=false +fi + +if [ -z ${TRAVIS_RUST_VERSION-} ]; then + case $(rustc -V) in + *nightly*) + TRAVIS_RUST_VERSION=nightly + ;; + *beta*) + TRAVIS_RUST_VERSION=beta + ;; + *) + TRAVIS_RUST_VERSION=stable + ;; + esac +fi + +if [ $TRAVIS_BRANCH != master ] || [ $TRAVIS_PULL_REQUEST = true ]; then main fi diff --git a/examples/baseline.rs b/examples/baseline.rs new file mode 100644 index 0000000000..73ef4c9a81 --- /dev/null +++ b/examples/baseline.rs @@ -0,0 +1,61 @@ +//! examples/baseline.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +// NOTE: does NOT properly work on QEMU +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [foo])] + fn init() { + println!("init(baseline = {:?})", start); + + // `foo` inherits the baseline of `init`: `Instant(0)` + spawn.foo().unwrap(); + } + + #[task(schedule = [foo])] + fn foo() { + static mut ONCE: bool = true; + + println!("foo(baseline = {:?})", scheduled); + + if *ONCE { + *ONCE = false; + + rtfm::pend(Interrupt::UART0); + } else { + debug::exit(debug::EXIT_SUCCESS); + } + } + + #[interrupt(spawn = [foo])] + fn UART0() { + println!("UART0(baseline = {:?})", start); + + // `foo` inherits the baseline of `UART0`: its `start` time + spawn.foo().unwrap(); + } + + extern "C" { + fn UART1(); + } +}; diff --git a/examples/capacity.rs b/examples/capacity.rs new file mode 100644 index 0000000000..2dea2c300e --- /dev/null +++ b/examples/capacity.rs @@ -0,0 +1,57 @@ +//! examples/capacity.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [foo])] + fn init() { + rtfm::pend(Interrupt::UART0); + } + + #[interrupt(spawn = [foo, bar])] + fn UART0() { + spawn.foo(0).unwrap(); + spawn.foo(1).unwrap(); + spawn.foo(2).unwrap(); + spawn.foo(3).unwrap(); + + spawn.bar().unwrap(); + } + + #[task(capacity = 4)] + fn foo(x: u32) { + println!("foo({})", x); + } + + #[task] + fn bar() { + println!("bar"); + + debug::exit(debug::EXIT_SUCCESS); + } + + // Interrupt handlers used to dispatch software tasks + extern "C" { + fn UART1(); + } +}; diff --git a/examples/custom-type.rs b/examples/custom-type.rs deleted file mode 100644 index 826e9dd132..0000000000 --- a/examples/custom-type.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -pub struct Foo; - -app! { - device: stm32f103xx, - - resources: { - static CO_OWNED: Foo = Foo; - static ON: Foo = Foo; - static OWNED: Foo = Foo; - static SHARED: Foo = Foo; - }, - - idle: { - resources: [OWNED, SHARED], - }, - - tasks: { - SYS_TICK: { - path: sys_tick, - resources: [CO_OWNED, ON, SHARED], - }, - - TIM2: { - enabled: false, - path: tim2, - priority: 1, - resources: [CO_OWNED], - }, - }, -} - -fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} - -fn idle(_t: &mut Threshold, _r: ::idle::Resources) -> ! { - loop {} -} - -fn sys_tick(_t: &mut Threshold, _r: SYS_TICK::Resources) {} - -fn tim2(_t: &mut Threshold, _r: TIM2::Resources) {} diff --git a/examples/full-syntax.rs b/examples/full-syntax.rs deleted file mode 100644 index 9bdcd7b428..0000000000 --- a/examples/full-syntax.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! A showcase of the `app!` macro syntax -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static CO_OWNED: u32 = 0; - static ON: bool = false; - static OWNED: bool = false; - static SHARED: bool = false; - }, - - init: { - // This is the path to the `init` function - // - // `init` doesn't necessarily has to be in the root of the crate - path: main::init, - }, - - idle: { - // This is a path to the `idle` function - // - // `idle` doesn't necessarily has to be in the root of the crate - path: main::idle, - resources: [OWNED, SHARED], - }, - - tasks: { - SYS_TICK: { - path: sys_tick, - // If omitted priority is assumed to be 1 - // priority: 1, - resources: [CO_OWNED, ON, SHARED], - }, - - TIM2: { - // Tasks are enabled, between `init` and `idle`, by default but they - // can start disabled if `false` is specified here - enabled: false, - path: tim2, - priority: 1, - resources: [CO_OWNED], - }, - }, -} - -mod main { - use rtfm::{self, Resource, Threshold}; - - pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} - - pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! { - loop { - *r.OWNED = !*r.OWNED; - - if *r.OWNED { - if r.SHARED.claim(t, |shared, _| *shared) { - rtfm::wfi(); - } - } else { - r.SHARED.claim_mut(t, |shared, _| *shared = !*shared); - } - } - } -} - -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - *r.ON = !*r.ON; - - *r.CO_OWNED += 1; -} - -fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { - *r.CO_OWNED += 1; -} diff --git a/examples/generics.rs b/examples/generics.rs deleted file mode 100644 index aceba1a920..0000000000 --- a/examples/generics.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Working with resources in a generic fashion -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; -use stm32f103xx::{GPIOA, SPI1}; - -app! { - device: stm32f103xx, - - resources: { - static GPIOA: GPIOA; - static SPI1: SPI1; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [GPIOA, SPI1], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [GPIOA, SPI1], - }, - }, -} - -fn init(p: init::Peripherals) -> init::LateResources { - init::LateResources { - GPIOA: p.device.GPIOA, - SPI1: p.device.SPI1, - } -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// A generic function that uses some resources -fn work(t: &mut Threshold, gpioa: &G, spi1: &S) -where - G: Resource, - S: Resource, -{ - gpioa.claim(t, |_gpioa, t| { - // drive NSS low - - spi1.claim(t, |_spi1, _| { - // transfer data - }); - - // drive NSS high - }); -} - -// This task needs critical sections to access the resources -fn exti0(t: &mut Threshold, r: EXTI0::Resources) { - work(t, &r.GPIOA, &r.SPI1); -} - -// This task has direct access to the resources -fn exti1(t: &mut Threshold, r: EXTI1::Resources) { - work(t, &r.GPIOA, &r.SPI1); -} diff --git a/examples/idle.rs b/examples/idle.rs new file mode 100644 index 0000000000..013cccea27 --- /dev/null +++ b/examples/idle.rs @@ -0,0 +1,43 @@ +//! examples/idle.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() { + println!("init"); + } + + #[idle] + fn idle() -> ! { + static mut X: u32 = 0; + + // Safe access to local `static mut` variable + let _x: &'static mut u32 = X; + + println!("idle"); + + debug::exit(debug::EXIT_SUCCESS); + + loop {} + } +}; diff --git a/examples/init.rs b/examples/init.rs new file mode 100644 index 0000000000..d6caa60967 --- /dev/null +++ b/examples/init.rs @@ -0,0 +1,44 @@ +//! examples/init.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::app; + +// NOTE: This convenience macro will appear in all the other examples and +// will always look the same +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() { + static mut X: u32 = 0; + + // Cortex-M peripherals + let _core: rtfm::Peripherals = core; + + // Device specific peripherals + let _device: lm3s6965::Peripherals = device; + + // Safe access to local `static mut` variable + let _x: &'static mut u32 = X; + + println!("init"); + + debug::exit(debug::EXIT_SUCCESS); + } +}; diff --git a/examples/interrupt.rs b/examples/interrupt.rs new file mode 100644 index 0000000000..19b1fed0d5 --- /dev/null +++ b/examples/interrupt.rs @@ -0,0 +1,61 @@ +//! examples/interrupt.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() { + // Pends the UART0 interrupt but its handler won't run until *after* + // `init` returns because interrupts are disabled + rtfm::pend(Interrupt::UART0); + + println!("init"); + } + + #[idle] + fn idle() -> ! { + // interrupts are enabled again; the `UART0` handler runs at this point + + println!("idle"); + + rtfm::pend(Interrupt::UART0); + + debug::exit(debug::EXIT_SUCCESS); + + loop {} + } + + #[interrupt] + fn UART0() { + static mut TIMES: u32 = 0; + + // Safe access to local `static mut` variable + *TIMES += 1; + + println!( + "UART0 called {} time{}", + *TIMES, + if *TIMES > 1 { "s" } else { "" } + ); + } +}; diff --git a/examples/late-resources.rs b/examples/late-resources.rs deleted file mode 100644 index 3bfc38846d..0000000000 --- a/examples/late-resources.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Demonstrates initialization of resources in `init`. -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - // Usually, resources are initialized with a constant initializer: - static ON: bool = false; - - // However, there are cases where this is not possible or not desired. - // For example, there may not be a sensible value to use, or the type may - // not be constructible in a constant (like `Vec`). - // - // While it is possible to use an `Option` in some cases, that requires - // you to properly initialize it and `.unwrap()` it at every use. It - // also consumes more memory. - // - // To solve this, it is possible to defer initialization of resources to - // `init` by omitting the initializer. Doing that will require `init` to - // return the values of all "late" resources. - static IP_ADDRESS: u32; - - // PORT is used by 2 tasks, making it a shared resource. This just tests - // another internal code path and is not important for the example. - static PORT: u16; - }, - - idle: { - // Test that late resources can be used in idle - resources: [IP_ADDRESS], - }, - - tasks: { - SYS_TICK: { - priority: 1, - path: sys_tick, - resources: [IP_ADDRESS, PORT, ON], - }, - - EXTI0: { - priority: 2, - path: exti0, - resources: [PORT], - } - } -} - -// The signature of `init` is now required to have a specific return type. -fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources { - // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet - // initialized. - //_r.IP_ADDRESS; // doesn't compile - - // ...obtain value for IP_ADDRESS from EEPROM/DHCP... - let ip_address = 0x7f000001; - - init::LateResources { - // This struct will contain fields for all resources with omitted - // initializers. - IP_ADDRESS: ip_address, - PORT: 0, - } -} - -fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) { - // Other tasks can access late resources like any other, since they are - // guaranteed to be initialized when tasks are run. - - r.IP_ADDRESS; -} - -fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} - -fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! { - loop { - rtfm::wfi(); - } -} diff --git a/examples/late.rs b/examples/late.rs new file mode 100644 index 0000000000..6d76c58ab2 --- /dev/null +++ b/examples/late.rs @@ -0,0 +1,65 @@ +//! examples/late.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use heapless::{ + consts::*, + spsc::{Consumer, Producer, Queue}, +}; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + // Late resources + static mut P: Producer<'static, u32, U4> = (); + static mut C: Consumer<'static, u32, U4> = (); + + #[init] + fn init() { + // NOTE: we use `Option` here to work around the lack of + // a stable `const` constructor + static mut Q: Option> = None; + + *Q = Some(Queue::new()); + let (p, c) = Q.as_mut().unwrap().split(); + + // Initialization of late resources + P = p; + C = c; + } + + #[idle(resources = [C])] + fn idle() -> ! { + loop { + if let Some(byte) = resources.C.dequeue() { + println!("received message: {}", byte); + + debug::exit(debug::EXIT_SUCCESS); + } else { + rtfm::pend(Interrupt::UART0); + } + } + } + + #[interrupt(resources = [P])] + fn UART0() { + resources.P.enqueue(42).unwrap(); + } +}; diff --git a/examples/lock.rs b/examples/lock.rs new file mode 100644 index 0000000000..097bd5c3e3 --- /dev/null +++ b/examples/lock.rs @@ -0,0 +1,71 @@ +//! examples/lock.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + static mut SHARED: u32 = 0; + + #[init] + fn init() { + rtfm::pend(Interrupt::GPIOA); + } + + // when omitted priority is assumed to be `1` + #[interrupt(resources = [SHARED])] + fn GPIOA() { + println!("A"); + + // the lower priority task requires a critical section to access the data + resources.SHARED.lock(|shared| { + // data can only be modified within this critical section (closure) + *shared += 1; + + // GPIOB will *not* run right now due to the critical section + rtfm::pend(Interrupt::GPIOB); + + println!("B - SHARED = {}", *shared); + + // GPIOC does not contend for `SHARED` so it's allowed to run now + rtfm::pend(Interrupt::GPIOC); + }); + + // critical section is over: GPIOB can now start + + println!("E"); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[interrupt(priority = 2, resources = [SHARED])] + fn GPIOB() { + // the higher priority task does *not* need a critical section + *resources.SHARED += 1; + + println!("D - SHARED = {}", *resources.SHARED); + } + + #[interrupt(priority = 3)] + fn GPIOC() { + println!("C"); + } +}; diff --git a/examples/message.rs b/examples/message.rs new file mode 100644 index 0000000000..1ff08b22a7 --- /dev/null +++ b/examples/message.rs @@ -0,0 +1,61 @@ +//! examples/message.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [foo])] + fn init() { + spawn.foo(/* no message */).unwrap(); + } + + #[task(spawn = [bar])] + fn foo() { + static mut COUNT: u32 = 0; + + println!("foo"); + + spawn.bar(*COUNT).unwrap(); + *COUNT += 1; + } + + #[task(spawn = [baz])] + fn bar(x: u32) { + println!("bar({})", x); + + spawn.baz(x + 1, x + 2).unwrap(); + } + + #[task(spawn = [foo])] + fn baz(x: u32, y: u32) { + println!("baz({}, {})", x, y); + + if x + y > 4 { + debug::exit(debug::EXIT_SUCCESS); + } + + spawn.foo().unwrap(); + } + + extern "C" { + fn UART0(); + } +}; diff --git a/examples/nested.rs b/examples/nested.rs deleted file mode 100644 index 46c00b2b09..0000000000 --- a/examples/nested.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Nesting claims and how the preemption threshold works -//! -//! If you run this program you'll hit the breakpoints as indicated by the -//! letters in the comments: A, then B, then C, etc. -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; -use stm32f103xx::Interrupt; - -app! { - device: stm32f103xx, - - resources: { - static LOW: u64 = 0; - static HIGH: u64 = 0; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [LOW, HIGH], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [LOW], - }, - - EXTI2: { - path: exti2, - priority: 3, - resources: [HIGH], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - // A - rtfm::bkpt(); - - // Sets task `exti0` as pending - // - // Because `exti0` has higher priority than `idle` it will be executed - // immediately - rtfm::set_pending(Interrupt::EXTI0); // ~> exti0 - - loop { - rtfm::wfi(); - } -} - -#[allow(non_snake_case)] -fn exti0( - t: &mut Threshold, - EXTI0::Resources { - LOW: mut low, - HIGH: mut high, - }: EXTI0::Resources, -) { - // Because this task has a priority of 1 the preemption threshold `t` also - // starts at 1 - - // B - rtfm::bkpt(); - - // Because `exti1` has higher priority than `exti0` it can preempt it - rtfm::set_pending(Interrupt::EXTI1); // ~> exti1 - - // A claim creates a critical section - low.claim_mut(t, |_low, t| { - // This claim increases the preemption threshold to 2 - // - // 2 is just high enough to not race with task `exti1` for access to the - // `LOW` resource - - // D - rtfm::bkpt(); - - // Now `exti1` can't preempt this task because its priority is equal to - // the current preemption threshold - rtfm::set_pending(Interrupt::EXTI1); - - // But `exti2` can, because its priority is higher than the current - // preemption threshold - rtfm::set_pending(Interrupt::EXTI2); // ~> exti2 - - // F - rtfm::bkpt(); - - // Claims can be nested - high.claim_mut(t, |_high, _| { - // This claim increases the preemption threshold to 3 - - // Now `exti2` can't preempt this task - rtfm::set_pending(Interrupt::EXTI2); - - // G - rtfm::bkpt(); - }); - - // Upon leaving the critical section the preemption threshold drops back - // to 2 and `exti2` immediately preempts this task - // ~> exti2 - }); - - // Once again the preemption threshold drops but this time to 1. Now the - // pending `exti1` task can preempt this task - // ~> exti1 -} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) { - // C, I - rtfm::bkpt(); -} - -fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) { - // E, H - rtfm::bkpt(); -} diff --git a/examples/not-send.rs b/examples/not-send.rs new file mode 100644 index 0000000000..be78c332ff --- /dev/null +++ b/examples/not-send.rs @@ -0,0 +1,58 @@ +//! `examples/not-send.rs` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::marker::PhantomData; + +use cortex_m_semihosting::debug; +use rtfm::app; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[app(device = lm3s6965)] +const APP: () = { + static mut SHARED: Option = None; + + #[init(spawn = [baz, quux])] + fn init() { + spawn.baz().unwrap(); + spawn.quux().unwrap(); + } + + #[task(spawn = [bar])] + fn foo() { + // scenario 1: message passed to task that runs at the same priority + spawn.bar(NotSend { _0: PhantomData }).ok(); + } + + #[task] + fn bar(_x: NotSend) { + // scenario 1 + } + + #[task(priority = 2, resources = [SHARED])] + fn baz() { + // scenario 2: resource shared between tasks that run at the same priority + *resources.SHARED = Some(NotSend { _0: PhantomData }); + } + + #[task(priority = 2, resources = [SHARED])] + fn quux() { + // scenario 2 + let _not_send = resources.SHARED.take().unwrap(); + + debug::exit(debug::EXIT_SUCCESS); + } + + extern "C" { + fn UART0(); + fn UART1(); + } +}; diff --git a/examples/not-sync.rs b/examples/not-sync.rs new file mode 100644 index 0000000000..d94e0a0467 --- /dev/null +++ b/examples/not-sync.rs @@ -0,0 +1,41 @@ +//! `examples/not-sync.rs` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_halt; + +use core::marker::PhantomData; + +use cortex_m_semihosting::debug; +use rtfm::app; + +pub struct NotSync { + _0: PhantomData<*const ()>, +} + +#[app(device = lm3s6965)] +const APP: () = { + static SHARED: NotSync = NotSync { _0: PhantomData }; + + #[init] + fn init() { + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(resources = [SHARED])] + fn foo() { + let _: &NotSync = resources.SHARED; + } + + #[task(resources = [SHARED])] + fn bar() { + let _: &NotSync = resources.SHARED; + } + + extern "C" { + fn UART0(); + } +}; diff --git a/examples/one-task.rs b/examples/one-task.rs deleted file mode 100644 index dc2bfd29b4..0000000000 --- a/examples/one-task.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! An application with one task -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m; -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use cortex_m::peripheral::syst::SystClkSource; -use rtfm::{app, Threshold}; -use stm32f103xx::GPIOC; - -app! { - device: stm32f103xx, - - // Here data resources are declared - // - // Data resources are static variables that are safe to share across tasks - resources: { - // Declaration of resources looks exactly like declaration of static - // variables - static ON: bool = false; - }, - - // Here tasks are declared - // - // Each task corresponds to an interrupt or an exception. Every time the - // interrupt or exception becomes *pending* the corresponding task handler - // will be executed. - tasks: { - // Here we declare that we'll use the SYS_TICK exception as a task - SYS_TICK: { - // Path to the task handler - path: sys_tick, - - // These are the resources this task has access to. - // - // The resources listed here must also appear in `app.resources` - resources: [ON], - }, - } -} - -fn init(mut p: init::Peripherals, r: init::Resources) { - // `init` can modify all the `resources` declared in `app!` - r.ON; - - // power on GPIOC - p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled()); - - // configure PC13 as output - p.device.GPIOC.bsrr.write(|w| w.bs13().set()); - p.device - .GPIOC - .crh - .modify(|_, w| w.mode13().output().cnf13().push()); - - // configure the system timer to generate one interrupt every second - p.core.SYST.set_clock_source(SystClkSource::Core); - p.core.SYST.set_reload(8_000_000); // 1s - p.core.SYST.enable_interrupt(); - p.core.SYST.enable_counter(); -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// This is the task handler of the SYS_TICK exception -// -// `_t` is the preemption threshold token. We won't use it in this program. -// -// `r` is the set of resources this task has access to. `SYS_TICK::Resources` -// has one field per resource declared in `app!`. -#[allow(unsafe_code)] -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // toggle state - *r.ON = !*r.ON; - - if *r.ON { - // set the pin PC13 high - // NOTE(unsafe) atomic write to a stateless register - unsafe { - (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set()); - } - } else { - // set the pin PC13 low - // NOTE(unsafe) atomic write to a stateless register - unsafe { - (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset()); - } - } -} diff --git a/examples/periodic.rs b/examples/periodic.rs new file mode 100644 index 0000000000..0fb8bdf4fa --- /dev/null +++ b/examples/periodic.rs @@ -0,0 +1,43 @@ +//! examples/periodic.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use rtfm::{app, Instant}; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +const PERIOD: u32 = 8_000_000; + +// NOTE: does NOT work on QEMU! +#[app(device = lm3s6965)] +const APP: () = { + #[init(schedule = [foo])] + fn init() { + schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); + } + + #[task(schedule = [foo])] + fn foo() { + let now = Instant::now(); + println!("foo(scheduled = {:?}, now = {:?})", scheduled, now); + + schedule.foo(scheduled + PERIOD.cycles()).unwrap(); + } + + extern "C" { + fn UART0(); + } +}; diff --git a/examples/preemption.rs b/examples/preemption.rs deleted file mode 100644 index 340b976682..0000000000 --- a/examples/preemption.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Two tasks running at *different* priorities with access to the same resource -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static COUNTER: u64 = 0; - }, - - tasks: { - // The `SYS_TICK` task has higher priority than `TIM2` - SYS_TICK: { - path: sys_tick, - priority: 2, - resources: [COUNTER], - }, - - TIM2: { - path: tim2, - priority: 1, - resources: [COUNTER], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) { - // .. -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // .. - - // This task can't be preempted by `tim2` so it has direct access to the - // resource data - *r.COUNTER += 1; - - // .. -} - -fn tim2(t: &mut Threshold, mut r: TIM2::Resources) { - // .. - - // As this task runs at lower priority it needs a critical section to - // prevent `sys_tick` from preempting it while it modifies this resource - // data. The critical section is required to prevent data races which can - // lead to undefined behavior. - r.COUNTER.claim_mut(t, |counter, _t| { - // `claim_mut` creates a critical section - *counter += 1; - }); - - // .. -} diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs new file mode 100644 index 0000000000..b7fe252357 --- /dev/null +++ b/examples/ramfunc.rs @@ -0,0 +1,53 @@ +//! examples/ramfunc.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [bar])] + fn init() { + spawn.bar().unwrap(); + } + + #[inline(never)] + #[task] + fn foo() { + println!("foo"); + + debug::exit(debug::EXIT_SUCCESS); + } + + // run this task from RAM + #[inline(never)] + #[link_section = ".data.bar"] + #[task(priority = 2, spawn = [foo])] + fn bar() { + spawn.foo().unwrap(); + } + + extern "C" { + fn UART0(); + + // run the task dispatcher from RAM + #[link_section = ".data.UART1"] + fn UART1(); + } +}; diff --git a/examples/resource.rs b/examples/resource.rs new file mode 100644 index 0000000000..2777da1747 --- /dev/null +++ b/examples/resource.rs @@ -0,0 +1,60 @@ +//! examples/resource.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + // A resource + static mut SHARED: u32 = 0; + + #[init] + fn init() { + rtfm::pend(Interrupt::UART0); + rtfm::pend(Interrupt::UART1); + } + + #[idle] + fn idle() -> ! { + debug::exit(debug::EXIT_SUCCESS); + + // error: `SHARED` can't be accessed from this context + // SHARED += 1; + + loop {} + } + + // `SHARED` can be access from this context + #[interrupt(resources = [SHARED])] + fn UART0() { + *resources.SHARED += 1; + + println!("UART0: SHARED = {}", resources.SHARED); + } + + // `SHARED` can be access from this context + #[interrupt(resources = [SHARED])] + fn UART1() { + *resources.SHARED += 1; + + println!("UART1: SHARED = {}", resources.SHARED); + } +}; diff --git a/examples/safe-static-mut-ref.rs b/examples/safe-static-mut-ref.rs deleted file mode 100644 index 9579f52340..0000000000 --- a/examples/safe-static-mut-ref.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Safe creation of `&'static mut` references -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { - device: stm32f103xx, - - resources: { - static BUFFER: [u8; 16] = [0; 16]; - }, - - init: { - resources: [BUFFER], - }, -} - -fn init(_p: init::Peripherals, r: init::Resources) { - let _buf: &'static mut [u8; 16] = r.BUFFER; -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} diff --git a/examples/schedule.rs b/examples/schedule.rs new file mode 100644 index 0000000000..9fb2796d1a --- /dev/null +++ b/examples/schedule.rs @@ -0,0 +1,51 @@ +//! examples/schedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use rtfm::{app, Instant}; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +// NOTE: does NOT work on QEMU! +#[app(device = lm3s6965)] +const APP: () = { + #[init(schedule = [foo, bar])] + fn init() { + let now = Instant::now(); + + println!("init @ {:?}", now); + + // Schedule `foo` to run 8e6 cycles (clock cycles) in the future + schedule.foo(now + 8_000_000.cycles()).unwrap(); + + // Schedule `bar` to run 4e6 cycles in the future + schedule.bar(now + 4_000_000.cycles()).unwrap(); + } + + #[task] + fn foo() { + println!("foo @ {:?}", Instant::now()); + } + + #[task] + fn bar() { + println!("bar @ {:?}", Instant::now()); + } + + extern "C" { + fn UART0(); + } +}; diff --git a/examples/singleton.rs b/examples/singleton.rs new file mode 100644 index 0000000000..888a5a6787 --- /dev/null +++ b/examples/singleton.rs @@ -0,0 +1,69 @@ +//! examples/singleton.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use alloc_singleton::stable::pool::{Box, Pool}; +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[Singleton(Send)] + static mut M: [u32; 2] = [0; 2]; + + static mut P: Pool = (); + + #[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) { + println!("foo({})", x); + + resources.P.lock(|p| p.dealloc(x)); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(priority = 2, resources = [P])] + fn bar(x: Box) { + println!("bar({})", x); + + resources.P.dealloc(x); + } + + extern "C" { + fn UART0(); + fn UART1(); + } +}; diff --git a/examples/smallest.rs b/examples/smallest.rs new file mode 100644 index 0000000000..e4d86be9c9 --- /dev/null +++ b/examples/smallest.rs @@ -0,0 +1,17 @@ +//! examples/smallest.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +// panic-handler crate +extern crate panic_semihosting; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} +}; diff --git a/examples/static.rs b/examples/static.rs new file mode 100644 index 0000000000..3dc0e89c0d --- /dev/null +++ b/examples/static.rs @@ -0,0 +1,47 @@ +//! examples/static.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use lm3s6965::Interrupt; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + static KEY: u32 = (); + + #[init] + fn init() { + rtfm::pend(Interrupt::UART0); + rtfm::pend(Interrupt::UART1); + + KEY = 0xdeadbeef; + } + + #[interrupt(resources = [KEY])] + fn UART0() { + println!("UART0(KEY = {:#x})", resources.KEY); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[interrupt(priority = 2, resources = [KEY])] + fn UART1() { + println!("UART1(KEY = {:#x})", resources.KEY); + } +}; diff --git a/examples/task.rs b/examples/task.rs new file mode 100644 index 0000000000..b1cd7ae1df --- /dev/null +++ b/examples/task.rs @@ -0,0 +1,61 @@ +//! examples/task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::app; + +macro_rules! println { + ($($tt:tt)*) => { + if let Ok(mut stdout) = cortex_m_semihosting::hio::hstdout() { + use core::fmt::Write; + + writeln!(stdout, $($tt)*).ok(); + } + }; +} + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [foo])] + fn init() { + spawn.foo().unwrap(); + } + + #[task(spawn = [bar, baz])] + fn foo() { + println!("foo"); + + // spawns `bar` onto the task scheduler + // `foo` and `bar` have the same priority so `bar` will not run until + // after `foo` terminates + spawn.bar().unwrap(); + + // spawns `baz` onto the task scheduler + // `baz` has higher priority than `foo` so it immediately preempts `foo` + spawn.baz().unwrap(); + } + + #[task] + fn bar() { + println!("bar"); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(priority = 2)] + fn baz() { + println!("baz"); + } + + // Interrupt handlers used to dispatch software tasks + extern "C" { + fn UART0(); + fn UART1(); + } +}; diff --git a/examples/two-tasks.rs b/examples/two-tasks.rs deleted file mode 100644 index 23489151a1..0000000000 --- a/examples/two-tasks.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! Two tasks running at the *same* priority with access to the same resource -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static COUNTER: u64 = 0; - }, - - // Both SYS_TICK and TIM2 have access to the `COUNTER` data - tasks: { - SYS_TICK: { - path: sys_tick, - resources: [COUNTER], - }, - - TIM2: { - path: tim2, - resources: [COUNTER], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) { - // .. -} - -fn idle() -> ! { - loop { - rtfm::wfi(); - } -} - -// As both tasks are running at the same priority one can't preempt the other. -// Thus both tasks have direct access to the resource -fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { - // .. - - *r.COUNTER += 1; - - // .. -} - -fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { - // .. - - *r.COUNTER += 1; - - // .. -} diff --git a/examples/types.rs b/examples/types.rs new file mode 100644 index 0000000000..660620858f --- /dev/null +++ b/examples/types.rs @@ -0,0 +1,54 @@ +//! examples/types.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +extern crate panic_semihosting; + +use cortex_m_semihosting::debug; +use rtfm::{app, Instant}; + +#[app(device = lm3s6965)] +const APP: () = { + static mut SHARED: u32 = 0; + + #[init(schedule = [foo], spawn = [foo])] + fn init() { + let _: Instant = start; + let _: rtfm::Peripherals = core; + let _: lm3s6965::Peripherals = device; + let _: init::Schedule = schedule; + let _: init::Spawn = spawn; + + debug::exit(debug::EXIT_SUCCESS); + } + + #[exception(schedule = [foo], spawn = [foo])] + fn SVCall() { + let _: Instant = start; + let _: SVCall::Schedule = schedule; + let _: SVCall::Spawn = spawn; + } + + #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])] + fn UART0() { + let _: Instant = start; + let _: resources::SHARED = resources.SHARED; + let _: UART0::Schedule = schedule; + let _: UART0::Spawn = spawn; + } + + #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] + fn foo() { + let _: Instant = scheduled; + let _: foo::Resources = resources; + let _: foo::Schedule = schedule; + let _: foo::Spawn = spawn; + } + + extern "C" { + fn UART1(); + } +}; diff --git a/examples/zero-tasks.rs b/examples/zero-tasks.rs deleted file mode 100644 index abd1c4cd25..0000000000 --- a/examples/zero-tasks.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Minimal example with zero tasks -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename -extern crate stm32f103xx; // the device crate - -// import the procedural macro -use rtfm::app; - -// This macro call indicates that this is a RTFM application -// -// This macro will expand to a `main` function so you don't need to supply -// `main` yourself. -app! { - // this is the path to the device crate - device: stm32f103xx, -} - -// The initialization phase. -// -// This runs first and within a *global* critical section. Nothing can preempt -// this function. -fn init(p: init::Peripherals) { - // This function has access to all the peripherals of the device - p.core.SYST; - p.device.GPIOA; - p.device.RCC; - // .. -} - -// The idle loop. -// -// This runs after `init` and has a priority of 0. All tasks can preempt this -// function. This function can never return so it must contain some sort of -// endless loop. -fn idle() -> ! { - loop { - // This puts the processor to sleep until there's a task to service - rtfm::wfi(); - } -} diff --git a/gen-examples.sh b/gen-examples.sh deleted file mode 100644 index 20c9d7c732..0000000000 --- a/gen-examples.sh +++ /dev/null @@ -1,57 +0,0 @@ -# Converts the examples in the `examples` directory into documentation in the -# `examples` module (`src/examples/*.rs`) - -set -ex - -main() { - local examples=( - zero-tasks - one-task - two-tasks - preemption - nested - late-resources - safe-static-mut-ref - generics - full-syntax - ) - - rm -rf src/examples - - mkdir src/examples - - cat >src/examples/mod.rs <<'EOF' -//! Examples -// Auto-generated. Do not modify. -EOF - - local i=0 out= - for ex in ${examples[@]}; do - name=_${i}_${ex//-/_} - out=src/examples/${name}.rs - - echo "pub mod $name;" >> src/examples/mod.rs - - grep '//!' examples/$ex.rs > $out - echo '//!' >> $out - echo '//! ```' >> $out - grep -v '//!' examples/$ex.rs | ( - IFS='' - - while read line; do - echo "//! $line" >> $out; - done - ) - echo '//! ```' >> $out - echo '// Auto-generated. Do not modify.' >> $out - - - chmod -x $out - - i=$(( i + 1 )) - done - - chmod -x src/examples/mod.rs -} - -main diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 410be2fc59..684a7e1241 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,20 +1,22 @@ [package] authors = ["Jorge Aparicio "] -categories = ["concurrency", "embedded", "no-std"] -description = "Procedural macros of the cortex-m-rtfm crate" -documentation = "https://docs.rs/cortex-m-rtfm-macros" -keywords = ["arm", "cortex-m"] -license = "MIT OR Apache-2.0" name = "cortex-m-rtfm-macros" -repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.3.2" - -[dependencies] -failure = "0.1.1" -proc-macro2 = "0.4.6" -quote = "0.6.3" -rtfm-syntax = "0.3.4" -syn = "0.14.2" +version = "0.4.0-beta.1" [lib] proc-macro = true + +[dependencies] +quote = "0.6.8" +proc-macro2 = "0.4.20" + +[dependencies.syn] +features = ["extra-traits", "full"] +version = "0.15.6" + +[dependencies.rand] +default-features = false +version = "0.5.5" + +[features] +timer-queue = [] \ No newline at end of file diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index 666dc306a3..04b462fa7a 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,77 +1,243 @@ -use std::cmp; -use std::collections::HashMap; +use std::{ + cmp, + collections::{HashMap, HashSet}, +}; -use syn::Ident; +use syn::{Attribute, Ident, Type}; -use check::App; +use syntax::{App, Idents}; pub type Ownerships = HashMap; +pub struct Analysis { + /// Capacities of free queues + pub capacities: Capacities, + pub dispatchers: Dispatchers, + // Ceilings of free queues + pub free_queues: HashMap, + pub resources_assert_send: HashSet>, + pub tasks_assert_send: HashSet, + /// Types of RO resources that need to be Sync + pub assert_sync: HashSet>, + // Resource ownership + pub ownerships: Ownerships, + // Ceilings of ready queues + pub ready_queues: HashMap, + 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, + pub interrupt: Ident, + /// Tasks dispatched at this priority level + pub tasks: Vec, + // 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; - 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; + +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::>(); + 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 = 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, } diff --git a/macros/src/check.rs b/macros/src/check.rs index b81fc4d435..f28322074e 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,95 +1,115 @@ -use std::collections::HashMap; +use std::{collections::HashSet, iter}; -use syn::{Ident, Path}; -use syntax::check::{self, Idents, Idle, Init, Statics}; -use syntax::{self, Result}; +use proc_macro2::Span; +use syn::parse; -pub struct App { - pub device: Path, - pub idle: Idle, - pub init: Init, - pub resources: Statics, - pub tasks: Tasks, -} +use syntax::App; -pub type Tasks = HashMap; +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> { 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 { - 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> { + 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::>() + .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 { - 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::>()?, - }; - - Ok(app) -} - -fn task(name: &str, task: syntax::check::Task) -> Result { - 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(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs new file mode 100644 index 0000000000..ff1062ae4b --- /dev/null +++ b/macros/src/codegen.rs @@ -0,0 +1,1815 @@ +use proc_macro::TokenStream; +use std::{ + collections::HashMap, + sync::atomic::{AtomicUsize, Ordering}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use proc_macro2::Span; +use quote::quote; +use rand::{Rng, SeedableRng}; +use syn::{ArgCaptured, Ident, IntSuffix, LitInt}; + +use analyze::{Analysis, Ownership}; +use syntax::{App, Idents, Static}; + +// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names. +// In some instances we also use the pseudo-hygienic names for safety, for example the user should +// not modify the priority field of resources. +type Aliases = HashMap; + +struct Context { + // Alias + #[cfg(feature = "timer-queue")] + baseline: Ident, + // Dispatcher -> Alias (`enum`) + enums: HashMap, + // Task -> Alias (`static` / resource) + free_queues: Aliases, + // Alias (`fn`) + idle: Ident, + // Alias (`fn`) + init: Ident, + // Task -> Alias (`static`) + inputs: Aliases, + // Alias + priority: Ident, + // Dispatcher -> Alias (`static` / resource) + ready_queues: HashMap, + // For non-singletons this maps the resource name to its `static mut` variable name + statics: Aliases, + /// Task -> Alias (`struct`) + resources: HashMap, + // Task -> Alias (`static`) + #[cfg(feature = "timer-queue")] + scheduleds: Aliases, + // Task -> Alias (`fn`) + spawn_fn: Aliases, + // Alias (`enum`) + schedule_enum: Ident, + // Task -> Alias (`fn`) + schedule_fn: Aliases, + tasks: Aliases, + // Alias (`struct` / `static mut`) + timer_queue: Ident, +} + +impl Default for Context { + fn default() -> Self { + Context { + #[cfg(feature = "timer-queue")] + baseline: mk_ident(), + enums: HashMap::new(), + free_queues: Aliases::new(), + idle: mk_ident(), + init: mk_ident(), + inputs: Aliases::new(), + priority: mk_ident(), + ready_queues: HashMap::new(), + statics: Aliases::new(), + resources: HashMap::new(), + #[cfg(feature = "timer-queue")] + scheduleds: Aliases::new(), + spawn_fn: Aliases::new(), + schedule_enum: mk_ident(), + schedule_fn: Aliases::new(), + tasks: Aliases::new(), + timer_queue: mk_ident(), + } + } +} + +struct Resources { + alias: Ident, + decl: proc_macro2::TokenStream, +} + +pub fn app(app: &App, analysis: &Analysis) -> TokenStream { + let mut ctxt = Context::default(); + + let device = &app.args.device; + + let resources = resources(&mut ctxt, &app, analysis); + + let tasks = tasks(&mut ctxt, &app, analysis); + + let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis); + + let init_fn = init(&mut ctxt, &app, analysis); + let init_arg = if cfg!(feature = "timer-queue") { + quote!(rtfm::Peripherals { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: &mut p.DCB, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + SCB: &mut p.SCB, + TPIU: p.TPIU, + }) + } else { + quote!(rtfm::Peripherals { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: p.DCB, + DWT: p.DWT, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + SCB: &mut p.SCB, + SYST: p.SYST, + TPIU: p.TPIU, + }) + }; + + let post_init = post_init(&ctxt, &app, analysis); + + let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis); + + let exceptions = exceptions(&mut ctxt, app, analysis); + + let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis); + + let spawn = spawn(&mut ctxt, app, analysis); + + let schedule = match () { + #[cfg(feature = "timer-queue")] + () => schedule(&ctxt, app), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let timer_queue = timer_queue(&ctxt, app, analysis); + + let pre_init = pre_init(&ctxt, analysis); + + let assertions = assertions(app, analysis); + + let init = &ctxt.init; + quote!( + #resources + + #spawn + + #timer_queue + + #schedule + + #dispatchers_data + + #(#exceptions)* + + #root_interrupts + + // We put these items into a pseudo-module to avoid a collision between the `interrupt` + // import and user code + const APP: () = { + use #device::interrupt; + + #scoped_interrupts + + #(#dispatchers)* + }; + + #(#tasks)* + + #init_fn + + #idle_fn + + #[allow(unsafe_code)] + #[rtfm::export::entry] + #[doc(hidden)] + unsafe fn main() -> ! { + #assertions + + rtfm::export::interrupt::disable(); + + #pre_init + + #init(#init_arg); + + #post_init + + rtfm::export::interrupt::enable(); + + #idle_expr + } + ) + .into() +} + +fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + let mut module = vec![]; + for (name, res) in &app.resources { + let attrs = &res.attrs; + let mut_ = &res.mutability; + let ty = &res.ty; + let expr = &res.expr; + + if res.singleton { + items.push(quote!( + #(#attrs)* + pub static #mut_ #name: #ty = #expr; + )); + + let alias = mk_ident(); + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + items.push(mk_resource( + ctxt, + name, + quote!(#name), + *ceiling, + quote!(&mut <#name as owned_singleton::Singleton>::new()), + app, + Some(&mut module), + )) + } + + ctxt.statics.insert(name.clone(), alias); + } else { + let alias = mk_ident(); + let symbol = format!("{}::{}", name, alias); + + items.push( + expr.as_ref() + .map(|expr| { + quote!( + #(#attrs)* + #[export_name = #symbol] + static mut #alias: #ty = #expr; + ) + }) + .unwrap_or_else(|| { + quote!( + #(#attrs)* + #[export_name = #symbol] + static mut #alias: rtfm::export::MaybeUninit<#ty> = + rtfm::export::MaybeUninit::uninitialized(); + ) + }), + ); + + if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { + if res.mutability.is_some() { + let ptr = if res.expr.is_none() { + quote!(unsafe { #alias.get_mut() }) + } else { + quote!(unsafe { &mut #alias }) + }; + + items.push(mk_resource( + ctxt, + name, + quote!(#ty), + *ceiling, + ptr, + app, + Some(&mut module), + )); + } + } + + ctxt.statics.insert(name.clone(), alias); + } + } + + if !module.is_empty() { + items.push(quote!( + /// Resource proxies + pub mod resources { + #(#module)* + })); + } + + quote!(#(#items)*) +} + +fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let attrs = &app.init.attrs; + let locals = mk_locals(&app.init.statics, true); + let stmts = &app.init.stmts; + let assigns = app + .init + .assigns + .iter() + .map(|assign| { + if app + .resources + .get(&assign.left) + .map(|r| r.expr.is_none()) + .unwrap_or(false) + { + let alias = &ctxt.statics[&assign.left]; + let expr = &assign.right; + quote!(unsafe { #alias.set(#expr); }) + } else { + let left = &assign.left; + let right = &assign.right; + quote!(#left = #right;) + } + }) + .collect::>(); + + let prelude = prelude( + ctxt, + Kind::Init, + &app.init.args.resources, + &app.init.args.spawn, + &app.init.args.schedule, + app, + 255, + analysis, + ); + + let module = module( + ctxt, + Kind::Init, + !app.init.args.schedule.is_empty(), + !app.init.args.spawn.is_empty(), + app, + ); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::artificial(0);), + + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &app.init.unsafety; + let device = &app.args.device; + let init = &ctxt.init; + let name = format!("init::{}", init); + quote!( + #module + + #(#attrs)* + #[export_name = #name] + #unsafety fn #init(mut core: rtfm::Peripherals) { + #(#locals)* + + #baseline_let + + #prelude + + let mut device = unsafe { #device::Peripherals::steal() }; + + #start_let + + #(#stmts)* + + #(#assigns)* + } + ) +} + +fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut exprs = vec![]; + + // TODO turn the assertions that check that the priority is not larger than what's supported by + // the device into compile errors + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + for (name, interrupt) in &app.interrupts { + let priority = interrupt.args.priority; + exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name))); + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.NVIC.set_priority( + #device::Interrupt::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + for (name, exception) in &app.exceptions { + let priority = exception.args.priority; + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.SCB.set_priority( + rtfm::export::SystemHandler::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + if !analysis.timer_queue.tasks.is_empty() { + let priority = analysis.timer_queue.priority; + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + for (priority, dispatcher) in &analysis.dispatchers { + let name = &dispatcher.interrupt; + exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name))); + exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits)))); + exprs.push(quote!(p.NVIC.set_priority( + #device::Interrupt::#name, + ((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits), + ))); + } + + if app.idle.is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1))); + } + + // Enable and start the system timer + if !analysis.timer_queue.tasks.is_empty() { + let tq = &ctxt.timer_queue; + exprs.push(quote!(#tq.get_mut().syst.set_clock_source(rtfm::export::SystClkSource::Core))); + exprs.push(quote!(#tq.get_mut().syst.enable_counter())); + } + + // Enable cycle counter + if cfg!(feature = "timer-queue") { + exprs.push(quote!(p.DCB.enable_trace())); + exprs.push(quote!(p.DWT.enable_cycle_counter())); + } + + quote!(#(#exprs;)*) +} + +/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument) +fn module( + ctxt: &mut Context, + kind: Kind, + schedule: bool, + spawn: bool, + app: &App, +) -> proc_macro2::TokenStream { + let mut items = vec![]; + let mut fields = vec![]; + + let name = kind.ident(); + let priority = &ctxt.priority; + let device = &app.args.device; + + let mut lt = None; + match kind { + Kind::Init => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: rtfm::Instant, + )); + } + + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::Peripherals<'a>, + /// Device specific peripherals + pub device: #device::Peripherals, + )); + lt = Some(quote!('a)); + } + Kind::Idle => {} + Kind::Exception(_) | Kind::Interrupt(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// Time at which this handler started executing + pub start: rtfm::Instant, + )); + } + } + Kind::Task(_) => { + if cfg!(feature = "timer-queue") { + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: rtfm::Instant, + )); + } + } + } + + if schedule { + lt = Some(quote!('a)); + + fields.push(quote!( + /// Tasks that can be scheduled from this context + pub schedule: Schedule<'a>, + )); + + items.push(quote!( + /// Tasks that can be scheduled from this context + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell, + } + )); + } + + if spawn { + lt = Some(quote!('a)); + + fields.push(quote!( + /// Tasks that can be spawned from this context + pub spawn: Spawn<'a>, + )); + + if kind.is_idle() { + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell, + } + )); + } else { + let baseline_field = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!( + #[doc(hidden)] + pub #baseline: rtfm::Instant, + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #baseline_field + #[doc(hidden)] + pub #priority: &'a core::cell::Cell, + } + )); + } + } + + let mut root = None; + if let Some(resources) = ctxt.resources.get(&kind) { + lt = Some(quote!('a)); + + root = Some(resources.decl.clone()); + + let alias = &resources.alias; + items.push(quote!( + #[doc(inline)] + pub use super::#alias as Resources; + )); + + fields.push(quote!( + /// Resources available in this context + pub resources: Resources<'a>, + )); + }; + + let doc = match kind { + Kind::Exception(_) => "Exception handler", + Kind::Idle => "Idle loop", + Kind::Init => "Initialization function", + Kind::Interrupt(_) => "Interrupt handler", + Kind::Task(_) => "Software task", + }; + + quote!( + #root + + #[doc = #doc] + pub mod #name { + /// Variables injected into this context by the `app` attribute + pub struct Context<#lt> { + #(#fields)* + } + + #(#items)* + } + ) +} + +/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into +/// a function scope +fn prelude( + ctxt: &mut Context, + kind: Kind, + resources: &Idents, + spawn: &Idents, + schedule: &Idents, + app: &App, + logical_prio: u8, + analysis: &Analysis, +) -> proc_macro2::TokenStream { + let mut items = vec![]; + + let lt = if kind.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + let module = kind.ident(); + + let priority = &ctxt.priority; + if !resources.is_empty() { + let mut defs = vec![]; + let mut exprs = vec![]; + + // NOTE This field is just to avoid unused type parameter errors around `'a` + defs.push(quote!(#[allow(dead_code)] #priority: &'a core::cell::Cell)); + exprs.push(quote!(#priority)); + + let mut may_call_lock = false; + let mut needs_unsafe = false; + for name in resources { + let res = &app.resources[name]; + let initialized = res.expr.is_some(); + let singleton = res.singleton; + let mut_ = res.mutability; + let ty = &res.ty; + + if kind.is_init() { + let mut force_mut = false; + if !analysis.ownerships.contains_key(name) { + // owned by Init + if singleton { + needs_unsafe = true; + defs.push(quote!(pub #name: #name)); + exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new())); + continue; + } else { + defs.push(quote!(pub #name: &'static #mut_ #ty)); + } + } else { + // owned by someone else + if singleton { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a mut #name)); + exprs + .push(quote!(#name: &mut <#name as owned_singleton::Singleton>::new())); + continue; + } else { + force_mut = true; + defs.push(quote!(pub #name: &'a mut #ty)); + } + } + + let alias = &ctxt.statics[name]; + // Resources assigned to init are always const initialized + needs_unsafe = true; + if force_mut { + exprs.push(quote!(#name: &mut #alias)); + } else { + exprs.push(quote!(#name: &#mut_ #alias)); + } + } else { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(logical_prio) { + may_call_lock = true; + if singleton { + if mut_.is_none() { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a #name)); + exprs + .push(quote!(#name: &<#name as owned_singleton::Singleton>::new())); + continue; + } else { + // Generate a resource proxy + defs.push(quote!(pub #name: resources::#name<'a>)); + exprs.push(quote!(#name: resources::#name { #priority })); + continue; + } + } else { + if mut_.is_none() { + defs.push(quote!(pub #name: &'a #ty)); + } else { + // Generate a resource proxy + defs.push(quote!(pub #name: resources::#name<'a>)); + exprs.push(quote!(#name: resources::#name { #priority })); + continue; + } + } + } else { + if singleton { + if kind.runs_once() { + needs_unsafe = true; + defs.push(quote!(pub #name: #name)); + exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new())); + } else { + needs_unsafe = true; + defs.push(quote!(pub #name: &'a mut #name)); + exprs.push( + quote!(#name: &mut <#name as owned_singleton::Singleton>::new()), + ); + } + continue; + } else { + defs.push(quote!(pub #name: &#lt #mut_ #ty)); + } + } + + let alias = &ctxt.statics[name]; + needs_unsafe = true; + if initialized { + exprs.push(quote!(#name: &#mut_ #alias)); + } else { + let method = if mut_.is_some() { + quote!(get_mut) + } else { + quote!(get_ref) + }; + exprs.push(quote!(#name: #alias.#method() )); + } + } + } + + let alias = mk_ident(); + let unsafety = if needs_unsafe { + Some(quote!(unsafe)) + } else { + None + }; + + let doc = format!("`{}::Resources`", kind.ident().to_string()); + let decl = quote!( + #[doc = #doc] + #[allow(non_snake_case)] + pub struct #alias<'a> { #(#defs,)* } + ); + items.push(quote!( + #[allow(unused_variables)] + #[allow(unsafe_code)] + #[allow(unused_mut)] + let mut resources = #unsafety { #alias { #(#exprs,)* } }; + )); + + ctxt.resources + .insert(kind.clone(), Resources { alias, decl }); + + if may_call_lock { + items.push(quote!( + use rtfm::Mutex; + )); + } + } + + if !spawn.is_empty() { + // Populate `spawn_fn` + for task in spawn { + if ctxt.spawn_fn.contains_key(task) { + continue; + } + + ctxt.spawn_fn.insert(task.clone(), mk_ident()); + } + + if kind.is_idle() { + items.push(quote!( + #[allow(unused_variables)] + let spawn = #module::Spawn { #priority }; + )); + } else { + let baseline_expr = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(#baseline) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + items.push(quote!( + #[allow(unused_variables)] + let spawn = #module::Spawn { #priority, #baseline_expr }; + )); + } + } + + if !schedule.is_empty() { + // Populate `schedule_fn` + for task in schedule { + if ctxt.schedule_fn.contains_key(task) { + continue; + } + + ctxt.schedule_fn.insert(task.clone(), mk_ident()); + } + + items.push(quote!( + #[allow(unused_imports)] + use rtfm::U32Ext; + + #[allow(unused_variables)] + let schedule = #module::Schedule { #priority }; + )); + } + + if items.is_empty() { + quote!() + } else { + quote!( + let ref #priority = core::cell::Cell::new(#logical_prio); + + #(#items)* + ) + } +} + +fn idle( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + if let Some(idle) = app.idle.as_ref() { + let attrs = &idle.attrs; + let locals = mk_locals(&idle.statics, true); + let stmts = &idle.stmts; + + let prelude = prelude( + ctxt, + Kind::Idle, + &idle.args.resources, + &idle.args.spawn, + &idle.args.schedule, + app, + 0, + analysis, + ); + + let module = module( + ctxt, + Kind::Idle, + !idle.args.schedule.is_empty(), + !idle.args.spawn.is_empty(), + app, + ); + + let unsafety = &idle.unsafety; + let idle = &ctxt.idle; + + let name = format!("idle::{}", idle); + ( + quote!( + #module + + #(#attrs)* + #[export_name = #name] + #unsafety fn #idle() -> ! { + #(#locals)* + + #prelude + + #(#stmts)* + }), + quote!(#idle()), + ) + } else { + ( + quote!(), + quote!(loop { + rtfm::export::wfi(); + }), + ) + } +} + +fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec { + app.exceptions + .iter() + .map(|(ident, exception)| { + let attrs = &exception.attrs; + let statics = &exception.statics; + let stmts = &exception.stmts; + + let prelude = prelude( + ctxt, + Kind::Exception(ident.clone()), + &exception.args.resources, + &exception.args.spawn, + &exception.args.schedule, + app, + exception.args.priority, + analysis, + ); + + let module = module( + ctxt, + Kind::Exception(ident.clone()), + !exception.args.schedule.is_empty(), + !exception.args.spawn.is_empty(), + app, + ); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::now();), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &exception.unsafety; + quote!( + #module + + #[rtfm::export::exception] + #[doc(hidden)] + #(#attrs)* + #unsafety fn #ident() { + #(#statics)* + + #baseline_let + + #prelude + + #start_let + + rtfm::export::run(move || { + #(#stmts)* + }) + }) + }) + .collect() +} + +fn interrupts( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let mut root = vec![]; + let mut scoped = vec![]; + + for (ident, interrupt) in &app.interrupts { + let attrs = &interrupt.attrs; + let statics = &interrupt.statics; + let stmts = &interrupt.stmts; + + let prelude = prelude( + ctxt, + Kind::Interrupt(ident.clone()), + &interrupt.args.resources, + &interrupt.args.spawn, + &interrupt.args.schedule, + app, + interrupt.args.priority, + analysis, + ); + + root.push(module( + ctxt, + Kind::Interrupt(ident.clone()), + !interrupt.args.schedule.is_empty(), + !interrupt.args.spawn.is_empty(), + app, + )); + + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + let baseline_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!(let #baseline = rtfm::Instant::now();), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let start_let = match () { + #[cfg(feature = "timer-queue")] + () => quote!( + #[allow(unused_variables)] + let start = #baseline; + ), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let unsafety = &interrupt.unsafety; + scoped.push(quote!( + #[interrupt] + #(#attrs)* + #unsafety fn #ident() { + #(#statics)* + + #baseline_let + + #prelude + + #start_let + + rtfm::export::run(move || { + #(#stmts)* + }) + })); + } + + (quote!(#(#root)*), quote!(#(#scoped)*)) +} + +fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + for (name, task) in &app.tasks { + #[cfg(feature = "timer-queue")] + let scheduleds_alias = mk_ident(); + let free_alias = mk_ident(); + let inputs_alias = mk_ident(); + let task_alias = mk_ident(); + + let attrs = &task.attrs; + let inputs = &task.inputs; + let locals = mk_locals(&task.statics, false); + let stmts = &task.stmts; + + let prelude = prelude( + ctxt, + Kind::Task(name.clone()), + &task.args.resources, + &task.args.spawn, + &task.args.schedule, + app, + task.args.priority, + analysis, + ); + + let ty = tuple_ty(inputs); + + let capacity_lit = mk_capacity_literal(analysis.capacities[name]); + let capacity_ty = mk_typenum_capacity(analysis.capacities[name], true); + + let resource = mk_resource( + ctxt, + &free_alias, + quote!(rtfm::export::FreeQueue<#capacity_ty>), + *analysis.free_queues.get(name).unwrap_or(&0), + quote!(#free_alias.get_mut()), + app, + None, + ); + + let scheduleds_static = match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias); + + quote!( + #[export_name = #scheduleds_symbol] + static mut #scheduleds_alias: + rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> = + rtfm::export::MaybeUninit::uninitialized(); + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let scheduled_let = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(let scheduled = #baseline;) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let baseline_arg = match () { + #[cfg(feature = "timer-queue")] + () => { + let baseline = &ctxt.baseline; + quote!(#baseline: rtfm::Instant,) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + let task_symbol = format!("{}::{}", name, task_alias); + let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias); + let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias); + let unsafety = &task.unsafety; + items.push(quote!( + // FIXME(MaybeUninit) MaybeUninit won't be necessary when core::mem::MaybeUninit + // stabilizes because heapless constructors will work in const context + #[export_name = #free_symbol] + static mut #free_alias: rtfm::export::MaybeUninit< + rtfm::export::FreeQueue<#capacity_ty> + > = rtfm::export::MaybeUninit::uninitialized(); + + #resource + + #[export_name = #inputs_symbol] + static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> = + rtfm::export::MaybeUninit::uninitialized(); + + #scheduleds_static + + #(#attrs)* + #[export_name = #task_symbol] + #unsafety fn #task_alias(#baseline_arg #(#inputs,)*) { + #(#locals)* + + #prelude + + #scheduled_let + + #(#stmts)* + } + )); + + items.push(module( + ctxt, + Kind::Task(name.clone()), + !task.args.schedule.is_empty(), + !task.args.spawn.is_empty(), + app, + )); + + #[cfg(feature = "timer-queue")] + ctxt.scheduleds.insert(name.clone(), scheduleds_alias); + ctxt.free_queues.insert(name.clone(), free_alias); + ctxt.inputs.insert(name.clone(), inputs_alias); + ctxt.tasks.insert(name.clone(), task_alias); + } + + quote!(#(#items)*) +} + +fn dispatchers( + ctxt: &mut Context, + app: &App, + analysis: &Analysis, +) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + let mut data = vec![]; + let mut dispatchers = vec![]; + + for (level, dispatcher) in &analysis.dispatchers { + let ready_alias = mk_ident(); + let enum_alias = mk_ident(); + let tasks = &dispatcher.tasks; + let capacity = mk_typenum_capacity(dispatcher.capacity, true); + + let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias); + let e = quote!(rtfm::export); + let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>); + let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0); + let resource = mk_resource( + ctxt, + &ready_alias, + ty.clone(), + ceiling, + quote!(#ready_alias.get_mut()), + app, + None, + ); + data.push(quote!( + #[allow(dead_code)] + #[allow(non_camel_case_types)] + enum #enum_alias { #(#tasks,)* } + + #[export_name = #symbol] + static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninitialized(); + + #resource + )); + + let interrupt = &dispatcher.interrupt; + + let arms = dispatcher + .tasks + .iter() + .map(|task| { + let inputs = &ctxt.inputs[task]; + let free = &ctxt.free_queues[task]; + let pats = tuple_pat(&app.tasks[task].inputs); + let alias = &ctxt.tasks[task]; + + let baseline_let; + let call; + match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds = &ctxt.scheduleds[task]; + baseline_let = quote!( + let baseline = + ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index))); + ); + call = quote!(#alias(baseline, #pats)); + } + #[cfg(not(feature = "timer-queue"))] + () => { + baseline_let = quote!(); + call = quote!(#alias(#pats)); + } + }; + + quote!(#enum_alias::#task => { + #baseline_let + let input = ptr::read(#inputs.get_ref().get_unchecked(usize::from(index))); + #free.get_mut().split().0.enqueue_unchecked(index); + let (#pats) = input; + #call + }) + }) + .collect::>(); + + let attrs = &dispatcher.attrs; + dispatchers.push(quote!( + #(#attrs)* + #[interrupt] + unsafe fn #interrupt() { + use core::ptr; + + rtfm::export::run(|| { + while let Some((task, index)) = #ready_alias.get_mut().split().1.dequeue() { + match task { + #(#arms)* + } + } + }); + } + )); + + ctxt.ready_queues.insert(*level, ready_alias); + ctxt.enums.insert(*level, enum_alias); + } + + (quote!(#(#data)*), quote!(#(#dispatchers)*)) +} + +fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + + // Generate `spawn` functions + let device = &app.args.device; + let priority = &ctxt.priority; + #[cfg(feature = "timer-queue")] + let baseline = &ctxt.baseline; + for (task, alias) in &ctxt.spawn_fn { + let free = &ctxt.free_queues[task]; + let level = app.tasks[task].args.priority; + let ready = &ctxt.ready_queues[&level]; + let enum_ = &ctxt.enums[&level]; + let dispatcher = &analysis.dispatchers[&level].interrupt; + let inputs = &ctxt.inputs[task]; + let args = &app.tasks[task].inputs; + let ty = tuple_ty(args); + let pats = tuple_pat(args); + + let scheduleds_write = match () { + #[cfg(feature = "timer-queue")] + () => { + let scheduleds = &ctxt.scheduleds[task]; + quote!( + ptr::write( + #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), + #baseline, + ); + ) + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + let baseline_arg = match () { + #[cfg(feature = "timer-queue")] + () => quote!(#baseline: rtfm::Instant,), + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + + items.push(quote!( + #[inline(always)] + unsafe fn #alias( + #baseline_arg + #priority: &core::cell::Cell, + #(#args,)* + ) -> Result<(), #ty> { + use core::ptr; + + use rtfm::Mutex; + + if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { + ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats)); + #scheduleds_write + + #ready { #priority }.lock(|rq| { + rq.split().0.enqueue_unchecked((#enum_::#task, index)) + }); + + rtfm::pend(#device::Interrupt::#dispatcher); + + Ok(()) + } else { + Err((#pats)) + } + } + )) + } + + // Generate `spawn` structs; these call the `spawn` functions generated above + for (name, spawn) in app.spawn_callers() { + if spawn.is_empty() { + continue; + } + + #[cfg(feature = "timer-queue")] + let is_idle = name.to_string() == "idle"; + + let mut methods = vec![]; + for task in spawn { + let alias = &ctxt.spawn_fn[task]; + let inputs = &app.tasks[task].inputs; + let ty = tuple_ty(inputs); + let pats = tuple_pat(inputs); + + let instant = match () { + #[cfg(feature = "timer-queue")] + () => { + if is_idle { + quote!(rtfm::Instant::now(),) + } else { + quote!(self.#baseline,) + } + } + #[cfg(not(feature = "timer-queue"))] + () => quote!(), + }; + methods.push(quote!( + #[allow(unsafe_code)] + #[inline] + pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> { + unsafe { #alias(#instant &self.#priority, #pats) } + } + )); + } + + items.push(quote!( + impl<'a> #name::Spawn<'a> { + #(#methods)* + } + )); + } + + quote!(#(#items)*) +} + +#[cfg(feature = "timer-queue")] +fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream { + let mut items = vec![]; + + // Generate `schedule` functions + let priority = &ctxt.priority; + let timer_queue = &ctxt.timer_queue; + for (task, alias) in &ctxt.schedule_fn { + let free = &ctxt.free_queues[task]; + let enum_ = &ctxt.schedule_enum; + let inputs = &ctxt.inputs[task]; + let scheduleds = &ctxt.scheduleds[task]; + let args = &app.tasks[task].inputs; + let ty = tuple_ty(args); + let pats = tuple_pat(args); + + items.push(quote!( + #[inline(always)] + unsafe fn #alias( + #priority: &core::cell::Cell, + instant: rtfm::Instant, + #(#args,)* + ) -> Result<(), #ty> { + use core::ptr; + + use rtfm::Mutex; + + if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) { + ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats)); + ptr::write( + #scheduleds.get_mut().get_unchecked_mut(usize::from(index)), + instant, + ); + + let nr = rtfm::export::NotReady { + instant, + index, + task: #enum_::#task, + }; + + ({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr)); + + Ok(()) + } else { + Err((#pats)) + } + } + )) + } + + // Generate `Schedule` structs; these call the `schedule` functions generated above + for (name, schedule) in app.schedule_callers() { + if schedule.is_empty() { + continue; + } + + debug_assert!(!schedule.is_empty()); + + let mut methods = vec![]; + for task in schedule { + let alias = &ctxt.schedule_fn[task]; + let inputs = &app.tasks[task].inputs; + let ty = tuple_ty(inputs); + let pats = tuple_pat(inputs); + + methods.push(quote!( + #[inline] + pub fn #task( + &self, + instant: rtfm::Instant, + #(#inputs,)* + ) -> Result<(), #ty> { + unsafe { #alias(&self.#priority, instant, #pats) } + } + )); + } + + items.push(quote!( + impl<'a> #name::Schedule<'a> { + #(#methods)* + } + )); + } + + quote!(#(#items)*) +} + +fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let tasks = &analysis.timer_queue.tasks; + + if tasks.is_empty() { + return quote!(); + } + + let mut items = vec![]; + + let enum_ = &ctxt.schedule_enum; + items.push(quote!( + #[allow(dead_code)] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #enum_ { #(#tasks,)* } + )); + + let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); + let tq = &ctxt.timer_queue; + let symbol = format!("TIMER_QUEUE::{}", tq); + items.push(quote!( + #[export_name = #symbol] + static mut #tq: + rtfm::export::MaybeUninit> = + rtfm::export::MaybeUninit::uninitialized(); + )); + + items.push(mk_resource( + ctxt, + tq, + quote!(rtfm::export::TimerQueue<#enum_, #cap>), + analysis.timer_queue.ceiling, + quote!(#tq.get_mut()), + app, + None, + )); + + let priority = &ctxt.priority; + let device = &app.args.device; + let arms = tasks + .iter() + .map(|task| { + let level = app.tasks[task].args.priority; + let tenum = &ctxt.enums[&level]; + let ready = &ctxt.ready_queues[&level]; + let dispatcher = &analysis.dispatchers[&level].interrupt; + + quote!( + #enum_::#task => { + (#ready { #priority }).lock(|rq| { + rq.split().0.enqueue_unchecked((#tenum::#task, index)) + }); + + rtfm::pend(#device::Interrupt::#dispatcher); + } + ) + }) + .collect::>(); + + let logical_prio = analysis.timer_queue.priority; + items.push(quote!( + #[rtfm::export::exception] + #[doc(hidden)] + unsafe fn SysTick() { + use rtfm::Mutex; + + let ref #priority = core::cell::Cell::new(#logical_prio); + + rtfm::export::run(|| { + rtfm::export::sys_tick(#tq { #priority }, |task, index| { + match task { + #(#arms)* + } + }); + }) + } + )); + + quote!(#(#items)*) +} + +fn pre_init(ctxt: &Context, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut exprs = vec![]; + + // FIXME(MaybeUninit) Because we are using a fake MaybeUninit we need to set the Option tag to + // Some; otherwise the get_ref and get_mut could result in UB. Also heapless collections can't + // be constructed in const context; we have to initialize them at runtime (i.e. here). + + // these are `MaybeUninit` arrays + for inputs in ctxt.inputs.values() { + exprs.push(quote!(#inputs.set(core::mem::uninitialized());)) + } + + #[cfg(feature = "timer-queue")] + for inputs in ctxt.scheduleds.values() { + exprs.push(quote!(#inputs.set(core::mem::uninitialized());)) + } + + // these are `MaybeUninit` `ReadyQueue`s + for queue in ctxt.ready_queues.values() { + exprs.push(quote!(#queue.set(rtfm::export::ReadyQueue::new());)) + } + + // these are `MaybeUninit` `FreeQueue`s + for free in ctxt.free_queues.values() { + exprs.push(quote!(#free.set(rtfm::export::FreeQueue::new());)) + } + + // end-of-FIXME + + // Initialize the timer queue + if !analysis.timer_queue.tasks.is_empty() { + let tq = &ctxt.timer_queue; + exprs.push(quote!(#tq.set(rtfm::export::TimerQueue::new(p.SYST));)); + } + + // Populate the `FreeQueue`s + for (task, alias) in &ctxt.free_queues { + let capacity = analysis.capacities[task]; + exprs.push(quote!( + for i in 0..#capacity { + #alias.get_mut().enqueue_unchecked(i); + } + )) + } + + // Set the cycle count to 0 and disable it while `init` executes + if cfg!(feature = "timer-queue") { + exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);)); + exprs.push(quote!(p.DWT.cyccnt.write(0);)); + } + + quote!( + let mut p = rtfm::export::Peripherals::steal(); + #(#exprs)* + ) +} + +fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream { + let mut items = vec![]; + + for ty in &analysis.assert_sync { + items.push(quote!(rtfm::export::assert_sync::<#ty>())); + } + + for task in &analysis.tasks_assert_send { + let ty = tuple_ty(&app.tasks[task].inputs); + items.push(quote!(rtfm::export::assert_send::<#ty>())); + } + + // all late resources need to be `Send` + for ty in &analysis.resources_assert_send { + items.push(quote!(rtfm::export::assert_send::<#ty>())); + } + + quote!(#(#items;)*) +} + +fn mk_resource( + ctxt: &Context, + struct_: &Ident, + ty: proc_macro2::TokenStream, + ceiling: u8, + ptr: proc_macro2::TokenStream, + app: &App, + module: Option<&mut Vec>, +) -> proc_macro2::TokenStream { + let priority = &ctxt.priority; + let device = &app.args.device; + + let mut items = vec![]; + + let path = if let Some(module) = module { + let doc = format!("`{}`", ty); + module.push(quote!( + #[doc = #doc] + pub struct #struct_<'a> { + #[doc(hidden)] + pub #priority: &'a core::cell::Cell, + } + )); + + quote!(resources::#struct_) + } else { + items.push(quote!( + struct #struct_<'a> { + #priority: &'a core::cell::Cell, + } + )); + + quote!(#struct_) + }; + + items.push(quote!( + unsafe impl<'a> rtfm::Mutex for #path<'a> { + const CEILING: u8 = #ceiling; + const NVIC_PRIO_BITS: u8 = #device::NVIC_PRIO_BITS; + type Data = #ty; + + #[inline(always)] + unsafe fn priority(&self) -> &core::cell::Cell { + &self.#priority + } + + #[inline(always)] + fn ptr(&self) -> *mut Self::Data { + unsafe { #ptr } + } + } + )); + + quote!(#(#items)*) +} + +fn mk_capacity_literal(capacity: u8) -> LitInt { + LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) +} + +fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream { + let capacity = if power_of_two { + capacity + .checked_next_power_of_two() + .expect("capacity.next_power_of_two()") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtfm::export::consts::#ident) +} + +fn mk_ident() -> Ident { + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + + let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + + let secs = elapsed.as_secs(); + let nanos = elapsed.subsec_nanos(); + + let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32; + let mut seed: [u8; 16] = [0; 16]; + + for (i, v) in seed.iter_mut().take(8).enumerate() { + *v = ((secs >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() { + *v = ((nanos >> (i * 8)) & 0xFF) as u8 + } + + for (i, v) in seed.iter_mut().skip(12).enumerate() { + *v = ((count >> (i * 8)) & 0xFF) as u8 + } + + let mut rng = rand::rngs::SmallRng::from_seed(seed); + Ident::new( + &(0..16) + .map(|i| { + if i == 0 || rng.gen() { + ('a' as u8 + rng.gen::() % 25) as char + } else { + ('0' as u8 + rng.gen::() % 10) as char + } + }) + .collect::(), + Span::call_site(), + ) +} + +// `once = true` means that these locals will be called from a function that will run *once* +fn mk_locals(locals: &HashMap, once: bool) -> proc_macro2::TokenStream { + let lt = if once { Some(quote!('static)) } else { None }; + + let locals = locals + .iter() + .map(|(name, static_)| { + let attrs = &static_.attrs; + let expr = &static_.expr; + let ident = name; + let ty = &static_.ty; + + quote!( + #[allow(non_snake_case)] + let #ident: &#lt mut #ty = { + #(#attrs)* + static mut #ident: #ty = #expr; + + unsafe { &mut #ident } + }; + ) + }) + .collect::>(); + + quote!(#(#locals)*) +} + +fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { + if inputs.len() == 1 { + let pat = &inputs[0].pat; + quote!(#pat) + } else { + let pats = inputs.iter().map(|i| &i.pat).collect::>(); + + quote!(#(#pats,)*) + } +} + +fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + quote!(#ty) + } else { + let tys = inputs.iter().map(|i| &i.ty).collect::>(); + + quote!((#(#tys,)*)) + } +} + +#[derive(Clone, Eq, Hash, PartialEq)] +enum Kind { + Exception(Ident), + Idle, + Init, + Interrupt(Ident), + Task(Ident), +} + +impl Kind { + fn ident(&self) -> Ident { + match self { + Kind::Init => Ident::new("init", Span::call_site()), + Kind::Idle => Ident::new("idle", Span::call_site()), + Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), + } + } + + fn is_idle(&self) -> bool { + *self == Kind::Idle + } + + fn is_init(&self) -> bool { + *self == Kind::Init + } + + fn runs_once(&self) -> bool { + match *self { + Kind::Init | Kind::Idle => true, + _ => false, + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 65d5ad89d0..e382b410dd 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,185 +1,312 @@ -//! Procedural macros of the `cortex-m-rtfm` crate // #![deny(warnings)] #![recursion_limit = "128"] -#[macro_use] -extern crate failure; extern crate proc_macro; extern crate proc_macro2; -extern crate syn; -#[macro_use] extern crate quote; -extern crate rtfm_syntax as syntax; +extern crate rand; +extern crate syn; use proc_macro::TokenStream; -use syntax::{App, Result}; +use syn::parse_macro_input; mod analyze; mod check; -mod trans; +mod codegen; +mod syntax; -/// The `app!` macro, a macro used to specify the tasks and resources of a RTFM application. +/// Attribute used to declare a RTFM application /// -/// The contents of this macro uses a `key: value` syntax. All the possible keys are shown below: +/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively +/// used as a `mod` item: its value must be a block that contains items commonly found in modules, +/// like functions and `static` variables. /// -/// ``` text -/// app! { -/// device: .., +/// The `app` attribute has one mandatory argument: /// -/// resources: { .. }, +/// - `device = `. 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` 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: ::Peripherals`. Exclusive access to device-specific peripherals. +/// `` 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 = `. This is the static priority of the exception handler. The value must +/// be in the range `1..=(1 << ::NVIC_PRIO_BITS)` where `` 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: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// +/// - `spawn: ::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: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// +/// - `spawn: ::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()`. +/// +/// The `task` attribute accepts the following optional arguments. +/// +/// - `capacity = `. 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 = `. 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: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// +/// - `spawn: ::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 { - 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) } diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs new file mode 100644 index 0000000000..24586dcf5b --- /dev/null +++ b/macros/src/syntax.rs @@ -0,0 +1,1235 @@ +use std::{ + collections::{HashMap, HashSet}, + iter, u8, +}; + +use proc_macro2::Span; +use syn::{ + braced, bracketed, parenthesized, + parse::{self, Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + token::Brace, + ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, + ItemForeignMod, ItemStatic, LitInt, Path, PathArguments, PathSegment, ReturnType, Stmt, Token, + Type, TypeTuple, Visibility, +}; + +pub struct AppArgs { + pub device: Path, +} + +impl Parse for AppArgs { + fn parse(input: ParseStream) -> parse::Result { + let mut device = None; + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "device" => { + if device.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + device = Some(input.parse()?); + } + _ => { + return Err(parse::Error::new( + ident.span(), + "expected `device`; other keys are not accepted", + )) + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + Ok(AppArgs { + device: device.ok_or(parse::Error::new( + Span::call_site(), + "`device` argument is required", + ))?, + }) + } +} + +pub struct Input { + _const_token: Token![const], + _ident: Ident, + _colon_token: Token![:], + _ty: TypeTuple, + _eq_token: Token![=], + _brace_token: Brace, + pub items: Vec, + _semi_token: Token![;], +} + +impl Parse for Input { + fn parse(input: ParseStream) -> parse::Result { + fn parse_items(input: ParseStream) -> parse::Result> { + let mut items = vec![]; + + while !input.is_empty() { + items.push(input.parse()?); + } + + Ok(items) + } + + let content; + Ok(Input { + _const_token: input.parse()?, + _ident: input.parse()?, + _colon_token: input.parse()?, + _ty: input.parse()?, + _eq_token: input.parse()?, + _brace_token: braced!(content in input), + items: content.call(parse_items)?, + _semi_token: input.parse()?, + }) + } +} + +pub struct App { + pub args: AppArgs, + pub idle: Option, + pub init: Init, + pub exceptions: Exceptions, + pub interrupts: Interrupts, + pub resources: Resources, + pub tasks: Tasks, + pub free_interrupts: FreeInterrupts, +} + +impl App { + pub fn parse(items: Vec, args: AppArgs) -> parse::Result { + let mut idle = None; + let mut init = None; + let mut exceptions = HashMap::new(); + let mut interrupts = HashMap::new(); + let mut resources = HashMap::new(); + let mut tasks = HashMap::new(); + let mut free_interrupts = None; + + for item in items { + match item { + Item::Fn(mut item) => { + if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { + if idle.is_some() { + return Err(parse::Error::new( + item.span(), + "`#[idle]` function must appear at most once", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + idle = Some(Idle::check(args, item)?); + } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { + if init.is_some() { + return Err(parse::Error::new( + item.span(), + "`#[init]` function must appear exactly once", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + init = Some(Init::check(args, item)?); + } else if let Some(pos) = + item.attrs.iter().position(|attr| eq(attr, "exception")) + { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + exceptions.insert(item.ident.clone(), Exception::check(args, item)?); + } else if let Some(pos) = + item.attrs.iter().position(|attr| eq(attr, "interrupt")) + { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); + } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { + if exceptions.contains_key(&item.ident) + || interrupts.contains_key(&item.ident) + || tasks.contains_key(&item.ident) + { + return Err(parse::Error::new( + item.ident.span(), + "this task is defined multiple times", + )); + } + + let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; + + tasks.insert(item.ident.clone(), Task::check(args, item)?); + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + Item::Static(item) => { + if resources.contains_key(&item.ident) { + return Err(parse::Error::new( + item.ident.span(), + "this resource is listed twice", + )); + } + + resources.insert(item.ident.clone(), Resource::check(item)?); + } + Item::ForeignMod(item) => { + if free_interrupts.is_some() { + return Err(parse::Error::new( + item.abi.extern_token.span(), + "`extern` block can only appear at most once", + )); + } + + free_interrupts = Some(FreeInterrupt::parse(item)?); + } + _ => { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )) + } + } + } + + Ok(App { + args, + idle, + init: init.ok_or_else(|| { + parse::Error::new(Span::call_site(), "`#[init]` function is missing") + })?, + exceptions, + interrupts, + resources, + tasks, + free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), + }) + } + + /// Returns an iterator over all resource accesses. + /// + /// Each resource access include the priority it's accessed at (`u8`) and the name of the + /// resource (`Ident`). A resource may appear more than once in this iterator + pub fn resource_accesses(&self) -> impl Iterator { + self.idle + .as_ref() + .map(|idle| -> Box> { + Box::new(idle.args.resources.iter().map(|res| (0, res))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(self.exceptions.values().flat_map(|e| { + e.args + .resources + .iter() + .map(move |res| (e.args.priority, res)) + })) + .chain(self.interrupts.values().flat_map(|i| { + i.args + .resources + .iter() + .map(move |res| (i.args.priority, res)) + })) + .chain(self.tasks.values().flat_map(|t| { + t.args + .resources + .iter() + .map(move |res| (t.args.priority, res)) + })) + } + + /// Returns an iterator over all `spawn` calls + /// + /// Each spawn call includes the priority of the task from which it's issued and the name of the + /// task that's spawned. A task may appear more that once in this iterator. + /// + /// A priority of `None` means that this being called from `init` + pub fn spawn_calls(&self) -> impl Iterator, &Ident)> { + self.init + .args + .spawn + .iter() + .map(|s| (None, s)) + .chain( + self.idle + .as_ref() + .map(|idle| -> Box> { + Box::new(idle.args.spawn.iter().map(|s| (Some(0), s))) + }) + .unwrap_or_else(|| Box::new(iter::empty())), + ) + .chain( + self.exceptions + .values() + .flat_map(|e| e.args.spawn.iter().map(move |s| (Some(e.args.priority), s))), + ) + .chain( + self.interrupts + .values() + .flat_map(|i| i.args.spawn.iter().map(move |s| (Some(i.args.priority), s))), + ) + .chain( + self.tasks + .values() + .flat_map(|t| t.args.spawn.iter().map(move |s| (Some(t.args.priority), s))), + ) + } + + /// Returns an iterator over all `schedule` calls + /// + /// Each spawn call includes the priority of the task from which it's issued and the name of the + /// task that's spawned. A task may appear more that once in this iterator. + #[allow(dead_code)] + pub fn schedule_calls(&self) -> impl Iterator, &Ident)> { + self.init + .args + .schedule + .iter() + .map(|s| (None, s)) + .chain( + self.idle + .as_ref() + .map(|idle| -> Box> { + Box::new(idle.args.schedule.iter().map(|s| (Some(0), s))) + }) + .unwrap_or_else(|| Box::new(iter::empty())), + ) + .chain(self.exceptions.values().flat_map(|e| { + e.args + .schedule + .iter() + .map(move |s| (Some(e.args.priority), s)) + })) + .chain(self.interrupts.values().flat_map(|i| { + i.args + .schedule + .iter() + .map(move |s| (Some(i.args.priority), s)) + })) + .chain(self.tasks.values().flat_map(|t| { + t.args + .schedule + .iter() + .map(move |s| (Some(t.args.priority), s)) + })) + } + + #[allow(dead_code)] + pub fn schedule_callers(&self) -> impl Iterator { + self.idle + .as_ref() + .map(|idle| -> Box> { + Box::new(iter::once(( + Ident::new("idle", Span::call_site()), + &idle.args.schedule, + ))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(iter::once(( + Ident::new("init", Span::call_site()), + &self.init.args.schedule, + ))) + .chain( + self.exceptions + .iter() + .map(|(name, exception)| (name.clone(), &exception.args.schedule)), + ) + .chain( + self.interrupts + .iter() + .map(|(name, interrupt)| (name.clone(), &interrupt.args.schedule)), + ) + .chain( + self.tasks + .iter() + .map(|(name, task)| (name.clone(), &task.args.schedule)), + ) + } + + pub fn spawn_callers(&self) -> impl Iterator { + self.idle + .as_ref() + .map(|idle| -> Box> { + Box::new(iter::once(( + Ident::new("idle", Span::call_site()), + &idle.args.spawn, + ))) + }) + .unwrap_or_else(|| Box::new(iter::empty())) + .chain(iter::once(( + Ident::new("init", Span::call_site()), + &self.init.args.spawn, + ))) + .chain( + self.exceptions + .iter() + .map(|(name, exception)| (name.clone(), &exception.args.spawn)), + ) + .chain( + self.interrupts + .iter() + .map(|(name, interrupt)| (name.clone(), &interrupt.args.spawn)), + ) + .chain( + self.tasks + .iter() + .map(|(name, task)| (name.clone(), &task.args.spawn)), + ) + } +} + +pub type Idents = HashSet; + +pub type Exceptions = HashMap; + +pub type Interrupts = HashMap; + +pub type Resources = HashMap; + +pub type Statics = Vec; + +pub type Tasks = HashMap; + +pub type FreeInterrupts = HashMap; + +pub struct Idle { + pub args: IdleArgs, + pub attrs: Vec, + pub unsafety: Option, + pub statics: HashMap, + pub stmts: Vec, +} + +pub type IdleArgs = InitArgs; + +impl Idle { + fn check(args: IdleArgs, item: ItemFn) -> parse::Result { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_bottom(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`idle` must have type signature `[unsafe] fn() -> !`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Idle { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics: Static::parse(statics)?, + stmts, + }) + } +} + +pub struct InitArgs { + pub resources: Idents, + pub schedule: Idents, + pub spawn: Idents, +} + +impl Default for InitArgs { + fn default() -> Self { + InitArgs { + resources: Idents::new(), + schedule: Idents::new(), + spawn: Idents::new(), + } + } +} + +impl Parse for InitArgs { + fn parse(input: ParseStream) -> parse::Result { + if input.is_empty() { + return Ok(InitArgs::default()); + } + + let mut resources = None; + let mut schedule = None; + let mut spawn = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = content.parse()?; + let _: Token![=] = content.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "schedule" if cfg!(not(feature = "timer-queue")) => { + return Err(parse::Error::new( + ident.span(), + "The `schedule` API requires that the `timer-queue` feature is \ + enabled in the `cortex-m-rtfm` crate", + )); + } + "resources" | "schedule" | "spawn" => {} // OK + _ => { + return Err(parse::Error::new( + ident.span(), + "expected one of: resources, schedule or spawn", + )) + } + } + + // .. [#(#idents)*] + let inner; + bracketed!(inner in content); + let mut idents = Idents::new(); + for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { + if idents.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "element appears more than once in list", + )); + } + + idents.insert(ident); + } + + let ident_s = ident.to_string(); + match &*ident_s { + "resources" => { + if resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + resources = Some(idents); + } + "schedule" => { + if schedule.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + schedule = Some(idents); + } + "spawn" => { + if spawn.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + spawn = Some(idents); + } + _ => unreachable!(), + } + + if content.is_empty() { + break; + } + + // , + let _: Token![,] = content.parse()?; + } + + Ok(InitArgs { + resources: resources.unwrap_or(Idents::new()), + schedule: schedule.unwrap_or(Idents::new()), + spawn: spawn.unwrap_or(Idents::new()), + }) + } +} + +pub struct Assign { + pub left: Ident, + pub right: Box, +} + +pub struct Init { + pub args: InitArgs, + pub attrs: Vec, + pub unsafety: Option, + pub statics: HashMap, + pub stmts: Vec, + pub assigns: Vec, +} + +impl Init { + fn check(args: InitArgs, item: ItemFn) -> parse::Result { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`init` must have type signature `[unsafe] fn()`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + let (stmts, assigns) = extract_assignments(stmts); + + Ok(Init { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics: Static::parse(statics)?, + stmts, + assigns, + }) + } +} + +pub struct Exception { + pub args: ExceptionArgs, + pub attrs: Vec, + pub unsafety: Option, + pub statics: Statics, + pub stmts: Vec, +} + +pub struct ExceptionArgs { + pub priority: u8, + pub resources: Idents, + pub schedule: Idents, + pub spawn: Idents, +} + +impl Parse for ExceptionArgs { + fn parse(input: ParseStream) -> parse::Result { + parse_args(input, false).map( + |TaskArgs { + priority, + resources, + schedule, + spawn, + .. + }| { + ExceptionArgs { + priority, + resources, + schedule, + spawn, + } + }, + ) + } +} + +impl Exception { + fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + if !valid_signature { + return Err(parse::Error::new( + item.span(), + "`exception` handlers must have type signature `[unsafe] fn()`", + )); + } + + let span = item.ident.span(); + match &*item.ident.to_string() { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" => {} // OK + "SysTick" => { + if cfg!(feature = "timer-queue") { + return Err(parse::Error::new( + span, + "the `SysTick` exception can't be used because it's used by \ + the runtime when the `timer-queue` feature is enabled", + )); + } + } + _ => { + return Err(parse::Error::new( + span, + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Exception { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics, + stmts, + }) + } +} + +pub struct Interrupt { + pub args: InterruptArgs, + pub attrs: Vec, + pub unsafety: Option, + pub statics: Statics, + pub stmts: Vec, +} + +pub type InterruptArgs = ExceptionArgs; + +impl Interrupt { + fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.inputs.is_empty() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`interrupt` handlers must have type signature `[unsafe] fn()`", + )); + } + + match &*item.ident.to_string() { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`interrupt` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + Ok(Interrupt { + args, + attrs: item.attrs, + unsafety: item.unsafety, + statics, + stmts, + }) + } +} + +pub struct Resource { + pub singleton: bool, + pub attrs: Vec, + pub mutability: Option, + pub ty: Box, + pub expr: Option>, +} + +impl Resource { + fn check(mut item: ItemStatic) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + item.span(), + "resources must have inherited / private visibility", + )); + } + + let uninitialized = match *item.expr { + Expr::Tuple(ref tuple) => tuple.elems.is_empty(), + _ => false, + }; + + let pos = item.attrs.iter().position(|attr| eq(attr, "Singleton")); + + if let Some(pos) = pos { + item.attrs[pos].path.segments.insert( + 0, + PathSegment::from(Ident::new("owned_singleton", Span::call_site())), + ); + } + + Ok(Resource { + singleton: pos.is_some(), + attrs: item.attrs, + mutability: item.mutability, + ty: item.ty, + expr: if uninitialized { None } else { Some(item.expr) }, + }) + } +} + +pub struct TaskArgs { + pub capacity: Option, + pub priority: u8, + pub resources: Idents, + pub spawn: Idents, + pub schedule: Idents, +} + +impl Default for TaskArgs { + fn default() -> Self { + TaskArgs { + capacity: None, + priority: 1, + resources: Idents::new(), + schedule: Idents::new(), + spawn: Idents::new(), + } + } +} + +impl Parse for TaskArgs { + fn parse(input: ParseStream) -> parse::Result { + parse_args(input, true) + } +} + +// Parser shared by TaskArgs and ExceptionArgs / InterruptArgs +fn parse_args(input: ParseStream, accept_capacity: bool) -> parse::Result { + if input.is_empty() { + return Ok(TaskArgs::default()); + } + + let mut capacity = None; + let mut priority = None; + let mut resources = None; + let mut schedule = None; + let mut spawn = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = content.parse()?; + let _: Token![=] = content.parse()?; + + let ident_s = ident.to_string(); + match &*ident_s { + "capacity" if accept_capacity => { + // #lit + let lit: LitInt = content.parse()?; + + if lit.suffix() != IntSuffix::None { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.value(); + if value > u64::from(u8::MAX) || value == 0 { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + capacity = Some(value as u8); + } + "priority" => { + // #lit + let lit: LitInt = content.parse()?; + + if lit.suffix() != IntSuffix::None { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.value(); + if value > u64::from(u8::MAX) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...255", + )); + } + + priority = Some(value as u8); + } + "schedule" if cfg!(not(feature = "timer-queue")) => { + return Err(parse::Error::new( + ident.span(), + "The `schedule` API requires that the `timer-queue` feature is \ + enabled in the `cortex-m-rtfm` crate", + )); + } + "resources" | "schedule" | "spawn" => { + // .. [#(#idents)*] + let inner; + bracketed!(inner in content); + let mut idents = Idents::new(); + for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { + if idents.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "element appears more than once in list", + )); + } + + idents.insert(ident); + } + + match &*ident_s { + "resources" => { + if resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + resources = Some(idents); + } + "schedule" => { + if schedule.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + schedule = Some(idents); + } + "spawn" => { + if spawn.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + spawn = Some(idents); + } + _ => unreachable!(), + } + } + _ => { + return Err(parse::Error::new( + ident.span(), + "expected one of: priority, resources, schedule or spawn", + )) + } + } + + if content.is_empty() { + break; + } + + // , + let _: Token![,] = content.parse()?; + } + + Ok(TaskArgs { + capacity, + priority: priority.unwrap_or(1), + resources: resources.unwrap_or(Idents::new()), + schedule: schedule.unwrap_or(Idents::new()), + spawn: spawn.unwrap_or(Idents::new()), + }) +} + +pub struct Static { + pub attrs: Vec, + pub ty: Box, + pub expr: Box, +} + +impl Static { + fn parse(items: Vec) -> parse::Result> { + let mut statics = HashMap::new(); + + for item in items { + if statics.contains_key(&item.ident) { + return Err(parse::Error::new( + item.ident.span(), + "this `static` is listed twice", + )); + } + + statics.insert( + item.ident, + Static { + attrs: item.attrs, + ty: item.ty, + expr: item.expr, + }, + ); + } + + Ok(statics) + } +} + +pub struct Task { + pub args: TaskArgs, + pub attrs: Vec, + pub unsafety: Option, + pub inputs: Vec, + pub statics: HashMap, + pub stmts: Vec, +} + +impl Task { + fn check(args: TaskArgs, item: ItemFn) -> parse::Result { + let valid_signature = item.vis == Visibility::Inherited + && item.constness.is_none() + && item.asyncness.is_none() + && item.abi.is_none() + && item.decl.generics.params.is_empty() + && item.decl.generics.where_clause.is_none() + && item.decl.variadic.is_none() + && is_unit(&item.decl.output); + + let span = item.span(); + + if !valid_signature { + return Err(parse::Error::new( + span, + "`task` handlers must have type signature `[unsafe] fn(..)`", + )); + } + + let (statics, stmts) = extract_statics(item.block.stmts); + + let mut inputs = vec![]; + for input in item.decl.inputs { + if let FnArg::Captured(capture) = input { + inputs.push(capture); + } else { + return Err(parse::Error::new( + span, + "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", + )); + } + } + + match &*item.ident.to_string() { + "init" | "idle" | "resources" => { + return Err(parse::Error::new( + span, + "`task` handlers can NOT be named `idle`, `init` or `resources`", + )); + } + _ => {} + } + + Ok(Task { + args, + attrs: item.attrs, + unsafety: item.unsafety, + inputs, + statics: Static::parse(statics)?, + stmts, + }) + } +} + +pub struct FreeInterrupt { + pub attrs: Vec, +} + +impl FreeInterrupt { + fn parse(mod_: ItemForeignMod) -> parse::Result { + let mut free_interrupts = FreeInterrupts::new(); + + for item in mod_.items { + if let ForeignItem::Fn(f) = item { + let valid_signature = f.vis == Visibility::Inherited + && f.decl.generics.params.is_empty() + && f.decl.generics.where_clause.is_none() + && f.decl.inputs.is_empty() + && f.decl.variadic.is_none() + && is_unit(&f.decl.output); + + if !valid_signature { + return Err(parse::Error::new( + f.span(), + "free interrupts must have type signature `fn()`", + )); + } + + if free_interrupts.contains_key(&f.ident) { + return Err(parse::Error::new( + f.ident.span(), + "this interrupt appears twice", + )); + } + + free_interrupts.insert(f.ident, FreeInterrupt { attrs: f.attrs }); + } else { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "`extern` block should only contains functions", + )); + } + } + + Ok(free_interrupts) + } +} + +fn eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { + let pair = attr.path.segments.first().unwrap(); + let segment = pair.value(); + segment.arguments == PathArguments::None && segment.ident.to_string() == name + } +} + +/// Extracts `static mut` vars from the beginning of the given statements +fn extract_statics(stmts: Vec) -> (Statics, Vec) { + let mut istmts = stmts.into_iter(); + + let mut statics = Statics::new(); + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Item(Item::Static(var)) => { + if var.mutability.is_some() { + statics.push(var); + } else { + stmts.push(Stmt::Item(Item::Static(var))); + break; + } + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + (statics, stmts) +} + +fn extract_assignments(stmts: Vec) -> (Vec, Vec) { + let mut istmts = stmts.into_iter().rev(); + + let mut assigns = vec![]; + let mut stmts = vec![]; + while let Some(stmt) = istmts.next() { + match stmt { + Stmt::Semi(Expr::Assign(assign), semi) => { + if let Expr::Path(ref expr) = *assign.left { + if expr.path.segments.len() == 1 { + assigns.push(Assign { + left: expr.path.segments[0].ident.clone(), + right: assign.right, + }); + continue; + } + } + + stmts.push(Stmt::Semi(Expr::Assign(assign), semi)); + } + _ => { + stmts.push(stmt); + break; + } + } + } + + stmts.extend(istmts); + + (stmts.into_iter().rev().collect(), assigns) +} + +fn is_bottom(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Never(_) = **ty { + true + } else { + false + } + } else { + false + } +} + +fn is_unit(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Tuple(ref tuple) = **ty { + tuple.elems.is_empty() + } else { + false + } + } else { + true + } +} diff --git a/macros/src/trans.rs b/macros/src/trans.rs deleted file mode 100644 index dcd6cfb673..0000000000 --- a/macros/src/trans.rs +++ /dev/null @@ -1,631 +0,0 @@ -use proc_macro2::{TokenStream, Span}; -use syn::{Ident, LitStr}; - -use analyze::Ownerships; -use check::{App, Kind}; - -fn krate() -> Ident { - Ident::new("rtfm", Span::call_site()) -} - -pub fn app(app: &App, ownerships: &Ownerships) -> TokenStream { - let mut root = vec![]; - let mut main = vec![quote!(#![allow(path_statements)])]; - - ::trans::tasks(app, ownerships, &mut root, &mut main); - ::trans::init(app, &mut main, &mut root); - ::trans::idle(app, ownerships, &mut main, &mut root); - ::trans::resources(app, ownerships, &mut root); - - root.push(quote! { - #[allow(unsafe_code)] - fn main() { - #(#main)* - } - }); - - quote!(#(#root)*) -} - -fn idle(app: &App, ownerships: &Ownerships, main: &mut Vec, root: &mut Vec) { - 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(&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(&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, root: &mut Vec) { - 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) { - 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, main: &mut Vec) { - 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(&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(&mut self, t: &mut Threshold, f: F) -> R - where - F: FnOnce(&mut Self::Data, &mut Threshold) -> R - { - unsafe { - #krate::claim( - &mut #_static, - #ceiling, - #device::NVIC_PRIO_BITS, - t, - f, - ) - } - } - } - }); - - if ceiling <= task.priority { - root.push(quote! { - #[allow(unsafe_code)] - impl core::ops::Deref for #tname::#rname { - type Target = #ty; - - fn deref(&self) -> &Self::Target { - unsafe { &#_static } - } - } - - #[allow(unsafe_code)] - impl core::ops::DerefMut for #tname::#rname { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut #_static } - } - } - }) - } - - fields.push(quote! { - pub #rname: #rname, - }); - - exprs.push(quote! { - #rname: #rname { _0: PhantomData }, - }); - } - - items.push(quote! { - #[allow(non_snake_case)] - pub struct Resources { - #(#fields)* - } - }); - - items.push(quote! { - #[allow(unsafe_code)] - impl Resources { - pub unsafe fn new() -> Self { - Resources { - #(#exprs)* - } - } - } - }); - } - - let mut tys = vec![]; - let mut exprs = vec![]; - - let priority = task.priority; - if has_resources { - tys.push(quote!(&mut #krate::Threshold)); - exprs.push(quote! { - &mut if #priority == 1 << #device::NVIC_PRIO_BITS { - #krate::Threshold::new(::core::u8::MAX) - } else { - #krate::Threshold::new(#priority) - } - }); - } - - if has_resources { - tys.push(quote!(#tname::Resources)); - exprs.push(quote!(#tname::Resources::new())); - } - - let path = &task.path; - let _tname = Ident::new(&tname.to_string(), Span::call_site()); - let export_name = LitStr::new(&tname.to_string(), Span::call_site()); - root.push(quote! { - #[allow(non_snake_case)] - #[allow(unsafe_code)] - #[export_name = #export_name] - pub unsafe extern "C" fn #_tname() { - let f: fn(#(#tys,)*) = #path; - - f(#(#exprs,)*) - } - }); - - root.push(quote!{ - #[allow(non_snake_case)] - #[allow(unsafe_code)] - mod #tname { - #[allow(unused_imports)] - use core::marker::PhantomData; - - #[allow(dead_code)] - #[deny(const_err)] - pub const CHECK_PRIORITY: (u8, u8) = ( - #priority - 1, - (1 << ::#device::NVIC_PRIO_BITS) - #priority, - ); - - #(#items)* - } - }); - - // after miri landed (?) rustc won't analyze `const` items unless they are used so we force - // evaluation with this path statement - main.push(quote!(#tname::CHECK_PRIORITY;)); - } -} diff --git a/memory.x b/memory.x deleted file mode 100644 index 534d478665..0000000000 --- a/memory.x +++ /dev/null @@ -1,6 +0,0 @@ -/* STM32F103C8V6 */ -MEMORY -{ - FLASH : ORIGIN = 0x08000000, LENGTH = 64K - RAM : ORIGIN = 0x20000000, LENGTH = 20K -} diff --git a/src/examples/_0_zero_tasks.rs b/src/examples/_0_zero_tasks.rs deleted file mode 100644 index 0484bb9d5e..0000000000 --- a/src/examples/_0_zero_tasks.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! Minimal example with zero tasks -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; // IMPORTANT always do this rename -//! extern crate stm32f103xx; // the device crate -//! -//! // import the procedural macro -//! use rtfm::app; -//! -//! // This macro call indicates that this is a RTFM application -//! // -//! // This macro will expand to a `main` function so you don't need to supply -//! // `main` yourself. -//! app! { -//! // this is the path to the device crate -//! device: stm32f103xx, -//! } -//! -//! // The initialization phase. -//! // -//! // This runs first and within a *global* critical section. Nothing can preempt -//! // this function. -//! fn init(p: init::Peripherals) { -//! // This function has access to all the peripherals of the device -//! p.core.SYST; -//! p.device.GPIOA; -//! p.device.RCC; -//! // .. -//! } -//! -//! // The idle loop. -//! // -//! // This runs after `init` and has a priority of 0. All tasks can preempt this -//! // function. This function can never return so it must contain some sort of -//! // endless loop. -//! fn idle() -> ! { -//! loop { -//! // This puts the processor to sleep until there's a task to service -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_1_one_task.rs b/src/examples/_1_one_task.rs deleted file mode 100644 index b9075a5916..0000000000 --- a/src/examples/_1_one_task.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! An application with one task -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m; -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use cortex_m::peripheral::syst::SystClkSource; -//! use rtfm::{app, Threshold}; -//! use stm32f103xx::GPIOC; -//! -//! app! { -//! device: stm32f103xx, -//! -//! // Here data resources are declared -//! // -//! // Data resources are static variables that are safe to share across tasks -//! resources: { -//! // Declaration of resources looks exactly like declaration of static -//! // variables -//! static ON: bool = false; -//! }, -//! -//! // Here tasks are declared -//! // -//! // Each task corresponds to an interrupt or an exception. Every time the -//! // interrupt or exception becomes *pending* the corresponding task handler -//! // will be executed. -//! tasks: { -//! // Here we declare that we'll use the SYS_TICK exception as a task -//! SYS_TICK: { -//! // Path to the task handler -//! path: sys_tick, -//! -//! // These are the resources this task has access to. -//! // -//! // The resources listed here must also appear in `app.resources` -//! resources: [ON], -//! }, -//! } -//! } -//! -//! fn init(mut p: init::Peripherals, r: init::Resources) { -//! // `init` can modify all the `resources` declared in `app!` -//! r.ON; -//! -//! // power on GPIOC -//! p.device.RCC.apb2enr.modify(|_, w| w.iopcen().enabled()); -//! -//! // configure PC13 as output -//! p.device.GPIOC.bsrr.write(|w| w.bs13().set()); -//! p.device -//! .GPIOC -//! .crh -//! .modify(|_, w| w.mode13().output().cnf13().push()); -//! -//! // configure the system timer to generate one interrupt every second -//! p.core.SYST.set_clock_source(SystClkSource::Core); -//! p.core.SYST.set_reload(8_000_000); // 1s -//! p.core.SYST.enable_interrupt(); -//! p.core.SYST.enable_counter(); -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // This is the task handler of the SYS_TICK exception -//! // -//! // `_t` is the preemption threshold token. We won't use it in this program. -//! // -//! // `r` is the set of resources this task has access to. `SYS_TICK::Resources` -//! // has one field per resource declared in `app!`. -//! #[allow(unsafe_code)] -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // toggle state -//! *r.ON = !*r.ON; -//! -//! if *r.ON { -//! // set the pin PC13 high -//! // NOTE(unsafe) atomic write to a stateless register -//! unsafe { -//! (*GPIOC::ptr()).bsrr.write(|w| w.bs13().set()); -//! } -//! } else { -//! // set the pin PC13 low -//! // NOTE(unsafe) atomic write to a stateless register -//! unsafe { -//! (*GPIOC::ptr()).bsrr.write(|w| w.br13().reset()); -//! } -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_2_two_tasks.rs b/src/examples/_2_two_tasks.rs deleted file mode 100644 index 516ff0c979..0000000000 --- a/src/examples/_2_two_tasks.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Two tasks running at the *same* priority with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static COUNTER: u64 = 0; -//! }, -//! -//! // Both SYS_TICK and TIM2 have access to the `COUNTER` data -//! tasks: { -//! SYS_TICK: { -//! path: sys_tick, -//! resources: [COUNTER], -//! }, -//! -//! TIM2: { -//! path: tim2, -//! resources: [COUNTER], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) { -//! // .. -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // As both tasks are running at the same priority one can't preempt the other. -//! // Thus both tasks have direct access to the resource -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // .. -//! -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! -//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { -//! // .. -//! -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_3_preemption.rs b/src/examples/_3_preemption.rs deleted file mode 100644 index 14c9d9256b..0000000000 --- a/src/examples/_3_preemption.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Two tasks running at *different* priorities with access to the same resource -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static COUNTER: u64 = 0; -//! }, -//! -//! tasks: { -//! // The `SYS_TICK` task has higher priority than `TIM2` -//! SYS_TICK: { -//! path: sys_tick, -//! priority: 2, -//! resources: [COUNTER], -//! }, -//! -//! TIM2: { -//! path: tim2, -//! priority: 1, -//! resources: [COUNTER], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) { -//! // .. -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! // .. -//! -//! // This task can't be preempted by `tim2` so it has direct access to the -//! // resource data -//! *r.COUNTER += 1; -//! -//! // .. -//! } -//! -//! fn tim2(t: &mut Threshold, mut r: TIM2::Resources) { -//! // .. -//! -//! // As this task runs at lower priority it needs a critical section to -//! // prevent `sys_tick` from preempting it while it modifies this resource -//! // data. The critical section is required to prevent data races which can -//! // lead to undefined behavior. -//! r.COUNTER.claim_mut(t, |counter, _t| { -//! // `claim_mut` creates a critical section -//! *counter += 1; -//! }); -//! -//! // .. -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_4_nested.rs b/src/examples/_4_nested.rs deleted file mode 100644 index 26f8fd84ad..0000000000 --- a/src/examples/_4_nested.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Nesting claims and how the preemption threshold works -//! -//! If you run this program you'll hit the breakpoints as indicated by the -//! letters in the comments: A, then B, then C, etc. -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! use stm32f103xx::Interrupt; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static LOW: u64 = 0; -//! static HIGH: u64 = 0; -//! }, -//! -//! tasks: { -//! EXTI0: { -//! path: exti0, -//! priority: 1, -//! resources: [LOW, HIGH], -//! }, -//! -//! EXTI1: { -//! path: exti1, -//! priority: 2, -//! resources: [LOW], -//! }, -//! -//! EXTI2: { -//! path: exti2, -//! priority: 3, -//! resources: [HIGH], -//! }, -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, _r: init::Resources) {} -//! -//! fn idle() -> ! { -//! // A -//! rtfm::bkpt(); -//! -//! // Sets task `exti0` as pending -//! // -//! // Because `exti0` has higher priority than `idle` it will be executed -//! // immediately -//! rtfm::set_pending(Interrupt::EXTI0); // ~> exti0 -//! -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! #[allow(non_snake_case)] -//! fn exti0( -//! t: &mut Threshold, -//! EXTI0::Resources { -//! LOW: mut low, -//! HIGH: mut high, -//! }: EXTI0::Resources, -//! ) { -//! // Because this task has a priority of 1 the preemption threshold `t` also -//! // starts at 1 -//! -//! // B -//! rtfm::bkpt(); -//! -//! // Because `exti1` has higher priority than `exti0` it can preempt it -//! rtfm::set_pending(Interrupt::EXTI1); // ~> exti1 -//! -//! // A claim creates a critical section -//! low.claim_mut(t, |_low, t| { -//! // This claim increases the preemption threshold to 2 -//! // -//! // 2 is just high enough to not race with task `exti1` for access to the -//! // `LOW` resource -//! -//! // D -//! rtfm::bkpt(); -//! -//! // Now `exti1` can't preempt this task because its priority is equal to -//! // the current preemption threshold -//! rtfm::set_pending(Interrupt::EXTI1); -//! -//! // But `exti2` can, because its priority is higher than the current -//! // preemption threshold -//! rtfm::set_pending(Interrupt::EXTI2); // ~> exti2 -//! -//! // F -//! rtfm::bkpt(); -//! -//! // Claims can be nested -//! high.claim_mut(t, |_high, _| { -//! // This claim increases the preemption threshold to 3 -//! -//! // Now `exti2` can't preempt this task -//! rtfm::set_pending(Interrupt::EXTI2); -//! -//! // G -//! rtfm::bkpt(); -//! }); -//! -//! // Upon leaving the critical section the preemption threshold drops back -//! // to 2 and `exti2` immediately preempts this task -//! // ~> exti2 -//! }); -//! -//! // Once again the preemption threshold drops but this time to 1. Now the -//! // pending `exti1` task can preempt this task -//! // ~> exti1 -//! } -//! -//! fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) { -//! // C, I -//! rtfm::bkpt(); -//! } -//! -//! fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) { -//! // E, H -//! rtfm::bkpt(); -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_5_late_resources.rs b/src/examples/_5_late_resources.rs deleted file mode 100644 index 7ab90a4e20..0000000000 --- a/src/examples/_5_late_resources.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Demonstrates initialization of resources in `init`. -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! // Usually, resources are initialized with a constant initializer: -//! static ON: bool = false; -//! -//! // However, there are cases where this is not possible or not desired. -//! // For example, there may not be a sensible value to use, or the type may -//! // not be constructible in a constant (like `Vec`). -//! // -//! // While it is possible to use an `Option` in some cases, that requires -//! // you to properly initialize it and `.unwrap()` it at every use. It -//! // also consumes more memory. -//! // -//! // To solve this, it is possible to defer initialization of resources to -//! // `init` by omitting the initializer. Doing that will require `init` to -//! // return the values of all "late" resources. -//! static IP_ADDRESS: u32; -//! -//! // PORT is used by 2 tasks, making it a shared resource. This just tests -//! // another internal code path and is not important for the example. -//! static PORT: u16; -//! }, -//! -//! idle: { -//! // Test that late resources can be used in idle -//! resources: [IP_ADDRESS], -//! }, -//! -//! tasks: { -//! SYS_TICK: { -//! priority: 1, -//! path: sys_tick, -//! resources: [IP_ADDRESS, PORT, ON], -//! }, -//! -//! EXTI0: { -//! priority: 2, -//! path: exti0, -//! resources: [PORT], -//! } -//! } -//! } -//! -//! // The signature of `init` is now required to have a specific return type. -//! fn init(_p: init::Peripherals, _r: init::Resources) -> init::LateResources { -//! // `init::Resources` does not contain `IP_ADDRESS`, since it is not yet -//! // initialized. -//! //_r.IP_ADDRESS; // doesn't compile -//! -//! // ...obtain value for IP_ADDRESS from EEPROM/DHCP... -//! let ip_address = 0x7f000001; -//! -//! init::LateResources { -//! // This struct will contain fields for all resources with omitted -//! // initializers. -//! IP_ADDRESS: ip_address, -//! PORT: 0, -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, r: SYS_TICK::Resources) { -//! // Other tasks can access late resources like any other, since they are -//! // guaranteed to be initialized when tasks are run. -//! -//! r.IP_ADDRESS; -//! } -//! -//! fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} -//! -//! fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_6_safe_static_mut_ref.rs b/src/examples/_6_safe_static_mut_ref.rs deleted file mode 100644 index 8f7267f50c..0000000000 --- a/src/examples/_6_safe_static_mut_ref.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! Safe creation of `&'static mut` references -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::app; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static BUFFER: [u8; 16] = [0; 16]; -//! }, -//! -//! init: { -//! resources: [BUFFER], -//! }, -//! } -//! -//! fn init(_p: init::Peripherals, r: init::Resources) { -//! let _buf: &'static mut [u8; 16] = r.BUFFER; -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_7_generics.rs b/src/examples/_7_generics.rs deleted file mode 100644 index 5dafdbf2f0..0000000000 --- a/src/examples/_7_generics.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! Working with resources in a generic fashion -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Resource, Threshold}; -//! use stm32f103xx::{GPIOA, SPI1}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static GPIOA: GPIOA; -//! static SPI1: SPI1; -//! }, -//! -//! tasks: { -//! EXTI0: { -//! path: exti0, -//! priority: 1, -//! resources: [GPIOA, SPI1], -//! }, -//! -//! EXTI1: { -//! path: exti1, -//! priority: 2, -//! resources: [GPIOA, SPI1], -//! }, -//! }, -//! } -//! -//! fn init(p: init::Peripherals) -> init::LateResources { -//! init::LateResources { -//! GPIOA: p.device.GPIOA, -//! SPI1: p.device.SPI1, -//! } -//! } -//! -//! fn idle() -> ! { -//! loop { -//! rtfm::wfi(); -//! } -//! } -//! -//! // A generic function that uses some resources -//! fn work(t: &mut Threshold, gpioa: &G, spi1: &S) -//! where -//! G: Resource, -//! S: Resource, -//! { -//! gpioa.claim(t, |_gpioa, t| { -//! // drive NSS low -//! -//! spi1.claim(t, |_spi1, _| { -//! // transfer data -//! }); -//! -//! // drive NSS high -//! }); -//! } -//! -//! // This task needs critical sections to access the resources -//! fn exti0(t: &mut Threshold, r: EXTI0::Resources) { -//! work(t, &r.GPIOA, &r.SPI1); -//! } -//! -//! // This task has direct access to the resources -//! fn exti1(t: &mut Threshold, r: EXTI1::Resources) { -//! work(t, &r.GPIOA, &r.SPI1); -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/_8_full_syntax.rs b/src/examples/_8_full_syntax.rs deleted file mode 100644 index cc7fbc22c7..0000000000 --- a/src/examples/_8_full_syntax.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! A showcase of the `app!` macro syntax -//! -//! ``` -//! #![deny(unsafe_code)] -//! #![deny(warnings)] -//! #![no_std] -//! -//! extern crate cortex_m_rtfm as rtfm; -//! extern crate stm32f103xx; -//! -//! use rtfm::{app, Threshold}; -//! -//! app! { -//! device: stm32f103xx, -//! -//! resources: { -//! static CO_OWNED: u32 = 0; -//! static ON: bool = false; -//! static OWNED: bool = false; -//! static SHARED: bool = false; -//! }, -//! -//! init: { -//! // This is the path to the `init` function -//! // -//! // `init` doesn't necessarily has to be in the root of the crate -//! path: main::init, -//! }, -//! -//! idle: { -//! // This is a path to the `idle` function -//! // -//! // `idle` doesn't necessarily has to be in the root of the crate -//! path: main::idle, -//! resources: [OWNED, SHARED], -//! }, -//! -//! tasks: { -//! SYS_TICK: { -//! path: sys_tick, -//! // If omitted priority is assumed to be 1 -//! // priority: 1, -//! resources: [CO_OWNED, ON, SHARED], -//! }, -//! -//! TIM2: { -//! // Tasks are enabled, between `init` and `idle`, by default but they -//! // can start disabled if `false` is specified here -//! enabled: false, -//! path: tim2, -//! priority: 1, -//! resources: [CO_OWNED], -//! }, -//! }, -//! } -//! -//! mod main { -//! use rtfm::{self, Resource, Threshold}; -//! -//! pub fn init(_p: ::init::Peripherals, _r: ::init::Resources) {} -//! -//! pub fn idle(t: &mut Threshold, mut r: ::idle::Resources) -> ! { -//! loop { -//! *r.OWNED = !*r.OWNED; -//! -//! if *r.OWNED { -//! if r.SHARED.claim(t, |shared, _| *shared) { -//! rtfm::wfi(); -//! } -//! } else { -//! r.SHARED.claim_mut(t, |shared, _| *shared = !*shared); -//! } -//! } -//! } -//! } -//! -//! fn sys_tick(_t: &mut Threshold, mut r: SYS_TICK::Resources) { -//! *r.ON = !*r.ON; -//! -//! *r.CO_OWNED += 1; -//! } -//! -//! fn tim2(_t: &mut Threshold, mut r: TIM2::Resources) { -//! *r.CO_OWNED += 1; -//! } -//! ``` -// Auto-generated. Do not modify. diff --git a/src/examples/mod.rs b/src/examples/mod.rs deleted file mode 100644 index 64d1e2ecf4..0000000000 --- a/src/examples/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Examples -// Auto-generated. Do not modify. -pub mod _0_zero_tasks; -pub mod _1_one_task; -pub mod _2_two_tasks; -pub mod _3_preemption; -pub mod _4_nested; -pub mod _5_late_resources; -pub mod _6_safe_static_mut_ref; -pub mod _7_generics; -pub mod _8_full_syntax; diff --git a/src/export.rs b/src/export.rs new file mode 100644 index 0000000000..cb63e0cee3 --- /dev/null +++ b/src/export.rs @@ -0,0 +1,84 @@ +/// IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE +use core::{hint, ptr}; + +#[cfg(armv7m)] +use cortex_m::register::basepri; +pub use cortex_m::{ + asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource, + peripheral::Peripherals, +}; +pub use cortex_m_rt::{entry, exception}; +pub use heapless::consts; +use heapless::spsc::Queue; + +#[cfg(feature = "timer-queue")] +pub use crate::tq::{isr as sys_tick, NotReady, TimerQueue}; + +pub type FreeQueue = Queue; +pub type ReadyQueue = Queue<(T, u8), N>; + +#[cfg(armv7m)] +#[inline(always)] +pub fn run(f: F) +where + F: FnOnce(), +{ + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } +} + +#[cfg(not(armv7m))] +#[inline(always)] +pub fn run(f: F) +where + F: FnOnce(), +{ + f(); +} + +// TODO(MaybeUninit) Until core::mem::MaybeUninit is stabilized we use our own (inefficient) +// implementation +pub struct MaybeUninit { + value: Option, +} + +impl MaybeUninit { + 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() +where + T: Send, +{ +} + +#[inline(always)] +pub fn assert_sync() +where + T: Sync, +{ +} diff --git a/src/lib.rs b/src/lib.rs index 9d558875c7..74cf96ac6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,170 +1,342 @@ //! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers //! -//! This crate is based on [the RTFM framework] created by the Embedded Systems -//! group at [Luleå University of Technology][ltu], led by Prof. Per Lindgren, -//! and uses a simplified version of the Stack Resource Policy as scheduling -//! policy (check the [references] for details). +//! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the +//! library is `rtfm`. //! -//! [the RTFM framework]: http://www.rtfm-lang.org/ -//! [ltu]: https://www.ltu.se/?l=en -//! [per]: https://www.ltu.se/staff/p/pln-1.11258?l=en -//! [references]: ./index.html#references +//! [`cortex-m-rtfm`]: https://crates.io/crates/cortex-m-rtfm //! -//! # Features +//! The user level documentation can be found [here]. //! -//! - **Event triggered tasks** as the unit of concurrency. -//! - Support for prioritization of tasks and, thus, **preemptive -//! multitasking**. -//! - **Efficient and data race free memory sharing** through fine grained *non -//! global* critical sections. -//! - **Deadlock free execution** guaranteed at compile time. -//! - **Minimal scheduling overhead** as the scheduler has no "software -//! component": the hardware does all the scheduling. -//! - **Highly efficient memory usage**: All the tasks share a single call stack -//! and there's no hard dependency on a dynamic memory allocator. -//! - **All Cortex M devices are fully supported**. -//! - This task model is amenable to known WCET (Worst Case Execution Time) -//! analysis and scheduling analysis techniques. (Though we haven't yet -//! developed Rust friendly tooling for that.) +//! [here]: ../../book/index.html //! -//! # Constraints +//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component +//! of the framework. //! -//! - Tasks must run to completion. That's it, tasks can't contain endless -//! loops. However, you can run an endless event loop in the `idle` *loop*. +//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html //! -//! - Task priorities must remain constant at runtime. +//! # Cargo features //! -//! # Dependencies +//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule +//! tasks to run in the future. Also see [`Instant`] and [`Duration`]. //! -//! The application crate must depend on a device crate generated using -//! [`svd2rust`] v0.12.x and the "rt" feature of that crate must be enabled. The -//! SVD file used to generate the device crate *must* contain [``] -//! information. -//! -//! [`svd2rust`]: https://docs.rs/svd2rust/0.12.0/svd2rust/ -//! [``]: 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(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( - 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 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 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 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 for Instant { + type Output = Self; + + fn sub(mut self, dur: Duration) -> Self { + self -= dur; + self + } +} + +#[cfg(feature = "timer-queue")] +impl ops::Sub 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 { + 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 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 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; + + /// 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(&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(&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(interrupt: I) +/// This is a convenience function around +/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) +pub fn pend(interrupt: I) where I: Nr, { - // NOTE(safe) atomic write - let mut nvic: NVIC = unsafe { mem::transmute(()) }; - nvic.set_pending(interrupt); + NVIC::pend(interrupt) } diff --git a/src/tq.rs b/src/tq.rs new file mode 100644 index 0000000000..88db2f84a2 --- /dev/null +++ b/src/tq.rs @@ -0,0 +1,135 @@ +use core::cmp::{self, Ordering}; + +use cortex_m::peripheral::{SCB, SYST}; +use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; + +use crate::{Instant, Mutex}; + +pub struct TimerQueue +where + N: ArrayLength>, + T: Copy, +{ + pub syst: SYST, + pub queue: BinaryHeap, N, Min>, +} + +impl TimerQueue +where + N: ArrayLength>, + T: Copy, +{ + pub fn new(syst: SYST) -> Self { + TimerQueue { + syst, + queue: BinaryHeap::new(), + } + } + + #[inline] + pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady) { + 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 +where + T: Copy, +{ + pub index: u8, + pub instant: Instant, + pub task: T, +} + +impl Eq for NotReady where T: Copy {} + +impl Ord for NotReady +where + T: Copy, +{ + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant) + } +} + +impl PartialEq for NotReady +where + T: Copy, +{ + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl PartialOrd for NotReady +where + T: Copy, +{ + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } +} + +#[inline(always)] +pub fn isr(mut tq: TQ, mut f: F) +where + TQ: Mutex>, + T: Copy + Send, + N: ArrayLength>, + F: FnMut(T, u8), +{ + loop { + // XXX does `#[inline(always)]` improve performance or not? + let next = tq.lock(#[inline(always)] + |tq| { + if let Some(instant) = tq.queue.peek().map(|p| p.instant) { + let diff = instant.0.wrapping_sub(Instant::now().0); + + if diff < 0 { + // task became ready + let m = unsafe { tq.queue.pop_unchecked() }; + + Some((m.task, m.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + tq.syst.set_reload(cmp::min(MAX, diff as u32)); + + // start counting down from the new reload + tq.syst.clear_current(); + + None + } + } else { + // the queue is empty + tq.syst.disable_interrupt(); + None + } + }); + + if let Some((task, index)) = next { + f(task, index) + } else { + return; + } + } +} diff --git a/tests/cfail.rs b/tests/cfail.rs deleted file mode 100644 index 6572d6576b..0000000000 --- a/tests/cfail.rs +++ /dev/null @@ -1,17 +0,0 @@ -extern crate compiletest_rs as compiletest; - -use std::path::PathBuf; - -use compiletest::common::Mode; -use compiletest::Config; - -#[test] -fn cfail() { - let mut config = Config::default(); - config.mode = Mode::CompileFail; - config.src_base = PathBuf::from(format!("tests/cfail")); - config.target_rustcflags = - Some("-C panic=abort -L target/debug -L target/debug/deps ".to_string()); - - compiletest::run_tests(&config); -} diff --git a/tests/cfail/critical-section.rs b/tests/cfail/critical-section.rs deleted file mode 100644 index c0f475c474..0000000000 --- a/tests/cfail/critical-section.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(const_fn)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static ON: bool = false; - }, - - idle: { - resources: [ON], - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [ON], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle(t: &mut Threshold, r: idle::Resources) -> ! { - let state = rtfm::atomic(t, |t| { - // ERROR borrow can't escape this *global* critical section - r.ON.borrow(t) //~ error cannot infer an appropriate lifetime - }); - - let state = r.ON.claim(t, |state, _t| { - // ERROR borrow can't escape this critical section - state //~ error cannot infer an appropriate lifetime - }); - - loop {} -} - -fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} diff --git a/tests/cfail/duplicated-task.rs b/tests/cfail/duplicated-task.rs deleted file mode 100644 index 885c961c0b..0000000000 --- a/tests/cfail/duplicated-task.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { - //~^ error proc macro panicked - device: stm32f103xx, - - tasks: { - SYS_TICK: { - priority: 1, - }, - - SYS_TICK: { - priority: 2, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! {} diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs new file mode 100644 index 0000000000..692a57c796 --- /dev/null +++ b/tests/cfail/exception-divergent.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[exception] + fn SVCall() -> ! { + //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + loop {} + } +}; diff --git a/tests/cfail/exception-input.rs b/tests/cfail/exception-input.rs new file mode 100644 index 0000000000..cb0711ced0 --- /dev/null +++ b/tests/cfail/exception-input.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[exception] + fn SVCall(undef: u32) { + //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + } +}; diff --git a/tests/cfail/exception-invalid.rs b/tests/cfail/exception-invalid.rs new file mode 100644 index 0000000000..0a7fb520a2 --- /dev/null +++ b/tests/cfail/exception-invalid.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[exception] + fn NonMaskableInt() { + //~^ ERROR only exceptions with configurable priority can be used as hardware tasks + } +}; diff --git a/tests/cfail/exception-output.rs b/tests/cfail/exception-output.rs new file mode 100644 index 0000000000..758dbdd772 --- /dev/null +++ b/tests/cfail/exception-output.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[exception] + fn SVCall() -> u32 { + //~^ ERROR `exception` handlers must have type signature `[unsafe] fn()` + 0 + } +}; diff --git a/tests/cfail/exception-sys-tick.rs b/tests/cfail/exception-sys-tick.rs new file mode 100644 index 0000000000..69d73dbc43 --- /dev/null +++ b/tests/cfail/exception-sys-tick.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[exception] + fn SysTick() { + //~^ ERROR the `SysTick` exception can't be used because it's used by the runtime + } +}; diff --git a/tests/cfail/exception.rs b/tests/cfail/exception.rs deleted file mode 100644 index b4e025fcda..0000000000 --- a/tests/cfail/exception.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error proc macro panicked - device: stm32f103xx, - - tasks: { - // ERROR exceptions can't be enabled / disabled here - SYS_TICK: { - enabled: true, - priority: 1, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} diff --git a/tests/cfail/idle-input.rs b/tests/cfail/idle-input.rs new file mode 100644 index 0000000000..5095977e03 --- /dev/null +++ b/tests/cfail/idle-input.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[idle] + fn idle(undef: u32) { + //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !` + } +}; diff --git a/tests/cfail/idle-not-divergent.rs b/tests/cfail/idle-not-divergent.rs new file mode 100644 index 0000000000..e90eff08aa --- /dev/null +++ b/tests/cfail/idle-not-divergent.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[idle] + fn idle() { + //~^ ERROR `idle` must have type signature `[unsafe] fn() -> !` + } +}; diff --git a/tests/cfail/idle.rs b/tests/cfail/idle.rs deleted file mode 100644 index ef20e1a8d4..0000000000 --- a/tests/cfail/idle.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error mismatched types - device: stm32f103xx, -} - -fn init(_p: init::Peripherals) {} - -// ERROR `idle` must be a diverging function -fn idle() {} diff --git a/tests/cfail/init-divergent.rs b/tests/cfail/init-divergent.rs new file mode 100644 index 0000000000..400c805e91 --- /dev/null +++ b/tests/cfail/init-divergent.rs @@ -0,0 +1,17 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() -> ! { + //~^ ERROR `init` must have type signature `[unsafe] fn()` + loop {} + } +}; diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs new file mode 100644 index 0000000000..fa79099c35 --- /dev/null +++ b/tests/cfail/init-input.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(undef: u32) { + //~^ ERROR `init` must have type signature `[unsafe] fn()` + } +}; diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs new file mode 100644 index 0000000000..3ac495f533 --- /dev/null +++ b/tests/cfail/init-not-send.rs @@ -0,0 +1,30 @@ +//! This is equivalent to the `late-not-send` cfail test + +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use core::marker::PhantomData; + +use rtfm::app; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely +const APP: () = { + static mut X: Option = None; + + #[init(resources = [X])] + fn init() { + *resources.X = Some(NotSend { _0: PhantomData }) + } + + #[interrupt(resources = [X])] + fn UART0() {} +}; diff --git a/tests/cfail/init-output.rs b/tests/cfail/init-output.rs new file mode 100644 index 0000000000..1200aca774 --- /dev/null +++ b/tests/cfail/init-output.rs @@ -0,0 +1,17 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() -> u32 { + //~^ ERROR `init` must have type signature `[unsafe] fn()` + 0 + } +}; diff --git a/tests/cfail/init-resource-share-idle.rs b/tests/cfail/init-resource-share-idle.rs deleted file mode 100644 index 5b29f302a2..0000000000 --- a/tests/cfail/init-resource-share-idle.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ proc macro panicked - device: stm32f103xx, - - resources: { - static BUFFER: [u8; 16] = [0; 16]; - }, - - init: { - resources: [BUFFER], - }, - - idle: { - resources: [BUFFER], - //~^ error: this resource is owned by `init` and can't be shared - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle(_r: init::Resources) -> ! { - loop {} -} diff --git a/tests/cfail/init-resource-share-task.rs b/tests/cfail/init-resource-share-task.rs deleted file mode 100644 index a93e840ee5..0000000000 --- a/tests/cfail/init-resource-share-task.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ proc macro panicked - device: stm32f103xx, - - resources: { - static BUFFER: [u8; 16] = [0; 16]; - }, - - init: { - resources: [BUFFER], - }, - - tasks: { - SYS_TICK: { - path: sys_tick, - resources: [BUFFER], - //~^ error: this resource is owned by `init` and can't be shared - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} - -fn sys_tick() {} diff --git a/tests/cfail/init.rs b/tests/cfail/init.rs deleted file mode 100644 index 057a2ee2b3..0000000000 --- a/tests/cfail/init.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error mismatched types - device: stm32f103xx, -} - -// ERROR `init` must have signature `fn (init::Peripherals)` -fn init() {} - -fn idle() -> ! { - loop {} -} diff --git a/tests/cfail/insufficient-free-interrupts.rs b/tests/cfail/insufficient-free-interrupts.rs new file mode 100644 index 0000000000..baa2582bc7 --- /dev/null +++ b/tests/cfail/insufficient-free-interrupts.rs @@ -0,0 +1,17 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required +const APP: () = { + #[init] + fn init() {} + + #[task] + fn foo() {} +}; diff --git a/tests/cfail/interrupt-divergent.rs b/tests/cfail/interrupt-divergent.rs new file mode 100644 index 0000000000..4a01533077 --- /dev/null +++ b/tests/cfail/interrupt-divergent.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[interrupt] + fn UART0() -> ! { + //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + loop {} + } +}; diff --git a/tests/cfail/interrupt-input.rs b/tests/cfail/interrupt-input.rs new file mode 100644 index 0000000000..d0240f4e6e --- /dev/null +++ b/tests/cfail/interrupt-input.rs @@ -0,0 +1,19 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[interrupt] + fn UART0(undef: u32) { + //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + } +}; diff --git a/tests/cfail/interrupt-output.rs b/tests/cfail/interrupt-output.rs new file mode 100644 index 0000000000..37cd7c211e --- /dev/null +++ b/tests/cfail/interrupt-output.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[interrupt] + fn UART0() -> u32 { + //~^ ERROR `interrupt` handlers must have type signature `[unsafe] fn()` + 0 + } +}; diff --git a/tests/cfail/interrupt.rs b/tests/cfail/interrupt.rs deleted file mode 100644 index 522763a4b0..0000000000 --- a/tests/cfail/interrupt.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error no variant named `EXTI33` found for type - device: stm32f103xx, - - tasks: { - EXTI33: { - path: exti33, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} - -fn exti33() {} diff --git a/tests/cfail/late-assigned-to-init.rs b/tests/cfail/late-assigned-to-init.rs new file mode 100644 index 0000000000..70a361c1da --- /dev/null +++ b/tests/cfail/late-assigned-to-init.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + static mut X: u32 = (); + + #[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init` + fn init() {} +}; diff --git a/tests/cfail/late-not-send.rs b/tests/cfail/late-not-send.rs new file mode 100644 index 0000000000..b9180fed9d --- /dev/null +++ b/tests/cfail/late-not-send.rs @@ -0,0 +1,31 @@ +//! `init` has a static priority of `0`. Initializing resources from it is equivalent to sending a +//! message to the task that will own the resource + +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use core::marker::PhantomData; + +use rtfm::app; + +struct NotSend { + _0: PhantomData<*const ()>, +} + +#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely +const APP: () = { + static mut X: NotSend = (); + + #[init] + fn init() { + X = NotSend { _0: PhantomData }; + } + + #[interrupt(resources = [X])] + fn UART0() {} +}; diff --git a/tests/cfail/late-resource-init.rs b/tests/cfail/late-resource-init.rs deleted file mode 100644 index 5235d930a3..0000000000 --- a/tests/cfail/late-resource-init.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static A: u8 = 0; - static LATE: u8; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [A, LATE], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [A, LATE], - }, - }, -} - -fn init(_p: init::Peripherals, r: init::Resources) -> init::LateResources { - // Try to use a resource that's not yet initialized: - r.LATE; - //~^ error: no field `LATE` - - init::LateResources { - LATE: 0, - } -} - -fn idle() -> ! { - loop {} -} - -fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {} diff --git a/tests/cfail/late-uninit.rs b/tests/cfail/late-uninit.rs new file mode 100644 index 0000000000..eeb9bd4191 --- /dev/null +++ b/tests/cfail/late-uninit.rs @@ -0,0 +1,16 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + static mut X: u32 = (); //~ ERROR late resources MUST be initialized at the end of `init` + + #[init] + fn init() {} +}; diff --git a/tests/cfail/lock.rs b/tests/cfail/lock.rs deleted file mode 100644 index 9cb0f3e6b1..0000000000 --- a/tests/cfail/lock.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(const_fn)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static ON: bool = false; - static MAX: u8 = 0; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [MAX, ON], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [ON], - }, - - EXTI2: { - path: exti2, - priority: 16, - resources: [MAX], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - loop {} -} - -fn exti0(mut t: &mut Threshold, mut r: EXTI0::Resources) { - // ERROR need to lock to access the resource because priority < ceiling - if *r.ON { - //~^ error type `EXTI0::ON` cannot be dereferenced - } - - // OK need to lock to access the resource - if r.ON.claim(&mut t, |on, _| *on) {} - - // OK can claim a resource with maximum ceiling - r.MAX.claim_mut(&mut t, |max, _| *max += 1); -} - -fn exti1(mut t: &mut Threshold, r: EXTI1::Resources) { - // OK to directly access the resource because priority == ceiling - if *r.ON {} - - // though the resource can still be claimed -- the claim is a no-op - if r.ON.claim(&mut t, |on, _| *on) {} -} - -fn exti2(_t: &mut Threshold, _r: EXTI2::Resources) {} diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs new file mode 100644 index 0000000000..7e3ca3062b --- /dev/null +++ b/tests/cfail/needs-send.rs @@ -0,0 +1,30 @@ +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use core::marker::PhantomData; + +use rtfm::app; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +unsafe impl Sync for NotSend {} + +#[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely +const APP: () = { + #[init(spawn = [foo])] + fn init() {} + + #[task] + fn foo(_x: NotSend) {} + + extern "C" { + fn UART0(); + } +}; diff --git a/tests/cfail/needs-sync.rs b/tests/cfail/needs-sync.rs new file mode 100644 index 0000000000..f25f91a2f6 --- /dev/null +++ b/tests/cfail/needs-sync.rs @@ -0,0 +1,36 @@ +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use core::marker::PhantomData; + +use rtfm::app; + +pub struct NotSync { + _0: PhantomData<*const ()>, +} + +unsafe impl Send for NotSync {} + +#[app(device = lm3s6965)] //~ ERROR cannot be shared between threads safely +const APP: () = { + static X: NotSync = NotSync { _0: PhantomData }; + + #[init(spawn = [foo])] + fn init() {} + + #[task(priority = 1, resources = [X])] + fn foo() {} + + #[task(priority = 2, resources = [X])] + fn bar() {} + + extern "C" { + fn UART0(); + fn UART1(); + } +}; diff --git a/tests/cfail/peripheral-alias.rs b/tests/cfail/peripheral-alias.rs deleted file mode 100644 index 7f3790a552..0000000000 --- a/tests/cfail/peripheral-alias.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error proc macro panicked - device: stm32f103xx, - - tasks: { - EXTI0: { - enabled: true, - priority: 1, - // ERROR peripheral appears twice in this list - resources: [GPIOA, GPIOA], - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs deleted file mode 100644 index d63b9d05cf..0000000000 --- a/tests/cfail/priority-too-high.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error referenced constant has errors - //~^ error could not evaluate constant - //~| error constant evaluation error - device: stm32f103xx, - - tasks: { - SYS_TICK: { - path: sys_tick, - // ERROR priority must be in the range [1, 16] - priority: 17, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} - -fn sys_tick() {} diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs deleted file mode 100644 index 476b7a07f7..0000000000 --- a/tests/cfail/priority-too-low.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error referenced constant has errors - //~^ error could not evaluate constant - //~| error constant evaluation error - device: stm32f103xx, - - tasks: { - SYS_TICK: { - path: sys_tick, - // ERROR priority must be in the range [1, 16] - priority: 0, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} - -fn sys_tick() {} diff --git a/tests/cfail/resource-alias.rs b/tests/cfail/resource-alias.rs deleted file mode 100644 index 81eeea07d0..0000000000 --- a/tests/cfail/resource-alias.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::app; - -app! { //~ error proc macro panicked - device: stm32f103xx, - - resources: { - // resource `MAX` listed twice - MAX: u8 = 0; - MAX: u16 = 0; - }, - - tasks: { - EXTI0: { - enabled: true, - priority: 1, - }, - }, -} - -fn init(_p: init::Peripherals) {} - -fn idle() -> ! { - loop {} -} diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs new file mode 100644 index 0000000000..f6d08a65f4 --- /dev/null +++ b/tests/cfail/resource-not-declared.rs @@ -0,0 +1,14 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init(resources = [X])] //~ ERROR this resource has NOT been declared + fn init() {} +}; diff --git a/tests/cfail/resource-not-send-sync.rs b/tests/cfail/resource-not-send-sync.rs deleted file mode 100644 index 27e5cb0591..0000000000 --- a/tests/cfail/resource-not-send-sync.rs +++ /dev/null @@ -1,53 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(const_fn)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static SHARED: bool = false; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [SHARED], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [SHARED], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - loop {} -} - -fn is_send(_: &T) where T: Send {} -fn is_sync(_: &T) where T: Sync {} - -fn exti0(_t: &mut Threshold, r: EXTI0::Resources) { - // ERROR resource proxies can't be shared between tasks - is_sync(&r.SHARED); - //~^ error `*const ()` cannot be shared between threads safely - - // ERROR resource proxies are not `Send`able across tasks - is_send(&r.SHARED); - //~^ error `*const ()` cannot be sent between threads safely -} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) { -} diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs new file mode 100644 index 0000000000..970fc6cc05 --- /dev/null +++ b/tests/cfail/resource-pub.rs @@ -0,0 +1,17 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + pub static mut X: u32 = 0; + //~^ ERROR resources must have inherited / private visibility + + #[init] + fn init() {} +}; diff --git a/tests/cfail/task-divergent.rs b/tests/cfail/task-divergent.rs new file mode 100644 index 0000000000..3822d754ac --- /dev/null +++ b/tests/cfail/task-divergent.rs @@ -0,0 +1,24 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[task] + fn foo() -> ! { + //~^ ERROR `task` handlers must have type signature `[unsafe] fn(..)` + loop {} + } + + extern "C" { + fn UART0(); + } +}; diff --git a/tests/cfail/task-idle.rs b/tests/cfail/task-idle.rs new file mode 100644 index 0000000000..62d927b923 --- /dev/null +++ b/tests/cfail/task-idle.rs @@ -0,0 +1,23 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[task] + fn idle() { + //~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources` + } + + extern "C" { + fn UART0(); + } +}; diff --git a/tests/cfail/task-not-declared.rs b/tests/cfail/task-not-declared.rs new file mode 100644 index 0000000000..3e6d87c426 --- /dev/null +++ b/tests/cfail/task-not-declared.rs @@ -0,0 +1,14 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [X])] //~ ERROR this task has NOT been declared + fn init() {} +}; diff --git a/tests/cfail/token-outlive.rs b/tests/cfail/token-outlive.rs deleted file mode 100644 index 41ee827b7b..0000000000 --- a/tests/cfail/token-outlive.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(const_fn)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static STATE: bool = false; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [STATE], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [STATE], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - loop {} -} - -fn exti0(mut t: &mut Threshold, r: EXTI0::Resources) { - // ERROR token should not outlive the critical section - let t = r.STATE.claim(&mut t, |_state, t| t); - //~^ error cannot infer an appropriate lifetime -} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {} diff --git a/tests/cfail/token-transfer.rs b/tests/cfail/token-transfer.rs deleted file mode 100644 index 5c6a22b1e7..0000000000 --- a/tests/cfail/token-transfer.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![feature(const_fn)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Threshold}; - -app! { //~ error `*const ()` cannot be sent between threads safely - device: stm32f103xx, - - resources: { - static TOKEN: Option = None; - }, - - idle: { - resources: [TOKEN], - }, - - tasks: { - EXTI0: { - path: exti0, - resources: [TOKEN], - }, - } -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle(_t: &mut Threshold, _r: idle::Resources) -> ! { - loop {} -} - -fn exti0(_t: &mut Threshold, _r: EXTI0::Resources) {} diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs new file mode 100644 index 0000000000..78ae540764 --- /dev/null +++ b/tests/cfail/used-free-interrupt.rs @@ -0,0 +1,21 @@ +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() {} + + #[interrupt] + fn UART0() {} //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers + + extern "C" { + fn UART0(); + } +}; diff --git a/tests/cfail/wrong-threshold.rs b/tests/cfail/wrong-threshold.rs deleted file mode 100644 index 86d8e26230..0000000000 --- a/tests/cfail/wrong-threshold.rs +++ /dev/null @@ -1,47 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_std] - -extern crate cortex_m_rtfm as rtfm; -extern crate stm32f103xx; - -use rtfm::{app, Resource, Threshold}; - -app! { - device: stm32f103xx, - - resources: { - static A: u8 = 0; - static B: u8 = 0; - }, - - tasks: { - EXTI0: { - path: exti0, - priority: 1, - resources: [A, B], - }, - - EXTI1: { - path: exti1, - priority: 2, - resources: [A, B], - }, - }, -} - -fn init(_p: init::Peripherals, _r: init::Resources) {} - -fn idle() -> ! { - loop {} -} - -fn exti0(mut ot: &mut Threshold, r: EXTI0::Resources) { - r.A.claim(&mut ot, |_a, mut _it| { - //~^ error cannot borrow `ot` as mutable more than once at a time - // ERROR must use inner token `it` instead of the outer one (`ot`) - r.B.claim(&mut ot, |_b, _| {}) - }); -} - -fn exti1(_t: &mut Threshold, _r: EXTI1::Resources) {} diff --git a/tests/compiletest.rs b/tests/compiletest.rs new file mode 100644 index 0000000000..acc89546b1 --- /dev/null +++ b/tests/compiletest.rs @@ -0,0 +1,59 @@ +use std::{fs, path::PathBuf, process::Command}; + +use compiletest_rs::{common::Mode, Config}; +use tempdir::TempDir; + +#[test] +fn cfail() { + let mut config = Config::default(); + + config.mode = Mode::CompileFail; + config.src_base = PathBuf::from("tests/cfail"); + config.link_deps(); + + // remove duplicate and trailing `-L` flags + let mut s = String::new(); + if let Some(flags) = config.target_rustcflags.as_mut() { + let mut iter = flags.split_whitespace().peekable(); + + while let Some(flag) = iter.next() { + if flag == "-L" && (iter.peek() == Some(&"-L") || iter.peek() == None) { + iter.next(); + continue; + } + + s += flag; + s += " "; + } + + // path to proc-macro crate + s += "-L target/debug/deps "; + + // avoid "error: language item required, but not found: `eh_personality`" + s += "-C panic=abort "; + } + + let td = TempDir::new("rtfm").unwrap(); + for f in fs::read_dir("tests/cpass").unwrap() { + let f = f.unwrap().path(); + let name = f.file_stem().unwrap().to_str().unwrap(); + + assert!( + Command::new("rustc") + .args(s.split_whitespace()) + .arg(f.display().to_string()) + .arg("-o") + .arg(td.path().join(name).display().to_string()) + .arg("-C") + .arg("linker=true") + .status() + .unwrap() + .success() + ); + } + + config.target_rustcflags = Some(s); + config.clean_rmeta(); + + compiletest_rs::run_tests(&config); +} diff --git a/tests/cpass/late-not-send.rs b/tests/cpass/late-not-send.rs new file mode 100644 index 0000000000..b7d2d60a32 --- /dev/null +++ b/tests/cpass/late-not-send.rs @@ -0,0 +1,33 @@ +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use core::marker::PhantomData; + +use rtfm::app; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[app(device = lm3s6965)] +const APP: () = { + static mut X: NotSend = (); + static mut Y: Option = None; + + #[init(resources = [Y])] + fn init() { + *resources.Y = Some(NotSend { _0: PhantomData }); + + X = NotSend { _0: PhantomData }; + } + + #[idle(resources = [X, Y])] + fn idle() -> ! { + loop {} + } +}; diff --git a/tests/cpass/late-resource.rs b/tests/cpass/late-resource.rs new file mode 100644 index 0000000000..43f5170869 --- /dev/null +++ b/tests/cpass/late-resource.rs @@ -0,0 +1,22 @@ +//! Runtime initialized resources +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + static mut X: u32 = (); + static Y: u32 = (); + + #[init] + fn init() { + X = 0; + Y = 1; + } +}; diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs new file mode 100644 index 0000000000..0ca720cb36 --- /dev/null +++ b/tests/cpass/peripheral.rs @@ -0,0 +1,19 @@ +//! Core and device peripherals +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init() { + let _: rtfm::Peripherals = core; + let _: lm3s6965::Peripherals = device; + } +}; diff --git a/tests/cpass/resource.rs b/tests/cpass/resource.rs new file mode 100644 index 0000000000..6a7a873c80 --- /dev/null +++ b/tests/cpass/resource.rs @@ -0,0 +1,80 @@ +//! Check code generation of resources + +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + static mut O1: u32 = 0; // init + static mut O2: u32 = 0; // idle + static mut O3: u32 = 0; // EXTI0 + static O4: u32 = 0; // idle + static O5: u32 = 0; // EXTI1 + static O6: u32 = 0; // init + + static mut S1: u32 = 0; // idle & EXTI0 + static mut S2: u32 = 0; // EXTI0 & EXTI1 + static S3: u32 = 0; + + #[init(resources = [O1, O4, O5, O6, S3])] + fn init() { + // owned by `init` == `&'static mut` + let _: &'static mut u32 = resources.O1; + + // owned by `init` == `&'static` if read-only + let _: &'static u32 = resources.O6; + + // `init` has exclusive access to all resources + let _: &mut u32 = resources.O4; + let _: &mut u32 = resources.O5; + let _: &mut u32 = resources.S3; + } + + #[idle(resources = [O2, O4, S1, S3])] + fn idle() -> ! { + // owned by `idle` == `&'static mut` + let _: &'static mut u32 = resources.O2; + + // owned by `idle` == `&'static` if read-only + let _: &'static u32 = resources.O4; + + // shared with `idle` == `Mutex` + resources.S1.lock(|_| {}); + + // `&` if read-only + let _: &u32 = resources.S3; + + loop {} + } + + #[interrupt(resources = [O3, S1, S2, S3])] + fn UART0() { + // owned by interrupt == `&mut` + let _: &mut u32 = resources.O3; + + // no `Mutex` when access from highest priority task + let _: &mut u32 = resources.S1; + + // no `Mutex` when co-owned by cooperative (same priority) tasks + let _: &mut u32 = resources.S2; + + // `&` if read-only + let _: &u32 = resources.S3; + } + + #[interrupt(resources = [S2, O5])] + fn UART1() { + // owned by interrupt == `&` if read-only + let _: &u32 = resources.O5; + + // no `Mutex` when co-owned by cooperative (same priority) tasks + let _: &mut u32 = resources.S2; + } +}; diff --git a/tests/cpass/schedule.rs b/tests/cpass/schedule.rs new file mode 100644 index 0000000000..bc7fe34271 --- /dev/null +++ b/tests/cpass/schedule.rs @@ -0,0 +1,59 @@ +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::{app, Instant}; + +#[app(device = lm3s6965)] +const APP: () = { + #[init(schedule = [foo, bar, baz])] + fn init() { + let _: Result<(), ()> = schedule.foo(start + 10.cycles()); + let _: Result<(), u32> = schedule.bar(start + 20.cycles(), 0); + let _: Result<(), (u32, u32)> = schedule.baz(start + 30.cycles(), 0, 1); + } + + #[idle(schedule = [foo, bar, baz])] + fn idle() -> ! { + let _: Result<(), ()> = schedule.foo(Instant::now() + 40.cycles()); + let _: Result<(), u32> = schedule.bar(Instant::now() + 50.cycles(), 0); + let _: Result<(), (u32, u32)> = schedule.baz(Instant::now() + 60.cycles(), 0, 1); + + loop {} + } + + #[exception(schedule = [foo, bar, baz])] + fn SVCall() { + let _: Result<(), ()> = schedule.foo(start + 70.cycles()); + let _: Result<(), u32> = schedule.bar(start + 80.cycles(), 0); + let _: Result<(), (u32, u32)> = schedule.baz(start + 90.cycles(), 0, 1); + } + + #[interrupt(schedule = [foo, bar, baz])] + fn UART0() { + let _: Result<(), ()> = schedule.foo(start + 100.cycles()); + let _: Result<(), u32> = schedule.bar(start + 110.cycles(), 0); + let _: Result<(), (u32, u32)> = schedule.baz(start + 120.cycles(), 0, 1); + } + + #[task(schedule = [foo, bar, baz])] + fn foo() { + let _: Result<(), ()> = schedule.foo(scheduled + 130.cycles()); + let _: Result<(), u32> = schedule.bar(scheduled + 140.cycles(), 0); + let _: Result<(), (u32, u32)> = schedule.baz(scheduled + 150.cycles(), 0, 1); + } + + #[task] + fn bar(_x: u32) {} + + #[task] + fn baz(_x: u32, _y: u32) {} + + extern "C" { + fn UART1(); + } +}; diff --git a/tests/cpass/singleton.rs b/tests/cpass/singleton.rs new file mode 100644 index 0000000000..77159f3f09 --- /dev/null +++ b/tests/cpass/singleton.rs @@ -0,0 +1,67 @@ +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate owned_singleton; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[Singleton] + static mut O1: u32 = 0; + #[Singleton] + static mut O2: u32 = 0; + #[Singleton] + static mut O3: u32 = 0; + #[Singleton] + static O4: u32 = 0; + #[Singleton] + static O5: u32 = 0; + #[Singleton] + static O6: u32 = 0; + + #[Singleton] + static mut S1: u32 = 0; + #[Singleton] + static mut S2: u32 = 0; + + #[init(resources = [O1, O2, O3, O4, O5, O6, S1, S2])] + fn init() { + let _: O1 = resources.O1; + let _: &mut O2 = resources.O2; + let _: &mut O3 = resources.O3; + let _: O4 = resources.O4; + let _: &mut O5 = resources.O5; + let _: &mut O6 = resources.O6; + + let _: &mut S1 = resources.S1; + let _: &mut S2 = resources.S2; + } + + #[idle(resources = [O2, O5])] + fn idle() -> ! { + let _: O2 = resources.O2; + let _: O5 = resources.O5; + + loop {} + } + + #[interrupt(resources = [O3, O6, S1, S2])] + fn UART0() { + let _: &mut O3 = resources.O3; + let _: &O6 = resources.O6; + + let _: &mut S1 = resources.S1; + let _: &S2 = resources.S2; + } + + #[interrupt(resources = [S1, S2])] + fn UART1() { + let _: &mut S1 = resources.S1; + let _: &S2 = resources.S2; + } +}; diff --git a/tests/cpass/spawn.rs b/tests/cpass/spawn.rs new file mode 100644 index 0000000000..ba5a855f51 --- /dev/null +++ b/tests/cpass/spawn.rs @@ -0,0 +1,60 @@ +//! Check code generation of `spawn` +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init(spawn = [foo, bar, baz])] + fn init() { + let _: Result<(), ()> = spawn.foo(); + let _: Result<(), u32> = spawn.bar(0); + let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + } + + #[idle(spawn = [foo, bar, baz])] + fn idle() -> ! { + let _: Result<(), ()> = spawn.foo(); + let _: Result<(), u32> = spawn.bar(0); + let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + + loop {} + } + + #[exception(spawn = [foo, bar, baz])] + fn SVCall() { + let _: Result<(), ()> = spawn.foo(); + let _: Result<(), u32> = spawn.bar(0); + let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + } + + #[interrupt(spawn = [foo, bar, baz])] + fn UART0() { + let _: Result<(), ()> = spawn.foo(); + let _: Result<(), u32> = spawn.bar(0); + let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + } + + #[task(spawn = [foo, bar, baz])] + fn foo() { + let _: Result<(), ()> = spawn.foo(); + let _: Result<(), u32> = spawn.bar(0); + let _: Result<(), (u32, u32)> = spawn.baz(0, 1); + } + + #[task] + fn bar(_x: u32) {} + + #[task] + fn baz(_x: u32, _y: u32) {} + + extern "C" { + fn UART1(); + } +}; diff --git a/tests/cpass/unsafe.rs b/tests/cpass/unsafe.rs new file mode 100644 index 0000000000..86f461f13d --- /dev/null +++ b/tests/cpass/unsafe.rs @@ -0,0 +1,46 @@ +//! Check code generation of `unsafe` `init` / `idle` / `exception` / `interrupt` / `task` +#![feature(extern_crate_item_prelude)] // ??? +#![no_main] +#![no_std] + +extern crate lm3s6965; +extern crate panic_halt; +extern crate rtfm; + +use rtfm::app; + +unsafe fn foo() {} + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + unsafe fn init() { + foo(); + } + + #[idle] + unsafe fn idle() -> ! { + foo(); + + loop {} + } + + #[exception] + unsafe fn SVCall() { + foo(); + } + + #[interrupt] + unsafe fn UART0() { + foo(); + } + + #[task] + unsafe fn bar() { + foo(); + } + + extern "C" { + fn UART1(); + } +};