diff --git a/.travis.yml b/.travis.yml index 2c5fc7e6ed..ac5a7b8ab7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,22 +3,28 @@ language: rust matrix: include: # NOTE used to build docs on successful merges to master - # - env: TARGET=x86_64-unknown-linux-gnu - - # - env: TARGET=thumbv6m-none-eabi - # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - # - env: TARGET=thumbv7m-none-eabi - # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - - env: TARGET=x86_64-unknown-linux-gnu - rust: nightly - # if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + # MSRV + - env: TARGET=thumbv7m-none-eabi + rust: 1.36.0 + if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=thumbv6m-none-eabi + if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + - env: TARGET=thumbv7m-none-eabi + if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + + # compile-fail tests + - env: TARGET=x86_64-unknown-linux-gnu rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) + # heterogeneous multi-core support + - env: TARGET=thumbv6m-none-eabi + rust: nightly + if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) - env: TARGET=thumbv7m-none-eabi rust: nightly if: (branch = staging OR branch = trying) OR (type = pull_request AND branch = master) diff --git a/Cargo.toml b/Cargo.toml index ef6ac65499..1449d6ef53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,61 +12,75 @@ license = "MIT OR Apache-2.0" name = "cortex-m-rtfm" readme = "README.md" repository = "https://github.com/japaric/cortex-m-rtfm" -version = "0.5.0-alpha.1" +version = "0.5.0-beta.1" [lib] name = "rtfm" [[example]] name = "baseline" -required-features = ["timer-queue"] +required-features = ["__v7"] [[example]] name = "periodic" -required-features = ["timer-queue"] +required-features = ["__v7"] [[example]] name = "pool" -# this example doesn't need this feature but only works on ARMv7-M -# specifying the feature here avoids compiling this for ARMv6-M -required-features = ["timer-queue"] +required-features = ["__v7"] [[example]] name = "schedule" -required-features = ["timer-queue"] +required-features = ["__v7"] + +[[example]] +name = "t-cfg" +required-features = ["__v7"] + +[[example]] +name = "t-schedule" +required-features = ["__v7"] [[example]] name = "types" -required-features = ["timer-queue"] +required-features = ["__v7"] [dependencies] -cortex-m = "0.5.8" -cortex-m-rt = "0.6.7" -cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" } -heapless = "0.5.0-alpha.1" +cortex-m = "0.6.0" +cortex-m-rtfm-macros = { path = "macros" } +rtfm-core = { git = "https://github.com/japaric/rtfm-core" } +cortex-m-rt = "0.6.9" +heapless = "0.5.0" + +[dependencies.microamp] +optional = true +version = "0.1.0-alpha.2" [dev-dependencies] -cortex-m-semihosting = "0.3.2" lm3s6965 = "0.1.3" panic-halt = "0.2.0" +cortex-m-semihosting = "0.3.3" [dev-dependencies.panic-semihosting] features = ["exit"] -version = "0.5.1" - -[features] -timer-queue = ["cortex-m-rtfm-macros/timer-queue"] +version = "0.5.2" [target.x86_64-unknown-linux-gnu.dev-dependencies] -compiletest_rs = "0.3.21" -tempdir = "0.3.7" +compiletest_rs = "0.3.22" -[package.metadata.docs.rs] -features = ["timer-queue"] +[features] +heterogeneous = ["cortex-m-rtfm-macros/heterogeneous", "microamp"] +homogeneous = ["cortex-m-rtfm-macros/homogeneous"] +# used for testing this crate; do not use in applications +__v7 =[] [profile.release] codegen-units = 1 lto = true [workspace] -members = ["macros"] \ No newline at end of file +members = [ + "heterogeneous", + "homogeneous", + "macros", +] diff --git a/README.md b/README.md index db23d2b4d0..ff33da9859 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,7 @@ A concurrency framework for building real time systems. - **Highly efficient memory usage**: All the tasks share a single call stack and there's no hard dependency on a dynamic memory allocator. -- **All Cortex-M devices are supported**. The core features of RTFM are - supported on all Cortex-M devices. The timer queue is currently only supported - on ARMv7-M devices. +- **All Cortex-M devices are fully supported**. - This task model is amenable to known WCET (Worst Case Execution Time) analysis and scheduling analysis techniques. (Though we haven't yet developed Rust diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md index 873410a52e..8b62633688 100644 --- a/book/en/src/SUMMARY.md +++ b/book/en/src/SUMMARY.md @@ -4,7 +4,7 @@ - [RTFM by example](./by-example.md) - [The `app` attribute](./by-example/app.md) - [Resources](./by-example/resources.md) - - [Tasks](./by-example/tasks.md) + - [Software tasks](./by-example/tasks.md) - [Timer queue](./by-example/timer-queue.md) - [Types, Send and Sync](./by-example/types-send-sync.md) - [Starting a new project](./by-example/new.md) @@ -18,3 +18,5 @@ - [Ceiling analysis](./internals/ceilings.md) - [Software tasks](./internals/tasks.md) - [Timer queue](./internals/timer-queue.md) +- [Homogeneous multi-core support](./homogeneous.md) +- [Heterogeneous multi-core support](./heterogeneous.md) diff --git a/book/en/src/by-example/app.md b/book/en/src/by-example/app.md index d595570cc7..ebb71f1be0 100644 --- a/book/en/src/by-example/app.md +++ b/book/en/src/by-example/app.md @@ -10,8 +10,8 @@ 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 *peripheral access crate* (PAC) generated using [`svd2rust`] -**v0.14.x**. The `app` attribute will expand into a suitable entry point so it's -not required to use the [`cortex_m_rt::entry`] attribute. +**v0.14.x** or newer. The `app` attribute will expand into a suitable entry +point so it's not required to use the [`cortex_m_rt::entry`] attribute. [`app`]: ../../api/cortex_m_rtfm_macros/attr.app.html [`svd2rust`]: https://crates.io/crates/svd2rust @@ -28,22 +28,23 @@ not required to use the [`cortex_m_rt::entry`] attribute. Within the pseudo-module the `app` attribute expects to find an initialization function marked with the `init` attribute. This function must have signature -`fn(init::Context) [-> init::LateResources]`. +`fn(init::Context) [-> init::LateResources]` (the return type is not always +required). This initialization function will be the first part of the application to run. The `init` function will run *with interrupts disabled* and has exclusive access -to Cortex-M and device specific peripherals through the `core` and `device` -variables fields of `init::Context`. Not all Cortex-M peripherals are available -in `core` because the RTFM runtime takes ownership of some of them -- for more -details see the [`rtfm::Peripherals`] struct. +to Cortex-M and, optionally, device specific peripherals through the `core` and +`device` fields of `init::Context`. `static mut` variables declared at the beginning of `init` will be transformed into `&'static mut` references that are safe to access. [`rtfm::Peripherals`]: ../../api/rtfm/struct.Peripherals.html -The example below shows the types of the `core` and `device` variables and -showcases safe access to a `static mut` variable. +The example below shows the types of the `core` and `device` fields and +showcases safe access to a `static mut` variable. The `device` field is only +available when the `peripherals` argument is set to `true` (it defaults to +`false`). ``` rust {{#include ../../../../examples/init.rs}} @@ -64,7 +65,7 @@ signature `fn(idle::Context) - > !`. When present, the runtime will execute the `idle` task after `init`. Unlike `init`, `idle` will run *with interrupts enabled* and it's not allowed to return -so it runs forever. +so it must run forever. When no `idle` function is declared, the runtime sets the [SLEEPONEXIT] bit and then sends the microcontroller to sleep after running `init`. @@ -84,21 +85,67 @@ The example below shows that `idle` runs after `init`. $ cargo run --example idle {{#include ../../../../ci/expected/idle.run}}``` -## `interrupt` / `exception` +## Hardware tasks -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. +To declare interrupt handlers the framework provides a `#[task]` attribute that +can be attached to functions. This attribute takes a `binds` argument whose +value is the name of the interrupt to which the handler will be bound to; the +function adornated with this attribute becomes the interrupt handler. Within the +framework these type of tasks are referred to as *hardware* tasks, because they +start executing in reaction to a hardware event. + +The example below demonstrates the use of the `#[task]` attribute to declare an +interrupt handler. Like in the case of `#[init]` and `#[idle]` local `static +mut` variables are safe to use within a hardware task. ``` rust -{{#include ../../../../examples/interrupt.rs}} +{{#include ../../../../examples/hardware.rs}} ``` ``` console $ cargo run --example interrupt -{{#include ../../../../ci/expected/interrupt.run}}``` +{{#include ../../../../ci/expected/hardware.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. +applications one can write using only the `cortex-m-rt` crate. From this point +we start introducing features unique to RTFM. + +## Priorities + +The static priority of each handler can be declared in the `task` attribute +using the `priority` argument. Tasks can have priorities in the range `1..=(1 << +NVIC_PRIO_BITS)` where `NVIC_PRIO_BITS` is a constant defined in the `device` +crate. When the `priority` argument is omitted the priority is assumed to be +`1`. The `idle` task has a non-configurable static priority of `0`, the lowest +priority. + +When several tasks are ready to be executed the one with *highest* static +priority will be executed first. Task prioritization can be observed in the +following scenario: an interrupt signal arrives during the execution of a low +priority task; the signal puts the higher priority task in the pending state. +The difference in priority results in the higher priority task preempting the +lower priority one: the execution of the lower priority task is suspended and +the higher priority task is executed to completion. Once the higher priority +task has terminated the lower priority task is resumed. + +The following example showcases the priority based scheduling of tasks. + +``` rust +{{#include ../../../../examples/preempt.rs}} +``` + +``` console +$ cargo run --example interrupt +{{#include ../../../../ci/expected/preempt.run}}``` + +Note that the task `gpiob` does *not* preempt task `gpioc` because its priority +is the *same* as `gpioc`'s. However, once `gpioc` terminates the execution of +task `gpiob` is prioritized over `gpioa`'s due to its higher priority. `gpioa` +is resumed only after `gpiob` terminates. + +One more note about priorities: choosing a priority higher than what the device +supports (that is `1 << NVIC_PRIO_BITS`) will result in a compile error. Due to +limitations in the language the error message is currently far from helpful: it +will say something along the lines of "evaluation of constant value failed" and +the span of the error will *not* point out to the problematic interrupt value -- +we are sorry about this! diff --git a/book/en/src/by-example/new.md b/book/en/src/by-example/new.md index 91b31bb87d..a71172e72b 100644 --- a/book/en/src/by-example/new.md +++ b/book/en/src/by-example/new.md @@ -36,8 +36,7 @@ $ cargo add lm3s6965 --vers 0.1.3 $ rm memory.x build.rs ``` -3. Add the `cortex-m-rtfm` crate as a dependency and, if you need it, enable the - `timer-queue` feature. +3. Add the `cortex-m-rtfm` crate as a dependency. ``` console $ cargo add cortex-m-rtfm --allow-prerelease diff --git a/book/en/src/by-example/resources.md b/book/en/src/by-example/resources.md index 06f2f06c5f..e8f61d5653 100644 --- a/book/en/src/by-example/resources.md +++ b/book/en/src/by-example/resources.md @@ -1,22 +1,27 @@ ## 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 framework provides an abstraction to share data between any of the contexts +we saw in the previous section (task handlers, `init` and `idle`): resources. -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`) one must first declare the -resource in the `resources` argument of its attribute. +Resources are data visible only to functions declared within the `#[app]` +pseudo-module. The framework gives the user complete control over which context +can access which resource. -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. +All resources are declared as a single `struct` within the `#[app]` +pseudo-module. Each field in the structure corresponds to a different resource. +Resources can optionally be given an initial value using the `#[init]` +attribute. Resources that are not given an initial value are referred to as +*late* resources and are covered in more detail in a follow up section in this +page. + +Each context (task handler, `init` or `idle`) must declare the resources it +intends to access in its corresponding metadata attribute using the `resources` +argument. This argument takes a list of resource names as its value. The listed +resources are made available to the context under the `resources` field of the +`Context` structure. + +The example application shown below contains two interrupt handlers that share +access to a resource named `shared`. ``` rust {{#include ../../../../examples/resource.rs}} @@ -26,40 +31,39 @@ two handlers. $ cargo run --example resource {{#include ../../../../ci/expected/resource.run}}``` -## Priorities +Note that the `shared` resource cannot accessed from `idle`. Attempting to do +so results in a compile error. -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 thus 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. +## `lock` -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. +In the presence of preemption critical sections are required to mutate shared +data in a data race free manner. As the framework has complete knowledge over +the priorities of tasks and which tasks can access which resources it enforces +that critical sections are used where required for memory safety. -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. +Where a critical section is required the framework hands out a resource proxy +instead of a reference. This resource proxy is a structure that implements the +[`Mutex`] trait. The only method on this trait, [`lock`], runs its closure +argument in a critical section. [`Mutex`]: ../../api/rtfm/trait.Mutex.html +[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock + +The critical section created by the `lock` API is based on dynamic priorities: +it temporarily raises the dynamic priority of the context to a *ceiling* +priority that prevents other tasks from preempting the critical section. This +synchronization protocol is known as the [Immediate Ceiling Priority Protocol +(ICPP)][icpp]. + +[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol In the example below we have three interrupt handlers with priorities ranging from one to three. The two handlers with the lower priorities contend for the -`SHARED` resource. The lowest priority handler needs to [`lock`] the -`SHARED` resource to access its data, whereas the mid priority handler can -directly access its data. The highest priority handler is free to preempt -the critical section created by the lowest priority handler. - -[`lock`]: ../../api/rtfm/trait.Mutex.html#method.lock +`shared` resource. The lowest priority handler needs to `lock` the +`shared` resource to access its data, whereas the mid priority handler can +directly access its data. The highest priority handler, which cannot access +the `shared` resource, is free to preempt the critical section created by the +lowest priority handler. ``` rust {{#include ../../../../examples/lock.rs}} @@ -69,27 +73,17 @@ the critical section created by the lowest priority handler. $ cargo run --example lock {{#include ../../../../ci/expected/lock.run}}``` -One more note about priorities: choosing a priority higher than what the device -supports (that is `1 << NVIC_PRIO_BITS`) will result in a compile error. Due to -limitations in the language the error message is currently far from helpful: it -will say something along the lines of "evaluation of constant value failed" and -the span of the error will *not* point out to the problematic interrupt value -- -we are sorry about this! - ## Late resources -Unlike normal `static` variables, which need to be assigned an initial value -when declared, resources can be initialized at runtime. We refer to these -runtime initialized resources as *late resources*. Late resources are useful for -*moving* (as in transferring ownership) peripherals initialized in `init` into -interrupt and exception handlers. +Late resources are resources that are not given an initial value at compile +using the `#[init]` attribute but instead are initialized are runtime using the +`init::LateResources` values returned by the `init` function. -Late resources are declared like normal resources but that are given an initial -value of `()` (the unit value). `init` must return the initial values of all -late resources packed in a `struct` of type `init::LateResources`. +Late resources are useful for *moving* (as in transferring the ownership of) +peripherals initialized in `init` into interrupt handlers. The example below uses late resources to stablish a lockless, one-way channel -between the `UART0` interrupt handler and the `idle` function. A single producer +between the `UART0` interrupt handler and the `idle` task. A single producer single consumer [`Queue`] is used as the channel. The queue is split into consumer and producer end points in `init` and then each end point is stored in a different resource; `UART0` owns the producer resource and `idle` owns @@ -105,22 +99,32 @@ the consumer resource. $ cargo run --example late {{#include ../../../../ci/expected/late.run}}``` -## `static` resources +## Only shared access -`static` variables can also be used as resources. Tasks can only get `&` -(shared) references to these resources but locks are never required to access -their data. You can think of `static` resources as plain `static` variables that -can be initialized at runtime and have better scoping rules: you can control -which tasks can access the variable, instead of the variable being visible to -all the functions in the scope it was declared in. +By default the framework assumes that all tasks require exclusive access +(`&mut-`) to resources but it is possible to specify that a task only requires +shared access (`&-`) to a resource using the `&resource_name` syntax in the +`resources` list. -In the example below a key is loaded (or created) at runtime and then used from -two tasks that run at different priorities. +The advantage of specifying shared access (`&-`) to a resource is that no locks +are required to access the resource even if the resource is contended by several +tasks running at different priorities. The downside is that the task only gets a +shared reference (`&-`) to the resource, limiting the operations it can perform +on it, but where a shared reference is enough this approach reduces the number +of required locks. + +Note that in this release of RTFM it is not possible to request both exclusive +access (`&mut-`) and shared access (`&-`) to the *same* resource from different +tasks. Attempting to do so will result in a compile error. + +In the example below a key (e.g. a cryptographic key) is loaded (or created) at +runtime and then used from two tasks that run at different priorities without +any kind of lock. ``` rust -{{#include ../../../../examples/static.rs}} +{{#include ../../../../examples/only-shared-access.rs}} ``` ``` console -$ cargo run --example static -{{#include ../../../../ci/expected/static.run}}``` +$ cargo run --example only-shared-access +{{#include ../../../../ci/expected/only-shared-access.run}}``` diff --git a/book/en/src/by-example/tasks.md b/book/en/src/by-example/tasks.md index edcdbed0c5..1c9302f4b2 100644 --- a/book/en/src/by-example/tasks.md +++ b/book/en/src/by-example/tasks.md @@ -1,22 +1,23 @@ # Software tasks -RTFM treats interrupt and exception handlers as *hardware* tasks. Hardware tasks -are invoked by the hardware in response to events, like pressing a button. RTFM -also supports *software* tasks which can be spawned by the software from any -execution context. +In addition to hardware tasks, which are invoked by the hardware in response to +hardware events, RTFM also supports *software* tasks which can be spawned by the +application 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 can also be assigned priorities and, under the hood, are +dispatched from interrupt handlers. RTFM requires that free interrupts are +declared in an `extern` block when using software tasks; some of these free +interrupts will be used to dispatch the software tasks. An advantage of software +tasks over hardware tasks is that many tasks can be mapped to a single interrupt +handler. -Software tasks are 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.). +Software tasks are also declared using the `task` attribute but the `binds` +argument must be omitted. To be able to spawn a software task from a context +the name of the task must appear in the `spawn` argument of the context +attribute (`init`, `idle`, `task`, etc.). The example below showcases three software tasks that run at 2 different -priorities. The three tasks map to 2 interrupts handlers. +priorities. The three software tasks are mapped to 2 interrupts handlers. ``` rust {{#include ../../../../examples/task.rs}} @@ -44,15 +45,17 @@ $ cargo run --example message ## Capacity -Task dispatchers do *not* use any dynamic memory allocation. The memory required -to store messages is statically reserved. The framework will reserve enough -space for every context to be able to spawn each task at most once. This is a -sensible default but the "inbox" capacity of each task can be controlled using -the `capacity` argument of the `task` attribute. +RTFM does *not* perform any form of heap-based memory allocation. The memory +required to store messages is statically reserved. By default the framework +minimizes the memory footprint of the application so each task has a message +"capacity" of 1: meaning that at most one message can be posted to the task +before it gets a chance to run. This default can be overridden for each task +using the `capacity` argument. This argument takes a positive integer that +indicates how many messages the task message buffer can hold. The example below sets the capacity of the software task `foo` to 4. If the capacity is not specified then the second `spawn.foo` call in `UART0` would -fail. +fail (panic). ``` rust {{#include ../../../../examples/capacity.rs}} @@ -61,3 +64,54 @@ fail. ``` console $ cargo run --example capacity {{#include ../../../../ci/expected/capacity.run}}``` + +## Error handling + +The `spawn` API returns the `Err` variant when there's no space to send the +message. In most scenarios spawning errors are handled in one of two ways: + +- Panicking, using `unwrap`, `expect`, etc. This approach is used to catch the + programmer error (i.e. bug) of selecting a capacity that was too small. When + this panic is encountered during testing choosing a bigger capacity and + recompiling the program may fix the issue but sometimes it's necessary to dig + deeper and perform a timing analysis of the application to check if the + platform can deal with peak payload or if the processor needs to be replaced + with a faster one. + +- Ignoring the result. In soft real time and non real time applications it may + be OK to occasionally lose data or fail to respond to some events during event + bursts. In those scenarios silently letting a `spawn` call fail may be + acceptable. + +It should be noted that retrying a `spawn` call is usually the wrong approach as +this operation will likely never succeed in practice. Because there are only +context switches towards *higher* priority tasks retrying the `spawn` call of a +lower priority task will never let the scheduler dispatch said task meaning that +its message buffer will never be emptied. This situation is depicted in the +following snippet: + +``` rust +#[rtfm::app(..)] +const APP: () = { + #[init(spawn = [foo, bar])] + fn init(cx: init::Context) { + cx.spawn.foo().unwrap(); + cx.spawn.bar().unwrap(); + } + + #[task(priority = 2, spawn = [bar])] + fn foo(cx: foo::Context) { + // .. + + // the program will get stuck here + while cx.spawn.bar(payload).is_err() { + // retry the spawn call if it failed + } + } + + #[task(priority = 1)] + fn bar(cx: bar::Context, payload: i32) { + // .. + } +}; +``` diff --git a/book/en/src/by-example/timer-queue.md b/book/en/src/by-example/timer-queue.md index 167939cecf..57e9d01bd0 100644 --- a/book/en/src/by-example/timer-queue.md +++ b/book/en/src/by-example/timer-queue.md @@ -1,37 +1,43 @@ # Timer queue -When the `timer-queue` feature is enabled the RTFM framework includes a *global -timer queue* that applications can use to *schedule* software tasks to run at -some time in the future. +In contrast with the `spawn` API, which immediately spawns a software task onto +the scheduler, the `schedule` API can be used to schedule a task to run some +time in the future. -> **NOTE**: The timer-queue feature can't be enabled when the target is -> `thumbv6m-none-eabi` because there's no timer queue support for ARMv6-M. This -> may change in the future. +To use the `schedule` API a monotonic timer must be first defined using the +`monotonic` argument of the `#[app]` attribute. This argument takes a path to a +type that implements the [`Monotonic`] trait. The associated type, `Instant`, of +this trait represents a timestamp in arbitrary units and it's used extensively +in the `schedule` API -- it is suggested to model this type after [the one in +the standard library][std-instant]. -> **NOTE**: When the `timer-queue` feature is enabled you will *not* be able to -> use the `SysTick` exception as a hardware task because the runtime uses it to -> implement the global timer queue. +Although not shown in the trait definition (due to limitations in the trait / +type system) the subtraction of two `Instant`s should return some `Duration` +type (see [`core::time::Duration`]) and this `Duration` type must implement the +`TryInto` trait. The implementation of this trait must convert the +`Duration` value, which uses some arbitrary unit of time, into the "system timer +(SYST) clock cycles" time unit. The result of the conversion must be a 32-bit +integer. If the result of the conversion doesn't fit in a 32-bit number then the +operation must return an error, any error type. -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. +[`Monotonic`]: ../../api/rtfm/trait.Monotonic.html +[std-instant]: https://doc.rust-lang.org/std/time/struct.Instant.html +[`core::time::Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html -[`Instant`]: ../../api/rtfm/struct.Instant.html +For ARMv7+ targets the `rtfm` crate provides a `Monotonic` implementation based +on the built-in CYCle CouNTer (CYCCNT). Note that this is a 32-bit timer clocked +at the frequency of the CPU and as such it is not suitable for tracking time +spans in the order of seconds. -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 +To be able to schedule a software task from a context the name of the task must +first appear in the `schedule` argument of the context attribute. When +scheduling a task the (user-defined) `Instant` at which the task should be +executed must be passed as the first argument of the `schedule` invocation. The example below schedules two tasks from `init`: `foo` and `bar`. `foo` is scheduled to run 8 million clock cycles in the future. Next, `bar` is scheduled -to run 4 million clock cycles in the future. `bar` runs before `foo` since it -was scheduled to run first. +to run 4 million clock cycles in the future. Thus `bar` runs before `foo` since +it was scheduled to run first. > **IMPORTANT**: The examples that use the `schedule` API or the `Instant` > abstraction will **not** properly work on QEMU because the Cortex-M cycle @@ -41,12 +47,19 @@ was scheduled to run first. {{#include ../../../../examples/schedule.rs}} ``` -Running the program on real hardware produces the following output in the console: +Running the program on real hardware produces the following output in the +console: ``` text {{#include ../../../../ci/expected/schedule.run}} ``` +When the `schedule` API is being used the runtime internally uses the `SysTick` +interrupt handler and the system timer peripheral (`SYST`) so neither can be +used by the application. This is accomplished by changing the type of +`init::Context.core` from `cortex_m::Peripherals` to `rtfm::Peripherals`. The +latter structure contains all the fields of the former minus the `SYST` one. + ## Periodic tasks Software tasks have access to the `Instant` at which they were scheduled to run @@ -80,9 +93,10 @@ the task. Depending on the priority of the task and the load of the system the What do you think will be the value of `scheduled` for software tasks that are *spawned* instead of scheduled? The answer is that spawned tasks inherit the *baseline* time of the context that spawned it. The baseline of hardware tasks -is `start`, the baseline of software tasks is `scheduled` and the baseline of -`init` is `start = Instant(0)`. `idle` doesn't really have a baseline but tasks -spawned from it will use `Instant::now()` as their baseline time. +is their `start` time, the baseline of software tasks is their `scheduled` time +and the baseline of `init` is the system start time or time zero +(`Instant::zero()`). `idle` doesn't really have a baseline but tasks spawned +from it will use `Instant::now()` as their baseline time. The example below showcases the different meanings of the *baseline*. diff --git a/book/en/src/by-example/tips.md b/book/en/src/by-example/tips.md index 4936bd279f..3852b449d5 100644 --- a/book/en/src/by-example/tips.md +++ b/book/en/src/by-example/tips.md @@ -2,10 +2,21 @@ ## Generics -Resources shared between two or more tasks implement the `Mutex` trait in *all* -contexts, even on those where a critical section is not required to access the -data. This lets you easily write generic code that operates on resources and can -be called from different tasks. Here's one such example: +Resources may appear in contexts as resource proxies or as unique references +(`&mut-`) depending on the priority of the task. Because the same resource may +appear as *different* types in different contexts one cannot refactor a common +operation that uses resources into a plain function; however, such refactor is +possible using *generics*. + +All resource proxies implement the `rtfm::Mutex` trait. On the other hand, +unique references (`&mut-`) do *not* implement this trait (due to limitations in +the trait system) but one can wrap these references in the [`rtfm::Exclusive`] +newtype which does implement the `Mutex` trait. With the help of this newtype +one can write a generic function that operates on generic resources and call it +from different tasks to perform some operation on the same set of resources. +Here's one such example: + +[`rtfm::Exclusive`]: ../../api/rtfm/struct.Exclusive.html ``` rust {{#include ../../../../examples/generics.rs}} @@ -15,17 +26,15 @@ be called from different tasks. Here's one such example: $ cargo run --example generics {{#include ../../../../ci/expected/generics.run}}``` -This also lets you change the static priorities of tasks without having to -rewrite code. If you consistently use `lock`s to access the data behind shared -resources then your code will continue to compile when you change the priority -of tasks. +Using generics also lets you change the static priorities of tasks during +development without having to rewrite a bunch code every time. ## Conditional compilation -You can use conditional compilation (`#[cfg]`) on resources (`static [mut]` -items) and tasks (`fn` items). The effect of using `#[cfg]` attributes is that -the resource / task will *not* be available through the corresponding `Context` -`struct` if the condition doesn't hold. +You can use conditional compilation (`#[cfg]`) on resources (the fields of +`struct Resources`) and tasks (the `fn` items). The effect of using `#[cfg]` +attributes is that the resource / task will *not* be available through the +corresponding `Context` `struct` if the condition doesn't hold. The example below logs a message whenever the `foo` task is spawned, but only if the program has been compiled using the `dev` profile. @@ -34,6 +43,12 @@ the program has been compiled using the `dev` profile. {{#include ../../../../examples/cfg.rs}} ``` +``` console +$ cargo run --example cfg --release + +$ cargo run --example cfg +{{#include ../../../../ci/expected/cfg.run}}``` + ## Running tasks from RAM The main goal of moving the specification of RTFM applications to attributes in @@ -70,25 +85,13 @@ One can look at the output of `cargo-nm` to confirm that `bar` ended in RAM ``` console $ cargo nm --example ramfunc --release | grep ' foo::' -{{#include ../../../../ci/expected/ramfunc.grep.foo}}``` +{{#include ../../../../ci/expected/ramfunc.grep.foo}} +``` ``` console $ cargo nm --example ramfunc --release | grep ' bar::' -{{#include ../../../../ci/expected/ramfunc.grep.bar}}``` - -## `binds` - -You can give hardware tasks more task-like names using the `binds` argument: you -name the function as you wish and specify the name of the interrupt / exception -in the `binds` argument. Types like `Spawn` will be placed in a module named -after the function, not the interrupt / exception. Example below: - -``` rust -{{#include ../../../../examples/binds.rs}} +{{#include ../../../../ci/expected/ramfunc.grep.bar}} ``` -``` console -$ cargo run --example binds -{{#include ../../../../ci/expected/binds.run}}``` ## Indirection for faster message passing @@ -100,10 +103,10 @@ instead of sending the buffer by value, one can send an owning pointer into the buffer. One can use a global allocator to achieve indirection (`alloc::Box`, -`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.34.0, +`alloc::Rc`, etc.), which requires using the nightly channel as of Rust v1.37.0, or one can use a statically allocated memory pool like [`heapless::Pool`]. -[`heapless::Pool`]: https://docs.rs/heapless/0.4.3/heapless/pool/index.html +[`heapless::Pool`]: https://docs.rs/heapless/0.5.0/heapless/pool/index.html Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes. @@ -111,7 +114,7 @@ Here's an example where `heapless::Pool` is used to "box" buffers of 128 bytes. {{#include ../../../../examples/pool.rs}} ``` ``` console -$ cargo run --example binds +$ cargo run --example pool {{#include ../../../../ci/expected/pool.run}}``` ## Inspecting the expanded code @@ -131,33 +134,18 @@ $ cargo build --example foo $ rustfmt target/rtfm-expansion.rs -$ tail -n30 target/rtfm-expansion.rs +$ tail target/rtfm-expansion.rs ``` ``` rust #[doc = r" Implementation details"] const APP: () = { + #[doc = r" Always include the device crate which contains the vector table"] use lm3s6965 as _; #[no_mangle] - unsafe fn main() -> ! { + unsafe extern "C" fn main() -> ! { rtfm::export::interrupt::disable(); - let mut core = rtfm::export::Peripherals::steal(); - let late = init( - init::Locals::new(), - init::Context::new(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: core.DCB, - DWT: core.DWT, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - SYST: core.SYST, - TPIU: core.TPIU, - }), - ); + let mut core: rtfm::export::Peripherals = core::mem::transmute(()); core.SCB.scr.modify(|r| r | 1 << 1); rtfm::export::interrupt::enable(); loop { @@ -175,5 +163,5 @@ crate and print the output to the console. ``` console $ # produces the same output as before -$ cargo expand --example smallest | tail -n30 +$ cargo expand --example smallest | tail ``` diff --git a/book/en/src/by-example/types-send-sync.md b/book/en/src/by-example/types-send-sync.md index 99f9f19030..fd344e3e0b 100644 --- a/book/en/src/by-example/types-send-sync.md +++ b/book/en/src/by-example/types-send-sync.md @@ -1,8 +1,8 @@ # Types, Send and Sync -The `app` attribute injects a context, a collection of variables, into every -function. All these variables have predictable, non-anonymous types so you can -write plain functions that take them as arguments. +Every function within the `APP` pseudo-module has a `Context` structure as its +first parameter. All the fields of these structures have predictable, +non-anonymous types so you can write plain functions that take them as arguments. The API reference specifies how these types are generated from the input. You can also generate documentation for you binary crate (`cargo doc --bin `); @@ -20,8 +20,8 @@ The example below shows the different types generates by the `app` attribute. [`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 +tasks that run at *different* priorities. This occurs in a few places: in +message passing, in shared resources and in the initialization of late resources. [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html @@ -30,7 +30,7 @@ The `app` attribute will enforce that `Send` is implemented where required so you don't need to worry much about it. It's more important to know where you do *not* need the `Send` trait: on types that are transferred between tasks that run at the *same* priority. This occurs in two places: in message passing and in -shared `static mut` resources. +shared resources. The example below shows where a type that doesn't implement `Send` can be used. @@ -39,9 +39,11 @@ The example below shows where a type that doesn't implement `Send` can be used. ``` It's important to note that late initialization of resources is effectively a -send operation where the initial value is sent from `idle`, which has the lowest -priority of `0`, to a task with will run with a priority greater than or equal -to `1`. Thus all late resources need to implement the `Send` trait. +send operation where the initial value is sent from the background context, +which has the lowest priority of `0`, to a task, which will run at a priority +greater than or equal to `1`. Thus all late resources need to implement the +`Send` trait, except for those exclusively accessed by `idle`, which runs at a +priority of `0`. Sharing a resource with `init` can be used to implement late initialization, see example below. For that reason, resources shared with `init` must also implement @@ -56,14 +58,14 @@ the `Send` trait. 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. +or more, tasks that run at different priorities and may get a shared reference +(`&-`) to a resource. This only occurs with shared access (`&-`) resources. [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html The `app` attribute will enforce that `Sync` is implemented where required but -it's important to know where the `Sync` bound is not required: in `static` -resources shared between tasks that run at the *same* priority. +it's important to know where the `Sync` bound is not required: shared access +(`&-`) resources contended by tasks that run at the *same* priority. The example below shows where a type that doesn't implement `Sync` can be used. diff --git a/book/en/src/heterogeneous.md b/book/en/src/heterogeneous.md new file mode 100644 index 0000000000..cd9fcf87cb --- /dev/null +++ b/book/en/src/heterogeneous.md @@ -0,0 +1,6 @@ +# Heterogeneous multi-core support + +This section covers the *experimental* heterogeneous multi-core support provided +by RTFM behind the `heterogeneous` Cargo feature. + +**Content coming soon** diff --git a/book/en/src/homogeneous.md b/book/en/src/homogeneous.md new file mode 100644 index 0000000000..14cf9250c0 --- /dev/null +++ b/book/en/src/homogeneous.md @@ -0,0 +1,6 @@ +# Homogeneous multi-core support + +This section covers the *experimental* homogeneous multi-core support provided +by RTFM behind the `homogeneous` Cargo feature. + +**Content coming soon** diff --git a/book/en/src/internals/access.md b/book/en/src/internals/access.md index 513cef1b5a..a4c9ca0930 100644 --- a/book/en/src/internals/access.md +++ b/book/en/src/internals/access.md @@ -21,7 +21,7 @@ This makes it impossible for the user code to refer to these static variables. Access to the resources is then given to each task using a `Resources` struct whose fields correspond to the resources the task has access to. There's one such struct per task and the `Resources` struct is initialized with either a -mutable reference (`&mut`) to the static variables or with a resource proxy (see +unique reference (`&mut-`) to the static variables or with a resource proxy (see section on [critical sections](critical-sections.html)). The code below is an example of the kind of source level transformation that diff --git a/book/en/src/internals/ceilings.md b/book/en/src/internals/ceilings.md index c13df537db..6b0530c45e 100644 --- a/book/en/src/internals/ceilings.md +++ b/book/en/src/internals/ceilings.md @@ -16,61 +16,65 @@ that has a logical priority of `0` whereas `init` is completely omitted from the analysis -- the reason for that is that `init` never uses (or needs) critical sections to access static variables. -In the previous section we showed that a shared resource may appear as a mutable -reference or behind a proxy depending on the task that has access to it. Which -version is presented to the task depends on the task priority and the resource -ceiling. If the task priority is the same as the resource ceiling then the task -gets a mutable reference to the resource memory, otherwise the task gets a -proxy -- this also applies to `idle`. `init` is special: it always gets a -mutable reference to resources. +In the previous section we showed that a shared resource may appear as a unique +reference (`&mut-`) or behind a proxy depending on the task that has access to +it. Which version is presented to the task depends on the task priority and the +resource ceiling. If the task priority is the same as the resource ceiling then +the task gets a unique reference (`&mut-`) to the resource memory, otherwise the +task gets a proxy -- this also applies to `idle`. `init` is special: it always +gets a unique reference (`&mut-`) to resources. An example to illustrate the ceiling analysis: ``` rust #[rtfm::app(device = ..)] const APP: () = { - // accessed by `foo` (prio = 1) and `bar` (prio = 2) - // CEILING = 2 - static mut X: u64 = 0; + struct Resources { + // accessed by `foo` (prio = 1) and `bar` (prio = 2) + // -> CEILING = 2 + #[init(0)] + x: u64, - // accessed by `idle` (prio = 0) - // CEILING = 0 - static mut Y: u64 = 0; + // accessed by `idle` (prio = 0) + // -> CEILING = 0 + #[init(0)] + y: u64, + } - #[init(resources = [X])] + #[init(resources = [x])] fn init(c: init::Context) { - // mutable reference because this is `init` - let x: &mut u64 = c.resources.X; + // unique reference because this is `init` + let x: &mut u64 = c.resources.x; - // mutable reference because this is `init` - let y: &mut u64 = c.resources.Y; + // unique reference because this is `init` + let y: &mut u64 = c.resources.y; // .. } // PRIORITY = 0 - #[idle(resources = [Y])] + #[idle(resources = [y])] fn idle(c: idle::Context) -> ! { - // mutable reference because priority (0) == resource ceiling (0) - let y: &'static mut u64 = c.resources.Y; + // unique reference because priority (0) == resource ceiling (0) + let y: &'static mut u64 = c.resources.y; loop { // .. } } - #[interrupt(binds = UART0, priority = 1, resources = [X])] + #[interrupt(binds = UART0, priority = 1, resources = [x])] fn foo(c: foo::Context) { // resource proxy because task priority (1) < resource ceiling (2) - let x: resources::X = c.resources.X; + let x: resources::x = c.resources.x; // .. } - #[interrupt(binds = UART1, priority = 2, resources = [X])] + #[interrupt(binds = UART1, priority = 2, resources = [x])] fn bar(c: foo::Context) { - // mutable reference because task priority (2) == resource ceiling (2) - let x: &mut u64 = c.resources.X; + // unique reference because task priority (2) == resource ceiling (2) + let x: &mut u64 = c.resources.x; // .. } diff --git a/book/en/src/internals/critical-sections.md b/book/en/src/internals/critical-sections.md index 54f02ac829..046098ef51 100644 --- a/book/en/src/internals/critical-sections.md +++ b/book/en/src/internals/critical-sections.md @@ -1,19 +1,19 @@ # Critical sections When a resource (static variable) is shared between two, or more, tasks that run -at different priorities some form of mutual exclusion is required to access the +at different priorities some form of mutual exclusion is required to mutate the memory in a data race free manner. In RTFM we use priority-based critical -sections to guarantee mutual exclusion (see the [Immediate Priority Ceiling -Protocol][ipcp]). +sections to guarantee mutual exclusion (see the [Immediate Ceiling Priority +Protocol][icpp]). -[ipcp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol +[icpp]: https://en.wikipedia.org/wiki/Priority_ceiling_protocol The critical section consists of temporarily raising the *dynamic* priority of the task. While a task is within this critical section all the other tasks that may request the resource are *not allowed to start*. How high must the dynamic priority be to ensure mutual exclusion on a particular -resource? The [ceiling analysis](ceiling-analysis.html) is in charge of +resource? The [ceiling analysis](ceilings.html) is in charge of answering that question and will be discussed in the next section. This section will focus on the implementation of the critical section. @@ -25,7 +25,7 @@ a data race the *lower priority* task must use a critical section when it needs to modify the shared memory. On the other hand, the higher priority task can directly modify the shared memory because it can't be preempted by the lower priority task. To enforce the use of a critical section on the lower priority -task we give it a *resource proxy*, whereas we give a mutable reference +task we give it a *resource proxy*, whereas we give a unique reference (`&mut-`) to the higher priority task. The example below shows the different types handed out to each task: @@ -33,12 +33,15 @@ The example below shows the different types handed out to each task: ``` rust #[rtfm::app(device = ..)] const APP: () = { - static mut X: u64 = 0; + struct Resources { + #[init(0)] + x: u64, + } - #[interrupt(binds = UART0, priority = 1, resources = [X])] + #[interrupt(binds = UART0, priority = 1, resources = [x])] fn foo(c: foo::Context) { // resource proxy - let mut x: resources::X = c.resources.X; + let mut x: resources::x = c.resources.x; x.lock(|x: &mut u64| { // critical section @@ -46,9 +49,9 @@ const APP: () = { }); } - #[interrupt(binds = UART1, priority = 2, resources = [X])] + #[interrupt(binds = UART1, priority = 2, resources = [x])] fn bar(c: foo::Context) { - let mut x: &mut u64 = c.resources.X; + let mut x: &mut u64 = c.resources.x; *x += 1; } @@ -69,14 +72,14 @@ fn bar(c: bar::Context) { } pub mod resources { - pub struct X { + pub struct x { // .. } } pub mod foo { pub struct Resources { - pub X: resources::X, + pub x: resources::x, } pub struct Context { @@ -87,7 +90,7 @@ pub mod foo { pub mod bar { pub struct Resources<'a> { - pub X: rtfm::Exclusive<'a, u64>, // newtype over `&'a mut u64` + pub x: &'a mut u64, } pub struct Context { @@ -97,9 +100,9 @@ pub mod bar { } const APP: () = { - static mut X: u64 = 0; + static mut x: u64 = 0; - impl rtfm::Mutex for resources::X { + impl rtfm::Mutex for resources::x { type T = u64; fn lock(&mut self, f: impl FnOnce(&mut u64) -> R) -> R { @@ -111,7 +114,7 @@ const APP: () = { unsafe fn UART0() { foo(foo::Context { resources: foo::Resources { - X: resources::X::new(/* .. */), + x: resources::x::new(/* .. */), }, // .. }) @@ -121,7 +124,7 @@ const APP: () = { unsafe fn UART1() { bar(bar::Context { resources: bar::Resources { - X: rtfm::Exclusive(&mut X), + x: &mut x, }, // .. }) @@ -158,7 +161,7 @@ In this particular example we could implement the critical section as follows: > **NOTE:** this is a simplified implementation ``` rust -impl rtfm::Mutex for resources::X { +impl rtfm::Mutex for resources::x { type T = u64; fn lock(&mut self, f: F) -> R @@ -170,7 +173,7 @@ impl rtfm::Mutex for resources::X { asm!("msr BASEPRI, 192" : : : "memory" : "volatile"); // run user code within the critical section - let r = f(&mut implementation_defined_name_for_X); + let r = f(&mut x); // end of critical section: restore dynamic priority to its static value (`1`) asm!("msr BASEPRI, 0" : : : "memory" : "volatile"); @@ -183,23 +186,23 @@ impl rtfm::Mutex for resources::X { Here it's important to use the `"memory"` clobber in the `asm!` block. It prevents the compiler from reordering memory operations across it. This is -important because accessing the variable `X` outside the critical section would +important because accessing the variable `x` outside the critical section would result in a data race. It's important to note that the signature of the `lock` method prevents nesting calls to it. This is required for memory safety, as nested calls would produce -multiple mutable references (`&mut-`) to `X` breaking Rust aliasing rules. See +multiple unique references (`&mut-`) to `x` breaking Rust aliasing rules. See below: ``` rust -#[interrupt(binds = UART0, priority = 1, resources = [X])] +#[interrupt(binds = UART0, priority = 1, resources = [x])] fn foo(c: foo::Context) { // resource proxy - let mut res: resources::X = c.resources.X; + let mut res: resources::x = c.resources.x; res.lock(|x: &mut u64| { res.lock(|alias: &mut u64| { - //~^ error: `res` has already been mutably borrowed + //~^ error: `res` has already been uniquely borrowed (`&mut-`) // .. }); }); @@ -223,18 +226,22 @@ Consider this program: ``` rust #[rtfm::app(device = ..)] const APP: () = { - static mut X: u64 = 0; - static mut Y: u64 = 0; + struct Resources { + #[init(0)] + x: u64, + #[init(0)] + y: u64, + } #[init] fn init() { rtfm::pend(Interrupt::UART0); } - #[interrupt(binds = UART0, priority = 1, resources = [X, Y])] + #[interrupt(binds = UART0, priority = 1, resources = [x, y])] fn foo(c: foo::Context) { - let mut x = c.resources.X; - let mut y = c.resources.Y; + let mut x = c.resources.x; + let mut y = c.resources.y; y.lock(|y| { *y += 1; @@ -259,12 +266,12 @@ const APP: () = { }) } - #[interrupt(binds = UART1, priority = 2, resources = [X])] + #[interrupt(binds = UART1, priority = 2, resources = [x])] fn bar(c: foo::Context) { // .. } - #[interrupt(binds = UART2, priority = 3, resources = [Y])] + #[interrupt(binds = UART2, priority = 3, resources = [y])] fn baz(c: foo::Context) { // .. } @@ -279,13 +286,13 @@ The code generated by the framework looks like this: // omitted: user code pub mod resources { - pub struct X<'a> { + pub struct x<'a> { priority: &'a Cell, } - impl<'a> X<'a> { + impl<'a> x<'a> { pub unsafe fn new(priority: &'a Cell) -> Self { - X { priority } + x { priority } } pub unsafe fn priority(&self) -> &Cell { @@ -293,7 +300,7 @@ pub mod resources { } } - // repeat for `Y` + // repeat for `y` } pub mod foo { @@ -303,34 +310,35 @@ pub mod foo { } pub struct Resources<'a> { - pub X: resources::X<'a>, - pub Y: resources::Y<'a>, + pub x: resources::x<'a>, + pub y: resources::y<'a>, } } const APP: () = { + use cortex_m::register::basepri; + #[no_mangle] - unsafe fn UART0() { + unsafe fn UART1() { // the static priority of this interrupt (as specified by the user) - const PRIORITY: u8 = 1; + const PRIORITY: u8 = 2; // take a snashot of the BASEPRI - let initial: u8; - asm!("mrs $0, BASEPRI" : "=r"(initial) : : : "volatile"); + let initial = basepri::read(); let priority = Cell::new(PRIORITY); - foo(foo::Context { - resources: foo::Resources::new(&priority), + bar(bar::Context { + resources: bar::Resources::new(&priority), // .. }); // roll back the BASEPRI to the snapshot value we took before - asm!("msr BASEPRI, $0" : : "r"(initial) : : "volatile"); + basepri::write(initial); // same as the `asm!` block we saw before } - // similarly for `UART1` + // similarly for `UART0` / `foo` and `UART2` / `baz` - impl<'a> rtfm::Mutex for resources::X<'a> { + impl<'a> rtfm::Mutex for resources::x<'a> { type T = u64; fn lock(&mut self, f: impl FnOnce(&mut u64) -> R) -> R { @@ -342,26 +350,24 @@ const APP: () = { if current < CEILING { // raise dynamic priority self.priority().set(CEILING); - let hw = logical2hw(CEILING); - asm!("msr BASEPRI, $0" : : "r"(hw) : "memory" : "volatile"); + basepri::write(logical2hw(CEILING)); - let r = f(&mut X); + let r = f(&mut y); // restore dynamic priority - let hw = logical2hw(current); - asm!("msr BASEPRI, $0" : : "r"(hw) : "memory" : "volatile"); + basepri::write(logical2hw(current)); self.priority().set(current); r } else { // dynamic priority is high enough - f(&mut X) + f(&mut y) } } } } - // repeat for `Y` + // repeat for resource `y` }; ``` @@ -373,38 +379,38 @@ fn foo(c: foo::Context) { // NOTE: BASEPRI contains the value `0` (its reset value) at this point // raise dynamic priority to `3` - unsafe { asm!("msr BASEPRI, 160" : : : "memory" : "volatile") } + unsafe { basepri::write(160) } - // the two operations on `Y` are merged into one - Y += 2; + // the two operations on `y` are merged into one + y += 2; - // BASEPRI is not modified to access `X` because the dynamic priority is high enough - X += 1; + // BASEPRI is not modified to access `x` because the dynamic priority is high enough + x += 1; // lower (restore) the dynamic priority to `1` - unsafe { asm!("msr BASEPRI, 224" : : : "memory" : "volatile") } + unsafe { basepri::write(224) } // mid-point // raise dynamic priority to `2` - unsafe { asm!("msr BASEPRI, 192" : : : "memory" : "volatile") } + unsafe { basepri::write(192) } - X += 1; + x += 1; // raise dynamic priority to `3` - unsafe { asm!("msr BASEPRI, 160" : : : "memory" : "volatile") } + unsafe { basepri::write(160) } - Y += 1; + y += 1; // lower (restore) the dynamic priority to `2` - unsafe { asm!("msr BASEPRI, 192" : : : "memory" : "volatile") } + unsafe { basepri::write(192) } - // NOTE: it would be sound to merge this operation on X with the previous one but + // NOTE: it would be sound to merge this operation on `x` with the previous one but // compiler fences are coarse grained and prevent such optimization - X += 1; + x += 1; // lower (restore) the dynamic priority to `1` - unsafe { asm!("msr BASEPRI, 224" : : : "memory" : "volatile") } + unsafe { basepri::write(224) } // NOTE: BASEPRI contains the value `224` at this point // the UART0 handler will restore the value to `0` before returning @@ -425,7 +431,10 @@ handler through preemption. This is best observed in the following example: ``` rust #[rtfm::app(device = ..)] const APP: () = { - static mut X: u64 = 0; + struct Resources { + #[init(0)] + x: u64, + } #[init] fn init() { @@ -444,11 +453,11 @@ const APP: () = { // this function returns to `idle` } - #[task(binds = UART1, priority = 2, resources = [X])] + #[task(binds = UART1, priority = 2, resources = [x])] fn bar() { // BASEPRI is `0` (dynamic priority = 2) - X.lock(|x| { + x.lock(|x| { // BASEPRI is raised to `160` (dynamic priority = 3) // .. @@ -470,7 +479,7 @@ const APP: () = { } } - #[task(binds = UART2, priority = 3, resources = [X])] + #[task(binds = UART2, priority = 3, resources = [x])] fn baz() { // .. } @@ -493,8 +502,7 @@ const APP: () = { const PRIORITY: u8 = 2; // take a snashot of the BASEPRI - let initial: u8; - asm!("mrs $0, BASEPRI" : "=r"(initial) : : : "volatile"); + let initial = basepri::read(); let priority = Cell::new(PRIORITY); bar(bar::Context { @@ -503,7 +511,7 @@ const APP: () = { }); // BUG: FORGOT to roll back the BASEPRI to the snapshot value we took before - // asm!("msr BASEPRI, $0" : : "r"(initial) : : "volatile"); + basepri::write(initial); } }; ``` diff --git a/book/en/src/internals/interrupt-configuration.md b/book/en/src/internals/interrupt-configuration.md index b34b3089a6..98a98e5803 100644 --- a/book/en/src/internals/interrupt-configuration.md +++ b/book/en/src/internals/interrupt-configuration.md @@ -12,7 +12,7 @@ configuration is done before the `init` function runs. This example gives you an idea of the code that the RTFM framework runs: ``` rust -#[rtfm::app(device = ..)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(c: init::Context) { @@ -39,8 +39,7 @@ The framework generates an entry point that looks like this: unsafe fn main() -> ! { // transforms a logical priority into a hardware / NVIC priority fn logical2hw(priority: u8) -> u8 { - // this value comes from the device crate - const NVIC_PRIO_BITS: u8 = ..; + use lm3s6965::NVIC_PRIO_BITS; // the NVIC encodes priority in the higher bits of a bit // also a bigger numbers means lower priority diff --git a/book/en/src/internals/late-resources.md b/book/en/src/internals/late-resources.md index 71157f288c..8724fbb436 100644 --- a/book/en/src/internals/late-resources.md +++ b/book/en/src/internals/late-resources.md @@ -11,21 +11,22 @@ initialize late resources. ``` rust #[rtfm::app(device = ..)] const APP: () = { - // late resource - static mut X: Thing = {}; + struct Resources { + x: Thing, + } #[init] fn init() -> init::LateResources { // .. init::LateResources { - X: Thing::new(..), + x: Thing::new(..), } } - #[task(binds = UART0, resources = [X])] + #[task(binds = UART0, resources = [x])] fn foo(c: foo::Context) { - let x: &mut Thing = c.resources.X; + let x: &mut Thing = c.resources.x; x.frob(); @@ -50,7 +51,7 @@ fn foo(c: foo::Context) { // Public API pub mod init { pub struct LateResources { - pub X: Thing, + pub x: Thing, } // .. @@ -58,7 +59,7 @@ pub mod init { pub mod foo { pub struct Resources<'a> { - pub X: &'a mut Thing, + pub x: &'a mut Thing, } pub struct Context<'a> { @@ -70,7 +71,7 @@ pub mod foo { /// Implementation details const APP: () = { // uninitialized static - static mut X: MaybeUninit = MaybeUninit::uninit(); + static mut x: MaybeUninit = MaybeUninit::uninit(); #[no_mangle] unsafe fn main() -> ! { @@ -81,7 +82,7 @@ const APP: () = { let late = init(..); // initialization of late resources - X.write(late.X); + x.as_mut_ptr().write(late.x); cortex_m::interrupt::enable(); //~ compiler fence @@ -94,8 +95,8 @@ const APP: () = { unsafe fn UART0() { foo(foo::Context { resources: foo::Resources { - // `X` has been initialized at this point - X: &mut *X.as_mut_ptr(), + // `x` has been initialized at this point + x: &mut *x.as_mut_ptr(), }, // .. }) diff --git a/book/en/src/internals/non-reentrancy.md b/book/en/src/internals/non-reentrancy.md index 408a012e55..f1ce2cb0b6 100644 --- a/book/en/src/internals/non-reentrancy.md +++ b/book/en/src/internals/non-reentrancy.md @@ -13,24 +13,20 @@ are discouraged from directly invoking an interrupt handler. ``` rust #[rtfm::app(device = ..)] const APP: () = { - static mut X: u64 = 0; - #[init] fn init(c: init::Context) { .. } - #[interrupt(binds = UART0, resources = [X])] + #[interrupt(binds = UART0)] fn foo(c: foo::Context) { - let x: &mut u64 = c.resources.X; + static mut X: u64 = 0; - *x = 1; + let x: &mut u64 = X; + + // .. //~ `bar` can preempt `foo` at this point - *x = 2; - - if *x == 2 { - // something - } + // .. } #[interrupt(binds = UART1, priority = 2)] @@ -40,15 +36,15 @@ const APP: () = { } // this interrupt handler will invoke task handler `foo` resulting - // in mutable aliasing of the static variable `X` + // in aliasing of the static variable `X` unsafe { UART0() } } }; ``` The RTFM framework must generate the interrupt handler code that calls the user -defined task handlers. We are careful in making these handlers `unsafe` and / or -impossible to call from user code. +defined task handlers. We are careful in making these handlers impossible to +call from user code. The above example expands into: diff --git a/book/en/src/internals/tasks.md b/book/en/src/internals/tasks.md index 432c2e6e5a..dd3638a7b7 100644 --- a/book/en/src/internals/tasks.md +++ b/book/en/src/internals/tasks.md @@ -19,7 +19,7 @@ task. The ready queue is a SPSC (Single Producer Single Consumer) lock-free queue. The task dispatcher owns the consumer endpoint of the queue; the producer endpoint -is treated as a resource shared by the tasks that can `spawn` other tasks. +is treated as a resource contended by the tasks that can `spawn` other tasks. ## The task dispatcher @@ -244,7 +244,7 @@ const APP: () = { baz_INPUTS[index as usize].write(message); lock(self.priority(), RQ1_CEILING, || { - // put the task in the ready queu + // put the task in the ready queue RQ1.split().1.enqueue_unchecked(Ready { task: T1::baz, index, diff --git a/book/en/src/internals/timer-queue.md b/book/en/src/internals/timer-queue.md index 436f421d15..e0242f0592 100644 --- a/book/en/src/internals/timer-queue.md +++ b/book/en/src/internals/timer-queue.md @@ -47,7 +47,7 @@ mod foo { } const APP: () = { - use rtfm::Instant; + type Instant = ::Instant; // all tasks that can be `schedule`-d enum T { @@ -158,15 +158,14 @@ way it will run at the right priority. handler; basically, `enqueue_unchecked` delegates the task of setting up a new timeout interrupt to the `SysTick` handler. -## Resolution and range of `Instant` and `Duration` +## Resolution and range of `cyccnt::Instant` and `cyccnt::Duration` -In the current implementation the `DWT`'s (Data Watchpoint and Trace) cycle -counter is used as a monotonic timer. `Instant::now` returns a snapshot of this -timer; these DWT snapshots (`Instant`s) are used to sort entries in the timer -queue. The cycle counter is a 32-bit counter clocked at the core clock -frequency. This counter wraps around every `(1 << 32)` clock cycles; there's no -interrupt associated to this counter so nothing worth noting happens when it -wraps around. +RTFM provides a `Monotonic` implementation based on the `DWT`'s (Data Watchpoint +and Trace) cycle counter. `Instant::now` returns a snapshot of this timer; these +DWT snapshots (`Instant`s) are used to sort entries in the timer queue. The +cycle counter is a 32-bit counter clocked at the core clock frequency. This +counter wraps around every `(1 << 32)` clock cycles; there's no interrupt +associated to this counter so nothing worth noting happens when it wraps around. To order `Instant`s in the queue we need to compare two 32-bit integers. To account for the wrap-around behavior we use the difference between two @@ -264,11 +263,11 @@ The ceiling analysis would go like this: ## Changes in the `spawn` implementation -When the "timer-queue" feature is enabled the `spawn` implementation changes a -bit to track the baseline of tasks. As you saw in the `schedule` implementation -there's an `INSTANTS` buffers used to store the time at which a task was -scheduled to run; this `Instant` is read in the task dispatcher and passed to -the user code as part of the task context. +When the `schedule` API is used the `spawn` implementation changes a bit to +track the baseline of tasks. As you saw in the `schedule` implementation there's +an `INSTANTS` buffers used to store the time at which a task was scheduled to +run; this `Instant` is read in the task dispatcher and passed to the user code +as part of the task context. ``` rust const APP: () = { diff --git a/book/en/src/preface.md b/book/en/src/preface.md index e6a52b7bdc..15aaea5ce7 100644 --- a/book/en/src/preface.md +++ b/book/en/src/preface.md @@ -14,6 +14,6 @@ There is a translation of this book in [Russian]. **HEADS UP** This is an **alpha** pre-release; there may be breaking changes in the API and semantics before a proper release is made. -{{#include ../../../README.md:5:46}} +{{#include ../../../README.md:5:44}} -{{#include ../../../README.md:52:}} +{{#include ../../../README.md:50:}} diff --git a/build.rs b/build.rs index 2419b4eb84..14c3d248ac 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,10 @@ fn main() { println!("cargo:rustc-cfg=armv6m") } - if target.starts_with("thumbv7m") | target.starts_with("thumbv7em") { + if target.starts_with("thumbv7m") + | target.starts_with("thumbv7em") + | target.starts_with("thumbv8m") + { println!("cargo:rustc-cfg=armv7m") } diff --git a/ci/expected/cfg.run b/ci/expected/cfg.run new file mode 100644 index 0000000000..b584958b38 --- /dev/null +++ b/ci/expected/cfg.run @@ -0,0 +1,2 @@ +foo has been called 1 time +foo has been called 2 times diff --git a/ci/expected/generics.run b/ci/expected/generics.run index 7fa97758e1..fb31731e5e 100644 --- a/ci/expected/generics.run +++ b/ci/expected/generics.run @@ -1,6 +1,6 @@ UART1(STATE = 0) -SHARED: 0 -> 1 +shared: 0 -> 1 UART0(STATE = 0) -SHARED: 1 -> 2 +shared: 1 -> 2 UART1(STATE = 1) -SHARED: 2 -> 4 +shared: 2 -> 4 diff --git a/ci/expected/interrupt.run b/ci/expected/hardware.run similarity index 100% rename from ci/expected/interrupt.run rename to ci/expected/hardware.run diff --git a/ci/expected/lock.run b/ci/expected/lock.run index 156ac22286..a987b37246 100644 --- a/ci/expected/lock.run +++ b/ci/expected/lock.run @@ -1,5 +1,5 @@ A -B - SHARED = 1 +B - shared = 1 C -D - SHARED = 2 +D - shared = 2 E diff --git a/ci/expected/only-shared-access.run b/ci/expected/only-shared-access.run new file mode 100644 index 0000000000..1d4eed0051 --- /dev/null +++ b/ci/expected/only-shared-access.run @@ -0,0 +1,2 @@ +UART1(key = 0xdeadbeef) +UART0(key = 0xdeadbeef) diff --git a/ci/expected/preempt.run b/ci/expected/preempt.run new file mode 100644 index 0000000000..87777410c5 --- /dev/null +++ b/ci/expected/preempt.run @@ -0,0 +1,5 @@ +GPIOA - start + GPIOC - start + GPIOC - end + GPIOB +GPIOA - end diff --git a/ci/expected/ramfunc.grep.bar b/ci/expected/ramfunc.grep.bar index 7f69d25cbf..1fa5bad1dd 100644 --- a/ci/expected/ramfunc.grep.bar +++ b/ci/expected/ramfunc.grep.bar @@ -1,3 +1 @@ -20000100 B bar::FREE_QUEUE::lk14244m263eivix -200000dc B bar::INPUTS::mi89534s44r1mnj1 -20000000 T bar::ns9009yhw2dc2y25 +20000000 t ramfunc::bar::h9d6714fe5a3b0c89 \ No newline at end of file diff --git a/ci/expected/ramfunc.grep.foo b/ci/expected/ramfunc.grep.foo index a076ac0ad6..845f277f73 100644 --- a/ci/expected/ramfunc.grep.foo +++ b/ci/expected/ramfunc.grep.foo @@ -1,3 +1 @@ -20000100 B foo::FREE_QUEUE::ujkptet2nfdw5t20 -200000dc B foo::INPUTS::thvubs85b91dg365 -000002c6 T foo::sidaht420cg1mcm8 +00000162 t ramfunc::foo::h30e7789b08c08e19 \ No newline at end of file diff --git a/ci/expected/resource.run b/ci/expected/resource.run index 9c70856ab2..a587a94207 100644 --- a/ci/expected/resource.run +++ b/ci/expected/resource.run @@ -1,2 +1,2 @@ -UART0: SHARED = 1 -UART1: SHARED = 2 +UART0: shared = 1 +UART1: shared = 2 diff --git a/ci/expected/static.run b/ci/expected/static.run deleted file mode 100644 index 2c295c998d..0000000000 --- a/ci/expected/static.run +++ /dev/null @@ -1,2 +0,0 @@ -UART1(KEY = 0xdeadbeef) -UART0(KEY = 0xdeadbeef) diff --git a/ci/expected/task.run b/ci/expected/task.run index 309fdb99e8..de45dce64b 100644 --- a/ci/expected/task.run +++ b/ci/expected/task.run @@ -1,3 +1,5 @@ -foo +foo - start +foo - middle baz +foo - end bar diff --git a/ci/install.sh b/ci/install.sh index 9000772307..6f8d815142 100644 --- a/ci/install.sh +++ b/ci/install.sh @@ -1,17 +1,20 @@ set -euxo pipefail main() { - if [ $TARGET != x86_64-unknown-linux-gnu ]; then - rustup target add $TARGET + if [ $TARGET = x86_64-unknown-linux-gnu ]; then + ( cd .. && cargo install microamp-tools --version 0.1.0-alpha.2 -f ) + rustup target add thumbv6m-none-eabi thumbv7m-none-eabi fi + rustup target add $TARGET + 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 # install mdbook curl -LSfs https://japaric.github.io/trust/install.sh | \ - sh -s -- --git rust-lang-nursery/mdbook --tag v0.2.1 + sh -s -- --git rust-lang-nursery/mdbook --tag v0.3.1 pip install linkchecker --user } diff --git a/ci/script.sh b/ci/script.sh index 2292d474ba..9cb03fcea9 100644 --- a/ci/script.sh +++ b/ci/script.sh @@ -37,61 +37,75 @@ main() { mkdir -p ci/builds if [ $T = x86_64-unknown-linux-gnu ]; then - # compile-fail and compile-pass tests + if [ $TRAVIS_RUST_VERSION = nightly ]; then + # compile-fail tests + cargo test --test single --target $T - # TODO how to run a subset of these tests when timer-queue is disabled? - cargo test --features "timer-queue" --test compiletest --target $T + # multi-core compile-pass tests + pushd heterogeneous + local exs=( + smallest + x-init-2 + x-init + x-schedule + x-spawn + ) + for ex in ${exs[@]}; do + cargo microamp --example $ex --target thumbv7m-none-eabi,thumbv6m-none-eabi --check + done + + popd + + else + if [ $TRAVIS_RUST_VERSION != nightly ]; then + rm -f .cargo/config + cargo doc + ( cd book/en && mdbook build ) + ( cd book/ru && mdbook build ) + + local td=$(mktemp -d) + cp -r target/doc $td/api + mkdir $td/book + cp -r book/en/book $td/book/en + cp -r book/ru/book $td/book/ru + cp LICENSE-* $td/book/en + cp LICENSE-* $td/book/ru + + linkchecker $td/book/en/ + linkchecker $td/book/ru/ + linkchecker $td/api/rtfm/ + linkchecker $td/api/cortex_m_rtfm_macros/ + fi + fi cargo check --target $T - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo check --features "timer-queue" --target $T - fi - - if [ $TRAVIS_RUST_VERSION != nightly ]; then - rm -f .cargo/config - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo doc --features timer-queue - else - cargo doc - fi - ( cd book/en && mdbook build ) - ( cd book/ru && mdbook build ) - - local td=$(mktemp -d) - cp -r target/doc $td/api - mkdir $td/book - cp -r book/en/book $td/book/en - cp -r book/ru/book $td/book/ru - cp LICENSE-* $td/book/en - cp LICENSE-* $td/book/ru - - linkchecker $td/book/en/ - linkchecker $td/book/ru/ - linkchecker $td/api/rtfm/ - linkchecker $td/api/cortex_m_rtfm_macros/ - fi + ( cd macros && cargo test --target $T ) return fi - cargo check --target $T --examples - if [ $TARGET != thumbv6m-none-eabi ]; then - cargo check --features "timer-queue" --target $T --examples + if [ $TARGET = thumbv6m-none-eabi ]; then + cargo check --target $T --examples + else + cargo check --target $T --examples --features __v7 fi + cargo check -p homogeneous --target $T --examples + # run-pass tests case $T in thumbv6m-none-eabi | thumbv7m-none-eabi) local exs=( idle init - interrupt + hardware + preempt binds resource lock late - static + only-shared-access task message @@ -103,79 +117,81 @@ main() { shared-with-init generics + cfg pool ramfunc ) for ex in ${exs[@]}; do - if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then - # LLD doesn't support this at the moment - continue - fi - if [ $ex = pool ]; then - if [ $TARGET != thumbv6m-none-eabi ]; then - local td=$(mktemp -d) - - local features="timer-queue" - cargo run --example $ex --target $TARGET --features $features >\ - $td/pool.run - grep 'foo(0x2' $td/pool.run - grep 'bar(0x2' $td/pool.run - arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \ - ci/builds/${ex}_${features/,/_}_debug_1.hex - - cargo run --example $ex --target $TARGET --features $features --release >\ - $td/pool.run - grep 'foo(0x2' $td/pool.run - grep 'bar(0x2' $td/pool.run - arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \ - ci/builds/${ex}_${features/,/_}_release_1.hex - - rm -rf $td + if [ $TARGET = thumbv6m-none-eabi ]; then + continue fi + local td=$(mktemp -d) + + cargo run --example $ex --target $TARGET --features __v7 >\ + $td/pool.run + grep 'foo(0x2' $td/pool.run + grep 'bar(0x2' $td/pool.run + arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \ + ci/builds/${ex}___v7_debug_1.hex + + cargo run --example $ex --target $TARGET --features __v7 --release >\ + $td/pool.run + grep 'foo(0x2' $td/pool.run + grep 'bar(0x2' $td/pool.run + arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \ + ci/builds/${ex}___v7_release_1.hex + + rm -rf $td + continue fi - if [ $ex != types ]; then - arm_example "run" $ex "debug" "" "1" - arm_example "run" $ex "release" "" "1" + if [ $ex = types ]; then + if [ $TARGET = thumbv6m-none-eabi ]; then + continue + fi + + arm_example "run" $ex "debug" "__v7" "1" + arm_example "run" $ex "release" "__v7" "1" + + continue fi - if [ $TARGET != thumbv6m-none-eabi ]; then - arm_example "run" $ex "debug" "timer-queue" "1" - arm_example "run" $ex "release" "timer-queue" "1" + arm_example "run" $ex "debug" "" "1" + if [ $ex = types ]; then + arm_example "run" $ex "release" "" "1" + else + arm_example "build" $ex "release" "" "1" fi done local built=() cargo clean for ex in ${exs[@]}; do - if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then - # LLD doesn't support this at the moment - continue - fi + if [ $ex = types ] || [ $ex = pool ]; then + if [ $TARGET = thumbv6m-none-eabi ]; then + continue + fi - if [ $ex != types ] && [ $ex != pool ]; then + arm_example "build" $ex "debug" "__v7" "2" + cmp ci/builds/${ex}___v7_debug_1.hex \ + ci/builds/${ex}___v7_debug_2.hex + arm_example "build" $ex "release" "__v7" "2" + cmp ci/builds/${ex}___v7_release_1.hex \ + ci/builds/${ex}___v7_release_2.hex + else arm_example "build" $ex "debug" "" "2" cmp ci/builds/${ex}_debug_1.hex \ ci/builds/${ex}_debug_2.hex arm_example "build" $ex "release" "" "2" cmp ci/builds/${ex}_release_1.hex \ ci/builds/${ex}_release_2.hex - - built+=( $ex ) fi - if [ $TARGET != thumbv6m-none-eabi ]; then - arm_example "build" $ex "debug" "timer-queue" "2" - cmp ci/builds/${ex}_timer-queue_debug_1.hex \ - ci/builds/${ex}_timer-queue_debug_2.hex - arm_example "build" $ex "release" "timer-queue" "2" - cmp ci/builds/${ex}_timer-queue_release_1.hex \ - ci/builds/${ex}_timer-queue_release_2.hex - fi + built+=( $ex ) done ( cd target/$TARGET/release/examples/ && size ${built[@]} ) diff --git a/examples/baseline.rs b/examples/baseline.rs index d743107dc1..b7144dd188 100644 --- a/examples/baseline.rs +++ b/examples/baseline.rs @@ -5,27 +5,26 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; // NOTE: does NOT properly work on QEMU -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(spawn = [foo])] - fn init(c: init::Context) { - hprintln!("init(baseline = {:?})", c.start).unwrap(); + fn init(cx: init::Context) { + hprintln!("init(baseline = {:?})", cx.start).unwrap(); // `foo` inherits the baseline of `init`: `Instant(0)` - c.spawn.foo().unwrap(); + cx.spawn.foo().unwrap(); } #[task(schedule = [foo])] - fn foo(c: foo::Context) { + fn foo(cx: foo::Context) { static mut ONCE: bool = true; - hprintln!("foo(baseline = {:?})", c.scheduled).unwrap(); + hprintln!("foo(baseline = {:?})", cx.scheduled).unwrap(); if *ONCE { *ONCE = false; @@ -36,12 +35,12 @@ const APP: () = { } } - #[interrupt(spawn = [foo])] - fn UART0(c: UART0::Context) { - hprintln!("UART0(baseline = {:?})", c.start).unwrap(); + #[task(binds = UART0, spawn = [foo])] + fn uart0(cx: uart0::Context) { + hprintln!("UART0(baseline = {:?})", cx.start).unwrap(); // `foo` inherits the baseline of `UART0`: its `start` time - c.spawn.foo().unwrap(); + cx.spawn.foo().unwrap(); } extern "C" { diff --git a/examples/binds.rs b/examples/binds.rs index 3d2d9b541b..b10cb43498 100644 --- a/examples/binds.rs +++ b/examples/binds.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; // `examples/interrupt.rs` rewritten to use `binds` #[rtfm::app(device = lm3s6965)] @@ -31,7 +30,7 @@ const APP: () = { loop {} } - #[interrupt(binds = UART0)] + #[task(binds = UART0)] fn foo(_: foo::Context) { static mut TIMES: u32 = 0; diff --git a/examples/capacity.rs b/examples/capacity.rs index 07edd9b8e1..ebc86b8047 100644 --- a/examples/capacity.rs +++ b/examples/capacity.rs @@ -5,10 +5,9 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -17,8 +16,8 @@ const APP: () = { rtfm::pend(Interrupt::UART0); } - #[interrupt(spawn = [foo, bar])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, spawn = [foo, bar])] + fn uart0(c: uart0::Context) { c.spawn.foo(0).unwrap(); c.spawn.foo(1).unwrap(); c.spawn.foo(2).unwrap(); diff --git a/examples/cfg.rs b/examples/cfg.rs index 03f9dbdcb2..2a43b5c902 100644 --- a/examples/cfg.rs +++ b/examples/cfg.rs @@ -5,38 +5,49 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - +use cortex_m_semihosting::debug; #[cfg(debug_assertions)] use cortex_m_semihosting::hprintln; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - #[cfg(debug_assertions)] // <- `true` when using the `dev` profile - static mut COUNT: u32 = 0; - - #[init] - fn init(_: init::Context) { - // .. + struct Resources { + #[cfg(debug_assertions)] // <- `true` when using the `dev` profile + #[init(0)] + count: u32, } - #[task(priority = 3, resources = [COUNT], spawn = [log])] - fn foo(c: foo::Context) { + #[init(spawn = [foo])] + fn init(cx: init::Context) { + cx.spawn.foo().unwrap(); + cx.spawn.foo().unwrap(); + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); + + loop {} + } + + #[task(capacity = 2, resources = [count], spawn = [log])] + fn foo(_cx: foo::Context) { #[cfg(debug_assertions)] { - *c.resources.COUNT += 1; + *_cx.resources.count += 1; - c.spawn.log(*c.resources.COUNT).ok(); + _cx.spawn.log(*_cx.resources.count).unwrap(); } // this wouldn't compile in `release` mode - // *resources.COUNT += 1; + // *_cx.resources.count += 1; // .. } #[cfg(debug_assertions)] - #[task] + #[task(capacity = 2)] fn log(_: log::Context, n: u32) { hprintln!( "foo has been called {} time{}", diff --git a/examples/generics.rs b/examples/generics.rs index e624da39cf..eafc630882 100644 --- a/examples/generics.rs +++ b/examples/generics.rs @@ -5,15 +5,17 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; -use rtfm::Mutex; +use panic_semihosting as _; +use rtfm::{Exclusive, Mutex}; #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -21,42 +23,43 @@ const APP: () = { rtfm::pend(Interrupt::UART1); } - #[interrupt(resources = [SHARED])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, resources = [shared])] + fn uart0(c: uart0::Context) { static mut STATE: u32 = 0; hprintln!("UART0(STATE = {})", *STATE).unwrap(); - advance(STATE, c.resources.SHARED); + // second argument has type `resources::shared` + advance(STATE, c.resources.shared); rtfm::pend(Interrupt::UART1); debug::exit(debug::EXIT_SUCCESS); } - #[interrupt(priority = 2, resources = [SHARED])] - fn UART1(mut c: UART1::Context) { + #[task(binds = UART1, priority = 2, resources = [shared])] + fn uart1(c: uart1::Context) { static mut STATE: u32 = 0; hprintln!("UART1(STATE = {})", *STATE).unwrap(); - // just to show that `SHARED` can be accessed directly and .. - *c.resources.SHARED += 0; - // .. also through a (no-op) `lock` - c.resources.SHARED.lock(|shared| *shared += 0); + // just to show that `shared` can be accessed directly + *c.resources.shared += 0; - advance(STATE, c.resources.SHARED); + // second argument has type `Exclusive` + advance(STATE, Exclusive(c.resources.shared)); } }; +// the second parameter is generic: it can be any type that implements the `Mutex` trait fn advance(state: &mut u32, mut shared: impl Mutex) { *state += 1; - let (old, new) = shared.lock(|shared| { + let (old, new) = shared.lock(|shared: &mut u32| { let old = *shared; *shared += *state; (old, *shared) }); - hprintln!("SHARED: {} -> {}", old, new).unwrap(); + hprintln!("shared: {} -> {}", old, new).unwrap(); } diff --git a/examples/interrupt.rs b/examples/hardware.rs similarity index 84% rename from examples/interrupt.rs rename to examples/hardware.rs index dd6efa0df5..77f19d90d0 100644 --- a/examples/interrupt.rs +++ b/examples/hardware.rs @@ -1,14 +1,13 @@ -//! examples/interrupt.rs +//! examples/hardware.rs #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -16,7 +15,7 @@ const APP: () = { fn init(_: init::Context) { // Pends the UART0 interrupt but its handler won't run until *after* // `init` returns because interrupts are disabled - rtfm::pend(Interrupt::UART0); + rtfm::pend(Interrupt::UART0); // equivalent to NVIC::pend hprintln!("init").unwrap(); } @@ -34,8 +33,8 @@ const APP: () = { loop {} } - #[interrupt] - fn UART0(_: UART0::Context) { + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { static mut TIMES: u32 = 0; // Safe access to local `static mut` variable diff --git a/examples/idle.rs b/examples/idle.rs index d10cc43ed0..c6f676b0cf 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/init.rs b/examples/init.rs index df687794a8..194e3ec482 100644 --- a/examples/init.rs +++ b/examples/init.rs @@ -5,21 +5,20 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, peripherals = true)] const APP: () = { #[init] - fn init(c: init::Context) { + fn init(cx: init::Context) { static mut X: u32 = 0; // Cortex-M peripherals - let _core: rtfm::Peripherals = c.core; + let _core: cortex_m::Peripherals = cx.core; // Device specific peripherals - let _device: lm3s6965::Peripherals = c.device; + let _device: lm3s6965::Peripherals = cx.device; // Safe access to local `static mut` variable let _x: &'static mut u32 = X; diff --git a/examples/late.rs b/examples/late.rs index 0074fb3233..2eb12d6a83 100644 --- a/examples/late.rs +++ b/examples/late.rs @@ -5,38 +5,37 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use heapless::{ consts::*, + i, spsc::{Consumer, Producer, Queue}, }; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { // Late resources - static mut P: Producer<'static, u32, U4> = (); - static mut C: Consumer<'static, u32, U4> = (); + struct Resources { + p: Producer<'static, u32, U4>, + c: Consumer<'static, u32, U4>, + } #[init] fn init(_: init::Context) -> init::LateResources { - // NOTE: we use `Option` here to work around the lack of - // a stable `const` constructor - static mut Q: Option> = None; + static mut Q: Queue = Queue(i::Queue::new()); - *Q = Some(Queue::new()); - let (p, c) = Q.as_mut().unwrap().split(); + let (p, c) = Q.split(); // Initialization of late resources - init::LateResources { P: p, C: c } + init::LateResources { p, c } } - #[idle(resources = [C])] + #[idle(resources = [c])] fn idle(c: idle::Context) -> ! { loop { - if let Some(byte) = c.resources.C.dequeue() { + if let Some(byte) = c.resources.c.dequeue() { hprintln!("received message: {}", byte).unwrap(); debug::exit(debug::EXIT_SUCCESS); @@ -46,8 +45,8 @@ const APP: () = { } } - #[interrupt(resources = [P])] - fn UART0(c: UART0::Context) { - c.resources.P.enqueue(42).unwrap(); + #[task(binds = UART0, resources = [p])] + fn uart0(c: uart0::Context) { + c.resources.p.enqueue(42).unwrap(); } }; diff --git a/examples/lock.rs b/examples/lock.rs index 814c73640f..f33a60a47d 100644 --- a/examples/lock.rs +++ b/examples/lock.rs @@ -5,14 +5,16 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -20,21 +22,21 @@ const APP: () = { } // when omitted priority is assumed to be `1` - #[interrupt(resources = [SHARED])] - fn GPIOA(mut c: GPIOA::Context) { + #[task(binds = GPIOA, resources = [shared])] + fn gpioa(mut c: gpioa::Context) { hprintln!("A").unwrap(); // the lower priority task requires a critical section to access the data - c.resources.SHARED.lock(|shared| { + c.resources.shared.lock(|shared| { // data can only be modified within this critical section (closure) *shared += 1; // GPIOB will *not* run right now due to the critical section rtfm::pend(Interrupt::GPIOB); - hprintln!("B - SHARED = {}", *shared).unwrap(); + hprintln!("B - shared = {}", *shared).unwrap(); - // GPIOC does not contend for `SHARED` so it's allowed to run now + // GPIOC does not contend for `shared` so it's allowed to run now rtfm::pend(Interrupt::GPIOC); }); @@ -45,16 +47,16 @@ const APP: () = { debug::exit(debug::EXIT_SUCCESS); } - #[interrupt(priority = 2, resources = [SHARED])] - fn GPIOB(mut c: GPIOB::Context) { + #[task(binds = GPIOB, priority = 2, resources = [shared])] + fn gpiob(c: gpiob::Context) { // the higher priority task does *not* need a critical section - *c.resources.SHARED += 1; + *c.resources.shared += 1; - hprintln!("D - SHARED = {}", *c.resources.SHARED).unwrap(); + hprintln!("D - shared = {}", *c.resources.shared).unwrap(); } - #[interrupt(priority = 3)] - fn GPIOC(_: GPIOC::Context) { + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) { hprintln!("C").unwrap(); } }; diff --git a/examples/message.rs b/examples/message.rs index 1fd3b9d4c5..8bfed523d5 100644 --- a/examples/message.rs +++ b/examples/message.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/not-send.rs b/examples/not-send.rs index c1b6bcddf8..d27cc82f13 100644 --- a/examples/not-send.rs +++ b/examples/not-send.rs @@ -5,11 +5,10 @@ #![no_main] #![no_std] -extern crate panic_halt; - use core::marker::PhantomData; use cortex_m_semihosting::debug; +use panic_halt as _; use rtfm::app; pub struct NotSend { @@ -18,7 +17,10 @@ pub struct NotSend { #[app(device = lm3s6965)] const APP: () = { - static mut SHARED: Option = None; + struct Resources { + #[init(None)] + shared: Option, + } #[init(spawn = [baz, quux])] fn init(c: init::Context) { @@ -37,16 +39,16 @@ const APP: () = { // scenario 1 } - #[task(priority = 2, resources = [SHARED])] - fn baz(mut c: baz::Context) { + #[task(priority = 2, resources = [shared])] + fn baz(c: baz::Context) { // scenario 2: resource shared between tasks that run at the same priority - *c.resources.SHARED = Some(NotSend { _0: PhantomData }); + *c.resources.shared = Some(NotSend { _0: PhantomData }); } - #[task(priority = 2, resources = [SHARED])] - fn quux(mut c: quux::Context) { + #[task(priority = 2, resources = [shared])] + fn quux(c: quux::Context) { // scenario 2 - let _not_send = c.resources.SHARED.take().unwrap(); + let _not_send = c.resources.shared.take().unwrap(); debug::exit(debug::EXIT_SUCCESS); } diff --git a/examples/not-sync.rs b/examples/not-sync.rs index bc71406571..7ce2a82f3f 100644 --- a/examples/not-sync.rs +++ b/examples/not-sync.rs @@ -5,11 +5,10 @@ #![no_main] #![no_std] -extern crate panic_halt; - use core::marker::PhantomData; use cortex_m_semihosting::debug; +use panic_halt as _; pub struct NotSync { _0: PhantomData<*const ()>, @@ -17,21 +16,24 @@ pub struct NotSync { #[rtfm::app(device = lm3s6965)] const APP: () = { - static SHARED: NotSync = NotSync { _0: PhantomData }; + struct Resources { + #[init(NotSync { _0: PhantomData })] + shared: NotSync, + } #[init] fn init(_: init::Context) { debug::exit(debug::EXIT_SUCCESS); } - #[task(resources = [SHARED])] + #[task(resources = [&shared])] fn foo(c: foo::Context) { - let _: &NotSync = c.resources.SHARED; + let _: &NotSync = c.resources.shared; } - #[task(resources = [SHARED])] + #[task(resources = [&shared])] fn bar(c: bar::Context) { - let _: &NotSync = c.resources.SHARED; + let _: &NotSync = c.resources.shared; } extern "C" { diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs new file mode 100644 index 0000000000..c7060b14db --- /dev/null +++ b/examples/only-shared-access.rs @@ -0,0 +1,38 @@ +//! examples/static.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::{debug, hprintln}; +use lm3s6965::Interrupt; +use panic_semihosting as _; + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + struct Resources { + key: u32, + } + + #[init] + fn init(_: init::Context) -> init::LateResources { + rtfm::pend(Interrupt::UART0); + rtfm::pend(Interrupt::UART1); + + init::LateResources { key: 0xdeadbeef } + } + + #[task(binds = UART0, resources = [&key])] + fn uart0(cx: uart0::Context) { + let key: &u32 = cx.resources.key; + hprintln!("UART0(key = {:#x})", key).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(binds = UART1, priority = 2, resources = [&key])] + fn uart1(cx: uart1::Context) { + hprintln!("UART1(key = {:#x})", cx.resources.key).unwrap(); + } +}; diff --git a/examples/periodic.rs b/examples/periodic.rs index f784118367..ec110e1181 100644 --- a/examples/periodic.rs +++ b/examples/periodic.rs @@ -5,27 +5,26 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::hprintln; -use rtfm::Instant; +use panic_semihosting as _; +use rtfm::cyccnt::{Instant, U32Ext}; const PERIOD: u32 = 8_000_000; // NOTE: does NOT work on QEMU! -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo])] - fn init(c: init::Context) { - c.schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); + fn init(cx: init::Context) { + cx.schedule.foo(Instant::now() + PERIOD.cycles()).unwrap(); } #[task(schedule = [foo])] - fn foo(c: foo::Context) { + fn foo(cx: foo::Context) { let now = Instant::now(); - hprintln!("foo(scheduled = {:?}, now = {:?})", c.scheduled, now).unwrap(); + hprintln!("foo(scheduled = {:?}, now = {:?})", cx.scheduled, now).unwrap(); - c.schedule.foo(c.scheduled + PERIOD.cycles()).unwrap(); + cx.schedule.foo(cx.scheduled + PERIOD.cycles()).unwrap(); } extern "C" { diff --git a/examples/pool.rs b/examples/pool.rs index 0b594b192d..8c44cb1786 100644 --- a/examples/pool.rs +++ b/examples/pool.rs @@ -5,14 +5,13 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use heapless::{ pool, pool::singleton::{Box, Pool}, }; use lm3s6965::Interrupt; +use panic_semihosting as _; use rtfm::app; // Declare a pool of 128-byte memory blocks @@ -30,8 +29,8 @@ const APP: () = { rtfm::pend(Interrupt::I2C0); } - #[interrupt(priority = 2, spawn = [foo, bar])] - fn I2C0(c: I2C0::Context) { + #[task(binds = I2C0, priority = 2, spawn = [foo, bar])] + fn i2c0(c: i2c0::Context) { // claim a memory block, leave it uninitialized and .. let x = P::alloc().unwrap().freeze(); diff --git a/examples/preempt.rs b/examples/preempt.rs new file mode 100644 index 0000000000..d7a7e64432 --- /dev/null +++ b/examples/preempt.rs @@ -0,0 +1,37 @@ +//! examples/preempt.rs + +#![no_main] +#![no_std] + +use cortex_m_semihosting::{debug, hprintln}; +use lm3s6965::Interrupt; +use panic_semihosting as _; +use rtfm::app; + +#[app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) { + rtfm::pend(Interrupt::GPIOA); + } + + #[task(binds = GPIOA, priority = 1)] + fn gpioa(_: gpioa::Context) { + hprintln!("GPIOA - start").unwrap(); + rtfm::pend(Interrupt::GPIOC); + hprintln!("GPIOA - end").unwrap(); + debug::exit(debug::EXIT_SUCCESS); + } + + #[task(binds = GPIOB, priority = 2)] + fn gpiob(_: gpiob::Context) { + hprintln!(" GPIOB").unwrap(); + } + + #[task(binds = GPIOC, priority = 2)] + fn gpioc(_: gpioc::Context) { + hprintln!(" GPIOC - start").unwrap(); + rtfm::pend(Interrupt::GPIOB); + hprintln!(" GPIOC - end").unwrap(); + } +}; diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs index 4b0d69c793..c38635ff1f 100644 --- a/examples/ramfunc.rs +++ b/examples/ramfunc.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { diff --git a/examples/resource.rs b/examples/resource.rs index 06bdf395ae..8632525e30 100644 --- a/examples/resource.rs +++ b/examples/resource.rs @@ -5,15 +5,17 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; use lm3s6965::Interrupt; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { - // A resource - static mut SHARED: u32 = 0; + struct Resources { + // A resource + #[init(0)] + shared: u32, + } #[init] fn init(_: init::Context) { @@ -21,29 +23,31 @@ const APP: () = { rtfm::pend(Interrupt::UART1); } + // `shared` cannot be accessed from this context #[idle] - fn idle(_: idle::Context) -> ! { + fn idle(_cx: idle::Context) -> ! { debug::exit(debug::EXIT_SUCCESS); - // error: `SHARED` can't be accessed from this context - // SHARED += 1; + // error: no `resources` field in `idle::Context` + // _cx.resources.shared += 1; loop {} } - // `SHARED` can be access from this context - #[interrupt(resources = [SHARED])] - fn UART0(mut c: UART0::Context) { - *c.resources.SHARED += 1; + // `shared` can be accessed from this context + #[task(binds = UART0, resources = [shared])] + fn uart0(cx: uart0::Context) { + let shared: &mut u32 = cx.resources.shared; + *shared += 1; - hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap(); + hprintln!("UART0: shared = {}", shared).unwrap(); } - // `SHARED` can be access from this context - #[interrupt(resources = [SHARED])] - fn UART1(mut c: UART1::Context) { - *c.resources.SHARED += 1; + // `shared` can be accessed from this context + #[task(binds = UART1, resources = [shared])] + fn uart1(cx: uart1::Context) { + *cx.resources.shared += 1; - hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap(); + hprintln!("UART1: shared = {}", cx.resources.shared).unwrap(); } }; diff --git a/examples/schedule.rs b/examples/schedule.rs index eaafb4c947..27d3bd1f59 100644 --- a/examples/schedule.rs +++ b/examples/schedule.rs @@ -5,25 +5,24 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::hprintln; -use rtfm::Instant; +use panic_halt as _; +use rtfm::cyccnt::{Instant, U32Ext as _}; // NOTE: does NOT work on QEMU! -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo, bar])] - fn init(c: init::Context) { + fn init(cx: init::Context) { let now = Instant::now(); hprintln!("init @ {:?}", now).unwrap(); // Schedule `foo` to run 8e6 cycles (clock cycles) in the future - c.schedule.foo(now + 8_000_000.cycles()).unwrap(); + cx.schedule.foo(now + 8_000_000.cycles()).unwrap(); // Schedule `bar` to run 4e6 cycles in the future - c.schedule.bar(now + 4_000_000.cycles()).unwrap(); + cx.schedule.bar(now + 4_000_000.cycles()).unwrap(); } #[task] diff --git a/examples/shared-with-init.rs b/examples/shared-with-init.rs index 0fb9191cbd..14fa54b799 100644 --- a/examples/shared-with-init.rs +++ b/examples/shared-with-init.rs @@ -5,30 +5,32 @@ #![no_main] #![no_std] -extern crate panic_halt; - use cortex_m_semihosting::debug; use lm3s6965::Interrupt; +use panic_halt as _; use rtfm::app; pub struct MustBeSend; #[app(device = lm3s6965)] const APP: () = { - static mut SHARED: Option = None; + struct Resources { + #[init(None)] + shared: Option, + } - #[init(resources = [SHARED])] + #[init(resources = [shared])] fn init(c: init::Context) { // this `message` will be sent to task `UART0` let message = MustBeSend; - *c.resources.SHARED = Some(message); + *c.resources.shared = Some(message); rtfm::pend(Interrupt::UART0); } - #[interrupt(resources = [SHARED])] - fn UART0(c: UART0::Context) { - if let Some(message) = c.resources.SHARED.take() { + #[task(binds = UART0, resources = [shared])] + fn uart0(c: uart0::Context) { + if let Some(message) = c.resources.shared.take() { // `message` has been received drop(message); diff --git a/examples/smallest.rs b/examples/smallest.rs index c153716805..7b26a852c0 100644 --- a/examples/smallest.rs +++ b/examples/smallest.rs @@ -1,17 +1,10 @@ //! examples/smallest.rs -#![deny(unsafe_code)] -#![deny(warnings)] #![no_main] #![no_std] -// panic-handler crate -extern crate panic_semihosting; - +use panic_semihosting as _; // panic handler use rtfm::app; #[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} -}; +const APP: () = {}; diff --git a/examples/static.rs b/examples/static.rs deleted file mode 100644 index 2e3b5b4197..0000000000 --- a/examples/static.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! examples/static.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate panic_semihosting; - -use cortex_m_semihosting::{debug, hprintln}; -use lm3s6965::Interrupt; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - static KEY: u32 = (); - - #[init] - fn init(_: init::Context) -> init::LateResources { - rtfm::pend(Interrupt::UART0); - rtfm::pend(Interrupt::UART1); - - init::LateResources { KEY: 0xdeadbeef } - } - - #[interrupt(resources = [KEY])] - fn UART0(c: UART0::Context) { - hprintln!("UART0(KEY = {:#x})", c.resources.KEY).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); - } - - #[interrupt(priority = 2, resources = [KEY])] - fn UART1(c: UART1::Context) { - hprintln!("UART1(KEY = {:#x})", c.resources.KEY).unwrap(); - } -}; diff --git a/tests/cpass/binds.rs b/examples/t-binds.rs similarity index 68% rename from tests/cpass/binds.rs rename to examples/t-binds.rs index 897e083abc..dda8e20178 100644 --- a/tests/cpass/binds.rs +++ b/examples/t-binds.rs @@ -1,24 +1,25 @@ -//! Check that `binds` works as advertised +//! [compile-pass] Check that `binds` works as advertised + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(_: init::Context) {} - #[exception(binds = SVCall)] + // Cortex-M exception + #[task(binds = SVCall)] fn foo(c: foo::Context) { foo_trampoline(c) } - #[interrupt(binds = UART0)] + // LM3S6965 interrupt + #[task(binds = UART0)] fn bar(c: bar::Context) { bar_trampoline(c) } diff --git a/tests/cpass/cfg.rs b/examples/t-cfg.rs similarity index 61% rename from tests/cpass/cfg.rs rename to examples/t-cfg.rs index a0b6a870d0..e61ec795fe 100644 --- a/tests/cpass/cfg.rs +++ b/examples/t-cfg.rs @@ -1,18 +1,17 @@ -//! Compile-pass test that checks that `#[cfg]` attributes are respected +//! [compile-pass] check that `#[cfg]` attributes are respected -#![deny(unsafe_code)] -#![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { - #[cfg(never)] - static mut FOO: u32 = 0; + struct Resources { + #[cfg(never)] + #[init(0)] + foo: u32, + } #[init] fn init(_: init::Context) { @@ -28,13 +27,13 @@ const APP: () = { loop {} } - #[task(resources = [FOO], schedule = [quux], spawn = [quux])] + #[task(resources = [foo], schedule = [quux], spawn = [quux])] fn foo(_: foo::Context) { #[cfg(never)] static mut BAR: u32 = 0; } - #[task(priority = 3, resources = [FOO], schedule = [quux], spawn = [quux])] + #[task(priority = 3, resources = [foo], schedule = [quux], spawn = [quux])] fn bar(_: bar::Context) { #[cfg(never)] static mut BAR: u32 = 0; diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs new file mode 100644 index 0000000000..4fd3504ef9 --- /dev/null +++ b/examples/t-late-not-send.rs @@ -0,0 +1,36 @@ +//! [compile-pass] late resources don't need to be `Send` if they are owned by `idle` + +#![no_main] +#![no_std] + +use core::marker::PhantomData; + +use panic_halt as _; + +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + struct Resources { + x: NotSend, + #[init(None)] + y: Option, + } + + #[init(resources = [y])] + fn init(c: init::Context) -> init::LateResources { + // equivalent to late resource initialization + *c.resources.y = Some(NotSend { _0: PhantomData }); + + init::LateResources { + x: NotSend { _0: PhantomData }, + } + } + + #[idle(resources = [x, y])] + fn idle(_: idle::Context) -> ! { + loop {} + } +}; diff --git a/examples/t-resource.rs b/examples/t-resource.rs new file mode 100644 index 0000000000..303340edb4 --- /dev/null +++ b/examples/t-resource.rs @@ -0,0 +1,87 @@ +//! [compile-pass] Check code generation of resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + struct Resources { + #[init(0)] + o1: u32, // init + #[init(0)] + o2: u32, // idle + #[init(0)] + o3: u32, // EXTI0 + #[init(0)] + o4: u32, // idle + #[init(0)] + o5: u32, // EXTI1 + #[init(0)] + o6: u32, // init + #[init(0)] + s1: u32, // idle & uart0 + #[init(0)] + s2: u32, // uart0 & uart1 + #[init(0)] + s3: u32, // idle & uart0 + } + + #[init(resources = [o1, o4, o5, o6, s3])] + fn init(c: init::Context) { + // owned by `init` == `&'static mut` + let _: &'static mut u32 = c.resources.o1; + + // owned by `init` == `&'static` if read-only + let _: &'static u32 = c.resources.o6; + + // `init` has exclusive access to all resources + let _: &mut u32 = c.resources.o4; + let _: &mut u32 = c.resources.o5; + let _: &mut u32 = c.resources.s3; + } + + #[idle(resources = [o2, &o4, s1, &s3])] + fn idle(mut c: idle::Context) -> ! { + // owned by `idle` == `&'static mut` + let _: &'static mut u32 = c.resources.o2; + + // owned by `idle` == `&'static` if read-only + let _: &'static u32 = c.resources.o4; + + // shared with `idle` == `Mutex` + c.resources.s1.lock(|_| {}); + + // `&` if read-only + let _: &u32 = c.resources.s3; + + loop {} + } + + #[task(binds = UART0, resources = [o3, s1, s2, &s3])] + fn uart0(c: uart0::Context) { + // owned by interrupt == `&mut` + let _: &mut u32 = c.resources.o3; + + // no `Mutex` proxy when access from highest priority task + let _: &mut u32 = c.resources.s1; + + // no `Mutex` proxy when co-owned by cooperative (same priority) tasks + let _: &mut u32 = c.resources.s2; + + // `&` if read-only + let _: &u32 = c.resources.s3; + } + + #[task(binds = UART1, resources = [s2, &o5])] + fn uart1(c: uart1::Context) { + // owned by interrupt == `&` if read-only + let _: &u32 = c.resources.o5; + + // no `Mutex` proxy when co-owned by cooperative (same priority) tasks + let _: &mut u32 = c.resources.s2; + } +}; diff --git a/tests/cpass/schedule.rs b/examples/t-schedule.rs similarity index 82% rename from tests/cpass/schedule.rs rename to examples/t-schedule.rs index 346f9124a6..e6035b3f50 100644 --- a/tests/cpass/schedule.rs +++ b/examples/t-schedule.rs @@ -1,15 +1,14 @@ +//! [compile-pass] Check `schedule` code generation + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; +use rtfm::cyccnt::{Instant, U32Ext as _}; -use rtfm::Instant; - -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { #[init(schedule = [foo, bar, baz])] fn init(c: init::Context) { @@ -27,15 +26,15 @@ const APP: () = { loop {} } - #[exception(schedule = [foo, bar, baz])] - fn SVCall(c: SVCall::Context) { + #[task(binds = SVCall, schedule = [foo, bar, baz])] + fn svcall(c: svcall::Context) { let _: Result<(), ()> = c.schedule.foo(c.start + 70.cycles()); let _: Result<(), u32> = c.schedule.bar(c.start + 80.cycles(), 0); let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 90.cycles(), 0, 1); } - #[interrupt(schedule = [foo, bar, baz])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, schedule = [foo, bar, baz])] + fn uart0(c: uart0::Context) { let _: Result<(), ()> = c.schedule.foo(c.start + 100.cycles()); let _: Result<(), u32> = c.schedule.bar(c.start + 110.cycles(), 0); let _: Result<(), (u32, u32)> = c.schedule.baz(c.start + 120.cycles(), 0, 1); diff --git a/tests/cpass/spawn.rs b/examples/t-spawn.rs similarity index 83% rename from tests/cpass/spawn.rs rename to examples/t-spawn.rs index 0a27c4f6bc..682b9b89b0 100644 --- a/tests/cpass/spawn.rs +++ b/examples/t-spawn.rs @@ -1,12 +1,11 @@ -//! Check code generation of `spawn` +//! [compile-pass] Check code generation of `spawn` + #![deny(unsafe_code)] #![deny(warnings)] #![no_main] #![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; +use panic_halt as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -26,15 +25,15 @@ const APP: () = { loop {} } - #[exception(spawn = [foo, bar, baz])] - fn SVCall(c: SVCall::Context) { + #[task(binds = SVCall, spawn = [foo, bar, baz])] + fn svcall(c: svcall::Context) { let _: Result<(), ()> = c.spawn.foo(); let _: Result<(), u32> = c.spawn.bar(0); let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); } - #[interrupt(spawn = [foo, bar, baz])] - fn UART0(c: UART0::Context) { + #[task(binds = UART0, spawn = [foo, bar, baz])] + fn uart0(c: uart0::Context) { let _: Result<(), ()> = c.spawn.foo(); let _: Result<(), u32> = c.spawn.bar(0); let _: Result<(), (u32, u32)> = c.spawn.baz(0, 1); diff --git a/examples/task.rs b/examples/task.rs index 5bb32acb73..9e563d71ff 100644 --- a/examples/task.rs +++ b/examples/task.rs @@ -5,9 +5,8 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; #[rtfm::app(device = lm3s6965)] const APP: () = { @@ -18,16 +17,20 @@ const APP: () = { #[task(spawn = [bar, baz])] fn foo(c: foo::Context) { - hprintln!("foo").unwrap(); + hprintln!("foo - start").unwrap(); // spawns `bar` onto the task scheduler // `foo` and `bar` have the same priority so `bar` will not run until // after `foo` terminates c.spawn.bar().unwrap(); + hprintln!("foo - middle").unwrap(); + // spawns `baz` onto the task scheduler // `baz` has higher priority than `foo` so it immediately preempts `foo` c.spawn.baz().unwrap(); + + hprintln!("foo - end").unwrap(); } #[task] diff --git a/examples/types.rs b/examples/types.rs index c3dd89ca4c..fc391d0788 100644 --- a/examples/types.rs +++ b/examples/types.rs @@ -5,48 +5,51 @@ #![no_main] #![no_std] -extern crate panic_semihosting; - use cortex_m_semihosting::debug; -use rtfm::{Exclusive, Instant}; +use panic_semihosting as _; +use rtfm::cyccnt; -#[rtfm::app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)] const APP: () = { - static mut SHARED: u32 = 0; + struct Resources { + #[init(0)] + shared: u32, + } #[init(schedule = [foo], spawn = [foo])] - fn init(c: init::Context) { - let _: Instant = c.start; - let _: rtfm::Peripherals = c.core; - let _: lm3s6965::Peripherals = c.device; - let _: init::Schedule = c.schedule; - let _: init::Spawn = c.spawn; + fn init(cx: init::Context) { + let _: cyccnt::Instant = cx.start; + let _: rtfm::Peripherals = cx.core; + let _: lm3s6965::Peripherals = cx.device; + let _: init::Schedule = cx.schedule; + let _: init::Spawn = cx.spawn; debug::exit(debug::EXIT_SUCCESS); } - #[exception(schedule = [foo], spawn = [foo])] - fn SVCall(c: SVCall::Context) { - let _: Instant = c.start; - let _: SVCall::Schedule = c.schedule; - let _: SVCall::Spawn = c.spawn; + #[idle(schedule = [foo], spawn = [foo])] + fn idle(cx: idle::Context) -> ! { + let _: idle::Schedule = cx.schedule; + let _: idle::Spawn = cx.spawn; + + loop {} } - #[interrupt(resources = [SHARED], schedule = [foo], spawn = [foo])] - fn UART0(c: UART0::Context) { - let _: Instant = c.start; - let _: resources::SHARED = c.resources.SHARED; - let _: UART0::Schedule = c.schedule; - let _: UART0::Spawn = c.spawn; + #[task(binds = UART0, resources = [shared], schedule = [foo], spawn = [foo])] + fn uart0(cx: uart0::Context) { + let _: cyccnt::Instant = cx.start; + let _: resources::shared = cx.resources.shared; + let _: uart0::Schedule = cx.schedule; + let _: uart0::Spawn = cx.spawn; } - #[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])] - fn foo(c: foo::Context) { - let _: Instant = c.scheduled; - let _: Exclusive = c.resources.SHARED; - let _: foo::Resources = c.resources; - let _: foo::Schedule = c.schedule; - let _: foo::Spawn = c.spawn; + #[task(priority = 2, resources = [shared], schedule = [foo], spawn = [foo])] + fn foo(cx: foo::Context) { + let _: cyccnt::Instant = cx.scheduled; + let _: &mut u32 = cx.resources.shared; + let _: foo::Resources = cx.resources; + let _: foo::Schedule = cx.schedule; + let _: foo::Spawn = cx.spawn; } extern "C" { diff --git a/heterogeneous/Cargo.toml b/heterogeneous/Cargo.toml new file mode 100644 index 0000000000..fd05d07e08 --- /dev/null +++ b/heterogeneous/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +name = "heterogeneous" +# this crate is only used for testing +publish = false +version = "0.0.0-alpha.0" + +[dependencies] +bare-metal = "0.2.4" + +[dependencies.cortex-m-rtfm] +path = ".." +features = ["heterogeneous"] + +[dev-dependencies] +panic-halt = "0.2.0" +microamp = "0.1.0-alpha.1" diff --git a/heterogeneous/README.md b/heterogeneous/README.md new file mode 100644 index 0000000000..8e49ff8bea --- /dev/null +++ b/heterogeneous/README.md @@ -0,0 +1 @@ +This directory contains *heterogeneous* multi-core compile pass tests. diff --git a/heterogeneous/examples/smallest.rs b/heterogeneous/examples/smallest.rs new file mode 100644 index 0000000000..9b6bb82d02 --- /dev/null +++ b/heterogeneous/examples/smallest.rs @@ -0,0 +1,7 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = {}; diff --git a/heterogeneous/examples/x-init-2.rs b/heterogeneous/examples/x-init-2.rs new file mode 100644 index 0000000000..033753c209 --- /dev/null +++ b/heterogeneous/examples/x-init-2.rs @@ -0,0 +1,39 @@ +//! [compile-pass] Cross initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + struct Resources { + // owned by core #1 but initialized by core #0 + x: u32, + + // owned by core #0 but initialized by core #1 + y: u32, + } + + #[init(core = 0, late = [x])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { x: 0 } + } + + #[idle(core = 0, resources = [y])] + fn b(_: b::Context) -> ! { + loop {} + } + + #[init(core = 1)] + fn c(_: c::Context) -> c::LateResources { + c::LateResources { y: 0 } + } + + #[idle(core = 1, resources = [x])] + fn d(_: d::Context) -> ! { + loop {} + } +}; diff --git a/heterogeneous/examples/x-init.rs b/heterogeneous/examples/x-init.rs new file mode 100644 index 0000000000..4183713491 --- /dev/null +++ b/heterogeneous/examples/x-init.rs @@ -0,0 +1,26 @@ +//! [compile-pass] Split initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + struct Resources { + x: u32, + y: u32, + } + + #[init(core = 0, late = [x])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { x: 0 } + } + + #[init(core = 1)] + fn b(_: b::Context) -> b::LateResources { + b::LateResources { y: 0 } + } +}; diff --git a/heterogeneous/examples/x-schedule.rs b/heterogeneous/examples/x-schedule.rs new file mode 100644 index 0000000000..cbfc01f98d --- /dev/null +++ b/heterogeneous/examples/x-schedule.rs @@ -0,0 +1,36 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous, monotonic = heterogeneous::MT)] +const APP: () = { + #[init(core = 0, spawn = [ping])] + fn init(c: init::Context) { + c.spawn.ping().ok(); + } + + #[task(core = 0, schedule = [ping])] + fn pong(c: pong::Context) { + c.schedule.ping(c.scheduled + 1_000_000).ok(); + } + + #[task(core = 1, schedule = [pong])] + fn ping(c: ping::Context) { + c.schedule.pong(c.scheduled + 1_000_000).ok(); + } + + extern "C" { + #[core = 0] + fn I0(); + + #[core = 0] + fn I1(); + + #[core = 1] + fn I0(); + + #[core = 1] + fn I1(); + } +}; diff --git a/heterogeneous/examples/x-spawn.rs b/heterogeneous/examples/x-spawn.rs new file mode 100644 index 0000000000..3fc64f6fc6 --- /dev/null +++ b/heterogeneous/examples/x-spawn.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = heterogeneous)] +const APP: () = { + #[init(core = 0, spawn = [foo])] + fn init(c: init::Context) { + c.spawn.foo().ok(); + } + + #[task(core = 1)] + fn foo(_: foo::Context) {} + + extern "C" { + #[core = 1] + fn I0(); + } +}; diff --git a/heterogeneous/src/lib.rs b/heterogeneous/src/lib.rs new file mode 100644 index 0000000000..95ff184d04 --- /dev/null +++ b/heterogeneous/src/lib.rs @@ -0,0 +1,99 @@ +//! Fake multi-core PAC + +#![no_std] + +use core::{ + cmp::Ordering, + ops::{Add, Sub}, +}; + +use bare_metal::Nr; +use rtfm::{Fraction, Monotonic, MultiCore}; + +// both cores have the exact same interrupts +pub use Interrupt_0 as Interrupt_1; + +// Fake priority bits +pub const NVIC_PRIO_BITS: u8 = 3; + +pub fn xpend(_core: u8, _interrupt: impl Nr) {} + +/// Fake monotonic timer +pub struct MT; + +impl Monotonic for MT { + type Instant = Instant; + + fn ratio() -> Fraction { + Fraction { + numerator: 1, + denominator: 1, + } + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } + } + + fn zero() -> Instant { + Instant(0) + } +} + +impl MultiCore for MT {} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant(i32); + +impl Add for Instant { + type Output = Instant; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs as i32)) + } +} + +impl Sub for Instant { + type Output = u32; + + fn sub(self, rhs: Self) -> u32 { + self.0.checked_sub(rhs.0).unwrap() as u32 + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +// Fake interrupts +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Interrupt_0 { + I0 = 0, + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + I5 = 5, + I6 = 6, + I7 = 7, +} + +unsafe impl Nr for Interrupt_0 { + fn nr(&self) -> u8 { + *self as u8 + } +} diff --git a/homogeneous/Cargo.toml b/homogeneous/Cargo.toml new file mode 100644 index 0000000000..210ee2e8c8 --- /dev/null +++ b/homogeneous/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["Jorge Aparicio "] +edition = "2018" +name = "homogeneous" +# this crate is only used for testing +publish = false +version = "0.0.0-alpha.0" + +[dependencies] +bare-metal = "0.2.4" + +[dependencies.cortex-m-rtfm] +path = ".." +features = ["homogeneous"] + +[dev-dependencies] +panic-halt = "0.2.0" diff --git a/homogeneous/README.md b/homogeneous/README.md new file mode 100644 index 0000000000..17e9c6e11a --- /dev/null +++ b/homogeneous/README.md @@ -0,0 +1 @@ +This directory contains *homogeneous* multi-core compile pass tests. diff --git a/homogeneous/examples/smallest.rs b/homogeneous/examples/smallest.rs new file mode 100644 index 0000000000..b99476c750 --- /dev/null +++ b/homogeneous/examples/smallest.rs @@ -0,0 +1,7 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = {}; diff --git a/homogeneous/examples/x-init-2.rs b/homogeneous/examples/x-init-2.rs new file mode 100644 index 0000000000..de35cf6f5f --- /dev/null +++ b/homogeneous/examples/x-init-2.rs @@ -0,0 +1,39 @@ +//! [compile-pass] Cross initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + struct Resources { + // owned by core #1 but initialized by core #0 + x: u32, + + // owned by core #0 but initialized by core #1 + y: u32, + } + + #[init(core = 0, late = [x])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { x: 0 } + } + + #[idle(core = 0, resources = [y])] + fn b(_: b::Context) -> ! { + loop {} + } + + #[init(core = 1)] + fn c(_: c::Context) -> c::LateResources { + c::LateResources { y: 0 } + } + + #[idle(core = 1, resources = [x])] + fn d(_: d::Context) -> ! { + loop {} + } +}; diff --git a/homogeneous/examples/x-init.rs b/homogeneous/examples/x-init.rs new file mode 100644 index 0000000000..c359901c04 --- /dev/null +++ b/homogeneous/examples/x-init.rs @@ -0,0 +1,26 @@ +//! [compile-pass] Split initialization of late resources + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + struct Resources { + x: u32, + y: u32, + } + + #[init(core = 0, late = [x])] + fn a(_: a::Context) -> a::LateResources { + a::LateResources { x: 0 } + } + + #[init(core = 1)] + fn b(_: b::Context) -> b::LateResources { + b::LateResources { y: 0 } + } +}; diff --git a/homogeneous/examples/x-schedule.rs b/homogeneous/examples/x-schedule.rs new file mode 100644 index 0000000000..12b5cb80a6 --- /dev/null +++ b/homogeneous/examples/x-schedule.rs @@ -0,0 +1,36 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous, monotonic = homogeneous::MT)] +const APP: () = { + #[init(core = 0, spawn = [ping])] + fn init(c: init::Context) { + c.spawn.ping().ok(); + } + + #[task(core = 0, schedule = [ping])] + fn pong(c: pong::Context) { + c.schedule.ping(c.scheduled + 1_000_000).ok(); + } + + #[task(core = 1, schedule = [pong])] + fn ping(c: ping::Context) { + c.schedule.pong(c.scheduled + 1_000_000).ok(); + } + + extern "C" { + #[core = 0] + fn I0(); + + #[core = 0] + fn I1(); + + #[core = 1] + fn I0(); + + #[core = 1] + fn I1(); + } +}; diff --git a/homogeneous/examples/x-spawn.rs b/homogeneous/examples/x-spawn.rs new file mode 100644 index 0000000000..a76ac61c39 --- /dev/null +++ b/homogeneous/examples/x-spawn.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +use panic_halt as _; + +#[rtfm::app(cores = 2, device = homogeneous)] +const APP: () = { + #[init(core = 0, spawn = [foo])] + fn init(c: init::Context) { + c.spawn.foo().ok(); + } + + #[task(core = 1)] + fn foo(_: foo::Context) {} + + extern "C" { + #[core = 1] + fn I0(); + } +}; diff --git a/homogeneous/src/lib.rs b/homogeneous/src/lib.rs new file mode 100644 index 0000000000..95ff184d04 --- /dev/null +++ b/homogeneous/src/lib.rs @@ -0,0 +1,99 @@ +//! Fake multi-core PAC + +#![no_std] + +use core::{ + cmp::Ordering, + ops::{Add, Sub}, +}; + +use bare_metal::Nr; +use rtfm::{Fraction, Monotonic, MultiCore}; + +// both cores have the exact same interrupts +pub use Interrupt_0 as Interrupt_1; + +// Fake priority bits +pub const NVIC_PRIO_BITS: u8 = 3; + +pub fn xpend(_core: u8, _interrupt: impl Nr) {} + +/// Fake monotonic timer +pub struct MT; + +impl Monotonic for MT { + type Instant = Instant; + + fn ratio() -> Fraction { + Fraction { + numerator: 1, + denominator: 1, + } + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) } + } + + fn zero() -> Instant { + Instant(0) + } +} + +impl MultiCore for MT {} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant(i32); + +impl Add for Instant { + type Output = Instant; + + fn add(self, rhs: u32) -> Self { + Instant(self.0.wrapping_add(rhs as i32)) + } +} + +impl Sub for Instant { + type Output = u32; + + fn sub(self, rhs: Self) -> u32 { + self.0.checked_sub(rhs.0).unwrap() as u32 + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.0.wrapping_sub(rhs.0).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +// Fake interrupts +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Interrupt_0 { + I0 = 0, + I1 = 1, + I2 = 2, + I3 = 3, + I4 = 4, + I5 = 5, + I6 = 6, + I7 = 7, +} + +unsafe impl Nr for Interrupt_0 { + fn nr(&self) -> u8 { + *self as u8 + } +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index df20f8c0e5..ed7626f84e 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -15,12 +15,13 @@ version = "0.5.0-alpha.1" proc-macro = true [dependencies] -quote = "0.6.10" -proc-macro2 = "0.4.24" +proc-macro2 = "1" +quote = "1" +syn = "1" -[dependencies.syn] -features = ["extra-traits", "full"] -version = "0.15.23" +[dependencies.rtfm-syntax] +git = "https://github.com/japaric/rtfm-syntax" [features] -timer-queue = [] \ No newline at end of file +heterogeneous = [] +homogeneous = [] diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs index a47be77972..e3ed7781db 100644 --- a/macros/src/analyze.rs +++ b/macros/src/analyze.rs @@ -1,265 +1,59 @@ -use std::{ - cmp, - collections::{BTreeMap, HashMap, HashSet}, +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; + +use rtfm_syntax::{ + analyze::{self, Priority}, + ast::App, + Core, P, }; +use syn::Ident; -use syn::{Attribute, Ident, Type}; - -use crate::syntax::{App, Idents}; - -pub type Ownerships = HashMap; - +/// Extend the upstream `Analysis` struct with our field pub struct Analysis { - /// Capacities of free queues - pub capacities: Capacities, - pub dispatchers: Dispatchers, - // Ceilings of free queues - pub free_queues: HashMap, - 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, + parent: P, + pub interrupts: BTreeMap>, } -#[derive(Clone, Copy, PartialEq)] -pub enum Ownership { - // NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority) - Owned { priority: u8 }, - CoOwned { priority: u8 }, - Shared { ceiling: u8 }, -} +impl ops::Deref for Analysis { + type Target = analyze::Analysis; -impl Ownership { - pub fn needs_lock(&self, priority: u8) -> bool { - match *self { - Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, - Ownership::Shared { ceiling } => { - debug_assert!(ceiling >= priority); - - priority < ceiling - } - } - } - - pub fn is_owned(&self) -> bool { - match *self { - Ownership::Owned { .. } => true, - _ => false, - } + fn deref(&self) -> &Self::Target { + &self.parent } } -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, -} - -/// Priority -> Dispatcher -pub type Dispatchers = BTreeMap; - -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::CoOwned { priority: ceiling } - | Ownership::Shared { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Shared { - ceiling: cmp::max(ceiling, priority), - }; - - let res = &app.resources[res]; - if res.mutability.is_none() { - assert_sync.insert(res.ty.clone()); - } +// Assign an `extern` interrupt to each priority level +pub fn app(analysis: P, app: &App) -> P { + let mut interrupts = BTreeMap::new(); + for core in 0..app.args.cores { + let priorities = app + .software_tasks + .values() + .filter_map(|task| { + if task.args.core == core { + Some(task.args.priority) + } else { + None } - Ownership::Owned { priority: ceiling } if ceiling == priority => { - *ownership = Ownership::CoOwned { priority }; - } - _ => {} - } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); - 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; + if !priorities.is_empty() { + interrupts.insert( + core, + priorities + .iter() + .cloned() + .rev() + .zip(app.extern_interrupts[&core].keys().cloned()) + .collect(), + ); } } - // 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) -- first pass - // Also compute more Send-ness requirements - let mut free_queues = HashMap::new(); - let mut ready_queues = HashMap::new(); - for (priority, task) in app.spawn_calls() { - if let Some(priority) = priority { - // Users of `spawn` contend for the spawnee FREE_QUEUE - let c = free_queues.entry(task.clone()).or_default(); - *c = cmp::max(*c, priority); - - // Users of `spawn` contend for the spawnee's dispatcher READY_QUEUE - let c = ready_queues - .entry(app.tasks[task].args.priority) - .or_default(); - *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 ready queues (producer end point) -- second pass - // 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() { - // the system timer handler contends for the spawnee's dispatcher READY_QUEUE - let c = ready_queues - .entry(app.tasks[task].args.priority) - .or_default(); - *c = cmp::max(*c, tq_priority); - - if let Some(priority) = priority { - // Users of `schedule` contend for the spawnee task FREE_QUEUE - let c = free_queues.entry(task.clone()).or_default(); - *c = cmp::max(*c, priority); - - // Users of `schedule` contend for the timer queue - 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, + P::new(Analysis { + parent: analysis, + interrupts, + }) } diff --git a/macros/src/check.rs b/macros/src/check.rs index 8ad13f3c6c..0136370ce1 100644 --- a/macros/src/check.rs +++ b/macros/src/check.rs @@ -1,122 +1,225 @@ -use std::{collections::HashSet, iter}; +use std::collections::HashSet; use proc_macro2::Span; -use syn::parse; +use rtfm_syntax::{ + analyze::Analysis, + ast::{App, CustomArg}, +}; +use syn::{parse, Path}; -use crate::syntax::App; +pub struct Extra<'a> { + pub device: &'a Path, + pub monotonic: Option<&'a Path>, + pub peripherals: Option, +} -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)) +impl<'a> Extra<'a> { + pub fn monotonic(&self) -> &'a Path { + self.monotonic.expect("UNREACHABLE") + } +} + +pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result> { + if cfg!(feature = "homogeneous") { + // this RTFM mode uses the same namespace for all cores so we need to check that the + // identifiers used for each core `#[init]` and `#[idle]` functions don't collide + let mut seen = HashSet::new(); + + for name in app + .inits + .values() + .map(|init| &init.name) + .chain(app.idles.values().map(|idle| &idle.name)) + { + if seen.contains(name) { + return Err(parse::Error::new( + name.span(), + "this identifier is already being used by another core", + )); + } else { + seen.insert(name); + } + } + } + + // check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); + match &*name_s { + "SysTick" => { + if analysis.timer_queues.get(&task.args.core).is_some() { + return Err(parse::Error::new( + name.span(), + "this exception can't be used because it's being used by the runtime", + )); + } else { + // OK + } + } + + "NonMaskableInt" | "HardFault" => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + + _ => {} + } + } + + // check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app + .extern_interrupts + .iter() + .flat_map(|(_, interrupts)| interrupts.keys()) { - if !app.resources.contains_key(res) { - return Err(parse::Error::new( - res.span(), - "this resource has NOT been declared", - )); + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} } } - // 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`", - )); + // check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + for core in 0..app.args.cores { + let mut first = None; + let priorities = app + .software_tasks + .iter() + .filter_map(|(name, task)| { + if task.args.core == core { + first = Some(name); + Some(task.args.priority) + } else { + None + } + }) + .chain(analysis.timer_queues.get(&core).map(|tq| tq.priority)) + .collect::>(); + + let need = priorities.len(); + let given = app + .extern_interrupts + .get(&core) + .map(|ei| ei.len()) + .unwrap_or(0); + if need > given { + let s = if app.args.cores == 1 { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks (need: {}; given: {})", + need, given + ) + } else { + format!( + "not enough `extern` interrupts to dispatch \ + all software tasks on this core (need: {}; given: {})", + need, given + ) + }; + + return Err(parse::Error::new(first.unwrap().span(), &s)); } } - if app.resources.iter().any(|(_, res)| res.expr.is_none()) { - // Check that `init` returns `LateResources` if there's any declared late resource - if !app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "late resources have been specified so `init` must return `init::LateResources`", - )); - } - } else if app.init.returns_late_resources { - // If there are no late resources the signature should be `fn(init::Context)` - if app.init.returns_late_resources { - return Err(parse::Error::new( - app.init.span, - "`init` signature must be `fn(init::Context)` if there are no late resources", - )); + let mut device = None; + let mut monotonic = None; + let mut peripherals = None; + + for (k, v) in &app.args.custom { + let ks = k.to_string(); + + match &*ks { + "device" => match v { + CustomArg::Path(p) => device = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "monotonic" => match v { + CustomArg::Path(p) => monotonic = Some(p), + + _ => { + return Err(parse::Error::new( + k.span(), + "unexpected argument value; this should be a path", + )); + } + }, + + "peripherals" => match v { + CustomArg::Bool(x) if app.args.cores == 1 => { + peripherals = if *x { Some(0) } else { None } + } + + CustomArg::UInt(s) if app.args.cores != 1 => { + let x = s.parse::().ok(); + peripherals = if x.is_some() && x.unwrap() < app.args.cores { + Some(x.unwrap()) + } else { + return Err(parse::Error::new( + k.span(), + &format!( + "unexpected argument value; \ + this should be an integer in the range 0..={}", + app.args.cores + ), + )); + } + } + + _ => { + return Err(parse::Error::new( + k.span(), + if app.args.cores == 1 { + "unexpected argument value; this should be a boolean" + } else { + "unexpected argument value; this should be an integer" + }, + )); + } + }, + + _ => { + return Err(parse::Error::new(k.span(), "unexpected argument")); + } } } - // 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)) - }) - .unwrap_or_else(|| Box::new(iter::empty())) - .chain(&app.init.args.schedule) - .chain(&app.init.args.spawn) - .chain( - app.exceptions - .values() - .flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)), - ) - .chain( - app.interrupts - .values() - .flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)), - ) - .chain( - app.tasks - .values() - .flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)), - ) - { - if !app.tasks.contains_key(task) { - return Err(parse::Error::new( - task.span(), - "this task has NOT been declared", - )); - } - } - - // Check that there are enough free interrupts to dispatch all tasks - let ndispatchers = app - .tasks - .values() - .map(|t| t.args.priority) - .collect::>() - .len(); - if ndispatchers > app.free_interrupts.len() { + if !analysis.timer_queues.is_empty() && monotonic.is_none() { 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" }, - ), + "a `monotonic` timer must be specified to use the `schedule` API", )); } - // Check that free interrupts are not being used - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - - if app.free_interrupts.contains_key(name) { - return Err(parse::Error::new( - name.span(), - "free interrupts (`extern { .. }`) can't be used as interrupt handlers", - )); - } + if let Some(device) = device { + Ok(Extra { + device, + monotonic, + peripherals, + }) + } else { + Err(parse::Error::new( + Span::call_site(), + "a `device` argument must be specified in `#[rtfm::app]`", + )) } - - Ok(()) } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 88f11739e4..0213848157 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -1,137 +1,79 @@ -use proc_macro::TokenStream; -use std::collections::{BTreeMap, BTreeSet}; - -use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt}; +use rtfm_syntax::ast::App; -use crate::{ - analyze::{Analysis, Ownership}, - syntax::{App, Static}, -}; +use crate::{analyze::Analysis, check::Extra}; -pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { - let (const_app_resources, mod_resources) = resources(app, analysis); +mod assertions; +mod dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod locals; +mod module; +mod post_init; +mod pre_init; +mod resources; +mod resources_struct; +mod schedule; +mod schedule_body; +mod software_tasks; +mod spawn; +mod spawn_body; +mod timer_queue; +mod util; - let ( - const_app_exceptions, - exception_mods, - exception_locals, - exception_resources, - user_exceptions, - ) = exceptions(app, analysis); +// TODO document the syntax here or in `rtfm-syntax` +pub fn app(app: &App, analysis: &Analysis, extra: &Extra) -> TokenStream2 { + let mut const_app = vec![]; + let mut mains = vec![]; + let mut root = vec![]; + let mut user = vec![]; - let ( - const_app_interrupts, - interrupt_mods, - interrupt_locals, - interrupt_resources, - user_interrupts, - ) = interrupts(app, analysis); + // generate a `main` function for each core + for core in 0..app.args.cores { + let assertion_stmts = assertions::codegen(core, analysis, extra); - let (const_app_tasks, task_mods, task_locals, task_resources, user_tasks) = - tasks(app, analysis); + let (const_app_pre_init, pre_init_stmts) = pre_init::codegen(core, &app, analysis, extra); - let const_app_dispatchers = dispatchers(&app, analysis); + let (const_app_init, root_init, user_init, call_init) = + init::codegen(core, app, analysis, extra); - let const_app_spawn = spawn(app, analysis); + let (const_app_post_init, post_init_stmts) = post_init::codegen(core, analysis, extra); - let const_app_tq = timer_queue(app, analysis); + let (const_app_idle, root_idle, user_idle, call_idle) = + idle::codegen(core, app, analysis, extra); - let const_app_schedule = schedule(app); + user.push(quote!( + #user_init - let assertion_stmts = assertions(app, analysis); + #user_idle + )); - let pre_init_stmts = pre_init(&app, analysis); + root.push(quote!( + #(#root_init)* - let ( - const_app_init, - mod_init, - init_locals, - init_resources, - init_late_resources, - user_init, - call_init, - ) = init(app, analysis); + #(#root_idle)* + )); - let post_init_stmts = post_init(&app, analysis); - - let (const_app_idle, mod_idle, idle_locals, idle_resources, user_idle, call_idle) = - idle(app, analysis); - - let device = &app.args.device; - quote!( - #user_init - - #user_idle - - #(#user_exceptions)* - - #(#user_interrupts)* - - #(#user_tasks)* - - #mod_resources - - #init_locals - - #init_resources - - #init_late_resources - - #mod_init - - #idle_locals - - #idle_resources - - #mod_idle - - #(#exception_locals)* - - #(#exception_resources)* - - #(#exception_mods)* - - #(#interrupt_locals)* - - #(#interrupt_resources)* - - #(#interrupt_mods)* - - #(#task_locals)* - - #(#task_resources)* - - #(#task_mods)* - - /// Implementation details - const #name: () = { - // always include the device crate, which contains the vector table - use #device as _; - - #(#const_app_resources)* + const_app.push(quote!( + #(#const_app_pre_init)* #const_app_init + #(#const_app_post_init)* + #const_app_idle + )); - #(#const_app_exceptions)* - - #(#const_app_interrupts)* - - #(#const_app_dispatchers)* - - #(#const_app_tasks)* - - #(#const_app_spawn)* - - #(#const_app_tq)* - - #(#const_app_schedule)* - + let cfg_core = util::cfg_core(core, app.args.cores); + let main = util::suffixed("main", core); + let section = util::link_section("text", core); + mains.push(quote!( #[no_mangle] - unsafe fn main() -> ! { + #section + #cfg_core + unsafe extern "C" fn #main() -> ! { #(#assertion_stmts)* #(#pre_init_stmts)* @@ -142,2297 +84,79 @@ pub fn app(name: &Ident, app: &App, analysis: &Analysis) -> TokenStream { #call_idle } - }; - ) - .into() -} - -/* Main functions */ -/// In this pass we generate a static variable and a resource proxy for each resource -/// -/// If the user specified a resource like this: -/// -/// ``` -/// #[rtfm::app(device = ..)] -/// const APP: () = { -/// static mut X: UserDefinedStruct = (); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// } -/// ``` -/// -/// We'll generate code like this: -/// -/// - `const_app` -/// -/// ``` -/// const APP: () = { -/// static mut X: MaybeUninit = MaybeUninit::uninit(); -/// static mut Y: u64 = 0; -/// static mut Z: u32 = 0; -/// -/// impl<'a> Mutex for resources::X<'a> { .. } -/// -/// impl<'a> Mutex for resources::Y<'a> { .. } -/// -/// // but not for `Z` because it's not shared and thus requires no proxy -/// }; -/// ``` -/// -/// - `mod_resources` -/// -/// ``` -/// mod resources { -/// pub struct X<'a> { -/// priority: &'a Priority, -/// } -/// -/// impl<'a> X<'a> { -/// pub unsafe fn new(priority: &'a Priority) -> Self { -/// X { priority } -/// } -/// -/// pub unsafe fn priority(&self) -> &Priority { -/// self.priority -/// } -/// } -/// -/// // same thing for `Y` -/// -/// // but not for `Z` -/// } -/// ``` -fn resources( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // mod_resources - proc_macro2::TokenStream, -) { - let mut const_app = vec![]; - let mut mod_resources = vec![]; - - for (name, res) in &app.resources { - let cfgs = &res.cfgs; - let attrs = &res.attrs; - let ty = &res.ty; - - if let Some(expr) = res.expr.as_ref() { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: #ty = #expr; - )); - } else { - const_app.push(quote!( - #(#attrs)* - #(#cfgs)* - static mut #name: core::mem::MaybeUninit<#ty> = - core::mem::MaybeUninit::uninit(); - )); - } - - // generate a resource proxy when needed - if res.mutability.is_some() { - if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) { - let ptr = if res.expr.is_none() { - quote!(#name.as_mut_ptr()) - } else { - quote!(&mut #name) - }; - - mod_resources.push(quote!( - pub struct #name<'a> { - priority: &'a Priority, - } - - impl<'a> #name<'a> { - #[inline(always)] - pub unsafe fn new(priority: &'a Priority) -> Self { - #name { priority } - } - - #[inline(always)] - pub unsafe fn priority(&self) -> &Priority { - self.priority - } - } - )); - - const_app.push(impl_mutex( - app, - cfgs, - true, - name, - quote!(#ty), - *ceiling, - ptr, - )); - } - } - } - - let mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod resources { - use rtfm::export::Priority; - - #(#mod_resources)* - }) - }; - - (const_app, mod_resources) -} - -// For each exception we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the exception handler specified by the user -fn exceptions( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // exception_mods - Vec, - // exception_locals - Vec, - // exception_resources - Vec, - // user_exceptions - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, exception) in &app.exceptions { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &exception.args.priority; - let symbol = exception.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - #let_instant - - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); - - let mut needs_lt = false; - if !exception.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Exception(name.clone()), - exception.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Exception(name.clone()), - (!exception.args.resources.is_empty(), needs_lt), - !exception.args.schedule.is_empty(), - !exception.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &exception.attrs; - let context = &exception.context; - let (locals, lets) = locals(Kind::Exception(name.clone()), &exception.statics); - locals_structs.push(locals); - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let stmts = &exception.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } )); } - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} + let (const_app_resources, mod_resources) = resources::codegen(app, analysis, extra); -// For each interrupt we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the exception, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// -// - the interrupt handler specified by the user -fn interrupts( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // interrupt_mods - Vec, - // interrupt_locals - Vec, - // interrupt_resources - Vec, - // user_exceptions - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; + let (const_app_hardware_tasks, root_hardware_tasks, user_hardware_tasks) = + hardware_tasks::codegen(app, analysis, extra); - let device = &app.args.device; - for (name, interrupt) in &app.interrupts { - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(quote!(let instant = rtfm::Instant::now();)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - let priority = &interrupt.args.priority; - let symbol = interrupt.args.binds(name); - const_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; + let (const_app_software_tasks, root_software_tasks, user_software_tasks) = + software_tasks::codegen(app, analysis, extra); - #let_instant + let const_app_dispatchers = dispatchers::codegen(app, analysis, extra); - // check that this interrupt exists - let _ = #device::Interrupt::#symbol; + let const_app_spawn = spawn::codegen(app, analysis, extra); - rtfm::export::run(PRIORITY, || { - crate::#name( - #name::Locals::new(), - #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) - ) - }); - } - )); + let const_app_timer_queue = timer_queue::codegen(app, analysis, extra); - let mut needs_lt = false; - if !interrupt.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Interrupt(name.clone()), - interrupt.args.priority, - &mut needs_lt, - app, - analysis, - ); + let const_app_schedule = schedule::codegen(app, extra); - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Interrupt(name.clone()), - (!interrupt.args.resources.is_empty(), needs_lt), - !interrupt.args.schedule.is_empty(), - !interrupt.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &interrupt.attrs; - let context = &interrupt.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals, lets) = locals(Kind::Interrupt(name.clone()), &interrupt.statics); - locals_structs.push(locals); - let stmts = &interrupt.stmts; - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context) { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -// For each task we'll generate: -// -// - at the root of the crate: -// - a ${name}Resources struct (maybe) -// - a ${name}Locals struct -// -// - a module named after the task, see the `module` function for more details -// -// - hidden in `const APP` -// - the ${name}Resources constructor -// - an INPUTS buffer -// - a free queue and a corresponding resource -// - an INSTANTS buffer (if `timer-queue` is enabled) -// -// - the task handler specified by the user -fn tasks( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Vec, - // task_mods - Vec, - // task_locals - Vec, - // task_resources - Vec, - // user_tasks - Vec, -) { - let mut const_app = vec![]; - let mut mods = vec![]; - let mut locals_structs = vec![]; - let mut resources_structs = vec![]; - let mut user_code = vec![]; - - for (name, task) in &app.tasks { - let inputs = &task.inputs; - let (_, _, _, ty) = regroup_inputs(inputs); - - let cap = analysis.capacities[name]; - let cap_lit = mk_capacity_literal(cap); - let cap_ty = mk_typenum_capacity(cap, true); - - let task_inputs = mk_inputs_ident(name); - let task_instants = mk_instants_ident(name); - let task_fq = mk_fq_ident(name); - - let elems = (0..cap) - .map(|_| quote!(core::mem::MaybeUninit::uninit())) - .collect::>(); - - if cfg!(feature = "timer-queue") { - let elems = elems.clone(); - const_app.push(quote!( - /// Buffer that holds the instants associated to the inputs of a task - static mut #task_instants: [core::mem::MaybeUninit; #cap_lit] = - [#(#elems,)*]; - )); - } - - const_app.push(quote!( - /// Buffer that holds the inputs of a task - static mut #task_inputs: [core::mem::MaybeUninit<#ty>; #cap_lit] = - [#(#elems,)*]; - )); - - let doc = "Queue version of a free-list that keeps track of empty slots in the previous buffer(s)"; - let fq_ty = quote!(rtfm::export::FreeQueue<#cap_ty>); - const_app.push(quote!( - #[doc = #doc] - static mut #task_fq: #fq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #task_fq); - - if let Some(ceiling) = analysis.free_queues.get(name) { - const_app.push(quote!(struct #task_fq<'a> { - priority: &'a rtfm::export::Priority, - })); - - const_app.push(impl_mutex(app, &[], false, &task_fq, fq_ty, *ceiling, ptr)); - } - - let mut needs_lt = false; - if !task.args.resources.is_empty() { - let (item, constructor) = resources_struct( - Kind::Task(name.clone()), - task.args.priority, - &mut needs_lt, - app, - analysis, - ); - - resources_structs.push(item); - - const_app.push(constructor); - } - - mods.push(module( - Kind::Task(name.clone()), - (!task.args.resources.is_empty(), needs_lt), - !task.args.schedule.is_empty(), - !task.args.spawn.is_empty(), - false, - app, - )); - - let attrs = &task.attrs; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let context = &task.context; - let stmts = &task.stmts; - let (locals_struct, lets) = locals(Kind::Task(name.clone()), &task.statics); - locals_structs.push(locals_struct); - user_code.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(__locals: #name::Locals, #context: #name::Context #(,#inputs)*) { - use rtfm::Mutex as _; - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } - )); - } - - ( - const_app, - mods, - locals_structs, - resources_structs, - user_code, - ) -} - -/// For each task dispatcher we'll generate -/// -/// - A static variable that hold the ready queue (`RQ${priority}`) and a resource proxy for it -/// - An enumeration of all the tasks dispatched by this dispatcher `T${priority}` -/// - An interrupt handler that dispatches the tasks -fn dispatchers(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let device = &app.args.device; - for (level, dispatcher) in &analysis.dispatchers { - let rq = mk_rq_ident(*level); - let t = mk_t_ident(*level); - let cap = mk_typenum_capacity(dispatcher.capacity, true); - - let doc = format!( - "Queue of tasks ready to be dispatched at priority level {}", - level - ); - let rq_ty = quote!(rtfm::export::ReadyQueue<#t, #cap>); - items.push(quote!( - #[doc = #doc] - static mut #rq: #rq_ty = unsafe { - rtfm::export::Queue(rtfm::export::i::Queue::u8_sc()) - }; - )); - let ptr = quote!(&mut #rq); - - if let Some(ceiling) = analysis.ready_queues.get(&level) { - items.push(quote!( - struct #rq<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex(app, &[], false, &rq, rq_ty, *ceiling, ptr)); - } - - let variants = dispatcher - .tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - let doc = format!( - "Software tasks to be dispatched at priority level {}", - level - ); - items.push(quote!( - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - #[doc = #doc] - enum #t { - #(#variants,)* - } - )); - - let arms = dispatcher - .tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let (_, tupled, pats, _) = regroup_inputs(&task.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let input = quote!(#inputs.get_unchecked(usize::from(index)).as_ptr().read()); - let fq = quote!(#fq); - - let (let_instant, _instant) = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - let instant = - quote!(#instants.get_unchecked(usize::from(index)).as_ptr().read()); - - ( - Some(quote!(let instant = #instant;)), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - - let call = { - let pats = pats.clone(); - - quote!( - #name( - #name::Locals::new(), - #name::Context::new(priority #_instant) - #(,#pats)* - ) - ) - }; - - quote!( - #(#cfgs)* - #t::#name => { - let #tupled = #input; - #let_instant - #fq.split().0.enqueue_unchecked(index); - let priority = &rtfm::export::Priority::new(PRIORITY); - #call - } - ) - }) - .collect::>(); - - let doc = format!( - "interrupt handler used to dispatch tasks at priority {}", - level - ); - let attrs = &dispatcher.attrs; - let interrupt = &dispatcher.interrupt; - let rq = quote!((&mut #rq)); - items.push(quote!( - #[doc = #doc] - #(#attrs)* - #[no_mangle] - #[allow(non_snake_case)] - unsafe fn #interrupt() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - // check that this interrupt exists - let _ = #device::Interrupt::#interrupt; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = #rq.split().1.dequeue() { - match task { - #(#arms)* - } - } - }); - } - )); - } - - items -} - -/// Generates all the `Spawn.$task` related code -fn spawn(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let mut seen = BTreeSet::new(); - for (spawner, spawnees) in app.spawn_callers() { - if spawnees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let spawner_is_init = spawner == "init"; - let spawner_is_idle = spawner == "idle"; - for name in spawnees { - let spawnee = &app.tasks[name]; - let cfgs = &spawnee.cfgs; - let (args, _, untupled, ty) = regroup_inputs(&spawnee.inputs); - - if spawner_is_init { - // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` - // functions which are shared by other contexts - - let body = mk_spawn_body(&spawner, &name, app, analysis); - - let let_instant = if cfg!(feature = "timer-queue") { - Some(quote!(let instant = unsafe { rtfm::Instant::artificial(0) };)) - } else { - None - }; - methods.push(quote!( - #(#cfgs)* - fn #name(&self #(,#args)*) -> Result<(), #ty> { - #let_instant - #body - } - )); - } else { - let spawn = mk_spawn_ident(name); - - if !seen.contains(name) { - // generate a `spawn_${name}` function - seen.insert(name); - - let instant = if cfg!(feature = "timer-queue") { - Some(quote!(, instant: rtfm::Instant)) - } else { - None - }; - let body = mk_spawn_body(&spawner, &name, app, analysis); - let args = args.clone(); - items.push(quote!( - #(#cfgs)* - unsafe fn #spawn( - priority: &rtfm::export::Priority - #instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - let (let_instant, instant) = if cfg!(feature = "timer-queue") { - ( - Some(if spawner_is_idle { - quote!(let instant = rtfm::Instant::now();) - } else { - quote!(let instant = self.instant();) - }), - Some(quote!(, instant)), - ) - } else { - (None, None) - }; - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self #(,#args)*) -> Result<(), #ty> { - unsafe { - #let_instant - #spawn(self.priority() #instant #(,#untupled)*) - } - } - )); - } - } - - let lt = if spawner_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #spawner::Spawn<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates code related to the timer queue, namely -/// -/// - A static variable that holds the timer queue and a resource proxy for it -/// - The system timer exception, which moves tasks from the timer queue into the ready queues -fn timer_queue(app: &App, analysis: &Analysis) -> Vec { - let mut items = vec![]; - - let tasks = &analysis.timer_queue.tasks; - - if tasks.is_empty() { - return items; - } - - let variants = tasks - .iter() - .map(|task| { - let cfgs = &app.tasks[task].cfgs; - quote!( - #(#cfgs)* - #task - ) - }) - .collect::>(); - - items.push(quote!( - /// `schedule`-dable tasks - #[allow(non_camel_case_types)] - #[derive(Clone, Copy)] - enum T { - #(#variants,)* - } - )); - - let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false); - let ty = quote!(rtfm::export::TimerQueue); - items.push(quote!( - /// The timer queue - static mut TQ: core::mem::MaybeUninit<#ty> = core::mem::MaybeUninit::uninit(); - )); - - items.push(quote!( - struct TQ<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - items.push(impl_mutex( - app, - &[], - false, - &Ident::new("TQ", Span::call_site()), - ty, - analysis.timer_queue.ceiling, - quote!(TQ.as_mut_ptr()), - )); - - let device = &app.args.device; - let arms = tasks - .iter() - .map(|name| { - let task = &app.tasks[name]; - let cfgs = &task.cfgs; - let priority = task.args.priority; - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - quote!( - #(#cfgs)* - T::#name => { - let priority = &rtfm::export::Priority::new(PRIORITY); - (#rq { priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#t::#name, index)) - }); - - rtfm::pend(#device::Interrupt::#dispatcher) - } - ) - }) - .collect::>(); - - let priority = analysis.timer_queue.priority; - items.push(quote!( - /// The system timer - #[no_mangle] - unsafe fn SysTick() { - use rtfm::Mutex as _; - - /// System timer priority - const PRIORITY: u8 = #priority; - - rtfm::export::run(PRIORITY, || { - while let Some((task, index)) = (TQ { - // NOTE dynamic priority is always the static priority at this point - priority: &rtfm::export::Priority::new(PRIORITY), - }) - // NOTE `inline(always)` produces faster and smaller code - .lock(#[inline(always)] - |tq| tq.dequeue()) - { - match task { - #(#arms)* - } - } - }); - } - )); - - items -} - -/// Generates all the `Schedule.$task` related code -fn schedule(app: &App) -> Vec { - let mut items = vec![]; - if !cfg!(feature = "timer-queue") { - return items; - } - - let mut seen = BTreeSet::new(); - for (scheduler, schedulees) in app.schedule_callers() { - if schedulees.is_empty() { - continue; - } - - let mut methods = vec![]; - - let scheduler_is_init = scheduler == "init"; - for name in schedulees { - let schedulee = &app.tasks[name]; - - let (args, _, untupled, ty) = regroup_inputs(&schedulee.inputs); - - let cfgs = &schedulee.cfgs; - - let schedule = mk_schedule_ident(name); - if scheduler_is_init { - let body = mk_schedule_body(&scheduler, name, app); - - let args = args.clone(); - methods.push(quote!( - #(#cfgs)* - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - #body - } - )); - } else { - if !seen.contains(name) { - seen.insert(name); - - let body = mk_schedule_body(&scheduler, name, app); - let args = args.clone(); - - items.push(quote!( - #(#cfgs)* - fn #schedule( - priority: &rtfm::export::Priority, - instant: rtfm::Instant - #(,#args)* - ) -> Result<(), #ty> { - #body - } - )); - } - - methods.push(quote!( - #(#cfgs)* - #[inline(always)] - fn #name(&self, instant: rtfm::Instant #(,#args)*) -> Result<(), #ty> { - let priority = unsafe { self.priority() }; - - #schedule(priority, instant #(,#untupled)*) - } - )); - } - } - - let lt = if scheduler_is_init { - None - } else { - Some(quote!('a)) - }; - items.push(quote!( - impl<#lt> #scheduler::Schedule<#lt> { - #(#methods)* - } - )); - } - - items -} - -/// Generates `Send` / `Sync` compile time checks -fn assertions(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - for ty in &analysis.assert_sync { - stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); - } - - for task in &analysis.tasks_assert_send { - let (_, _, _, ty) = regroup_inputs(&app.tasks[task].inputs); - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - // all late resources need to be `Send` - for ty in &analysis.resources_assert_send { - stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); - } - - stmts -} - -/// Generates code that we must run before `init` runs. See comments inside -fn pre_init(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - stmts.push(quote!(rtfm::export::interrupt::disable();)); - - // populate the `FreeQueue`s - for name in app.tasks.keys() { - let fq = mk_fq_ident(name); - let cap = analysis.capacities[name]; - - stmts.push(quote!( - for i in 0..#cap { - #fq.enqueue_unchecked(i); - } - )); - } - - stmts.push(quote!( - let mut core = rtfm::export::Peripherals::steal(); - )); - - // Initialize the timer queue - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!(TQ.as_mut_ptr().write(rtfm::export::TimerQueue::new(core.SYST));)); - } - - // set interrupts priorities - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - for (handler, interrupt) in &app.interrupts { - let name = interrupt.args.binds(handler); - let priority = interrupt.args.priority; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!( - core.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - } - - // set task dispatcher priorities - for (priority, dispatcher) in &analysis.dispatchers { - let name = &dispatcher.interrupt; - - stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);)); - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!( - core.NVIC.set_priority( - #device::Interrupt::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - } - - // Set the cycle count to 0 and disable it while `init` executes - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DWT.ctrl.modify(|r| r & !1);)); - stmts.push(quote!(core.DWT.cyccnt.write(0);)); - } - - stmts -} - -// This generates -// -// - at the root of the crate -// - a initResources struct (maybe) -// - a initLateResources struct (maybe) -// - a initLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the initResources struct -// - a re-export of the initLateResources struct -// - a re-export of the initLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the initResources constructor -// -// - the user specified `init` function -// -// - a call to the user specified `init` function -fn init( - app: &App, - analysis: &Analysis, -) -> ( - // const_app - Option, - // mod_init - proc_macro2::TokenStream, - // init_locals - proc_macro2::TokenStream, - // init_resources - Option, - // init_late_resources - Option, - // user_init - proc_macro2::TokenStream, - // call_init - proc_macro2::TokenStream, -) { - let mut needs_lt = false; - let mut const_app = None; - let mut init_resources = None; - if !app.init.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Init, 0, &mut needs_lt, app, analysis); - - init_resources = Some(item); - const_app = Some(constructor); - } - - let core = if cfg!(feature = "timer-queue") { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: &mut core.DCB, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - TPIU: core.TPIU, - }) - } else { - quote!(rtfm::Peripherals { - CBP: core.CBP, - CPUID: core.CPUID, - DCB: core.DCB, - DWT: core.DWT, - FPB: core.FPB, - FPU: core.FPU, - ITM: core.ITM, - MPU: core.MPU, - SCB: &mut core.SCB, - SYST: core.SYST, - TPIU: core.TPIU, - }) - }; - - let call_init = quote!(let late = init(init::Locals::new(), init::Context::new(#core));); - - let late_fields = app - .resources - .iter() - .filter_map(|(name, res)| { - if res.expr.is_none() { - let ty = &res.ty; - - Some(quote!(pub #name: #ty)) - } else { - None - } - }) - .collect::>(); - - let attrs = &app.init.attrs; - let has_late_resources = !late_fields.is_empty(); - let (ret, init_late_resources) = if has_late_resources { - ( - Some(quote!(-> init::LateResources)), - Some(quote!( - /// Resources initialized at runtime - #[allow(non_snake_case)] - pub struct initLateResources { - #(#late_fields),* - } - )), - ) - } else { - (None, None) - }; - let context = &app.init.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (locals_struct, lets) = locals(Kind::Init, &app.init.statics); - let stmts = &app.init.stmts; - let user_init = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn init(__locals: init::Locals, #context: init::Context) #ret { - #use_u32ext - - #(#lets;)* - - #(#stmts)* - } + let cores = app.args.cores.to_string(); + let cfg_core = quote!(#[cfg(core = #cores)]); + let msg = format!( + "specified {} core{} but tried to compile for more than {0} core{1}", + app.args.cores, + if app.args.cores > 1 { "s" } else { "" } + ); + let check_excess_cores = quote!( + #cfg_core + compile_error!(#msg); ); - let mod_init = module( - Kind::Init, - (!app.init.args.resources.is_empty(), needs_lt), - !app.init.args.schedule.is_empty(), - !app.init.args.spawn.is_empty(), - has_late_resources, - app, - ); - - ( - const_app, - mod_init, - locals_struct, - init_resources, - init_late_resources, - user_init, - call_init, - ) -} - -/// Generates code that we must run after `init` returns. See comments inside -fn post_init(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - - // initialize late resources - for (name, res) in &app.resources { - if res.expr.is_some() { - continue; - } - - stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); - } - - // set exception priorities - for (handler, exception) in &app.exceptions { - let name = exception.args.binds(handler); - let priority = exception.args.priority; - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::#name, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } - - // set the system timer priority - if !analysis.timer_queue.tasks.is_empty() { - let priority = analysis.timer_queue.priority; - - // compile time assert that this priority is supported by the device - stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); - - stmts.push(quote!(core.SCB.set_priority( - rtfm::export::SystemHandler::SysTick, - rtfm::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } - - if app.idle.is_none() { - // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR - stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); - } - - // enable and start the system timer - if !analysis.timer_queue.tasks.is_empty() { - stmts.push(quote!((*TQ.as_mut_ptr()) - .syst - .set_clock_source(rtfm::export::SystClkSource::Core);)); - stmts.push(quote!((*TQ.as_mut_ptr()).syst.enable_counter();)); - } - - // enable the cycle counter - if cfg!(feature = "timer-queue") { - stmts.push(quote!(core.DCB.enable_trace();)); - stmts.push(quote!(core.DWT.enable_cycle_counter();)); - } - - stmts.push(quote!(rtfm::export::interrupt::enable();)); - - stmts -} - -// If the user specified `idle` this generates -// -// - at the root of the crate -// - an idleResources struct (maybe) -// - an idleLocals struct -// -// - an `init` module that contains -// - the `Context` struct -// - a re-export of the idleResources struct -// - a re-export of the idleLocals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -// -// - hidden in `const APP` -// - the idleResources constructor -// -// - the user specified `idle` function -// -// - a call to the user specified `idle` function -// -// Otherwise it uses `loop { WFI }` as `idle` -fn idle( - app: &App, - analysis: &Analysis, -) -> ( - // const_app_idle - Option, - // mod_idle - Option, - // idle_locals - Option, - // idle_resources - Option, - // user_idle - Option, - // call_idle - proc_macro2::TokenStream, -) { - if let Some(idle) = app.idle.as_ref() { - let mut needs_lt = false; - let mut const_app = None; - let mut idle_resources = None; - - if !idle.args.resources.is_empty() { - let (item, constructor) = resources_struct(Kind::Idle, 0, &mut needs_lt, app, analysis); - - idle_resources = Some(item); - const_app = Some(constructor); - } - - let call_idle = quote!(idle( - idle::Locals::new(), - idle::Context::new(&rtfm::export::Priority::new(0)) - )); - - let attrs = &idle.attrs; - let context = &idle.context; - let use_u32ext = if cfg!(feature = "timer-queue") { - Some(quote!( - use rtfm::U32Ext as _; - )) - } else { - None - }; - let (idle_locals, lets) = locals(Kind::Idle, &idle.statics); - let stmts = &idle.stmts; - let user_idle = quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn idle(__locals: idle::Locals, #context: idle::Context) -> ! { - #use_u32ext - use rtfm::Mutex as _; - - #(#lets;)* - - #(#stmts)* - } - ); - - let mod_idle = module( - Kind::Idle, - (!idle.args.resources.is_empty(), needs_lt), - !idle.args.schedule.is_empty(), - !idle.args.spawn.is_empty(), - false, - app, - ); - - ( - const_app, - Some(mod_idle), - Some(idle_locals), - idle_resources, - Some(user_idle), - call_idle, - ) - } else { - ( - None, - None, - None, - None, - None, - quote!(loop { - rtfm::export::wfi() - }), - ) - } -} - -/* Support functions */ -/// This function creates the `Resources` struct -/// -/// It's a bit unfortunate but this struct has to be created in the root because it refers to types -/// which may have been imported into the root. -fn resources_struct( - kind: Kind, - priority: u8, - needs_lt: &mut bool, - app: &App, - analysis: &Analysis, -) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { - let mut lt = None; - - let resources = match &kind { - Kind::Init => &app.init.args.resources, - Kind::Idle => &app.idle.as_ref().expect("UNREACHABLE").args.resources, - Kind::Interrupt(name) => &app.interrupts[name].args.resources, - Kind::Exception(name) => &app.exceptions[name].args.resources, - Kind::Task(name) => &app.tasks[name].args.resources, - }; - - let mut fields = vec![]; - let mut values = vec![]; - for name in resources { - let res = &app.resources[name]; - - let cfgs = &res.cfgs; - let mut_ = res.mutability; - let ty = &res.ty; - - if kind.is_init() { - if !analysis.ownerships.contains_key(name) { - // owned by `init` - fields.push(quote!( - #(#cfgs)* - pub #name: &'static #mut_ #ty - )); - - values.push(quote!( - #(#cfgs)* - #name: &#mut_ #name - )); - } else { - // owned by someone else - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: &'a mut #ty - )); - - values.push(quote!( - #(#cfgs)* - #name: &mut #name - )); - } - } else { - let ownership = &analysis.ownerships[name]; - - let mut exclusive = false; - if ownership.needs_lock(priority) { - if mut_.is_none() { - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: &'a #ty - )); - } else { - // resource proxy - lt = Some(quote!('a)); - - fields.push(quote!( - #(#cfgs)* - pub #name: resources::#name<'a> - )); - - values.push(quote!( - #(#cfgs)* - #name: resources::#name::new(priority) - )); - - continue; - } - } else { - let lt = if kind.runs_once() { - quote!('static) - } else { - lt = Some(quote!('a)); - quote!('a) - }; - - if ownership.is_owned() || mut_.is_none() { - fields.push(quote!( - #(#cfgs)* - pub #name: &#lt #mut_ #ty - )); - } else { - exclusive = true; - - fields.push(quote!( - #(#cfgs)* - pub #name: rtfm::Exclusive<#lt, #ty> - )); - } - } - - let is_late = res.expr.is_none(); - if is_late { - let expr = if mut_.is_some() { - quote!(&mut *#name.as_mut_ptr()) - } else { - quote!(&*#name.as_ptr()) - }; - - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(#expr) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - } else { - if exclusive { - values.push(quote!( - #(#cfgs)* - #name: rtfm::Exclusive(&mut #name) - )); - } else { - values.push(quote!( - #(#cfgs)* - #name: &#mut_ #name - )); - } - } - } - } - - if lt.is_some() { - *needs_lt = true; - - // the struct could end up empty due to `cfg` leading to an error due to `'a` being unused - fields.push(quote!( - #[doc(hidden)] - pub __marker__: core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__marker__: core::marker::PhantomData)) - } - - let ident = kind.resources_ident(); - let doc = format!("Resources {} has access to", ident); - let item = quote!( - #[allow(non_snake_case)] - #[doc = #doc] - pub struct #ident<#lt> { - #(#fields,)* - } - ); - let arg = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - let constructor = quote!( - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new(#arg) -> Self { - #ident { - #(#values,)* - } - } - } - ); - (item, constructor) -} - -/// Creates a `Mutex` implementation -fn impl_mutex( - app: &App, - cfgs: &[Attribute], - resources_prefix: bool, - name: &Ident, - ty: proc_macro2::TokenStream, - ceiling: u8, - ptr: proc_macro2::TokenStream, -) -> proc_macro2::TokenStream { - let path = if resources_prefix { - quote!(resources::#name) - } else { - quote!(#name) - }; - - let priority = if resources_prefix { - quote!(self.priority()) - } else { - quote!(self.priority) - }; - - let device = &app.args.device; + let name = &app.name; + let device = extra.device; quote!( - #(#cfgs)* - impl<'a> rtfm::Mutex for #path<'a> { - type T = #ty; + #(#user)* - #[inline(always)] - fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { - /// Priority ceiling - const CEILING: u8 = #ceiling; + #(#user_hardware_tasks)* - unsafe { - rtfm::export::lock( - #ptr, - #priority, - CEILING, - #device::NVIC_PRIO_BITS, - f, - ) - } - } - } + #(#user_software_tasks)* + + #(#root)* + + #mod_resources + + #(#root_hardware_tasks)* + + #(#root_software_tasks)* + + /// Implementation details + // the user can't access the items within this `const` item + const #name: () = { + /// Always include the device crate which contains the vector table + use #device as _; + + #check_excess_cores + + #(#const_app)* + + #(#const_app_resources)* + + #(#const_app_hardware_tasks)* + + #(#const_app_software_tasks)* + + #(#const_app_dispatchers)* + + #(#const_app_spawn)* + + #(#const_app_timer_queue)* + + #(#const_app_schedule)* + + #(#mains)* + }; ) } - -/// Creates a `Locals` struct and related code. This returns -/// -/// - `locals` -/// -/// ``` -/// pub struct Locals<'a> { -/// #[cfg(never)] -/// pub X: &'a mut X, -/// __marker__: PhantomData<&'a mut ()>, -/// } -/// ``` -/// -/// - `lt` -/// -/// ``` -/// 'a -/// ``` -/// -/// - `lets` -/// -/// ``` -/// #[cfg(never)] -/// let X = __locals.X -/// ``` -fn locals( - kind: Kind, - statics: &BTreeMap, -) -> ( - // locals - proc_macro2::TokenStream, - // lets - Vec, -) { - let runs_once = kind.runs_once(); - let ident = kind.locals_ident(); - - let mut lt = None; - let mut fields = vec![]; - let mut lets = vec![]; - let mut items = vec![]; - let mut values = vec![]; - for (name, static_) in statics { - let lt = if runs_once { - quote!('static) - } else { - lt = Some(quote!('a)); - quote!('a) - }; - - let cfgs = &static_.cfgs; - let expr = &static_.expr; - let ty = &static_.ty; - fields.push(quote!( - #(#cfgs)* - #name: &#lt mut #ty - )); - items.push(quote!( - #(#cfgs)* - static mut #name: #ty = #expr - )); - values.push(quote!( - #(#cfgs)* - #name: &mut #name - )); - lets.push(quote!( - #(#cfgs)* - let #name = __locals.#name - )); - } - - if lt.is_some() { - fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); - values.push(quote!(__marker__: core::marker::PhantomData)); - } - - let locals = quote!( - #[allow(non_snake_case)] - #[doc(hidden)] - pub struct #ident<#lt> { - #(#fields),* - } - - impl<#lt> #ident<#lt> { - #[inline(always)] - unsafe fn new() -> Self { - #(#items;)* - - #ident { - #(#values),* - } - } - } - ); - - (locals, lets) -} - -/// This function creates a module that contains -// -// - the Context struct -// - a re-export of the ${name}Resources struct (maybe) -// - a re-export of the ${name}LateResources struct (maybe) -// - a re-export of the ${name}Locals struct -// - the Spawn struct (maybe) -// - the Schedule struct (maybe, if `timer-queue` is enabled) -fn module( - kind: Kind, - resources: (/* has */ bool, /* 'a */ bool), - schedule: bool, - spawn: bool, - late_resources: bool, - app: &App, -) -> proc_macro2::TokenStream { - let mut items = vec![]; - let mut fields = vec![]; - let mut values = vec![]; - - let name = kind.ident(); - - let mut needs_instant = false; - 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 - )); - - values.push(quote!(start: rtfm::Instant::artificial(0))); - } - - let device = &app.args.device; - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtfm::Peripherals<'a> - )); - fields.push(quote!( - /// Device specific peripherals - pub device: #device::Peripherals - )); - - values.push(quote!(core)); - values.push(quote!(device: #device::Peripherals::steal())); - 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 - )); - - values.push(quote!(start: instant)); - - needs_instant = true; - } - } - - Kind::Task(_) => { - if cfg!(feature = "timer-queue") { - fields.push(quote!( - /// The time at which this task was scheduled to run - pub scheduled: rtfm::Instant - )); - - values.push(quote!(scheduled: instant)); - - needs_instant = true; - } - } - } - - let ident = kind.locals_ident(); - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Locals; - )); - - if resources.0 { - let ident = kind.resources_ident(); - let lt = if resources.1 { - lt = Some(quote!('a)); - Some(quote!('a)) - } else { - None - }; - - items.push(quote!( - #[doc(inline)] - pub use super::#ident as Resources; - )); - - fields.push(quote!( - /// Resources this task has access to - pub resources: Resources<#lt> - )); - - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority)) - }; - values.push(quote!(resources: Resources::new(#priority))); - } - - if schedule { - let doc = "Tasks that can be `schedule`-d from this context"; - if kind.is_init() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule { - _not_send: core::marker::PhantomData<*mut ()>, - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule - )); - - values.push(quote!( - schedule: Schedule { _not_send: core::marker::PhantomData } - )); - } else { - lt = Some(quote!('a)); - - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Schedule<'a> { - priority: &'a rtfm::export::Priority, - } - - impl<'a> Schedule<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - &self.priority - } - } - )); - - fields.push(quote!( - #[doc = #doc] - pub schedule: Schedule<'a> - )); - - values.push(quote!( - schedule: Schedule { priority } - )); - } - } - - if spawn { - let doc = "Tasks that can be `spawn`-ed from this context"; - if kind.is_init() { - fields.push(quote!( - #[doc = #doc] - pub spawn: Spawn - )); - - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Spawn { - _not_send: core::marker::PhantomData<*mut ()>, - } - )); - - values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); - } else { - lt = Some(quote!('a)); - - fields.push(quote!( - #[doc = #doc] - pub spawn: Spawn<'a> - )); - - let mut instant_method = None; - if kind.is_idle() { - items.push(quote!( - #[doc = #doc] - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - priority: &'a rtfm::export::Priority, - } - )); - - values.push(quote!(spawn: Spawn { priority })); - } else { - let instant_field = if cfg!(feature = "timer-queue") { - needs_instant = true; - instant_method = Some(quote!( - pub unsafe fn instant(&self) -> rtfm::Instant { - self.instant - } - )); - Some(quote!(instant: rtfm::Instant,)) - } else { - None - }; - - items.push(quote!( - /// Tasks that can be spawned from this context - #[derive(Clone, Copy)] - pub struct Spawn<'a> { - #instant_field - priority: &'a rtfm::export::Priority, - } - )); - - let _instant = if needs_instant { - Some(quote!(, instant)) - } else { - None - }; - values.push(quote!( - spawn: Spawn { priority #_instant } - )); - } - - items.push(quote!( - impl<'a> Spawn<'a> { - #[doc(hidden)] - #[inline(always)] - pub unsafe fn priority(&self) -> &rtfm::export::Priority { - self.priority - } - - #instant_method - } - )); - } - } - - if late_resources { - items.push(quote!( - #[doc(inline)] - pub use super::initLateResources as LateResources; - )); - } - - let doc = match kind { - Kind::Exception(_) => "Hardware task (exception)", - Kind::Idle => "Idle loop", - Kind::Init => "Initialization function", - Kind::Interrupt(_) => "Hardware task (interrupt)", - Kind::Task(_) => "Software task", - }; - - let core = if kind.is_init() { - lt = Some(quote!('a)); - Some(quote!(core: rtfm::Peripherals<'a>,)) - } else { - None - }; - - let priority = if kind.is_init() { - None - } else { - Some(quote!(priority: &#lt rtfm::export::Priority)) - }; - - let instant = if needs_instant { - Some(quote!(, instant: rtfm::Instant)) - } else { - None - }; - items.push(quote!( - /// Execution context - pub struct Context<#lt> { - #(#fields,)* - } - - impl<#lt> Context<#lt> { - #[inline(always)] - pub unsafe fn new(#core #priority #instant) -> Self { - Context { - #(#values,)* - } - } - } - )); - - if !items.is_empty() { - quote!( - #[allow(non_snake_case)] - #[doc = #doc] - pub mod #name { - #(#items)* - } - ) - } else { - quote!() - } -} - -/// Creates the body of `spawn_${name}` -fn mk_spawn_body<'a>( - spawner: &Ident, - name: &Ident, - app: &'a App, - analysis: &Analysis, -) -> proc_macro2::TokenStream { - let spawner_is_init = spawner == "init"; - let device = &app.args.device; - - let spawnee = &app.tasks[name]; - let priority = spawnee.args.priority; - let dispatcher = &analysis.dispatchers[&priority].interrupt; - - let (_, tupled, _, _) = regroup_inputs(&spawnee.inputs); - - let inputs = mk_inputs_ident(name); - let fq = mk_fq_ident(name); - - let rq = mk_rq_ident(priority); - let t = mk_t_ident(priority); - - let write_instant = if cfg!(feature = "timer-queue") { - let instants = mk_instants_ident(name); - - Some(quote!( - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - )) - } else { - None - }; - - let (dequeue, enqueue) = if spawner_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - ( - quote!(#fq.dequeue()), - quote!(#rq.enqueue_unchecked((#t::#name, index));), - ) - } else { - ( - quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), - quote!((#rq { priority }).lock(|rq| { - rq.split().0.enqueue_unchecked((#t::#name, index)) - });), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - #write_instant - - #enqueue - - rtfm::pend(#device::Interrupt::#dispatcher); - - Ok(()) - } else { - Err(input) - } - } - ) -} - -/// Creates the body of `schedule_${name}` -fn mk_schedule_body<'a>(scheduler: &Ident, name: &Ident, app: &'a App) -> proc_macro2::TokenStream { - let scheduler_is_init = scheduler == "init"; - - let schedulee = &app.tasks[name]; - - let (_, tupled, _, _) = regroup_inputs(&schedulee.inputs); - - let fq = mk_fq_ident(name); - let inputs = mk_inputs_ident(name); - let instants = mk_instants_ident(name); - - let (dequeue, enqueue) = if scheduler_is_init { - // `init` has exclusive access to these queues so we can bypass the resources AND - // the consumer / producer split - let dequeue = quote!(#fq.dequeue()); - - (dequeue, quote!((*TQ.as_mut_ptr()).enqueue_unchecked(nr);)) - } else { - ( - quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), - quote!((TQ { priority }).lock(|tq| tq.enqueue_unchecked(nr));), - ) - }; - - quote!( - unsafe { - use rtfm::Mutex as _; - - let input = #tupled; - if let Some(index) = #dequeue { - #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); - - #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); - - let nr = rtfm::export::NotReady { - instant, - index, - task: T::#name, - }; - - #enqueue - - Ok(()) - } else { - Err(input) - } - } - ) -} - -/// `u8` -> (unsuffixed) `LitInt` -fn mk_capacity_literal(capacity: u8) -> LitInt { - LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site()) -} - -/// e.g. `4u8` -> `U4` -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) -} - -/// e.g. `foo` -> `foo_INPUTS` -fn mk_inputs_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INPUTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_INSTANTS` -fn mk_instants_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_INSTANTS", base), Span::call_site()) -} - -/// e.g. `foo` -> `foo_FQ` -fn mk_fq_ident(base: &Ident) -> Ident { - Ident::new(&format!("{}_FQ", base), Span::call_site()) -} - -/// e.g. `3` -> `RQ3` -fn mk_rq_ident(level: u8) -> Ident { - Ident::new(&format!("RQ{}", level), Span::call_site()) -} - -/// e.g. `3` -> `T3` -fn mk_t_ident(level: u8) -> Ident { - Ident::new(&format!("T{}", level), Span::call_site()) -} - -fn mk_spawn_ident(task: &Ident) -> Ident { - Ident::new(&format!("spawn_{}", task), Span::call_site()) -} - -fn mk_schedule_ident(task: &Ident) -> Ident { - Ident::new(&format!("schedule_{}", task), Span::call_site()) -} - -// Regroups a task inputs -// -// e.g. &[`input: Foo`], &[`mut x: i32`, `ref y: i64`] -fn regroup_inputs( - inputs: &[ArgCaptured], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec, - // tupled e.g. `_0`, `(_0, _1)` - proc_macro2::TokenStream, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec, - // ty e.g. `Foo`, `(i32, i64)` - proc_macro2::TokenStream, -) { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - - ( - vec![quote!(_0: #ty)], - quote!(_0), - vec![quote!(_0)], - quote!(#ty), - ) - } else { - let mut args = vec![]; - let mut pats = vec![]; - let mut tys = vec![]; - - for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); - let ty = &input.ty; - - args.push(quote!(#i: #ty)); - - pats.push(quote!(#i)); - - tys.push(quote!(#ty)); - } - - let tupled = { - let pats = pats.clone(); - quote!((#(#pats,)*)) - }; - let ty = quote!((#(#tys,)*)); - (args, tupled, pats, ty) - } -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -enum Kind { - Exception(Ident), - Idle, - Init, - Interrupt(Ident), - Task(Ident), -} - -impl Kind { - fn ident(&self) -> Ident { - let span = Span::call_site(); - match self { - Kind::Init => Ident::new("init", span), - Kind::Idle => Ident::new("idle", span), - Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(), - } - } - - fn locals_ident(&self) -> Ident { - Ident::new(&format!("{}Locals", self.ident()), Span::call_site()) - } - - fn resources_ident(&self) -> Ident { - Ident::new(&format!("{}Resources", self.ident()), Span::call_site()) - } - - 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/codegen/assertions.rs b/macros/src/codegen/assertions.rs new file mode 100644 index 0000000000..4a77352f57 --- /dev/null +++ b/macros/src/codegen/assertions.rs @@ -0,0 +1,33 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, check::Extra}; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(core: u8, analysis: &Analysis, extra: &Extra) -> Vec { + let mut stmts = vec![]; + + // we don't generate *all* assertions on all cores because the user could conditionally import a + // type only on some core (e.g. `#[cfg(core = "0")] use some::Type;`) + + if let Some(types) = analysis.send_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_send::<#ty>();)); + } + } + + if let Some(types) = analysis.sync_types.get(&core) { + for ty in types { + stmts.push(quote!(rtfm::export::assert_sync::<#ty>();)); + } + } + + // if the `schedule` API is used in more than one core then we need to check that the + // `monotonic` timer can be used in multi-core context + if analysis.timer_queues.len() > 1 && analysis.timer_queues.contains_key(&core) { + let monotonic = extra.monotonic(); + stmts.push(quote!(rtfm::export::assert_multicore::<#monotonic>();)); + } + + stmts +} diff --git a/macros/src/codegen/dispatchers.rs b/macros/src/codegen/dispatchers.rs new file mode 100644 index 0000000000..9a9cb102f9 --- /dev/null +++ b/macros/src/codegen/dispatchers.rs @@ -0,0 +1,189 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&receiver, dispatchers) in &analysis.channels { + let interrupts = &analysis.interrupts[&receiver]; + + for (&level, channels) in dispatchers { + let mut stmts = vec![]; + + for (&sender, channel) in channels { + let cfg_sender = util::cfg_core(sender, app.args.cores); + + let variants = channel + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!( + "Software tasks spawned from core #{} to be dispatched at priority level {} by core #{}", + sender, level, receiver, + ); + let t = util::spawn_t_ident(receiver, level, sender); + items.push(quote!( + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + #[doc = #doc] + enum #t { + #(#variants,)* + } + )); + + let n = util::capacity_typenum(channel.capacity, true); + let rq = util::rq_ident(receiver, level, sender); + let (rq_attr, rq_ty, rq_expr, section) = if sender == receiver { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCRQ<#t, #n>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + util::link_section("bss", sender), + ) + } else { + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }; + + ( + shared, + quote!(rtfm::export::MCRQ<#t, #n>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, + ) + }; + + let doc = format!( + "Queue of tasks sent by core #{} ready to be dispatched by core #{} at priority level {}", + sender, + receiver, + level + ); + items.push(quote!( + #[doc = #doc] + #rq_attr + #section + static mut #rq: #rq_ty = #rq_expr; + )); + + if let Some(ceiling) = channel.ceiling { + items.push(quote!( + #cfg_sender + struct #rq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &rq, + rq_ty, + ceiling, + quote!(&mut #rq), + )); + } + + let arms = channel + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + let cfgs = &task.cfgs; + let fq = util::fq_ident(name, sender); + let inputs = util::inputs_ident(name, sender); + let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs); + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + ( + quote!( + let instant = + #instants.get_unchecked(usize::from(index)).as_ptr().read(); + ), + quote!(, instant), + ) + } else { + (quote!(), quote!()) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + quote!( + #(#cfgs)* + #t::#name => { + let #tupled = + #inputs.get_unchecked(usize::from(index)).as_ptr().read(); + #let_instant + #fq.split().0.enqueue_unchecked(index); + let priority = &rtfm::export::Priority::new(PRIORITY); + #name( + #locals_new + #name::Context::new(priority #instant) + #(,#pats)* + ) + } + ) + }) + .collect::>(); + + stmts.push(quote!( + while let Some((task, index)) = #rq.split().1.dequeue() { + match task { + #(#arms)* + } + } + )); + } + + let doc = format!( + "Interrupt handler used by core #{} to dispatch tasks at priority {}", + receiver, level + ); + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let section = util::link_section("text", receiver); + let interrupt = util::suffixed(&interrupts[&level].to_string(), receiver); + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #cfg_receiver + #section + unsafe fn #interrupt() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtfm::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 0000000000..a9c2a2bdc7 --- /dev/null +++ b/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,132 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors + Vec, + // root_hardware_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_hardware_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let core = task.args.core; + let cfg_core = util::cfg_core(core, app.args.cores); + + let (let_instant, instant) = if app.uses_schedule(core) { + let m = extra.monotonic(); + + ( + Some(quote!(let instant = <#m as rtfm::Monotonic>::now();)), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + let locals_new = if task.locals.is_empty() { + quote!() + } else { + quote!(#name::Locals::new(),) + }; + + let symbol = if cfg!(feature = "homogeneous") { + util::suffixed(&task.args.binds.to_string(), core) + } else { + task.args.binds.clone() + }; + let priority = task.args.priority; + + let section = util::link_section("text", core); + const_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #section + #cfg_core + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + #let_instant + + rtfm::export::run(PRIORITY, || { + crate::#name( + #locals_new + #name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant) + ) + }); + } + )); + + let mut needs_lt = false; + + // `${task}Resources` + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::HardwareTask(name), + priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + root.push(module::codegen( + Context::HardwareTask(name), + needs_lt, + app, + extra, + )); + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = + locals::codegen(Context::HardwareTask(name), &task.locals, core, app); + + root.push(struct_); + locals_pat = Some(pat); + } + + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + let section = util::link_section("text", core); + // XXX shouldn't this have a cfg_core? + let locals_pat = locals_pat.iter(); + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + #section + fn #name(#(#locals_pat,)* #context: #name::Context) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs new file mode 100644 index 0000000000..35a7252307 --- /dev/null +++ b/macros/src/codegen/idle.rs @@ -0,0 +1,91 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[idle]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${idle}Resources` constructor + Option, + // root_idle -- items that must be placed in the root of the crate: + // - the `${idle}Locals` struct + // - the `${idle}Resources` struct + // - the `${idle}` module, which contains types like `${idle}::Context` + Vec, + // user_idle + Option, + // call_idle + TokenStream2, +) { + if let Some(idle) = app.idles.get(&core) { + let mut needs_lt = false; + let mut const_app = None; + let mut root_idle = vec![]; + let mut locals_pat = None; + let mut locals_new = None; + + if !idle.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Idle(core), 0, &mut needs_lt, app, analysis); + + root_idle.push(item); + const_app = Some(constructor); + } + + let name = &idle.name; + if !idle.locals.is_empty() { + let (locals, pat) = locals::codegen(Context::Idle(core), &idle.locals, core, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_idle.push(locals); + } + + root_idle.push(module::codegen(Context::Idle(core), needs_lt, app, extra)); + + let cfg_core = util::cfg_core(core, app.args.cores); + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + #cfg_core + #section + fn #name(#(#locals_pat,)* #context: #name::Context) -> ! { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + let locals_new = locals_new.iter(); + let call_idle = quote!(#name( + #(#locals_new,)* + #name::Context::new(&rtfm::export::Priority::new(0)) + )); + + (const_app, root_idle, user_idle, call_idle) + } else { + ( + None, + vec![], + None, + quote!(loop { + rtfm::export::wfi() + }), + ) + } +} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs new file mode 100644 index 0000000000..9c8ce31c7c --- /dev/null +++ b/macros/src/codegen/init.rs @@ -0,0 +1,116 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_idle -- the `${init}Resources` constructor + Option, + // root_init -- items that must be placed in the root of the crate: + // - the `${init}Locals` struct + // - the `${init}Resources` struct + // - the `${init}LateResources` struct + // - the `${init}` module, which contains types like `${init}::Context` + Vec, + // user_init -- the `#[init]` function written by the user + Option, + // call_init -- the call to the user `#[init]` if there's one + Option, +) { + if let Some(init) = app.inits.get(&core) { + let cfg_core = util::cfg_core(core, app.args.cores); + let mut needs_lt = false; + let name = &init.name; + + let mut root_init = vec![]; + + let ret = { + let late_fields = analysis + .late_resources + .get(&core) + .map(|resources| { + resources + .iter() + .map(|name| { + let ty = &app.late_resources[name].ty; + + quote!(pub #name: #ty) + }) + .collect::>() + }) + .unwrap_or(vec![]); + + if !late_fields.is_empty() { + let late_resources = util::late_resources_ident(&name); + + root_init.push(quote!( + /// Resources initialized at runtime + #cfg_core + #[allow(non_snake_case)] + pub struct #late_resources { + #(#late_fields),* + } + )); + + Some(quote!(-> #name::LateResources)) + } else { + None + } + }; + + let mut locals_pat = None; + let mut locals_new = None; + if !init.locals.is_empty() { + let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, core, app); + + locals_new = Some(quote!(#name::Locals::new())); + locals_pat = Some(pat); + root_init.push(struct_); + } + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let section = util::link_section("text", core); + let locals_pat = locals_pat.iter(); + let user_init = Some(quote!( + #(#attrs)* + #cfg_core + #[allow(non_snake_case)] + #section + fn #name(#(#locals_pat,)* #context: #name::Context) #ret { + #(#stmts)* + } + )); + + let mut const_app = None; + if !init.args.resources.is_empty() { + let (item, constructor) = + resources_struct::codegen(Context::Init(core), 0, &mut needs_lt, app, analysis); + + root_init.push(item); + const_app = Some(constructor); + } + + let locals_new = locals_new.iter(); + let call_init = + Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));)); + + root_init.push(module::codegen(Context::Init(core), needs_lt, app, extra)); + + (const_app, root_init, user_init, call_init) + } else { + (None, vec![], None, None) + } +} diff --git a/macros/src/codegen/locals.rs b/macros/src/codegen/locals.rs new file mode 100644 index 0000000000..cbfe05fbb9 --- /dev/null +++ b/macros/src/codegen/locals.rs @@ -0,0 +1,101 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + ast::{App, Local}, + Context, Core, Map, +}; + +use crate::codegen::util; + +pub fn codegen( + ctxt: Context, + locals: &Map, + core: Core, + app: &App, +) -> ( + // locals + TokenStream2, + // pat + TokenStream2, +) { + assert!(!locals.is_empty()); + + let runs_once = ctxt.runs_once(); + let ident = util::locals_ident(ctxt, app); + + let mut lt = None; + let mut fields = vec![]; + let mut items = vec![]; + let mut names = vec![]; + let mut values = vec![]; + let mut pats = vec![]; + let mut has_cfgs = false; + + for (name, local) in locals { + let lt = if runs_once { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + let cfgs = &local.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let section = if local.shared && cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + util::link_section("data", core) + }; + let expr = &local.expr; + let ty = &local.ty; + fields.push(quote!( + #(#cfgs)* + #name: &#lt mut #ty + )); + items.push(quote!( + #(#cfgs)* + #section + static mut #name: #ty = #expr + )); + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + names.push(name); + pats.push(quote!( + #(#cfgs)* + #name + )); + } + + if lt.is_some() && has_cfgs { + fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>)); + values.push(quote!(__marker__: core::marker::PhantomData)); + } + + let locals = quote!( + #[allow(non_snake_case)] + #[doc(hidden)] + pub struct #ident<#lt> { + #(#fields),* + } + + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new() -> Self { + #(#items;)* + + #ident { + #(#values),* + } + } + } + ); + + let ident = ctxt.ident(app); + ( + locals, + quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals), + ) +} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs new file mode 100644 index 0000000000..5f077a2200 --- /dev/null +++ b/macros/src/codegen/module.rs @@ -0,0 +1,328 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{check::Extra, codegen::util}; + +pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 { + let mut items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + + let name = ctxt.ident(app); + + let core = ctxt.core(app); + let mut needs_instant = false; + let mut lt = None; + match ctxt { + Context::Init(core) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// System start time = `Instant(0 /* cycles */)` + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: <#m as rtfm::Monotonic>::zero())); + + fields.push(quote!( + /// Core (Cortex-M) peripherals minus the SysTick + pub core: rtfm::Peripherals + )); + } else { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtfm::export::Peripherals + )); + } + + if extra.peripherals == Some(core) { + let device = extra.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + values.push(quote!(core)); + } + + Context::Idle(..) => {} + + Context::HardwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// Time at which this handler started executing + pub start: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(start: instant)); + + needs_instant = true; + } + } + + Context::SoftwareTask(..) => { + if app.uses_schedule(core) { + let m = extra.monotonic(); + + fields.push(quote!( + /// The time at which this task was scheduled to run + pub scheduled: <#m as rtfm::Monotonic>::Instant + )); + + values.push(quote!(scheduled: instant)); + + needs_instant = true; + } + } + } + + if ctxt.has_locals(app) { + let ident = util::locals_ident(ctxt, app); + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Locals; + )); + } + + if ctxt.has_resources(app) { + let ident = util::resources_ident(ctxt, app); + let lt = if resources_tick { + lt = Some(quote!('a)); + Some(quote!('a)) + } else { + None + }; + + items.push(quote!( + #[doc(inline)] + pub use super::#ident as Resources; + )); + + fields.push(quote!( + /// Resources this task has access to + pub resources: Resources<#lt> + )); + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority)) + }; + values.push(quote!(resources: Resources::new(#priority))); + } + + if ctxt.uses_schedule(app) { + let doc = "Tasks that can be `schedule`-d from this context"; + if ctxt.is_init() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule + )); + + values.push(quote!( + schedule: Schedule { _not_send: core::marker::PhantomData } + )); + } else { + lt = Some(quote!('a)); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Schedule<'a> { + priority: &'a rtfm::export::Priority, + } + + impl<'a> Schedule<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtfm::export::Priority { + &self.priority + } + } + )); + + fields.push(quote!( + #[doc = #doc] + pub schedule: Schedule<'a> + )); + + values.push(quote!( + schedule: Schedule { priority } + )); + } + } + + if ctxt.uses_spawn(app) { + let doc = "Tasks that can be `spawn`-ed from this context"; + if ctxt.is_init() { + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn + )); + + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn { + _not_send: core::marker::PhantomData<*mut ()>, + } + )); + + values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData })); + } else { + lt = Some(quote!('a)); + + fields.push(quote!( + #[doc = #doc] + pub spawn: Spawn<'a> + )); + + let mut instant_method = None; + if ctxt.is_idle() { + items.push(quote!( + #[doc = #doc] + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + values.push(quote!(spawn: Spawn { priority })); + } else { + let instant_field = if app.uses_schedule(core) { + let m = extra.monotonic(); + + needs_instant = true; + instant_method = Some(quote!( + pub unsafe fn instant(&self) -> <#m as rtfm::Monotonic>::Instant { + self.instant + } + )); + Some(quote!(instant: <#m as rtfm::Monotonic>::Instant,)) + } else { + None + }; + + items.push(quote!( + /// Tasks that can be spawned from this context + #[derive(Clone, Copy)] + pub struct Spawn<'a> { + #instant_field + priority: &'a rtfm::export::Priority, + } + )); + + let _instant = if needs_instant { + Some(quote!(, instant)) + } else { + None + }; + values.push(quote!( + spawn: Spawn { priority #_instant } + )); + } + + items.push(quote!( + impl<'a> Spawn<'a> { + #[doc(hidden)] + #[inline(always)] + pub unsafe fn priority(&self) -> &rtfm::export::Priority { + self.priority + } + + #instant_method + } + )); + } + } + + if let Context::Init(core) = ctxt { + let init = &app.inits[&core]; + if init.returns_late_resources { + let late_resources = util::late_resources_ident(&init.name); + + items.push(quote!( + #[doc(inline)] + pub use super::#late_resources as LateResources; + )); + } + } + + let doc = match ctxt { + Context::Idle(_) => "Idle loop", + Context::Init(_) => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let core = if ctxt.is_init() { + if app.uses_schedule(core) { + Some(quote!(core: rtfm::Peripherals,)) + } else { + Some(quote!(core: rtfm::export::Peripherals,)) + } + } else { + None + }; + + let priority = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + + let instant = if needs_instant { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant)) + } else { + None + }; + + items.push(quote!( + /// Execution context + pub struct Context<#lt> { + #(#fields,)* + } + + impl<#lt> Context<#lt> { + #[inline(always)] + pub unsafe fn new(#core #priority #instant) -> Self { + Context { + #(#values,)* + } + } + } + )); + + if !items.is_empty() { + let cfg_core = util::cfg_core(ctxt.core(app), app.args.cores); + + quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #cfg_core + pub mod #name { + #(#items)* + } + ) + } else { + quote!() + } +} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs new file mode 100644 index 0000000000..19773e45bd --- /dev/null +++ b/macros/src/codegen/post_init.rs @@ -0,0 +1,155 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs after `#[init]` returns +pub fn codegen( + core: u8, + analysis: &Analysis, + extra: &Extra, +) -> (Vec, Vec) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // initialize late resources + if let Some(late_resources) = analysis.late_resources.get(&core) { + for name in late_resources { + // if it's live + if analysis.locations.get(name).is_some() { + stmts.push(quote!(#name.as_mut_ptr().write(late.#name);)); + } + } + } + + if analysis.timer_queues.is_empty() { + // cross-initialization barriers -- notify *other* cores that their resources have been + // initialized + for (user, initializers) in &analysis.initialization_barriers { + if !initializers.contains(&core) { + continue; + } + + let ib = util::init_barrier(*user); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #shared + static #ib: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + stmts.push(quote!( + #ib.release(); + )); + } + + // then wait until the other cores have initialized *our* resources + if analysis.initialization_barriers.contains_key(&core) { + let ib = util::init_barrier(core); + + stmts.push(quote!( + #ib.wait(); + )); + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + if senders.get(&core) == Some(&false) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + } else { + // if the `schedule` API is used then we'll synchronize all cores to leave the + // `init`-ialization phase at the same time. In this case the rendezvous barrier makes the + // cross-initialization and spawn barriers unnecessary + + let m = extra.monotonic(); + + if analysis.timer_queues.len() == 1 { + // reset the monotonic timer / counter + stmts.push(quote!( + <#m as rtfm::Monotonic>::reset(); + )); + } else { + // in the multi-core case we need a rendezvous (RV) barrier between *all* the cores that + // use the `schedule` API; otherwise one of the cores could observe the before-reset + // value of the monotonic counter + // (this may be easier to implement with `AtomicU8.fetch_sub` but that API is not + // available on ARMv6-M) + + // this core will reset the monotonic counter + const FIRST: u8 = 0; + + if core == FIRST { + for &i in analysis.timer_queues.keys() { + let rv = util::rendezvous_ident(i); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #shared + static #rv: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // wait until all the other cores have reached the RV point + if i != FIRST { + stmts.push(quote!( + #rv.wait(); + )); + } + } + + let rv = util::rendezvous_ident(core); + stmts.push(quote!( + // the compiler fences are used to prevent `reset` from being re-ordering wrt to + // the atomic operations -- we don't know if `reset` contains load or store + // operations + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // reset the counter + <#m as rtfm::Monotonic>::reset(); + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + // now unblock all the other cores + #rv.release(); + )); + } else { + let rv = util::rendezvous_ident(core); + + // let the first core know that we have reached the RV point + stmts.push(quote!( + #rv.release(); + )); + + let rv = util::rendezvous_ident(FIRST); + + // wait until the first core has reset the monotonic timer + stmts.push(quote!( + #rv.wait(); + )); + } + } + } + + // enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtfm::export::interrupt::enable();)); + + (const_app, stmts) +} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs new file mode 100644 index 0000000000..605171b804 --- /dev/null +++ b/macros/src/codegen/pre_init.rs @@ -0,0 +1,159 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen( + core: u8, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // `const_app_pre_init` -- `static` variables for barriers + Vec, + // `pre_init_stmts` + Vec, +) { + let mut const_app = vec![]; + let mut stmts = vec![]; + + // disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtfm::export::interrupt::disable();)); + + // populate this core `FreeQueue`s + for (name, senders) in &analysis.free_queues { + let task = &app.software_tasks[name]; + let cap = task.args.capacity; + + for &sender in senders.keys() { + if sender == core { + let fq = util::fq_ident(name, sender); + + stmts.push(quote!( + (0..#cap).for_each(|i| #fq.enqueue_unchecked(i)); + )); + } + } + } + + stmts.push(quote!( + // NOTE(transmute) to avoid debug_assertion in multi-core mode + let mut core: rtfm::export::Peripherals = core::mem::transmute(()); + )); + + let device = extra.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // unmask interrupts and set their priorities + for (&priority, name) in analysis + .interrupts + .get(&core) + .iter() + .flat_map(|interrupts| *interrupts) + .chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + // we do exceptions in another pass + None + } + })) + { + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + // NOTE this also checks that the interrupt exists in the `Interrupt` enumeration + let interrupt = util::interrupt_ident(core, app.args.cores); + stmts.push(quote!( + core.NVIC.set_priority( + #device::#interrupt::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtfm::export::NVIC::unmask(#device::#interrupt::#name);)); + } + + // cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked + // we are ready to receive messages from *other* cores + if analysis.spawn_barriers.contains_key(&core) { + let sb = util::spawn_barrier(core); + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!( + #[rtfm::export::shared] + )) + } else { + None + }; + + const_app.push(quote!( + #shared + static #sb: rtfm::export::Barrier = rtfm::export::Barrier::new(); + )); + + // unblock cores that may send us a message + stmts.push(quote!( + #sb.release(); + )); + } + + // set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::#name, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + // initialize the SysTick + if let Some(tq) = analysis.timer_queues.get(&core) { + let priority = tq.priority; + + // compile time assert that this priority is supported by the device + stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];)); + + stmts.push(quote!(core.SCB.set_priority( + rtfm::export::SystemHandler::SysTick, + rtfm::export::logical2hw(#priority, #nvic_prio_bits), + );)); + + stmts.push(quote!( + core.SYST.set_clock_source(rtfm::export::SystClkSource::Core); + core.SYST.enable_counter(); + core.DCB.enable_trace(); + )); + } + + // if there's no user `#[idle]` then optimize returning from interrupt handlers + if app.idles.get(&core).is_none() { + // Set SLEEPONEXIT bit to enter sleep mode when returning from ISR + stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);)); + } + + // cross-spawn barriers: wait until other cores are ready to receive messages + for (&receiver, senders) in &analysis.spawn_barriers { + // only block here if `init` can send messages to `receiver` + if senders.get(&core) == Some(&true) { + let sb = util::spawn_barrier(receiver); + + stmts.push(quote!( + #sb.wait(); + )); + } + } + + (const_app, stmts) +} diff --git a/macros/src/codegen/resources.rs b/macros/src/codegen/resources.rs new file mode 100644 index 0000000000..bec46020e0 --- /dev/null +++ b/macros/src/codegen/resources.rs @@ -0,0 +1,125 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ + analyze::{Location, Ownership}, + ast::App, +}; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates `static [mut]` variables and resource proxies +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app -- the `static [mut]` variables behind the proxies + Vec, + // mod_resources -- the `resources` module + TokenStream2, +) { + let mut const_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res, expr, loc) in app.resources(analysis) { + let cfgs = &res.cfgs; + let ty = &res.ty; + + { + let (loc_attr, section) = match loc { + Location::Owned { + core, + cross_initialized: false, + } => ( + util::cfg_core(*core, app.args.cores), + util::link_section("data", *core), + ), + + // shared `static`s and cross-initialized resources need to be in `.shared` memory + _ => ( + if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }, + None, + ), + }; + + let (ty, expr) = if let Some(expr) = expr { + (quote!(#ty), quote!(#expr)) + } else { + ( + quote!(core::mem::MaybeUninit<#ty>), + quote!(core::mem::MaybeUninit::uninit()), + ) + }; + + let attrs = &res.attrs; + const_app.push(quote!( + #[allow(non_upper_case_globals)] + #(#attrs)* + #(#cfgs)* + #loc_attr + #section + static mut #name: #ty = #expr; + )); + } + + if let Some(Ownership::Contended { ceiling }) = analysis.ownerships.get(name) { + let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores); + + mod_resources.push(quote!( + #[allow(non_camel_case_types)] + #(#cfgs)* + #cfg_core + pub struct #name<'a> { + priority: &'a Priority, + } + + #(#cfgs)* + #cfg_core + impl<'a> #name<'a> { + #[inline(always)] + pub unsafe fn new(priority: &'a Priority) -> Self { + #name { priority } + } + + #[inline(always)] + pub unsafe fn priority(&self) -> &Priority { + self.priority + } + } + )); + + let ptr = if expr.is_none() { + quote!(#name.as_mut_ptr()) + } else { + quote!(&mut #name) + }; + + const_app.push(util::impl_mutex( + extra, + cfgs, + cfg_core.as_ref(), + true, + name, + quote!(#ty), + *ceiling, + ptr, + )); + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod resources { + use rtfm::export::Priority; + + #(#mod_resources)* + }) + }; + + (const_app, mod_resources) +} diff --git a/macros/src/codegen/resources_struct.rs b/macros/src/codegen/resources_struct.rs new file mode 100644 index 0000000000..07a60616fa --- /dev/null +++ b/macros/src/codegen/resources_struct.rs @@ -0,0 +1,182 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{analyze::Analysis, codegen::util}; + +pub fn codegen( + ctxt: Context, + priority: u8, + needs_lt: &mut bool, + app: &App, + analysis: &Analysis, +) -> (TokenStream2, TokenStream2) { + let mut lt = None; + + let resources = match ctxt { + Context::Init(core) => &app.inits[&core].args.resources, + Context::Idle(core) => &app.idles[&core].args.resources, + Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + let mut has_cfgs = false; + + for (name, access) in resources { + let (res, expr) = app.resource(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + has_cfgs |= !cfgs.is_empty(); + + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; + let ty = &res.ty; + + if ctxt.is_init() { + if !analysis.ownerships.contains_key(name) { + // owned by `init` + fields.push(quote!( + #(#cfgs)* + pub #name: &'static #mut_ #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } else { + // owned by someone else + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a mut #ty + )); + + values.push(quote!( + #(#cfgs)* + #name: &mut #name + )); + } + } else { + let ownership = &analysis.ownerships[name]; + + if ownership.needs_lock(priority) { + if mut_.is_none() { + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: &'a #ty + )); + } else { + // resource proxy + lt = Some(quote!('a)); + + fields.push(quote!( + #(#cfgs)* + pub #name: resources::#name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: resources::#name::new(priority) + + )); + + continue; + } + } else { + let lt = if ctxt.runs_once() { + quote!('static) + } else { + lt = Some(quote!('a)); + quote!('a) + }; + + if ownership.is_owned() || mut_.is_none() { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt #mut_ #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + pub #name: &#lt mut #ty + )); + } + } + + let is_late = expr.is_none(); + if is_late { + let expr = if mut_.is_some() { + quote!(&mut *#name.as_mut_ptr()) + } else { + quote!(&*#name.as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } else { + values.push(quote!( + #(#cfgs)* + #name: &#mut_ #name + )); + } + } + } + + if lt.is_some() { + *needs_lt = true; + + // the struct could end up empty due to `cfg`s leading to an error due to `'a` being unused + if has_cfgs { + fields.push(quote!( + #[doc(hidden)] + pub __marker__: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__marker__: core::marker::PhantomData)) + } + } + + let core = ctxt.core(app); + let cores = app.args.cores; + let cfg_core = util::cfg_core(core, cores); + let doc = format!("Resources `{}` has access to", ctxt.ident(app)); + let ident = util::resources_ident(ctxt, app); + let item = quote!( + #cfg_core + #[allow(non_snake_case)] + #[doc = #doc] + pub struct #ident<#lt> { + #(#fields,)* + } + ); + + let arg = if ctxt.is_init() { + None + } else { + Some(quote!(priority: &#lt rtfm::export::Priority)) + }; + let constructor = quote!( + #cfg_core + impl<#lt> #ident<#lt> { + #[inline(always)] + unsafe fn new(#arg) -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/macros/src/codegen/schedule.rs b/macros/src/codegen/schedule.rs new file mode 100644 index 0000000000..8cf6098501 --- /dev/null +++ b/macros/src/codegen/schedule.rs @@ -0,0 +1,99 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + check::Extra, + codegen::{schedule_body, util}, +}; + +/// Generates all `${ctxt}::Schedule` methods +pub fn codegen(app: &App, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (scheduler, schedulees) in app.schedule_callers() { + let m = extra.monotonic(); + let instant = quote!(<#m as rtfm::Monotonic>::Instant); + + let sender = scheduler.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in schedulees { + let schedulee = &app.software_tasks[name]; + let cfgs = &schedulee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs); + let args = &args; + + if scheduler.is_init() { + // `init` uses a special `schedule` implementation; it doesn't use the + // `schedule_${name}` functions which are shared by other contexts + + let body = schedule_body::codegen(scheduler, &name, app); + + let section = util::link_section("text", sender); + methods.push(quote!( + #(#cfgs)* + #section + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + #body + } + )); + } else { + let schedule = util::schedule_ident(name, sender); + + if !seen.contains(name) { + // generate a `schedule_${name}_S${sender}` function + seen.insert(name); + + let body = schedule_body::codegen(scheduler, &name, app); + + let section = util::link_section("text", sender); + items.push(quote!( + #cfg_sender + #(#cfgs)* + #section + unsafe fn #schedule( + priority: &rtfm::export::Priority, + instant: #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> { + unsafe { + #schedule(self.priority(), instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if scheduler.is_init() { + None + } else { + Some(quote!('a)) + }; + + let scheduler = scheduler.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #scheduler::Schedule<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/schedule_body.rs b/macros/src/codegen/schedule_body.rs new file mode 100644 index 0000000000..208fd0b79e --- /dev/null +++ b/macros/src/codegen/schedule_body.rs @@ -0,0 +1,61 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::codegen::util; + +pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 { + let sender = scheduler.core(app); + let schedulee = &app.software_tasks[name]; + let receiver = schedulee.args.core; + + let fq = util::fq_ident(name, sender); + let tq = util::tq_ident(sender); + let (dequeue, enqueue) = if scheduler.is_init() { + (quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);)) + } else { + ( + quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())), + quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));), + ) + }; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs); + let inputs = util::inputs_ident(name, sender); + let t = util::schedule_t_ident(sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + let nr = rtfm::export::NotReady { + instant, + index, + task: #t::#name, + }; + + #enqueue + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000000..be1eb05cb5 --- /dev/null +++ b/macros/src/codegen/software_tasks.rs @@ -0,0 +1,194 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{locals, module, resources_struct, util}, +}; + +pub fn codegen( + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> ( + // const_app_software_tasks -- free queues, buffers and `${task}Resources` constructors + Vec, + // root_software_tasks -- items that must be placed in the root of the crate: + // - `${task}Locals` structs + // - `${task}Resources` structs + // - `${task}` modules + Vec, + // user_software_tasks -- the `#[task]` functions written by the user + Vec, +) { + let mut const_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.software_tasks { + let receiver = task.args.core; + + let inputs = &task.inputs; + let (_, _, _, input_ty) = util::regroup_inputs(inputs); + + let cap = task.args.capacity; + let cap_lit = util::capacity_literal(cap); + let cap_ty = util::capacity_typenum(cap, true); + + // create free queues and inputs / instants buffers + if let Some(free_queues) = analysis.free_queues.get(name) { + for (&sender, &ceiling) in free_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let fq = util::fq_ident(name, sender); + + let (loc, fq_ty, fq_expr, bss, mk_uninit): ( + _, + _, + _, + _, + Box Option<_>>, + ) = if receiver == sender { + ( + cfg_sender.clone(), + quote!(rtfm::export::SCFQ<#cap_ty>), + quote!(rtfm::export::Queue(unsafe { + rtfm::export::iQueue::u8_sc() + })), + util::link_section("bss", sender), + Box::new(|| util::link_section_uninit(Some(sender))), + ) + } else { + let shared = if cfg!(feature = "heterogeneous") { + Some(quote!(#[rtfm::export::shared])) + } else { + None + }; + + ( + shared, + quote!(rtfm::export::MCFQ<#cap_ty>), + quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())), + None, + Box::new(|| util::link_section_uninit(None)), + ) + }; + let loc = &loc; + + const_app.push(quote!( + /// Queue version of a free-list that keeps track of empty slots in + /// the following buffers + #loc + #bss + static mut #fq: #fq_ty = #fq_expr; + )); + + // Generate a resource proxy if needed + if let Some(ceiling) = ceiling { + const_app.push(quote!( + #cfg_sender + struct #fq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + const_app.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &fq, + fq_ty, + ceiling, + quote!(&mut #fq), + )); + } + + let ref elems = (0..cap) + .map(|_| quote!(core::mem::MaybeUninit::uninit())) + .collect::>(); + + if app.uses_schedule(receiver) { + let m = extra.monotonic(); + let instants = util::instants_ident(name, sender); + + let uninit = mk_uninit(); + const_app.push(quote!( + #loc + #uninit + /// Buffer that holds the instants associated to the inputs of a task + static mut #instants: + [core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] = + [#(#elems,)*]; + )); + } + + let uninit = mk_uninit(); + let inputs = util::inputs_ident(name, sender); + const_app.push(quote!( + #loc + #uninit + /// Buffer that holds the inputs of a task + static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] = + [#(#elems,)*]; + )); + } + } + + // `${task}Resources` + let mut needs_lt = false; + if !task.args.resources.is_empty() { + let (item, constructor) = resources_struct::codegen( + Context::SoftwareTask(name), + task.args.priority, + &mut needs_lt, + app, + analysis, + ); + + root.push(item); + + const_app.push(constructor); + } + + // `${task}Locals` + let mut locals_pat = None; + if !task.locals.is_empty() { + let (struct_, pat) = + locals::codegen(Context::SoftwareTask(name), &task.locals, receiver, app); + + locals_pat = Some(pat); + root.push(struct_); + } + + let cfg_receiver = util::cfg_core(receiver, app.args.cores); + let section = util::link_section("text", receiver); + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let locals_pat = locals_pat.iter(); + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + #cfg_receiver + #section + fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) { + use rtfm::Mutex as _; + + #(#stmts)* + } + )); + + root.push(module::codegen( + Context::SoftwareTask(name), + needs_lt, + app, + extra, + )); + } + + (const_app, root, user_tasks) +} diff --git a/macros/src/codegen/spawn.rs b/macros/src/codegen/spawn.rs new file mode 100644 index 0000000000..c63c410b5d --- /dev/null +++ b/macros/src/codegen/spawn.rs @@ -0,0 +1,131 @@ +use std::collections::{BTreeMap, HashSet}; + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{ + analyze::Analysis, + check::Extra, + codegen::{spawn_body, util}, +}; + +/// Generates all `${ctxt}::Spawn` methods +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + let mut seen = BTreeMap::>::new(); + for (spawner, spawnees) in app.spawn_callers() { + let sender = spawner.core(app); + let cfg_sender = util::cfg_core(sender, app.args.cores); + let seen = seen.entry(sender).or_default(); + let mut methods = vec![]; + + for name in spawnees { + let spawnee = &app.software_tasks[name]; + let receiver = spawnee.args.core; + let cfgs = &spawnee.cfgs; + let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs); + let args = &args; + + if spawner.is_init() { + // `init` uses a special spawn implementation; it doesn't use the `spawn_${name}` + // functions which are shared by other contexts + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + let let_instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(let instant = unsafe { <#m as rtfm::Monotonic>::zero() };)) + } else { + None + }; + + let section = util::link_section("text", sender); + methods.push(quote!( + #(#cfgs)* + #section + fn #name(&self #(,#args)*) -> Result<(), #ty> { + #let_instant + #body + } + )); + } else { + let spawn = util::spawn_ident(name, sender); + + if !seen.contains(name) { + // generate a `spawn_${name}_S${sender}` function + seen.insert(name); + + let instant = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant)) + } else { + None + }; + + let body = spawn_body::codegen(spawner, &name, app, analysis, extra); + + let section = util::link_section("text", sender); + items.push(quote!( + #cfg_sender + #(#cfgs)* + #section + unsafe fn #spawn( + priority: &rtfm::export::Priority + #instant + #(,#args)* + ) -> Result<(), #ty> { + #body + } + )); + } + + let (let_instant, instant) = if app.uses_schedule(receiver) { + let m = extra.monotonic(); + + ( + Some(if spawner.is_idle() { + quote!(let instant = <#m as rtfm::Monotonic>::now();) + } else { + quote!(let instant = self.instant();) + }), + Some(quote!(, instant)), + ) + } else { + (None, None) + }; + + methods.push(quote!( + #(#cfgs)* + #[inline(always)] + fn #name(&self #(,#args)*) -> Result<(), #ty> { + unsafe { + #let_instant + #spawn(self.priority() #instant #(,#untupled)*) + } + } + )); + } + } + + let lt = if spawner.is_init() { + None + } else { + Some(quote!('a)) + }; + + let spawner = spawner.ident(app); + debug_assert!(!methods.is_empty()); + items.push(quote!( + #cfg_sender + impl<#lt> #spawner::Spawn<#lt> { + #(#methods)* + } + )); + } + + items +} diff --git a/macros/src/codegen/spawn_body.rs b/macros/src/codegen/spawn_body.rs new file mode 100644 index 0000000000..98bce07441 --- /dev/null +++ b/macros/src/codegen/spawn_body.rs @@ -0,0 +1,82 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::{ast::App, Context}; +use syn::Ident; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +pub fn codegen( + spawner: Context, + name: &Ident, + app: &App, + analysis: &Analysis, + extra: &Extra, +) -> TokenStream2 { + let sender = spawner.core(app); + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let receiver = spawnee.args.core; + + let write_instant = if app.uses_schedule(receiver) { + let instants = util::instants_ident(name, sender); + + Some(quote!( + #instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant); + )) + } else { + None + }; + + let t = util::spawn_t_ident(receiver, priority, sender); + let fq = util::fq_ident(name, sender); + let rq = util::rq_ident(receiver, priority, sender); + let (dequeue, enqueue) = if spawner.is_init() { + ( + quote!(#fq.dequeue()), + quote!(#rq.enqueue_unchecked((#t::#name, index));), + ) + } else { + ( + quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))), + quote!((#rq { priority }.lock(|rq| { + rq.split().0.enqueue_unchecked((#t::#name, index)) + }));), + ) + }; + + let device = extra.device; + let enum_ = util::interrupt_ident(receiver, app.args.cores); + let interrupt = &analysis.interrupts[&receiver][&priority]; + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::#enum_::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::#enum_::#interrupt); + ) + }; + + let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs); + let inputs = util::inputs_ident(name, sender); + quote!( + unsafe { + use rtfm::Mutex as _; + + let input = #tupled; + if let Some(index) = #dequeue { + #inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input); + + #write_instant + + #enqueue + + #pend + + Ok(()) + } else { + Err(input) + } + } + ) +} diff --git a/macros/src/codegen/timer_queue.rs b/macros/src/codegen/timer_queue.rs new file mode 100644 index 0000000000..3af7e432aa --- /dev/null +++ b/macros/src/codegen/timer_queue.rs @@ -0,0 +1,153 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rtfm_syntax::ast::App; + +use crate::{analyze::Analysis, check::Extra, codegen::util}; + +/// Generates timer queues and timer queue handlers +pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec { + let mut items = vec![]; + + for (&sender, timer_queue) in &analysis.timer_queues { + let cfg_sender = util::cfg_core(sender, app.args.cores); + let t = util::schedule_t_ident(sender); + + // Enumeration of `schedule`-able tasks + { + let variants = timer_queue + .tasks + .iter() + .map(|name| { + let cfgs = &app.software_tasks[name].cfgs; + + quote!( + #(#cfgs)* + #name + ) + }) + .collect::>(); + + let doc = format!("Tasks that can be scheduled from core #{}", sender); + items.push(quote!( + #cfg_sender + #[doc = #doc] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy)] + enum #t { + #(#variants,)* + } + )); + } + + let tq = util::tq_ident(sender); + + // Static variable and resource proxy + { + let doc = format!("Core #{} timer queue", sender); + let m = extra.monotonic(); + let n = util::capacity_typenum(timer_queue.capacity, false); + let tq_ty = quote!(rtfm::export::TimerQueue<#m, #t, #n>); + + let section = util::link_section("bss", sender); + items.push(quote!( + #cfg_sender + #[doc = #doc] + #section + static mut #tq: #tq_ty = rtfm::export::TimerQueue( + rtfm::export::BinaryHeap( + rtfm::export::iBinaryHeap::new() + ) + ); + + #cfg_sender + struct #tq<'a> { + priority: &'a rtfm::export::Priority, + } + )); + + items.push(util::impl_mutex( + extra, + &[], + cfg_sender.as_ref(), + false, + &tq, + tq_ty, + timer_queue.ceiling, + quote!(&mut #tq), + )); + } + + // Timer queue handler + { + let device = extra.device; + let arms = timer_queue + .tasks + .iter() + .map(|name| { + let task = &app.software_tasks[name]; + + let cfgs = &task.cfgs; + let priority = task.args.priority; + let receiver = task.args.core; + let rq = util::rq_ident(receiver, priority, sender); + let rqt = util::spawn_t_ident(receiver, priority, sender); + let enum_ = util::interrupt_ident(receiver, app.args.cores); + let interrupt = &analysis.interrupts[&receiver][&priority]; + + let pend = if sender != receiver { + quote!( + #device::xpend(#receiver, #device::#enum_::#interrupt); + ) + } else { + quote!( + rtfm::pend(#device::#enum_::#interrupt); + ) + }; + + quote!( + #(#cfgs)* + #t::#name => { + (#rq { priority: &rtfm::export::Priority::new(PRIORITY) }).lock(|rq| { + rq.split().0.enqueue_unchecked((#rqt::#name, index)) + }); + + #pend + } + ) + }) + .collect::>(); + + let priority = timer_queue.priority; + let sys_tick = util::suffixed("SysTick", sender); + let section = util::link_section("text", sender); + items.push(quote!( + #[no_mangle] + #cfg_sender + #section + unsafe fn #sys_tick() { + use rtfm::Mutex as _; + + /// The priority of this handler + const PRIORITY: u8 = #priority; + + rtfm::export::run(PRIORITY, || { + while let Some((task, index)) = (#tq { + // NOTE dynamic priority is always the static priority at this point + priority: &rtfm::export::Priority::new(PRIORITY), + }) + // NOTE `inline(always)` produces faster and smaller code + .lock(#[inline(always)] + |tq| tq.dequeue()) + { + match task { + #(#arms)* + } + } + }); + } + )); + } + } + + items +} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs new file mode 100644 index 0000000000..207272dc90 --- /dev/null +++ b/macros/src/codegen/util.rs @@ -0,0 +1,325 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; + +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use rtfm_syntax::{ast::App, Context, Core}; +use syn::{Attribute, Ident, LitInt, PatType}; + +use crate::check::Extra; + +/// Turns `capacity` into an unsuffixed integer literal +pub fn capacity_literal(capacity: u8) -> LitInt { + LitInt::new(&capacity.to_string(), Span::call_site()) +} + +/// Turns `capacity` into a type-level (`typenum`) integer +pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 { + let capacity = if round_up_to_power_of_two { + capacity.checked_next_power_of_two().expect("UNREACHABLE") + } else { + capacity + }; + + let ident = Ident::new(&format!("U{}", capacity), Span::call_site()); + + quote!(rtfm::export::consts::#ident) +} + +/// Generates a `#[cfg(core = "0")]` attribute if we are in multi-core mode +pub fn cfg_core(core: Core, cores: u8) -> Option { + if cores == 1 { + None + } else if cfg!(feature = "heterogeneous") { + let core = core.to_string(); + Some(quote!(#[cfg(core = #core)])) + } else { + None + } +} + +/// Identifier for the free queue +/// +/// There may be more than one free queue per task because we need one for each sender core so we +/// include the sender (e.g. `S0`) in the name +pub fn fq_ident(task: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("{}_S{}_FQ", task.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + extra: &Extra, + cfgs: &[Attribute], + cfg_core: Option<&TokenStream2>, + resources_prefix: bool, + name: &Ident, + ty: TokenStream2, + ceiling: u8, + ptr: TokenStream2, +) -> TokenStream2 { + let (path, priority) = if resources_prefix { + (quote!(resources::#name), quote!(self.priority())) + } else { + (quote!(#name), quote!(self.priority)) + }; + + let device = extra.device; + quote!( + #(#cfgs)* + #cfg_core + impl<'a> rtfm::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtfm::export::lock( + #ptr, + #priority, + CEILING, + #device::NVIC_PRIO_BITS, + f, + ) + } + } + } + ) +} + +/// Generates an identifier for a cross-initialization barrier +pub fn init_barrier(initializer: Core) -> Ident { + Ident::new(&format!("IB{}", initializer), Span::call_site()) +} + +/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API) +pub fn inputs_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INPUTS", task, sender), Span::call_site()) +} + +/// Generates an identifier for the `INSTANTS` buffer (`schedule` API) +pub fn instants_ident(task: &Ident, sender: Core) -> Ident { + Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site()) +} + +pub fn interrupt_ident(core: Core, cores: u8) -> Ident { + let span = Span::call_site(); + if cores == 1 { + Ident::new("Interrupt", span) + } else { + Ident::new(&format!("Interrupt_{}", core), span) + } +} + +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + match &*s { + "MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall" + | "DebugMonitor" | "PendSV" | "SysTick" => true, + + _ => false, + } +} + +/// Generates a pre-reexport identifier for the "late resources" struct +pub fn late_resources_ident(init: &Ident) -> Ident { + Ident::new( + &format!("{}LateResources", init.to_string()), + Span::call_site(), + ) +} + +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +pub fn link_section(section: &str, core: Core) -> Option { + if cfg!(feature = "homogeneous") { + let section = format!(".{}_{}.rtfm{}", section, core, link_section_index()); + Some(quote!(#[link_section = #section])) + } else { + None + } +} + +// NOTE `None` means in shared memory +pub fn link_section_uninit(core: Option) -> Option { + let section = if let Some(core) = core { + let index = link_section_index(); + + if cfg!(feature = "homogeneous") { + format!(".uninit_{}.rtfm{}", core, index) + } else { + format!(".uninit.rtfm{}", index) + } + } else { + if cfg!(feature = "heterogeneous") { + // `#[shared]` attribute sets the linker section + return None; + } + + format!(".uninit.rtfm{}", link_section_index()) + }; + + Some(quote!(#[link_section = #section])) +} + +/// Generates a pre-reexport identifier for the "locals" struct +pub fn locals_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Locals"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a rendezvous barrier +pub fn rendezvous_ident(core: Core) -> Ident { + Ident::new(&format!("RV{}", core), Span::call_site()) +} + +// Regroups the inputs of a task +// +// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Generates a pre-reexport identifier for the "resources" struct +pub fn resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init(core) => app.inits[&core].name.to_string(), + Context::Idle(core) => app.idles[&core].name.to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("Resources"); + + Ident::new(&s, Span::call_site()) +} + +/// Generates an identifier for a ready queue +/// +/// Each core may have several task dispatchers, one for each priority level. Each task dispatcher +/// in turn may use more than one ready queue because the queues are SPSC queues so one is needed +/// per sender core. +pub fn rq_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_RQ", receiver, priority, sender), + Span::call_site(), + ) +} + +/// Generates an identifier for a "schedule" function +/// +/// The methods of the `Schedule` structs invoke these functions. As one task may be `schedule`-ed +/// by different cores we need one "schedule" function per possible task-sender pair +pub fn schedule_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("schedule_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `schedule`-able tasks +pub fn schedule_t_ident(core: Core) -> Ident { + Ident::new(&format!("T{}", core), Span::call_site()) +} + +/// Generates an identifier for a cross-spawn barrier +pub fn spawn_barrier(receiver: Core) -> Ident { + Ident::new(&format!("SB{}", receiver), Span::call_site()) +} + +/// Generates an identifier for a "spawn" function +/// +/// The methods of the `Spawn` structs invoke these functions. As one task may be `spawn`-ed by +/// different cores we need one "spawn" function per possible task-sender pair +pub fn spawn_ident(name: &Ident, sender: Core) -> Ident { + Ident::new( + &format!("spawn_{}_S{}", name.to_string(), sender), + Span::call_site(), + ) +} + +/// Generates an identifier for the `enum` of `spawn`-able tasks +/// +/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue +/// for each of these `T` enums +pub fn spawn_t_ident(receiver: Core, priority: u8, sender: Core) -> Ident { + Ident::new( + &format!("R{}_P{}_S{}_T", receiver, priority, sender), + Span::call_site(), + ) +} + +pub fn suffixed(name: &str, core: u8) -> Ident { + let span = Span::call_site(); + + if cfg!(feature = "homogeneous") { + Ident::new(&format!("{}_{}", name, core), span) + } else { + Ident::new(name, span) + } +} + +/// Generates an identifier for a timer queue +/// +/// At most there's one timer queue per core +pub fn tq_ident(core: Core) -> Ident { + Ident::new(&format!("TQ{}", core), Span::call_site()) +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 736289cb4b..fdde5c6933 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,17 +1,17 @@ #![deny(warnings)] -#![recursion_limit = "128"] extern crate proc_macro; use proc_macro::TokenStream; use std::{fs, path::Path}; -use syn::parse_macro_input; +use rtfm_syntax::Settings; mod analyze; mod check; mod codegen; -mod syntax; +#[cfg(test)] +mod tests; /// Attribute used to declare a RTFM application /// @@ -22,104 +22,99 @@ mod syntax; /// The `app` attribute has one mandatory argument: /// /// - `device = `. The path must point to a device crate generated using [`svd2rust`] -/// **v0.14.x**. +/// **v0.14.x** or newer. /// /// [`svd2rust`]: https://crates.io/crates/svd2rust /// +/// and a few optional arguments: +/// +/// - `peripherals = `. Indicates whether the runtime takes the device peripherals and makes +/// them available to the `init` context. +/// +/// - `monotonic = `. This is a path to a zero-sized structure (e.g. `struct Foo;`) that +/// implements the `Monotonic` trait. This argument must be provided to use the `schedule` API. +/// /// The items allowed in the block value of the `const` item are specified below: /// -/// # 1. `static [mut]` variables +/// # 1. `struct Resources` /// -/// These variables are used as *resources*. Resources can be owned by tasks or shared between them. -/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared) -/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut` -/// reference to a `static mut` resource shared with higher priority tasks. -/// -/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock -/// -/// `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. -/// -/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html -/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html -/// -/// 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 `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. -/// -/// [`Mutex`]: ../rtfm/trait.Mutex.html +/// This structure contains the declaration of all the resources used by the application. Each field +/// in this structure corresponds to a different resource. Each resource may optionally be given an +/// initial value using the `#[init()]` attribute. Resources with no compile-time initial +/// value as referred to as *late* resources. /// /// # 2. `fn` /// -/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`, -/// `exception` or `task`. The attribute defines the role of the function in the application. +/// Functions must contain *one* of the following attributes: `init`, `idle` or `task`. The +/// attribute defines the role of the function in the application. /// /// ## a. `#[init]` /// /// This attribute indicates that the function is to be used as the *initialization function*. There /// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The -/// signature of the `init` function must be `[unsafe] fn ()`. +/// signature of the `init` function must be `fn (::Context) [-> ::LateResources]` +/// where `` is the name of the function adorned with the `#[init]` attribute. /// /// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled. /// Interrupts are re-enabled after `init` returns. /// /// The `init` attribute accepts the following optional arguments: /// -/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has +/// - `resources = [resource_a, resource_b, ..]`. This is the list of resources this context has /// access to. /// -/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can -/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue` -/// feature has been enabled. +/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can +/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `monotonic` +/// argument is passed to the `#[app]` attribute. /// -/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can +/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this context can /// immediately spawn. /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: +/// The first argument of the function, `::Context`, is a structure that contains the +/// following fields: /// -/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for -/// more details. +/// - `core`. Exclusive access to core peripherals. The type of this field is [`rtfm::Peripherals`] +/// when the `schedule` API is used and [`cortex_m::Peripherals`] when it's not. /// /// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html +/// [`cortex_m::Peripherals`]: https://docs.rs/cortex-m/0.6/cortex_m/peripheral/struct.Peripherals.html /// -/// - `device: ::Peripherals`. Exclusive access to device-specific peripherals. -/// `` is the path to the device crate declared in the top `app` attribute. +/// - `device: ::Peripherals`. Exclusive access to device-specific peripherals. This +/// field is only present when the `peripherals` argument of the `#[app]` attribute is set to +/// `true`. `` is the path to the device crate specified in the top `app` attribute. /// -/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**: -/// only present if the `timer-queue` feature is enabled. +/// - `start: `. The `start` time of the system: `::zero()`. `` is the +/// `Instant` type associated to the `Monotonic` implementation specified in the top `#[app]` +/// attribute. **NOTE**: this field is only present when the `schedule` is used. /// -/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function. -/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy -/// (`impl Mutex`). +/// - `resources: ::Resources`. A `struct` that contains all the resources that can be +/// accessed from this context. Each field is a different resource; each resource may appear as a +/// reference (`&[mut]-`) or as proxy structure that implements the [`rftm::Mutex`] trait. /// -/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks. -/// **NOTE**: only present if the `timer-queue` feature is enabled. +/// [`rtfm::Mutex`]: ../rtfm/trait.Mutex.html /// -/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks. +/// - `schedule: ::Schedule`. A `struct` that can be used to schedule *software* tasks. /// -/// Other properties / constraints: +/// - `spawn: ::Spawn`. A `struct` that can be used to spawn *software* tasks. /// -/// - The `init` function can **not** be called from software. +/// The return type `::LateResources` must only be specified when late resources, resources +/// with no initial value declared at compile time, are used. `::LateResources` is a +/// structure where each field corresponds to a different late resource. The +/// `::LateResources` value returned by the `#[init]` function is used to initialize the +/// late resources before `idle` or any task can start. +/// +/// Other properties: /// /// - The `static mut` variables declared at the beginning of this function will be transformed into /// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will /// become `FOO: &'static mut u32`. /// -/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late* -/// resources. -/// /// ## b. `#[idle]` /// /// This attribute indicates that the function is to be used as the *idle task*. There can be at /// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the -/// `idle` function must be `fn() -> !`. +/// `idle` function must be `fn(::Context) -> !` where `` is the name of the +/// function adorned with the `#[idle]` attribute. /// /// The `idle` task is a special task that always runs in the background. The `idle` task runs at /// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the @@ -135,38 +130,37 @@ mod syntax; /// /// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: +/// The first argument of the function, `idle::Context`, is a structure that contains the following +/// fields: /// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). +/// - `resources: _`. Same meaning / function as [`::Context.resources`](#a-init). /// -/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// - `schedule: idle::Schedule`. Same meaning / function as [`::Context.schedule`](#a-init). /// -/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init). +/// - `spawn: idle::Spawn`. Same meaning / function as [`::Context.spawn`](#a-init). /// -/// Other properties / constraints: -/// -/// - The `idle` function can **not** be called from software. +/// Other properties: /// /// - The `static mut` variables declared at the beginning of this function will be transformed into /// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will /// become `FOO: &'static mut u32`. /// -/// ## c. `#[exception]` +/// ## c. `#[task]` /// -/// 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()`. +/// This attribute indicates that the function is either a hardware task or a software task. The +/// signature of hardware tasks must be `fn(::Context)` whereas the signature of software +/// tasks must be `fn(::Context, )`. `` refers to the name of the function +/// adorned with the `#[task]` attribute. /// -/// The name of the function must match one of the Cortex-M exceptions that has [configurable -/// priority][system-handler]. +/// The `task` attribute accepts the following optional arguments. /// -/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html -/// -/// The `exception` attribute accepts the following optional arguments. +/// - `binds = `. Binds this task to a particular interrupt. When this argument is +/// present the task is treated as a hardware task; when it's omitted the task treated is treated as +/// a software task. /// /// - `priority = `. 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 +/// the device crate specified in the top `app` attribute. If this argument is omitted the priority /// is assumed to be 1. /// /// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). @@ -175,105 +169,26 @@ mod syntax; /// /// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). /// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: +/// The first argument of the function, `::Context`, is a structure that contains the +/// following fields: /// -/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only -/// present if the `timer-queue` feature is enabled. +/// - `start: `. For hardware tasks this is the time at which this handler started +/// executing. For software tasks this is the time at which the task was scheduled to run. **NOTE**: +/// only present when the `schedule` API is used. /// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). +/// - `resources: _`. Same meaning / function as [`::Context.resources`](#a-init). /// -/// - `schedule: ::Schedule`. Same meaning / function as [`init.schedule`](#a-init). +/// - `schedule: ::Schedule`. Same meaning / function as +/// [`::Context.schedule`](#a-init). /// -/// - `spawn: ::Spawn`. Same meaning / function as [`init.spawn`](#a-init). +/// - `spawn: ::Spawn`. Same meaning / function as +/// [`::Context.spawn`](#a-init). /// /// Other properties / constraints: /// -/// - `exception` handlers can **not** be called from software. -/// /// - The `static mut` variables declared at the beginning of this function will be transformed into -/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will -/// become `FOO: &mut u32`. -/// -/// ## d. `#[interrupt]` -/// -/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of -/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`. -/// -/// The name of the function must match one of the device specific interrupts. See your device crate -/// documentation (`Interrupt` enum) for more details. -/// -/// The `interrupt` attribute accepts the following optional arguments. -/// -/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception). -/// -/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init). -/// -/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init). -/// -/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init). -/// -/// The `app` attribute will injected a *context* into this function that comprises the following -/// variables: -/// -/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception). -/// -/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init). -/// -/// - `schedule: ::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`. +/// *non*-static `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` +/// will become `FOO: &mut u32`. /// /// # 3. `extern` block /// @@ -284,29 +199,30 @@ mod syntax; /// This `extern` block must only contain functions with signature `fn ()`. The names of these /// functions must match the names of the target device interrupts. /// -/// Importantly, attributes can be applied to the functions inside this block. These attributes will -/// be forwarded to the interrupt handlers generated by the `app` attribute. +/// Attributes can be applied to the functions inside this block. These attributes will be forwarded +/// to the interrupt handlers generated by the `app` attribute. #[proc_macro_attribute] pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - // Parse - let args = parse_macro_input!(args as syntax::AppArgs); - let input = parse_macro_input!(input as syntax::Input); + let mut settings = Settings::default(); + settings.optimize_priorities = true; + settings.parse_binds = true; + settings.parse_cores = cfg!(feature = "heterogeneous") || cfg!(feature = "homogeneous"); + settings.parse_extern_interrupt = true; + settings.parse_schedule = true; - let app = match syntax::App::parse(input.items, args) { + let (app, analysis) = match rtfm_syntax::parse(args, input, settings) { Err(e) => return e.to_compile_error().into(), - Ok(app) => app, + Ok(x) => x, }; - // Check the specification - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } + let extra = match check::app(&app, &analysis) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; - // Ceiling analysis - let analysis = analyze::app(&app); + let analysis = analyze::app(analysis, &app); - // Code generation - let ts = codegen::app(&input.ident, &app, &analysis); + let ts = codegen::app(&app, &analysis, &extra); // Try to write the expanded code to disk if Path::new("target").exists() { diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index c6814d5f25..0000000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,1382 +0,0 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - iter, u8, -}; - -use proc_macro2::Span; -use syn::{ - braced, bracketed, parenthesized, - parse::{self, Parse, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - token::Brace, - ArgCaptured, AttrStyle, Attribute, Expr, FnArg, ForeignItem, Ident, IntSuffix, Item, ItemFn, - ItemForeignMod, ItemStatic, LitInt, Pat, Path, PathArguments, 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], - pub 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 = BTreeMap::new(); - let mut interrupts = BTreeMap::new(); - let mut resources = BTreeMap::new(); - let mut tasks = BTreeMap::new(); - let mut free_interrupts = None; - - for item in items { - match item { - Item::Fn(mut item) => { - if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "idle")) { - if idle.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[idle]` function must appear at most once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - idle = Some(Idle::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "init")) { - if init.is_some() { - return Err(parse::Error::new( - item.span(), - "`#[init]` function must appear exactly once", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - init = Some(Init::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "exception")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - exceptions.insert(item.ident.clone(), Exception::check(args, item)?); - } else if let Some(pos) = - item.attrs.iter().position(|attr| eq(attr, "interrupt")) - { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - interrupts.insert(item.ident.clone(), Interrupt::check(args, item)?); - } else if let Some(pos) = item.attrs.iter().position(|attr| eq(attr, "task")) { - if exceptions.contains_key(&item.ident) - || interrupts.contains_key(&item.ident) - || tasks.contains_key(&item.ident) - { - return Err(parse::Error::new( - item.ident.span(), - "this task is defined multiple times", - )); - } - - let args = syn::parse2(item.attrs.swap_remove(pos).tts)?; - - tasks.insert(item.ident.clone(), Task::check(args, item)?); - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - Item::Static(item) => { - if resources.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this resource is listed twice", - )); - } - - resources.insert(item.ident.clone(), Resource::check(item)?); - } - Item::ForeignMod(item) => { - if free_interrupts.is_some() { - return Err(parse::Error::new( - item.abi.extern_token.span(), - "`extern` block can only appear at most once", - )); - } - - free_interrupts = Some(FreeInterrupt::parse(item)?); - } - _ => { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - - Ok(App { - args, - idle, - init: init.ok_or_else(|| { - parse::Error::new(Span::call_site(), "`#[init]` function is missing") - })?, - exceptions, - interrupts, - resources, - tasks, - free_interrupts: free_interrupts.unwrap_or_else(|| FreeInterrupts::new()), - }) - } - - /// Returns an iterator over all resource accesses. - /// - /// Each resource access include the priority it's accessed at (`u8`) and the name of the - /// resource (`Ident`). A resource may appear more than once in this iterator - pub fn resource_accesses(&self) -> impl Iterator { - 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 = BTreeSet; - -pub type Exceptions = BTreeMap; - -pub type Interrupts = BTreeMap; - -pub type Resources = BTreeMap; - -pub type Statics = Vec; - -pub type Tasks = BTreeMap; - -pub type FreeInterrupts = BTreeMap; - -pub struct Idle { - pub args: IdleArgs, - pub attrs: Vec, - pub context: Pat, - pub statics: BTreeMap, - pub stmts: Vec, -} - -pub type IdleArgs = InitArgs; - -impl Idle { - fn check(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_bottom(&item.decl.output); - - let span = item.span(); - - if valid_signature { - if let Some((context, _)) = check_inputs(item.decl.inputs, "idle") { - let (statics, stmts) = extract_statics(item.block.stmts); - - return Ok(Idle { - args, - attrs: item.attrs, - context, - statics: Static::parse(statics)?, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - "`idle` must have type signature `fn(idle::Context) -> !`", - )) - } -} - -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 Init { - pub args: InitArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, - pub returns_late_resources: bool, - pub span: Span, -} - -impl Init { - fn check(args: InitArgs, item: ItemFn) -> parse::Result { - let mut valid_signature = check_signature(&item) && item.decl.inputs.len() == 1; - - const DONT_CARE: bool = false; - - let returns_late_resources = match &item.decl.output { - ReturnType::Default => false, - ReturnType::Type(_, ty) => { - match &**ty { - Type::Tuple(t) => { - if t.elems.is_empty() { - // -> () - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - Type::Path(_) => { - if is_path(ty, &["init", "LateResources"]) { - // -> init::LateResources - true - } else { - valid_signature = false; - - DONT_CARE - } - } - - _ => { - valid_signature = false; - - DONT_CARE - } - } - } - }; - - let span = item.span(); - - if valid_signature { - if let Some((context, _)) = check_inputs(item.decl.inputs, "init") { - let (statics, stmts) = extract_statics(item.block.stmts); - - return Ok(Init { - args, - attrs: item.attrs, - statics: Static::parse(statics)?, - context, - stmts, - returns_late_resources, - span, - }); - } - } - - Err(parse::Error::new( - span, - "`init` must have type signature `fn(init::Context) [-> init::LateResources]`", - )) - } -} - -/// Union of `TaskArgs`, `ExceptionArgs` and `InterruptArgs` -pub struct Args { - pub binds: Option, - pub capacity: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl Default for Args { - fn default() -> Self { - Args { - binds: None, - capacity: None, - priority: 1, - resources: Idents::new(), - schedule: Idents::new(), - spawn: Idents::new(), - } - } -} - -pub struct Exception { - pub args: ExceptionArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub struct ExceptionArgs { - binds: Option, - pub priority: u8, - pub resources: Idents, - pub schedule: Idents, - pub spawn: Idents, -} - -impl ExceptionArgs { - /// Returns the name of the exception / interrupt this handler binds to - pub fn binds<'a>(&'a self, handler: &'a Ident) -> &'a Ident { - self.binds.as_ref().unwrap_or(handler) - } -} - -impl Parse for ExceptionArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ true, /* capacity */ false).map( - |Args { - binds, - priority, - resources, - schedule, - spawn, - .. - }| { - ExceptionArgs { - binds, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -impl Exception { - fn check(args: ExceptionArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - let span = item.span(); - - let name = item.ident.to_string(); - if valid_signature { - if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { - let span = item.ident.span(); - match &*args - .binds - .as_ref() - .map(|ident| ident.to_string()) - .unwrap_or(name) - { - "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); - - return Ok(Exception { - args, - attrs: item.attrs, - statics: Static::parse(statics)?, - context, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - &format!( - "this `exception` handler must have type signature `fn({}::Context)`", - name - ), - )) - } -} - -pub struct Interrupt { - pub args: InterruptArgs, - pub attrs: Vec, - pub statics: BTreeMap, - pub context: Pat, - pub stmts: Vec, -} - -pub type InterruptArgs = ExceptionArgs; - -impl Interrupt { - fn check(args: InterruptArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && item.decl.inputs.len() == 1 && is_unit(&item.decl.output); - - let span = item.span(); - - let name = item.ident.to_string(); - if valid_signature { - if let Some((context, _)) = check_inputs(item.decl.inputs, &name) { - match &*name { - "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); - - return Ok(Interrupt { - args, - attrs: item.attrs, - statics: Static::parse(statics)?, - context, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - format!( - "this `interrupt` handler must have type signature `fn({}::Context)`", - name - ), - )) - } -} - -pub struct Resource { - pub cfgs: Vec, - pub attrs: Vec, - pub mutability: Option, - pub ty: Box, - pub expr: Option>, -} - -impl Resource { - fn check(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 (cfgs, attrs) = extract_cfgs(item.attrs); - - Ok(Resource { - cfgs, - 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 Parse for TaskArgs { - fn parse(input: ParseStream<'_>) -> parse::Result { - parse_args(input, /* binds */ false, /* capacity */ true).map( - |Args { - capacity, - priority, - resources, - schedule, - spawn, - .. - }| { - TaskArgs { - capacity, - priority, - resources, - schedule, - spawn, - } - }, - ) - } -} - -// Parser shared by ExceptionArgs, InterruptArgs and TaskArgs -fn parse_args( - input: ParseStream<'_>, - accepts_binds: bool, - accepts_capacity: bool, -) -> parse::Result { - if input.is_empty() { - return Ok(Args::default()); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut resources = None; - let mut schedule = None; - let mut spawn = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = content.parse()?; - let _: Token![=] = content.parse()?; - - let ident_s = ident.to_string(); - match &*ident_s { - "binds" if accepts_binds => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #ident - let ident = content.parse()?; - - binds = Some(ident); - } - "capacity" if accepts_capacity => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value as u8); - } - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if lit.suffix() != IntSuffix::None { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.value(); - if value > u64::from(u8::MAX) || value == 0 { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - priority = Some(value as u8); - } - "schedule" if cfg!(not(feature = "timer-queue")) => { - return Err(parse::Error::new( - ident.span(), - "The `schedule` API requires that the `timer-queue` feature is \ - enabled in the `cortex-m-rtfm` crate", - )); - } - "resources" | "schedule" | "spawn" => { - // .. [#(#idents)*] - let inner; - bracketed!(inner in content); - let mut idents = Idents::new(); - for ident in inner.call(Punctuated::<_, Token![,]>::parse_terminated)? { - if idents.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "element appears more than once in list", - )); - } - - idents.insert(ident); - } - - match &*ident_s { - "resources" => { - if resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - resources = Some(idents); - } - "schedule" => { - if schedule.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - schedule = Some(idents); - } - "spawn" => { - if spawn.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - spawn = Some(idents); - } - _ => unreachable!(), - } - } - _ => { - return Err(parse::Error::new( - ident.span(), - format!( - "expected one of: {}{}priority, resources, schedule or spawn", - if accepts_binds { "binds, " } else { "" }, - if accepts_capacity { "capacity, " } else { "" }, - ), - )); - } - } - - if content.is_empty() { - break; - } - - // , - let _: Token![,] = content.parse()?; - } - - Ok(Args { - binds, - capacity, - priority: priority.unwrap_or(1), - resources: resources.unwrap_or(Idents::new()), - schedule: schedule.unwrap_or(Idents::new()), - spawn: spawn.unwrap_or(Idents::new()), - }) -} - -pub struct Static { - /// `#[cfg]` attributes - pub cfgs: Vec, - /// Attributes that are not `#[cfg]` - pub attrs: Vec, - pub ty: Box, - pub expr: Box, -} - -impl Static { - fn parse(items: Vec) -> parse::Result> { - let mut statics = BTreeMap::new(); - - for item in items { - if statics.contains_key(&item.ident) { - return Err(parse::Error::new( - item.ident.span(), - "this `static` is listed twice", - )); - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - - statics.insert( - item.ident, - Static { - cfgs, - attrs, - ty: item.ty, - expr: item.expr, - }, - ); - } - - Ok(statics) - } -} - -pub struct Task { - pub args: TaskArgs, - pub cfgs: Vec, - pub attrs: Vec, - pub inputs: Vec, - pub context: Pat, - pub statics: BTreeMap, - pub stmts: Vec, -} - -impl Task { - fn check(args: TaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = - check_signature(&item) && !item.decl.inputs.is_empty() && is_unit(&item.decl.output); - - let span = item.span(); - - let name = item.ident.to_string(); - if valid_signature { - if let Some((context, rest)) = check_inputs(item.decl.inputs, &name) { - let (statics, stmts) = extract_statics(item.block.stmts); - - let inputs = rest.map_err(|arg| { - parse::Error::new( - arg.span(), - "inputs must be named arguments (e.f. `foo: u32`) and not include `self`", - ) - })?; - - match &*name { - "init" | "idle" | "resources" => { - return Err(parse::Error::new( - span, - "`task` handlers can NOT be named `idle`, `init` or `resources`", - )); - } - _ => {} - } - - let (cfgs, attrs) = extract_cfgs(item.attrs); - return Ok(Task { - args, - cfgs, - attrs, - inputs, - context, - statics: Static::parse(statics)?, - stmts, - }); - } - } - - Err(parse::Error::new( - span, - &format!( - "this `task` handler must have type signature `fn({}::Context, ..)`", - name - ), - )) - } -} - -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 - } -} - -fn extract_cfgs(attrs: Vec) -> (Vec, Vec) { - let mut cfgs = vec![]; - let mut not_cfgs = vec![]; - - for attr in attrs { - if eq(&attr, "cfg") { - cfgs.push(attr); - } else { - not_cfgs.push(attr); - } - } - - (cfgs, not_cfgs) -} - -/// Extracts `static mut` vars from the beginning of the given statements -fn extract_statics(stmts: Vec) -> (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) -} - -// checks that the list of arguments has the form `#pat: #name::Context, (..)` -// -// if the check succeeds it returns `#pat` plus the remaining arguments -fn check_inputs( - inputs: Punctuated, - name: &str, -) -> Option<(Pat, Result, FnArg>)> { - let mut inputs = inputs.into_iter(); - - match inputs.next() { - Some(FnArg::Captured(first)) => { - if is_path(&first.ty, &[name, "Context"]) { - let rest = inputs - .map(|arg| match arg { - FnArg::Captured(arg) => Ok(arg), - _ => Err(arg), - }) - .collect::, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -/// checks that a function signature -/// -/// - has no bounds (like where clauses) -/// - is not `async` -/// - is not `const` -/// - is not `unsafe` -/// - is not generic (has no type parametrs) -/// - is not variadic -/// - uses the Rust ABI (and not e.g. "C") -fn check_signature(item: &ItemFn) -> bool { - item.vis == Visibility::Inherited - && item.constness.is_none() - && item.asyncness.is_none() - && item.abi.is_none() - && item.unsafety.is_none() - && item.decl.generics.params.is_empty() - && item.decl.generics.where_clause.is_none() - && item.decl.variadic.is_none() -} - -fn is_path(ty: &Type, segments: &[&str]) -> bool { - match ty { - Type::Path(tpath) if tpath.qself.is_none() => { - tpath.path.segments.len() == segments.len() - && tpath - .path - .segments - .iter() - .zip(segments) - .all(|(lhs, rhs)| lhs.ident == **rhs) - } - - _ => false, - } -} - -fn is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Never(_) = **ty { - true - } else { - false - } - } else { - false - } -} - -fn is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs new file mode 100644 index 0000000000..470c9058f2 --- /dev/null +++ b/macros/src/tests.rs @@ -0,0 +1,5 @@ +// NOTE these tests are specific to the Cortex-M port; `rtfm-syntax` has a more extensive test suite +// that tests functionality common to all the RTFM ports + +mod multi; +mod single; diff --git a/macros/src/tests/multi.rs b/macros/src/tests/multi.rs new file mode 100644 index 0000000000..b55c451f48 --- /dev/null +++ b/macros/src/tests/multi.rs @@ -0,0 +1,59 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let mut settings = Settings::default(); + settings.parse_cores = true; + settings.parse_extern_interrupt = true; + + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac, cores = 2), + quote!( + const APP: () = { + #[task(core = 0, priority = 1)] + fn a(_: a::Context) {} + + #[task(core = 0, priority = 2)] + fn b(_: b::Context) {} + + #[task(core = 1, priority = 1)] + fn c(_: c::Context) {} + + #[task(core = 1, priority = 2)] + fn d(_: d::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + #[core = 0] + fn B(); + + #[core = 0] + fn A(); + + #[core = 1] + fn A(); + + #[core = 1] + fn C(); + } + }; + ), + settings, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + + // first core + let interrupts0 = &analysis.interrupts[&0]; + assert_eq!(interrupts0.len(), 2); + assert_eq!(interrupts0[&2].to_string(), "B"); + assert_eq!(interrupts0[&1].to_string(), "A"); + + // second core + let interrupts1 = &analysis.interrupts[&1]; + assert_eq!(interrupts1.len(), 2); + assert_eq!(interrupts1[&2].to_string(), "A"); + assert_eq!(interrupts1[&1].to_string(), "C"); +} diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs new file mode 100644 index 0000000000..5d7a8a9d7c --- /dev/null +++ b/macros/src/tests/single.rs @@ -0,0 +1,34 @@ +use quote::quote; +use rtfm_syntax::Settings; + +#[test] +fn analyze() { + let mut settings = Settings::default(); + settings.parse_extern_interrupt = true; + let (app, analysis) = rtfm_syntax::parse2( + quote!(device = pac), + quote!( + const APP: () = { + #[task(priority = 1)] + fn a(_: a::Context) {} + + #[task(priority = 2)] + fn b(_: b::Context) {} + + // first interrupt is assigned to the highest priority dispatcher + extern "C" { + fn B(); + fn A(); + } + }; + ), + settings, + ) + .unwrap(); + + let analysis = crate::analyze::app(analysis, &app); + let interrupts = &analysis.interrupts[&0]; + assert_eq!(interrupts.len(), 2); + assert_eq!(interrupts[&2].to_string(), "B"); + assert_eq!(interrupts[&1].to_string(), "A"); +} diff --git a/src/cyccnt.rs b/src/cyccnt.rs new file mode 100644 index 0000000000..c8a1b7ee61 --- /dev/null +++ b/src/cyccnt.rs @@ -0,0 +1,216 @@ +//! Data Watchpoint Trace (DWT) unit's CYCle CouNTer + +use core::{ + cmp::Ordering, + convert::{Infallible, TryInto}, + fmt, + marker::PhantomData, + ops, +}; + +use cortex_m::peripheral::DWT; + +use crate::Fraction; + +/// A measurement of the CYCCNT. Opaque and useful only with `Duration` +/// +/// This data type is only available on ARMv7-M +/// +/// Note that this value is tied to the CYCCNT of one core and that sending it a different core +/// makes it lose its meaning -- each Cortex-M core has its own CYCCNT counter and these are usually +/// unsynchronized and they may even be running at different frequencies. +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Instant { + inner: i32, + _not_send_or_sync: PhantomData<*mut ()>, +} + +unsafe impl Sync for Instant {} + +unsafe impl Send for Instant {} + +impl Instant { + /// Returns an instant corresponding to "now" + pub fn now() -> Self { + Instant { + inner: DWT::get_cycle_count() as i32, + _not_send_or_sync: PhantomData, + } + } + + /// 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.inner - earlier.inner; + assert!(diff >= 0, "second instant is later than self"); + Duration { inner: diff as u32 } + } +} + +impl fmt::Debug for Instant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Instant") + .field(&(self.inner as u32)) + .finish() + } +} + +impl ops::AddAssign for Instant { + fn add_assign(&mut self, dur: Duration) { + debug_assert!(dur.inner < (1 << 31)); + self.inner = self.inner.wrapping_add(dur.inner as i32); + } +} + +impl ops::Add for Instant { + type Output = Self; + + fn add(mut self, dur: Duration) -> Self { + self += dur; + self + } +} + +impl ops::SubAssign for Instant { + fn sub_assign(&mut self, dur: Duration) { + // XXX should this be a non-debug assertion? + debug_assert!(dur.inner < (1 << 31)); + self.inner = self.inner.wrapping_sub(dur.inner as i32); + } +} + +impl ops::Sub for Instant { + type Output = Self; + + fn sub(mut self, dur: Duration) -> Self { + self -= dur; + self + } +} + +impl ops::Sub for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +impl Ord for Instant { + fn cmp(&self, rhs: &Self) -> Ordering { + self.inner.wrapping_sub(rhs.inner).cmp(&0) + } +} + +impl PartialOrd for Instant { + fn partial_cmp(&self, rhs: &Self) -> Option { + Some(self.cmp(rhs)) + } +} + +/// A `Duration` type to represent a span of time. +/// +/// This data type is only available on ARMv7-M +#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct Duration { + inner: u32, +} + +impl Duration { + /// Creates a new `Duration` from the specified number of clock cycles + pub fn from_cycles(cycles: u32) -> Self { + Duration { inner: cycles } + } + + /// Returns the total number of clock cycles contained by this `Duration` + pub fn as_cycles(&self) -> u32 { + self.inner + } +} + +impl TryInto for Duration { + type Error = Infallible; + + fn try_into(self) -> Result { + Ok(self.as_cycles()) + } +} + +impl ops::AddAssign for Duration { + fn add_assign(&mut self, dur: Duration) { + self.inner += dur.inner; + } +} + +impl ops::Add for Duration { + type Output = Self; + + fn add(self, other: Self) -> Self { + Duration { + inner: self.inner + other.inner, + } + } +} + +impl ops::SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + self.inner -= rhs.inner; + } +} + +impl ops::Sub for Duration { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + Duration { + inner: self.inner - rhs.inner, + } + } +} + +/// Adds the `cycles` method to the `u32` type +/// +/// This trait is only available on ARMv7-M +pub trait U32Ext { + /// Converts the `u32` value into clock cycles + fn cycles(self) -> Duration; +} + +impl U32Ext for u32 { + fn cycles(self) -> Duration { + Duration { inner: self } + } +} + +/// Implementation of the `Monotonic` trait based on CYCle CouNTer +pub struct CYCCNT; + +impl crate::Monotonic for CYCCNT { + type Instant = Instant; + + fn ratio() -> Fraction { + Fraction { + numerator: 1, + denominator: 1, + } + } + + unsafe fn reset() { + (0xE0001004 as *mut u32).write_volatile(0) + } + + fn now() -> Instant { + Instant::now() + } + + fn zero() -> Instant { + Instant { + inner: 0, + _not_send_or_sync: PhantomData, + } + } +} diff --git a/src/export.rs b/src/export.rs index afed9091a5..96c444bf46 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,21 +1,27 @@ -//! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE - -use core::{cell::Cell, u8}; - -#[cfg(armv7m)] -use cortex_m::register::basepri; -pub use cortex_m::{ - asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource, - peripheral::Peripherals, +use core::{ + cell::Cell, + sync::atomic::{AtomicBool, Ordering}, }; -use heapless::spsc::SingleCore; -pub use heapless::{consts, i, spsc::Queue}; -#[cfg(feature = "timer-queue")] pub use crate::tq::{NotReady, TimerQueue}; +#[cfg(armv7m)] +pub use cortex_m::register::basepri; +pub use cortex_m::{ + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, syst::SystClkSource, DWT, NVIC}, + Peripherals, +}; +use heapless::spsc::{MultiCore, SingleCore}; +pub use heapless::{consts, i::Queue as iQueue, spsc::Queue}; +pub use heapless::{i::BinaryHeap as iBinaryHeap, BinaryHeap}; +#[cfg(feature = "heterogeneous")] +pub use microamp::shared; -pub type FreeQueue = Queue; -pub type ReadyQueue = Queue<(T, u8), N, u8, SingleCore>; +pub type MCFQ = Queue; +pub type MCRQ = Queue<(T, u8), N, u8, MultiCore>; +pub type SCFQ = Queue; +pub type SCRQ = Queue<(T, u8), N, u8, SingleCore>; #[cfg(armv7m)] #[inline(always)] @@ -43,6 +49,26 @@ where f(); } +pub struct Barrier { + inner: AtomicBool, +} + +impl Barrier { + pub const fn new() -> Self { + Barrier { + inner: AtomicBool::new(false), + } + } + + pub fn release(&self) { + self.inner.store(true, Ordering::Release) + } + + pub fn wait(&self) { + while !self.inner.load(Ordering::Acquire) {} + } +} + // Newtype over `Cell` that forbids mutation through a shared reference pub struct Priority { inner: Cell, @@ -82,6 +108,13 @@ where { } +#[inline(always)] +pub fn assert_multicore() +where + T: super::MultiCore, +{ +} + #[cfg(armv7m)] #[inline(always)] pub unsafe fn lock( @@ -95,7 +128,7 @@ pub unsafe fn lock( if current < ceiling { if ceiling == (1 << nvic_prio_bits) { - priority.set(u8::MAX); + priority.set(u8::max_value()); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); r @@ -124,7 +157,7 @@ pub unsafe fn lock( let current = priority.get(); if current < ceiling { - priority.set(u8::MAX); + priority.set(u8::max_value()); let r = interrupt::free(|_| f(&mut *ptr)); priority.set(current); r diff --git a/src/lib.rs b/src/lib.rs index 1fe88c4728..502cbc6a2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ //! Real Time For the Masses (RTFM) framework for ARM Cortex-M microcontrollers //! -//! **HEADS UP** This is an **alpha** pre-release; there may be breaking changes in the API and +//! **HEADS UP** This is an **beta** pre-release; there may be breaking changes in the API and //! semantics before a proper release is made. //! //! **IMPORTANT**: This crate is published as [`cortex-m-rtfm`] on crates.io but the name of the @@ -12,10 +12,8 @@ //! //! [here]: https://japaric.github.io/rtfm5/book/en/ //! -//! Don't forget to check the documentation of the [`#[app]`] attribute, which is the main component -//! of the framework. -//! -//! [`#[app]`]: ../cortex_m_rtfm_macros/attr.app.html +//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports +//! section), which is the main component of the framework. //! //! # Minimum Supported Rust Version (MSRV) //! @@ -33,68 +31,48 @@ //! //! # Cargo features //! -//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule -//! tasks to run in the future. Also see [`Instant`] and [`Duration`]. +//! - `heterogeneous`. This opt-in feature enables the *experimental* heterogeneous multi-core +//! support. This feature depends on unstable feature and requires the use of the nightly channel. //! -//! [`Instant`]: struct.Instant.html -//! [`Duration`]: struct.Duration.html -//! -//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable `const_fn` -//! language feature to reduce static memory usage, runtime overhead and initialization overhead. -//! This feature requires a nightly compiler and may stop working at any time! +//! - `homogeneous`. This opt-in feature enables the *experimental* homogeneous multi-core support. #![deny(missing_docs)] +#![deny(rust_2018_compatibility)] +#![deny(rust_2018_idioms)] #![deny(warnings)] #![no_std] -#[cfg(feature = "timer-queue")] -use core::cmp::Ordering; -use core::{fmt, ops}; +use core::ops::Sub; -#[cfg(not(feature = "timer-queue"))] -use cortex_m::peripheral::SYST; use cortex_m::{ interrupt::Nr, peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU}, }; +#[cfg(all(not(feature = "heterogeneous"), not(feature = "homogeneous")))] +use cortex_m_rt as _; // vector table pub use cortex_m_rtfm_macros::app; +pub use rtfm_core::{Exclusive, Mutex}; +#[cfg(armv7m)] +pub mod cyccnt; #[doc(hidden)] pub mod export; #[doc(hidden)] -#[cfg(feature = "timer-queue")] mod tq; -#[cfg(all(feature = "timer-queue", armv6m))] -compile_error!( - "The `timer-queue` feature is currently not supported on ARMv6-M (`thumbv6m-none-eabi`)" -); - -/// Core peripherals -/// -/// This is `cortex_m::Peripherals` minus the peripherals that the RTFM runtime uses -/// -/// - The `NVIC` field is never present. -/// - When the `timer-queue` feature is enabled the following fields are *not* present: `DWT` and -/// `SYST`. +/// `cortex_m::Peripherals` minus `SYST` #[allow(non_snake_case)] -pub struct Peripherals<'a> { +pub struct Peripherals { /// Cache and branch predictor maintenance operations (not present on Cortex-M0 variants) pub CBP: CBP, /// CPUID pub CPUID: CPUID, - /// Debug Control Block (by value if the `timer-queue` feature is disabled) - #[cfg(feature = "timer-queue")] - pub DCB: &'a mut DCB, - - /// Debug Control Block (borrowed if the `timer-queue` feature is enabled) - #[cfg(not(feature = "timer-queue"))] + /// Debug Control Block pub DCB: DCB, - /// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled) - #[cfg(not(feature = "timer-queue"))] + /// Data Watchpoint and Trace unit pub DWT: DWT, /// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants) @@ -109,246 +87,68 @@ pub struct Peripherals<'a> { /// Memory Protection Unit pub MPU: MPU, - // Nested Vector Interrupt Controller - // pub NVIC: NVIC, + /// 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, + pub SCB: SCB, + // SysTick: System Timer + // pub SYST: SYST, /// Trace Port Interface Unit (not present on Cortex-M0 variants) pub TPIU: TPIU, } -/// A measurement of a monotonically nondecreasing clock. Opaque and useful only with `Duration` -/// -/// This data type is only available when the `timer-queue` feature is enabled -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg(feature = "timer-queue")] -pub struct Instant(i32); - -#[cfg(feature = "timer-queue")] -impl Instant { - /// IMPLEMENTATION DETAIL. DO NOT USE - #[doc(hidden)] - pub unsafe 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) +impl From for Peripherals { + fn from(p: cortex_m::Peripherals) -> Self { + Self { + CBP: p.CBP, + CPUID: p.CPUID, + DCB: p.DCB, + DWT: p.DWT, + FPB: p.FPB, + FPU: p.FPU, + ITM: p.ITM, + MPU: p.MPU, + NVIC: p.NVIC, + SCB: p.SCB, + TPIU: p.TPIU, + } } } -#[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); - } +/// A fraction +pub struct Fraction { + /// The numerator + pub numerator: u32, + + /// The denominator + pub denominator: u32, } -#[cfg(feature = "timer-queue")] -impl ops::Add for Instant { - type Output = Self; +/// A monotonic clock / counter +pub trait Monotonic { + /// A measurement of this clock + type Instant: Copy + Ord + Sub; - fn add(mut self, dur: Duration) -> Self { - self += dur; - self - } + /// The ratio between the SysTick (system timer) frequency and this clock frequency + /// + /// The ratio must be expressed in *reduced* `Fraction` form to prevent overflows. That is + /// `2 / 3` instead of `4 / 6` + fn ratio() -> Fraction; + + /// Returns the current time + fn now() -> Self::Instant; + + /// Resets the counter to *zero* + unsafe fn reset(); + + /// A `Self::Instant` that represents a count of *zero* + fn zero() -> Self::Instant; } -#[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, Default, Eq, Ord, PartialEq, PartialOrd)] -#[cfg(feature = "timer-queue")] -pub struct Duration(u32); - -#[cfg(feature = "timer-queue")] -impl Duration { - /// Returns the total number of clock cycles contained by this `Duration` - pub fn as_cycles(&self) -> u32 { - self.0 - } -} - -#[cfg(feature = "timer-queue")] -impl ops::AddAssign for Duration { - fn add_assign(&mut self, dur: Duration) { - self.0 += dur.0; - } -} - -#[cfg(feature = "timer-queue")] -impl ops::Add 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. Entering and leaving these critical sections is always done -/// in constant time (a few instructions). -/// -/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers -pub trait Mutex { - /// Data protected by the mutex - type T; - - /// Creates a critical section and grants temporary access to the protected data - fn lock(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R; -} - -impl<'a, M> Mutex for &'a mut M -where - M: Mutex, -{ - type T = M::T; - - fn lock(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R { - (**self).lock(f) - } -} - -/// Newtype over `&'a mut T` that implements the `Mutex` trait -/// -/// The `Mutex` implementation for this type is a no-op, no critical section is created -pub struct Exclusive<'a, T>(pub &'a mut T); - -impl<'a, T> Mutex for Exclusive<'a, T> { - type T = T; - - fn lock(&mut self, f: impl FnOnce(&mut T) -> R) -> R { - f(self.0) - } -} - -impl<'a, T> fmt::Debug for Exclusive<'a, T> -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl<'a, T> fmt::Display for Exclusive<'a, T> -where - T: fmt::Display, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self).fmt(f) - } -} - -impl<'a, T> ops::Deref for Exclusive<'a, T> { - type Target = T; - - fn deref(&self) -> &T { - self.0 - } -} - -impl<'a, T> ops::DerefMut for Exclusive<'a, T> { - fn deref_mut(&mut self) -> &mut T { - self.0 - } -} +/// A marker trait that indicates that it is correct to use this type in multi-core context +pub trait MultiCore {} /// Sets the given `interrupt` as pending /// diff --git a/src/tq.rs b/src/tq.rs index 8ca1bd3f8e..4edb40a7fe 100644 --- a/src/tq.rs +++ b/src/tq.rs @@ -1,36 +1,34 @@ -use core::cmp::{self, Ordering}; +use core::{ + cmp::{self, Ordering}, + convert::TryInto, + mem, + ops::Sub, +}; use cortex_m::peripheral::{SCB, SYST}; use heapless::{binary_heap::Min, ArrayLength, BinaryHeap}; -use crate::Instant; +use crate::Monotonic; -pub struct TimerQueue +pub struct TimerQueue(pub BinaryHeap, N, Min>) where - N: ArrayLength>, + M: Monotonic, + ::Output: TryInto, + N: ArrayLength>, + T: Copy; + +impl TimerQueue +where + M: Monotonic, + ::Output: TryInto, + 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) { + pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady) { let mut is_empty = true; if self - .queue + .0 .peek() .map(|head| { is_empty = false; @@ -39,77 +37,102 @@ where .unwrap_or(true) { if is_empty { - self.syst.enable_interrupt(); + mem::transmute::<_, SYST>(()).enable_interrupt(); } // set SysTick pending SCB::set_pendst(); } - self.queue.push_unchecked(nr); + self.0.push_unchecked(nr); } #[inline] pub fn dequeue(&mut self) -> Option<(T, u8)> { - if let Some(instant) = self.queue.peek().map(|p| p.instant) { - let diff = instant.0.wrapping_sub(Instant::now().0); + unsafe { + if let Some(instant) = self.0.peek().map(|p| p.instant) { + let now = M::now(); - if diff < 0 { - // task became ready - let nr = unsafe { self.queue.pop_unchecked() }; + if instant < now { + // task became ready + let nr = self.0.pop_unchecked(); - Some((nr.task, nr.index)) + Some((nr.task, nr.index)) + } else { + // set a new timeout + const MAX: u32 = 0x00ffffff; + + let ratio = M::ratio(); + let dur = match (instant - now).try_into().ok().and_then(|x| { + x.checked_mul(ratio.numerator) + .map(|x| x / ratio.denominator) + }) { + None => MAX, + Some(x) => cmp::min(MAX, x), + }; + mem::transmute::<_, SYST>(()).set_reload(dur); + + // start counting down from the new reload + mem::transmute::<_, SYST>(()).clear_current(); + + None + } } else { - // set a new timeout - const MAX: u32 = 0x00ffffff; - - self.syst.set_reload(cmp::min(MAX, diff as u32)); - - // start counting down from the new reload - self.syst.clear_current(); + // the queue is empty + mem::transmute::<_, SYST>(()).disable_interrupt(); None } - } else { - // the queue is empty - self.syst.disable_interrupt(); - None } } } -pub struct NotReady +pub struct NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { pub index: u8, - pub instant: Instant, + pub instant: M::Instant, pub task: T, } -impl Eq for NotReady where T: Copy {} - -impl Ord for NotReady +impl Eq for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, +{ +} + +impl Ord for NotReady +where + T: Copy, + M: Monotonic, + ::Output: TryInto, { fn cmp(&self, other: &Self) -> Ordering { self.instant.cmp(&other.instant) } } -impl PartialEq for NotReady +impl PartialEq for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { fn eq(&self, other: &Self) -> bool { self.instant == other.instant } } -impl PartialOrd for NotReady +impl PartialOrd for NotReady where T: Copy, + M: Monotonic, + ::Output: TryInto, { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) diff --git a/tests/cfail/cfg-resources.rs b/tests/cfail/cfg-resources.rs deleted file mode 100644 index 5e20c4de6c..0000000000 --- a/tests/cfail/cfg-resources.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[cfg(never)] - static mut O1: u32 = 0; // init - #[cfg(never)] - static mut O2: u32 = 0; // idle - #[cfg(never)] - static mut O3: u32 = 0; // EXTI0 - #[cfg(never)] - static O4: u32 = 0; // idle - #[cfg(never)] - static O5: u32 = 0; // EXTI1 - #[cfg(never)] - static O6: u32 = 0; // init - - #[cfg(never)] - static mut S1: u32 = 0; // idle & EXTI0 - #[cfg(never)] - static mut S2: u32 = 0; // EXTI0 & EXTI1 - #[cfg(never)] - static S3: u32 = 0; - - #[init(resources = [O1, O4, O5, O6, S3])] - fn init(c: init::Context) { - c.resources.O1; //~ ERROR no field `O1` - c.resources.O4; //~ ERROR no field `O4` - c.resources.O5; //~ ERROR no field `O5` - c.resources.O6; //~ ERROR no field `O6` - c.resources.S3; //~ ERROR no field `S3` - } - - #[idle(resources = [O2, O4, S1, S3])] - fn idle(c: idle::Context) -> ! { - c.resources.O2; //~ ERROR no field `O2` - c.resources.O4; //~ ERROR no field `O4` - c.resources.S1; //~ ERROR no field `S1` - c.resources.S3; //~ ERROR no field `S3` - - loop {} - } - - #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0(c: UART0::Context) { - c.resources.O3; //~ ERROR no field `O3` - c.resources.S1; //~ ERROR no field `S1` - c.resources.S2; //~ ERROR no field `S2` - c.resources.S3; //~ ERROR no field `S3` - } - - #[interrupt(resources = [S2, O5])] - fn UART1(c: UART1::Context) { - c.resources.S2; //~ ERROR no field `S2` - c.resources.O5; //~ ERROR no field `O5` - } -}; diff --git a/tests/cfail/duplicate-args-2.rs b/tests/cfail/duplicate-args-2.rs deleted file mode 100644 index 5bef79b5f7..0000000000 --- a/tests/cfail/duplicate-args-2.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task( - priority = 1, - priority = 2, //~ ERROR argument appears more than once - )] - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/duplicate-args.rs b/tests/cfail/duplicate-args.rs deleted file mode 100644 index 6938cd0d72..0000000000 --- a/tests/cfail/duplicate-args.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task( - capacity = 1, - capacity = 2, //~ ERROR argument appears more than once - )] - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/exception-divergent.rs b/tests/cfail/exception-divergent.rs deleted file mode 100644 index 3fe9a36528..0000000000 --- a/tests/cfail/exception-divergent.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception] - fn SVCall(_: SVCall::Context) -> ! { - //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` - loop {} - } -}; diff --git a/tests/cfail/exception-input.rs b/tests/cfail/exception-input.rs deleted file mode 100644 index d1363fe58d..0000000000 --- a/tests/cfail/exception-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception] - fn SVCall(_: SVCall::Context, undef: u32) { - //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` - } -}; diff --git a/tests/cfail/exception-invalid.rs b/tests/cfail/exception-invalid.rs deleted file mode 100644 index 4bb8f1ec08..0000000000 --- a/tests/cfail/exception-invalid.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception] - fn NonMaskableInt(_: NonMaskableInt::Context) { - //~^ ERROR only exceptions with configurable priority can be used as hardware tasks - } -}; diff --git a/tests/cfail/exception-output.rs b/tests/cfail/exception-output.rs deleted file mode 100644 index 8f6729857e..0000000000 --- a/tests/cfail/exception-output.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception] - fn SVCall(_: SVCall::Context) -> u32 { - //~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)` - 0 - } -}; diff --git a/tests/cfail/exception-sys-tick.rs b/tests/cfail/exception-sys-tick.rs deleted file mode 100644 index d5eae20baa..0000000000 --- a/tests/cfail/exception-sys-tick.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception] - fn SysTick(_: SysTick::Context) { - //~^ ERROR the `SysTick` exception can't be used because it's used by the runtime - } -}; diff --git a/tests/cfail/idle-input.rs b/tests/cfail/idle-input.rs deleted file mode 100644 index feb83e8b72..0000000000 --- a/tests/cfail/idle-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[idle] - fn idle(_: idle::Context, undef: u32) { - //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` - } -}; diff --git a/tests/cfail/idle-not-divergent.rs b/tests/cfail/idle-not-divergent.rs deleted file mode 100644 index 505fba1455..0000000000 --- a/tests/cfail/idle-not-divergent.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[idle] - fn idle(_: idle::Context) { - //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` - } -}; diff --git a/tests/cfail/init-divergent.rs b/tests/cfail/init-divergent.rs deleted file mode 100644 index 0e779ffcee..0000000000 --- a/tests/cfail/init-divergent.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) -> ! { - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` - loop {} - } -}; diff --git a/tests/cfail/init-extra-late-resources.rs b/tests/cfail/init-extra-late-resources.rs deleted file mode 100644 index d2d4a6d7c2..0000000000 --- a/tests/cfail/init-extra-late-resources.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) -> init::LateResources {} - //~^ error: `init` signature must be `fn(init::Context)` if there are no late resources -}; diff --git a/tests/cfail/init-input.rs b/tests/cfail/init-input.rs deleted file mode 100644 index 9063efe355..0000000000 --- a/tests/cfail/init-input.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context, undef: u32) { - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` - } -}; diff --git a/tests/cfail/init-missing-late-resources.rs b/tests/cfail/init-missing-late-resources.rs deleted file mode 100644 index cec18babaf..0000000000 --- a/tests/cfail/init-missing-late-resources.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - static mut X: i32 = (); - - #[init] - fn init(_: init::Context) {} - //~^ error: late resources have been specified so `init` must return `init::LateResources` -}; diff --git a/tests/cfail/init-not-send.rs b/tests/cfail/init-not-send.rs deleted file mode 100644 index 5a33fac764..0000000000 --- a/tests/cfail/init-not-send.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! This is equivalent to the `late-not-send` cfail test - -#![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(c: init::Context) { - *c.resources.X = Some(NotSend { _0: PhantomData }) - } - - #[interrupt(resources = [X])] - fn UART0(_: UART0::Context) {} -}; diff --git a/tests/cfail/init-output.rs b/tests/cfail/init-output.rs deleted file mode 100644 index f88d5340c0..0000000000 --- a/tests/cfail/init-output.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) -> u32 { - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` - 0 - } -}; diff --git a/tests/cfail/insufficient-free-interrupts.rs b/tests/cfail/insufficient-free-interrupts.rs deleted file mode 100644 index 7148fbf34f..0000000000 --- a/tests/cfail/insufficient-free-interrupts.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context) {} -}; diff --git a/tests/cfail/interrupt-divergent.rs b/tests/cfail/interrupt-divergent.rs deleted file mode 100644 index b67601eea5..0000000000 --- a/tests/cfail/interrupt-divergent.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt] - fn UART0(_: UART0::Context) -> ! { - //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` - loop {} - } -}; diff --git a/tests/cfail/interrupt-input.rs b/tests/cfail/interrupt-input.rs deleted file mode 100644 index f11b2d3960..0000000000 --- a/tests/cfail/interrupt-input.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt] - fn UART0(_: UART0::Context, undef: u32) { - //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` - } -}; diff --git a/tests/cfail/interrupt-output.rs b/tests/cfail/interrupt-output.rs deleted file mode 100644 index 69e4957fd8..0000000000 --- a/tests/cfail/interrupt-output.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt] - fn UART0(_: UART0::Context) -> u32 { - //~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)` - 0 - } -}; diff --git a/tests/cfail/late-assigned-to-init.rs b/tests/cfail/late-assigned-to-init.rs deleted file mode 100644 index 00d6c8ceed..0000000000 --- a/tests/cfail/late-assigned-to-init.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - static mut X: u32 = (); - - #[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init` - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/late-not-send.rs b/tests/cfail/late-not-send.rs deleted file mode 100644 index 04a4af158b..0000000000 --- a/tests/cfail/late-not-send.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `init` has a static priority of `0`. Initializing resources from it is equivalent to sending a -//! message to the task that will own the resource - -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -struct NotSend { - _0: PhantomData<*const ()>, -} - -#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely -const APP: () = { - static mut X: NotSend = (); - - #[init] - fn init(_: init::Context) -> init::LateResources { - init::LateResources { - X: NotSend { _0: PhantomData }, - } - } - - #[interrupt(resources = [X])] - fn UART0(_: UART0::Context) {} -}; diff --git a/tests/cfail/needs-send.rs b/tests/cfail/needs-send.rs deleted file mode 100644 index 8dc9707fc9..0000000000 --- a/tests/cfail/needs-send.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -unsafe impl Sync for NotSend {} - -#[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely -const APP: () = { - #[init(spawn = [foo])] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context, _x: NotSend) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/needs-sync.rs b/tests/cfail/needs-sync.rs deleted file mode 100644 index 6025e7d56a..0000000000 --- a/tests/cfail/needs-sync.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -use rtfm::app; - -pub struct 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(_: init::Context) {} - - #[task(priority = 1, resources = [X])] - fn foo(_: foo::Context) {} - - #[task(priority = 2, resources = [X])] - fn bar(_: bar::Context) {} - - extern "C" { - fn UART0(); - fn UART1(); - } -}; diff --git a/tests/cfail/priority-too-high.rs b/tests/cfail/priority-too-high.rs deleted file mode 100644 index 817462a350..0000000000 --- a/tests/cfail/priority-too-high.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] //~ error evaluation of constant value failed -const APP: () = { - #[init] - fn init(_: init::Context) {} - - // OK, this is the maximum priority supported by the device - #[interrupt(priority = 8)] - fn UART0(_: UART0::Context) {} - - // this value is too high! - #[interrupt(priority = 9)] - fn UART1(_: UART1::Context) {} -}; diff --git a/tests/cfail/priority-too-low.rs b/tests/cfail/priority-too-low.rs deleted file mode 100644 index 361156df54..0000000000 --- a/tests/cfail/priority-too-low.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - // OK, this is the minimum priority that tasks can have - #[interrupt(priority = 1)] - fn UART0(_: UART0::Context) {} - - // this value is too low! - #[interrupt(priority = 0)] //~ error this literal must be in the range 1...255 - fn UART1(_: UART1::Context) {} -}; diff --git a/tests/cfail/resource-not-declared.rs b/tests/cfail/resource-not-declared.rs deleted file mode 100644 index a37be42d38..0000000000 --- a/tests/cfail/resource-not-declared.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init(resources = [X])] //~ ERROR this resource has NOT been declared - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/resource-pub.rs b/tests/cfail/resource-pub.rs deleted file mode 100644 index 3fb21f463c..0000000000 --- a/tests/cfail/resource-pub.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - pub static mut X: u32 = 0; - //~^ ERROR resources must have inherited / private visibility - - #[init] - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/task-divergent.rs b/tests/cfail/task-divergent.rs deleted file mode 100644 index 577f0e06aa..0000000000 --- a/tests/cfail/task-divergent.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - fn foo(_: foo::Context) -> ! { - //~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)` - loop {} - } - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/task-idle.rs b/tests/cfail/task-idle.rs deleted file mode 100644 index 963bf1ee81..0000000000 --- a/tests/cfail/task-idle.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - fn idle(_: idle::Context) { - //~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources` - } - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/task-not-declared.rs b/tests/cfail/task-not-declared.rs deleted file mode 100644 index 04309f599b..0000000000 --- a/tests/cfail/task-not-declared.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init(spawn = [X])] //~ ERROR this task has NOT been declared - fn init(_: init::Context) {} -}; diff --git a/tests/cfail/unsafe-exception.rs b/tests/cfail/unsafe-exception.rs deleted file mode 100644 index 353194a590..0000000000 --- a/tests/cfail/unsafe-exception.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[exception(binds = SVCall)] - unsafe fn foo(_: foo::Context) {} - //~^ ERROR this `exception` handler must have type signature `fn(foo::Context)` -}; diff --git a/tests/cfail/unsafe-idle.rs b/tests/cfail/unsafe-idle.rs deleted file mode 100644 index fab1b0f1f6..0000000000 --- a/tests/cfail/unsafe-idle.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[idle] - unsafe fn idle(_: idle::Context) -> ! { - //~^ ERROR `idle` must have type signature `fn(idle::Context) -> !` - loop {} - } -}; diff --git a/tests/cfail/unsafe-init.rs b/tests/cfail/unsafe-init.rs deleted file mode 100644 index d8bb560571..0000000000 --- a/tests/cfail/unsafe-init.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - unsafe fn init(_: init::Context) {} - //~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]` -}; diff --git a/tests/cfail/unsafe-interrupt.rs b/tests/cfail/unsafe-interrupt.rs deleted file mode 100644 index 93225edf14..0000000000 --- a/tests/cfail/unsafe-interrupt.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt(binds = UART0)] - unsafe fn foo(_: foo::Context) {} - //~^ ERROR this `interrupt` handler must have type signature `fn(foo::Context)` -}; diff --git a/tests/cfail/unsafe-task.rs b/tests/cfail/unsafe-task.rs deleted file mode 100644 index 58c4d70c21..0000000000 --- a/tests/cfail/unsafe-task.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[task] - unsafe fn foo(_: foo::Context) {} - //~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)` - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/used-free-interrupt-2.rs b/tests/cfail/used-free-interrupt-2.rs deleted file mode 100644 index ba9424fd4d..0000000000 --- a/tests/cfail/used-free-interrupt-2.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt(binds = UART0)] //~ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers - fn foo(_: foo::Context) {} - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/cfail/used-free-interrupt.rs b/tests/cfail/used-free-interrupt.rs deleted file mode 100644 index 1a56741b14..0000000000 --- a/tests/cfail/used-free-interrupt.rs +++ /dev/null @@ -1,22 +0,0 @@ -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(_: init::Context) {} - - #[interrupt] - fn UART0(_: UART0::Context) {} - //~^ ERROR free interrupts (`extern { .. }`) can't be used as interrupt handlers - - extern "C" { - fn UART0(); - } -}; diff --git a/tests/compiletest.rs b/tests/compiletest.rs deleted file mode 100644 index 58702eecdf..0000000000 --- a/tests/compiletest.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::{fs, path::PathBuf, process::Command}; - -use compiletest_rs::{common::Mode, Config}; -use tempdir::TempDir; - -#[test] -fn cfail() { - let mut config = Config::default(); - - config.mode = Mode::CompileFail; - config.src_base = PathBuf::from("tests/cfail"); - config.link_deps(); - - // remove duplicate and trailing `-L` flags - let mut s = String::new(); - if let Some(flags) = config.target_rustcflags.as_mut() { - let mut iter = flags.split_whitespace().peekable(); - - while let Some(flag) = iter.next() { - if flag == "-L" && (iter.peek() == Some(&"-L") || iter.peek() == None) { - iter.next(); - continue; - } - - s += flag; - s += " "; - } - - // path to proc-macro crate - s += "-L target/debug/deps "; - - // avoid "error: language item required, but not found: `eh_personality`" - s += "-C panic=abort "; - } - - let td = TempDir::new("rtfm").unwrap(); - for f in fs::read_dir("tests/cpass").unwrap() { - let f = f.unwrap().path(); - let name = f.file_stem().unwrap().to_str().unwrap(); - - assert!(Command::new("rustc") - .args(s.split_whitespace()) - .arg(f.display().to_string()) - .arg("-o") - .arg(td.path().join(name).display().to_string()) - .arg("-C") - .arg("linker=true") - .status() - .unwrap() - .success()); - } - - config.target_rustcflags = Some(s); - config.clean_rmeta(); - - compiletest_rs::run_tests(&config); -} diff --git a/tests/cpass/late-not-send.rs b/tests/cpass/late-not-send.rs deleted file mode 100644 index 0f69096748..0000000000 --- a/tests/cpass/late-not-send.rs +++ /dev/null @@ -1,34 +0,0 @@ -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use core::marker::PhantomData; - -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - static mut X: NotSend = (); - static mut Y: Option = None; - - #[init(resources = [Y])] - fn init(c: init::Context) -> init::LateResources { - *c.resources.Y = Some(NotSend { _0: PhantomData }); - - init::LateResources { - X: NotSend { _0: PhantomData }, - } - } - - #[idle(resources = [X, Y])] - fn idle(_: idle::Context) -> ! { - loop {} - } -}; diff --git a/tests/cpass/late-resource.rs b/tests/cpass/late-resource.rs deleted file mode 100644 index 37dcf33163..0000000000 --- a/tests/cpass/late-resource.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Runtime initialized resources -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - static mut X: u32 = (); - static Y: u32 = (); - - #[init] - fn init(_: init::Context) -> init::LateResources { - init::LateResources { X: 0, Y: 1 } - } -}; diff --git a/tests/cpass/peripheral.rs b/tests/cpass/peripheral.rs deleted file mode 100644 index 34352b845c..0000000000 --- a/tests/cpass/peripheral.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Core and device peripherals -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -#[rtfm::app(device = lm3s6965)] -const APP: () = { - #[init] - fn init(c: init::Context) { - let _: rtfm::Peripherals = c.core; - let _: lm3s6965::Peripherals = c.device; - } -}; diff --git a/tests/cpass/resource.rs b/tests/cpass/resource.rs deleted file mode 100644 index 4e92a0320b..0000000000 --- a/tests/cpass/resource.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! Check code generation of resources - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::Exclusive; - -#[rtfm::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(c: init::Context) { - // owned by `init` == `&'static mut` - let _: &'static mut u32 = c.resources.O1; - - // owned by `init` == `&'static` if read-only - let _: &'static u32 = c.resources.O6; - - // `init` has exclusive access to all resources - let _: &mut u32 = c.resources.O4; - let _: &mut u32 = c.resources.O5; - let _: &mut u32 = c.resources.S3; - } - - #[idle(resources = [O2, O4, S1, S3])] - fn idle(mut c: idle::Context) -> ! { - // owned by `idle` == `&'static mut` - let _: &'static mut u32 = c.resources.O2; - - // owned by `idle` == `&'static` if read-only - let _: &'static u32 = c.resources.O4; - - // shared with `idle` == `Mutex` - c.resources.S1.lock(|_| {}); - - // `&` if read-only - let _: &u32 = c.resources.S3; - - loop {} - } - - #[interrupt(resources = [O3, S1, S2, S3])] - fn UART0(c: UART0::Context) { - // owned by interrupt == `&mut` - let _: &mut u32 = c.resources.O3; - - // no `Mutex` proxy when access from highest priority task - let _: Exclusive = c.resources.S1; - - // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = c.resources.S2; - - // `&` if read-only - let _: &u32 = c.resources.S3; - } - - #[interrupt(resources = [S2, O5])] - fn UART1(c: UART1::Context) { - // owned by interrupt == `&` if read-only - let _: &u32 = c.resources.O5; - - // no `Mutex` proxy when co-owned by cooperative (same priority) tasks - let _: Exclusive = c.resources.S2; - } -}; diff --git a/tests/single.rs b/tests/single.rs new file mode 100644 index 0000000000..01b80312cb --- /dev/null +++ b/tests/single.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use compiletest_rs::{common::Mode, Config}; + +#[test] +fn ui() { + let mut config = Config::default(); + + config.mode = Mode::Ui; + config.src_base = PathBuf::from("ui/single"); + config.target_rustcflags = Some( + "--edition=2018 -L target/debug/deps -Z unstable-options --extern rtfm --extern lm3s6965" + .to_owned(), + ); + config.link_deps(); + config.clean_rmeta(); + + compiletest_rs::run_tests(&config); +} diff --git a/ui/single/exception-invalid.rs b/ui/single/exception-invalid.rs new file mode 100644 index 0000000000..54f5992805 --- /dev/null +++ b/ui/single/exception-invalid.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[task(binds = NonMaskableInt)] + fn nmi(_: nmi::Context) {} +}; diff --git a/ui/single/exception-invalid.stderr b/ui/single/exception-invalid.stderr new file mode 100644 index 0000000000..306074b095 --- /dev/null +++ b/ui/single/exception-invalid.stderr @@ -0,0 +1,8 @@ +error: only exceptions with configurable priority can be used as hardware tasks + --> $DIR/exception-invalid.rs:6:8 + | +6 | fn nmi(_: nmi::Context) {} + | ^^^ + +error: aborting due to previous error + diff --git a/ui/single/exception-systick-used.rs b/ui/single/exception-systick-used.rs new file mode 100644 index 0000000000..1155834f92 --- /dev/null +++ b/ui/single/exception-systick-used.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[task(binds = SysTick)] + fn sys_tick(_: sys_tick::Context) {} + + #[task(schedule = [foo])] + fn foo(_: foo::Context) {} +}; diff --git a/ui/single/exception-systick-used.stderr b/ui/single/exception-systick-used.stderr new file mode 100644 index 0000000000..e2ccbd3b88 --- /dev/null +++ b/ui/single/exception-systick-used.stderr @@ -0,0 +1,8 @@ +error: this exception can't be used because it's being used by the runtime + --> $DIR/exception-systick-used.rs:6:8 + | +6 | fn sys_tick(_: sys_tick::Context) {} + | ^^^^^^^^ + +error: aborting due to previous error + diff --git a/ui/single/extern-interrupt-not-enough.rs b/ui/single/extern-interrupt-not-enough.rs new file mode 100644 index 0000000000..39c5d8eaf3 --- /dev/null +++ b/ui/single/extern-interrupt-not-enough.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[task] + fn a(_: a::Context) {} +}; diff --git a/ui/single/extern-interrupt-not-enough.stderr b/ui/single/extern-interrupt-not-enough.stderr new file mode 100644 index 0000000000..43249c4954 --- /dev/null +++ b/ui/single/extern-interrupt-not-enough.stderr @@ -0,0 +1,8 @@ +error: not enough `extern` interrupts to dispatch all software tasks (need: 1; given: 0) + --> $DIR/extern-interrupt-not-enough.rs:6:8 + | +6 | fn a(_: a::Context) {} + | ^ + +error: aborting due to previous error + diff --git a/ui/single/extern-interrupt-used.rs b/ui/single/extern-interrupt-used.rs new file mode 100644 index 0000000000..59f38068c1 --- /dev/null +++ b/ui/single/extern-interrupt-used.rs @@ -0,0 +1,11 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[task(binds = UART0)] + fn a(_: a::Context) {} + + extern "C" { + fn UART0(); + } +}; diff --git a/ui/single/extern-interrupt-used.stderr b/ui/single/extern-interrupt-used.stderr new file mode 100644 index 0000000000..2e084caf1e --- /dev/null +++ b/ui/single/extern-interrupt-used.stderr @@ -0,0 +1,8 @@ +error: `extern` interrupts can't be used as hardware tasks + --> $DIR/extern-interrupt-used.rs:5:20 + | +5 | #[task(binds = UART0)] + | ^^^^^ + +error: aborting due to previous error + diff --git a/tests/cfail/cfg-static.rs b/ui/single/locals-cfg.rs similarity index 50% rename from tests/cfail/cfg-static.rs rename to ui/single/locals-cfg.rs index 91465a1e7a..8761f72ebf 100644 --- a/tests/cfail/cfg-static.rs +++ b/ui/single/locals-cfg.rs @@ -1,20 +1,13 @@ #![no_main] -#![no_std] -extern crate lm3s6965; -extern crate panic_halt; -extern crate rtfm; - -use rtfm::app; - -#[app(device = lm3s6965)] +#[rtfm::app(device = lm3s6965)] const APP: () = { #[init] fn init(_: init::Context) { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } #[idle] @@ -22,25 +15,25 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; loop {} } - #[exception] - fn SVCall(_: SVCall::Context) { + #[task(binds = SVCall)] + fn svcall(_: svcall::Context) { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } - #[interrupt] - fn UART0(_: UART0::Context) { + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } #[task] @@ -48,7 +41,7 @@ const APP: () = { #[cfg(never)] static mut FOO: u32 = 0; - FOO; //~ ERROR cannot find value `FOO` in this scope + FOO; } extern "C" { diff --git a/ui/single/locals-cfg.stderr b/ui/single/locals-cfg.stderr new file mode 100644 index 0000000000..fc324f139a --- /dev/null +++ b/ui/single/locals-cfg.stderr @@ -0,0 +1,33 @@ +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:10:9 + | +10 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:18:9 + | +18 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:28:9 + | +28 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:36:9 + | +36 | FOO; + | ^^^ not found in this scope + +error[E0425]: cannot find value `FOO` in this scope + --> $DIR/locals-cfg.rs:44:9 + | +44 | FOO; + | ^^^ not found in this scope + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/ui/single/resources-cfg.rs b/ui/single/resources-cfg.rs new file mode 100644 index 0000000000..906b3e25c9 --- /dev/null +++ b/ui/single/resources-cfg.rs @@ -0,0 +1,75 @@ +#![no_main] + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + struct Resources { + #[cfg(never)] + #[init(0)] + o1: u32, // init + + #[cfg(never)] + #[init(0)] + o2: u32, // idle + + #[cfg(never)] + #[init(0)] + o3: u32, // EXTI0 + + #[cfg(never)] + #[init(0)] + o4: u32, // idle + + #[cfg(never)] + #[init(0)] + o5: u32, // EXTI1 + + #[cfg(never)] + #[init(0)] + o6: u32, // init + + #[cfg(never)] + #[init(0)] + s1: u32, // idle & EXTI0 + + #[cfg(never)] + #[init(0)] + s2: u32, // EXTI0 & EXTI1 + + #[cfg(never)] + #[init(0)] + s3: u32, + } + + #[init(resources = [o1, o4, o5, o6, s3])] + fn init(c: init::Context) { + c.resources.o1; + c.resources.o4; + c.resources.o5; + c.resources.o6; + c.resources.s3; + } + + #[idle(resources = [o2, &o4, s1, &s3])] + fn idle(c: idle::Context) -> ! { + c.resources.o2; + c.resources.o4; + c.resources.s1; + c.resources.s3; + + loop {} + } + + #[task(binds = UART0, resources = [o3, s1, s2, &s3])] + fn uart0(c: uart0::Context) { + c.resources.o3; + c.resources.s1; + c.resources.s2; + c.resources.s3; + } + + #[task(binds = UART1, resources = [s2, &o5])] + fn uart1(c: uart1::Context) { + c.resources.s2; + c.resources.o5; + } +}; diff --git a/ui/single/resources-cfg.stderr b/ui/single/resources-cfg.stderr new file mode 100644 index 0000000000..a745e6e223 --- /dev/null +++ b/ui/single/resources-cfg.stderr @@ -0,0 +1,123 @@ +error[E0609]: no field `o1` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:45:21 + | +45 | c.resources.o1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o4` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:46:21 + | +46 | c.resources.o4; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o5` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:47:21 + | +47 | c.resources.o5; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o6` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:48:21 + | +48 | c.resources.o6; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s3` on type `initResources<'_>` + --> $DIR/resources-cfg.rs:49:21 + | +49 | c.resources.s3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o2` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:54:21 + | +54 | c.resources.o2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o4` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:55:21 + | +55 | c.resources.o4; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s1` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:56:21 + | +56 | c.resources.s1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s3` on type `idleResources<'_>` + --> $DIR/resources-cfg.rs:57:21 + | +57 | c.resources.s3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o3` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:64:21 + | +64 | c.resources.o3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s1` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:65:21 + | +65 | c.resources.s1; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s2` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:66:21 + | +66 | c.resources.s2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s3` on type `uart0Resources<'_>` + --> $DIR/resources-cfg.rs:67:21 + | +67 | c.resources.s3; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `s2` on type `uart1Resources<'_>` + --> $DIR/resources-cfg.rs:72:21 + | +72 | c.resources.s2; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error[E0609]: no field `o5` on type `uart1Resources<'_>` + --> $DIR/resources-cfg.rs:73:21 + | +73 | c.resources.o5; + | ^^ unknown field + | + = note: available fields are: `__marker__` + +error: aborting due to 15 previous errors + +For more information about this error, try `rustc --explain E0609`. diff --git a/ui/single/task-priority-too-high.rs b/ui/single/task-priority-too-high.rs new file mode 100644 index 0000000000..24cb11e593 --- /dev/null +++ b/ui/single/task-priority-too-high.rs @@ -0,0 +1,38 @@ +#![no_main] + +use rtfm::app; + +#[rtfm::app(device = lm3s6965)] +const APP: () = { + #[init] + fn init(_: init::Context) {} + + #[task(binds = GPIOA, priority = 1)] + fn gpioa(_: gpioa::Context) {} + + #[task(binds = GPIOB, priority = 2)] + fn gpiob(_: gpiob::Context) {} + + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) {} + + #[task(binds = GPIOD, priority = 4)] + fn gpiod(_: gpiod::Context) {} + + #[task(binds = GPIOE, priority = 5)] + fn gpioe(_: gpioe::Context) {} + + #[task(binds = UART0, priority = 6)] + fn uart0(_: uart0::Context) {} + + #[task(binds = UART1, priority = 7)] + fn uart1(_: uart1::Context) {} + + // OK, this is the maximum priority supported by the device + #[task(binds = SSI0, priority = 8)] + fn ssi0(_: ssi0::Context) {} + + // this value is too high! + #[task(binds = I2C0, priority = 9)] + fn i2c0(_: i2c0::Context) {} +}; diff --git a/ui/single/task-priority-too-high.stderr b/ui/single/task-priority-too-high.stderr new file mode 100644 index 0000000000..b402a95ca4 --- /dev/null +++ b/ui/single/task-priority-too-high.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/task-priority-too-high.rs:5:1 + | +5 | #[rtfm::app(device = lm3s6965)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ attempt to subtract with overflow + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`.