1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! A simple but powerful `async` RTOS based around Rust `Future`s.
//!
//! This provides a lightweight operating environment for running async Rust
//! code on ARM Cortex-M microprocessors, plus some useful doodads and gizmos.
//!
//! `lilos` is deliberately designed to be compact, to avoid the use of proc
//! macros, to be highly portable to different microcontrollers, and to be as
//! statically predictable as possible with no dynamic resource allocation.
//!
//! These are the API docs for the OS. If you'd like a higher-level introduction
//! with worked examples, please have a look at [the intro guide]!
//!
//! [the intro guide]: https://github.com/cbiffle/lilos/blob/main/doc/intro.adoc
//!
//! # About the OS
//!
//! `lilos` is designed around the notion of a fixed set of concurrent tasks
//! that run forever. To use the OS, your application startup routine calls
//! [`exec::run_tasks`], giving it an array of tasks you've defined; `run_tasks`
//! never returns.
//!
//! The OS provides *cooperative* multitasking: while tasks are concurrent, they
//! are not preemptive, and are not "threads" in the traditional sense. Tasks
//! don't even have their own stacks -- they return completely whenever they
//! yield the CPU.
//!
//! This would be incredibly frustrating to program, were it not for `Future`
//! and `async`.
//!
//! Each task co-routine must be a `Future` that can be polled but will never
//! complete (because, remember, tasks run forever). The OS provides an
//! *executor* that manages polling of a set of `Future`s.
//!
//! Rust's `async` keyword provides a convenient way to have the compiler
//! rewrite a normal function into a co-routine-style `Future`. This means that
//! writing co-routines to run on this OS looks *very much* like programming
//! with threads.
//!
//! For detailed discussion and some cookbook examples, see either [the intro
//! guide] or [the `examples` directory in the repo][examples].
//!
//! [examples]: https://github.com/cbiffle/lilos/tree/main/examples
//!
//!
//! # Feature flags
//!
//! `lilos` currently exposes the following Cargo features for
//! enabling/disabling portions of the system:
//!
//! - `systick` (on by default). Enables reliance on the ARM M-profile SysTick
//! timer for portable timekeeping. Disabling makes the executor smaller at the
//! cost of losing all `time` API. On platforms where the SysTick timer stops
//! during sleep, such as Nordic nRF52, you may want to disable this feature and
//! use a different timekeeping mechanism.
//!
//! - `mutex` (on by default). Enables access to the [`mutex`] module for
//! blocking access to shared data. Leaving this feature enabled has no cost if
//! you're not actually using `mutex`.
//!
//! - `spsc` (on by default). Enables access to the [`spsc`] module for
//! single-producer single-consumer inter-task queues. Leaving this feature
//! enabled has no cost if you're not actually using `spsc`.
//!
//!
//! # Composition and dynamic behavior
//!
//! The notion of a fixed set of tasks might seem limiting, but it's more
//! flexible than you might expect. Because `Future`s can be _composed_, the
//! fixed set of OS tasks can drive a _dynamic_ set of program `Future`s.
//!
//! For instance, a task can fork into several concurrent routines using macros
//! like [`select_biased!`] or [`join!`] from the `futures` crate.
//!
//! # Concurrency and interrupts
//!
//! The OS supports the use of interrupt handlers to wake tasks through the
//! [`Notify`][exec::Notify] mechanism, but most OS facilities are not available
//! in interrupt context.
//!
//! By default, interrupts are masked when task code is running, so tasks can be
//! confident that they will preempted if, and only if, they `await`.
//!
//! Each time through the task polling loop, the OS unmasks interrupts to let
//! any pending interrupts run. Because the Cortex-M collects pending interrupts
//! while interrupts are masked, we don't run the risk of missing events.
//!
//! Interrupts are also unmasked whenever the idle processor is woken from
//! sleep, in order to handle the event that woke it up.
//!
//! If your application requires tighter interrupt response time, you can
//! configure the OS at startup to permit preemption of various kinds --
//! including allowing preemption by only a subset of your interrupts. See the
//! [`exec`] module for more details and some customization options.
//!
//! # Cancellation
//!
//! Co-routine tasks in this OS are just `Future`s, which means they can be
//! dropped. `Future`s are typically dropped just after they resolve (often just
//! after an `await` keyword in the calling code), but it's also possible to
//! drop a `Future` while it is pending. This can happen explicitly (by calling
//! [`drop`]), or as a side effect of other operations; for example, the macro
//! [`select_biased!`] waits for one future to resolve, and then drops the
//! others, whether they're done or not.
//!
//! This means it's useful to consider what cancellation *means* for any
//! particular task, and to ensure that its results are what you intend. There's
//! more about this in [The Intro Guide] and the technical note on
//! [Cancellation].
//!
//! `lilos` itself tries to make it easier for you to handle cancellation in
//! your programs, by providing APIs that have reasonable behavior on cancel.
//! All of the core APIs aim for _strict_ cancel-safety, where dropping a future
//! and retrying the operation that produced it is equivalent to not dropping
//! the future, in terms of visible side effects. (Obviously doing more work
//! will take more CPU cycles; that's not what we mean by side effects.)
//!
//! If some code is useful, but can't achieve strict cancel safety, it should go
//! in a separate crate. For example, the `lilos-handoff` crate _used to_ be
//! part of the core OS, but was evicted because it can only achieve a weaker
//! notion of cancel safety.
//!
//! Any deviations from this principle are considered bugs, and should be
//! reported if you notice them!
//!
//! [`select_biased!`]: https://docs.rs/futures/latest/futures/macro.select_biased.html
//! [`join!`]: https://docs.rs/futures/latest/futures/macro.join.html
//! [The Intro Guide]: https://github.com/cbiffle/lilos/blob/main/doc/intro.adoc
//! [Cancellation]: https://github.com/cbiffle/lilos/blob/main/doc/cancellation.adoc

#![no_std]

#![warn(
    elided_lifetimes_in_paths,
    explicit_outlives_requirements,
    missing_debug_implementations,
    missing_docs,
    semicolon_in_expressions_from_macros,
    single_use_lifetimes,
    trivial_casts,
    trivial_numeric_casts,
    unreachable_pub,
    unsafe_op_in_unsafe_fn,
    unused_qualifications,
)]

/// Internal assert macro that doesn't stringify its expression or generate any
/// fancy messages. This means failures must be diagnosed by file:line only, so,
/// don't use this more than once on the same line. In exchange, this makes
/// asserts significantly smaller in terms of text size.
macro_rules! cheap_assert {
    ($x:expr) => {
        if !$x { panic!(); };
    }
}
#[allow(unused_imports)]
pub(crate) use cheap_assert;

#[macro_use]
pub mod list;
pub mod exec;
pub mod util;
pub mod atomic;

#[cfg(feature = "systick")]
pub mod time;
#[cfg(feature = "mutex")]
pub mod mutex;
#[cfg(feature = "spsc")]
pub mod spsc;