This release is mostly internal cleanups, plus support for portable-atomic and
critical-section. The changes are basically size-neutral; ARMv6-M targets get
slightly smaller, fancier ARM targets are unchanged or slightly larger.
No porting should be necessary unless I've made a mistake.
I've moved the OS over to portable-atomic internally and deprecated my version
of it. To retain compatibility with earlier versions of lilos, I've enabled
the cortex-m crate's critical-section-single-core feature. This generates
effectively the same code that I had written by hand. This won't require changes
in most applications, but it will allow you to write atomic data structures
that are portable to platforms other than lilos.
However, if you were using some other critical-section implementation,
this will break you. I don't consider this a breaking change in lilos because
that other critical-section implementation was almost certainly wrong for
lilos. In a future (breaking) version where we support SMP, I will switch
lilos to a bring-your-own-critical-section model.
Other than that, changes from the application's perspective are:
-
lilosnow provides an implementation ofcritical-sectionfor Rust embedded crates that expect one. It works by globally disabling interrupts. (Note that most crates that usecritical-sectionare doing it to defend against task preemption, which we don't support.) -
The
lilos::atomicmodule is now deprecated, and I suggest using theportable-atomiccrate in its place. -
Generated code tends to be a bit smaller.
PeriodicGate::is_lagginglets code detect if its timing loop is falling behind. I've used this to skip display updates in a GUI when I/O operations interfere with rendering.
-
Fixed the described return type of
create_static_mutex!. It's been wrong since the macro was written, so the actual type isn't changing and no code should break. -
Fixed the example code for
PeriodicGate, which was missing amut.
-
Make
rustcaware of the customcfgvalues that we use under the hood, so that it can detect typos in them. (I love thisrustcfeature.) -
Several changes to eliminate new warnings in recent Clippy versions (should not affect programs using
lilos). All these changes are to the text of comments; Clippy having opinions about my comment wording is not my favorite.
Internal changes to data structures mean the OS minimum RAM requirement is down by 1/3 (from 60 bytes to 40), and programs I've tested are at least 100 bytes smaller in flash.
-
The original
listmodule, and theListandNodetypes it contained, are now deprecated. The newlilos-listcrate provides a dramatically improved version. In particular, this eliminates the need for the "two-phase init" dance with pinned OS types that has hauntedlilosfrom the early days.- Uses of the old
Listtype should use the new one fromlilos-list. - Instead of using macros to create a list, write:
pin!(List::new()). - The
Nodetype is now private. Instead, the "contents" field that used to be passed tocreate_node!is now a parameter to theList::joinfunction.
- Uses of the old
-
Two-phase init of
Mutexis now deprecated. You can now just writepin!(Mutex::create(something)). (It'screateand notnewbecause I'm preservingnewin deprecated form for backwards compatibility with 1.1.) The mutex creation macros still work. -
It is now possible to create a
MutexorListin aconst fncontext, including in a static initializer.
-
The
lilos-listcrate exists now. -
rwlock0.2 uses the newlilos-listinternally, and no longer requires (or supports) two-phase init. I've removed the creation macro. -
semaphore0.2 uses the newlilos-listinternally, and no longer requires (or supports) two-phase init. I've removed the creation macro here, too.
-
All internal use of lists has been rewritten to use
lilos-list. -
All code using any kind of lists, particularly timer lists, should now be slightly smaller.
-
Removed an internal mostly-unused
Wakerimplementation, reducing the base flash requirement of the OS for applications that use the newlilos-listmodule. -
spscnow usesget_uncheckedin a couple of places to avoid generating bounds checks. This is the first place inliloswhere I've used unsafe code for performance or size reasons, and I hope not to make a habit of it.
- It turns out that
List's ordering behavior has been subtly inconsistent for quite some time, but in a way that was impossible to observe in previous versions. The extensions to theListAPI (below) exposed this fact, and it is fixed in this version.
-
ListandNodenow take a second generic parameter,M, which for compatibility with 1.0 is defaulted to().Mallows arbitrary application-defined metadata to be associated with each node if desired. This is useful for implementing more complex synchronization primitives, and is not used by the core OS directly. -
The
create_node!macro now allows itswakerparameter to be omitted, defaulting tonoop_waker. -
Renamed
List::wake_less_thantoList::wake_thru, since its behavior (waking nodes with contents less than or equal to a value) didn't match its name. The old name is still available as an alias, for compatibility with 1.x, but is deprecated. -
Added
List::wake_one_ifas a generalized version ofList::wake_onefor doing conditional wakes. -
Added
List::wake_whileas a generalized version ofList::wake_thru. This has the side effect of making the order of nodes with the samecontentsvalue observable, which is what exposed the list ordering bug mentioned above (now fixed). -
Added
List::is_emptyfor checking if any nodes are waiting on a list. (It's surprising that I haven't needed this before!) -
The
CancelSafetype fromlilos::mutexis now available inlilos::utilindependent of which features are turned on, to make it easier to use in external lock types.lilos::mutex::CancelSafeis now an alias.
- Added an initial prototype of
lilos-rwlock, a read-write lock.
This release should have no user-visible changes, only internal improvements.
- Added an initial prototype of the
lilos-semaphorecrate, which provides a counting semaphore designed for use withlilos. The crate's version is set to0.1.0to reflect that it's not a final or released API just yet.
-
Switched from
pin_project_litetopin_projectto gain some features that let me remove several unsafe blocks. -
Switched back to a Cargo workspace.
The only user-facing change in this release is that the internal AtomicExt
trait has been sealed, preventing implementation outside of lilos. This trait
was never intended to be exposed, so I'm treating this as a bugfix and yanking
1.0.0 to avoid confusing anyone.
After five years of development, I'm prepared to declare this a 1.0 release!
For an example of the changes required to port from 0.3.x to 1.0, see the upgrade commit in the Keypad:GO! firmware.
Minimum Supported Rust Version is still 1.69, the earliest stable toolchain that
can build lilos and applications. I'm relying on CI to check this and not
using 1.69 for day-to-day development because it predates Cargo's sparse index
fix.
-
Time-related public API is now centralized in the
timemodule. In earlier versions this was split betweentimeandexecfor historical reasons. For porting existing programs, look for uses of thesleep_*,with_*, andPeriodicGateAPIs; they have moved. I've been careful not to rename any of these APIs, so you should just need to replace theexecmodule withtimewhere you see compile errors.- This renaming breaks compatibility with older versions of the debugger, so, update.
- I removed some rarely-used and hard-to-make-correct corners of the API,
such as
every_until. There is no direct replacement.
-
Renamed
spscandhandoffsplit handle types:Pushis nowPusher,Popis nowPopper. (Because their old names sounded like what you'd name the future type for completing a push or pop, and I felt like that was misleading.) -
Totally reworked the Mutex API to further improve robustness in the face of cancellation.
- The basic locking operation
lock(and its friendtry_lock) now return a "permit" that can be used to do one synchronous thing to the guarded data. (This operation was namedperform/try_performin 0.3.x.) - If you need to access the guarded data on either side of an
awaitpoint, thelock_assuming_cancel_safeoperation becomes available if you opt-in. (This operation was namedlockin all prior versions, but was renamed to make it harder to grab by accident and easier to spot in code review.)
- The basic locking operation
-
Removed operations that were marked as deprecated during the 0.3.x series:
spsc::Push::push. Please usereserveinstead. -
The
handoffmodule is no longer part of theliloscrate. It has been extracted into a separatelilos-handoffcrate. If you want it, add that crate to your project, and change references fromlilos::handofftolilos_handoff. (It's incredibly useful, but has some gotchas, so it seemed inappropriate in the core API.) Chances are pretty good that more "contrib" code like this will appear in a separate crate soon.
spsc::Queuewas intended todropany unpopped elements when theQueueitself was dropped, but @OscarFKE pointed out that a typo prevented this from actually happening! This has been fixed and queue contents should now be dropped when you'd expect.
-
Changes to the internal representation of the timer list may render applications built with this version of
lilosincompatible with older versions oflildbg. -
Fixed yet another soundness issue in
List, where theinsert_and_waitoperation was linking theNodeinto theListeagerly, before being polled. This exposed it to being passed toforget, which would leave the list in a corrupt state; once a future is polled, it's known to be pinned and can no longer be forgotten. The good news is, the issues withListhave all been in slightly different areas, and I think it's rapidly approaching "correct." In practice, it was impossible to trigger this bug unless you've got custom synchronization primitives that useListdirectly, and you do some very strange things involving callingforgeton futures. -
Removed accidental double-drop of
WakerinList. InlilostheDropimpl forWakeris a no-op, which is how this went unnoticed for this long. But it was still wrong, so, fixed now. -
Some of the test suite run scripts had rotted, since I normally run the tests in GDB. Thanks to @KoviRobi for the fix there.
-
The codebase should be ready for the
static_mut_refslint in Rust 1.77, which will become a hard error in the eagerly-awaited 2024 edition. -
Concrete future types are exposed for a few more operations, in case you need to store them in a struct or something:
exec::Notify::untilandexec::Notify::until_racy
-
@helixbass contributed a build of the OS testsuite for STM32F3. While the testsuite itself is processor-independent, this will make it easier for people to use the testsuite on the popular STM32F3-DISCOVERY board.
-
@kovirobi fixed the units on
initialize_sys_tick, which incorrectly claimed to be in megahertz instead of hertz. -
@kovirobi also contributed a QEMU-based test suite for CI, which is pretty slick.
-
Code simplifications and cleanup from @mattfbacon.
-
Basic support for Cortex-M4 without hardfloat from @timokroeger.
-
Fixed bug that meant
mutexwouldn't build if you didn't also havehandoffenabled. Applications relying on default features wouldn't hit this since they're both on by default. -
Added
exec::with_deadlineandexec::with_timeout, more convenient methods for time-limiting the execution of a future. Compared to the traditional method of usingselect_biased!, these functions are significantly less verbose, more obvious, and produce smaller code too. This also relieves applications from depending on thefuturescrate for timeouts if they don't want to.
- Fix bug in
handoffwhen built withoutdebug_assertions-- I introduced this in 0.3.4 in my code size "improvements." This has demonstrated the need to build the examples in both release and debug modes for coverage.
-
Changed the lifetimes on the
reserveoperation onspsc::Push. The original definition allowed for an unlikely but easily reachable deadlock, where code could simultaneously wait for two permits from the same queue -- the second permit will never arrive. I consider this a bug fix, and it won't break code unless that code contains a deadlock, so I'm including this in a minor rev. Currently not planning on yanking 0.3.3. -
Further code size improvements.
- Fixed a bug I introduced into
yield_cpuin 0.3.1. Please move away from 0.3.1/0.3.2 at your convenience. No other changes in this release.
- Added explicit cancel safety annotations to all async/future operations.
Currently everything except
handoffis either strict cancel-safe or deprecated! Yay. - Made certain implementation details cheaper on ARMv6-M.
- Took a pass over all the rustdoc and tidied things up. There are now more examples and stuff.
-
Based on watching people learning
async, I'm adding a new operation tolilos::spsc::Push:reserve. This is similar to the oldpushbut resolves to aPermitthat lets you do the push synchronously. This lets you avoid losing data if cancelled, which is critical for building higher-level cancel-safe abstractions. Because I'm increasingly convinced of its danger, I have deprecatedlilos::spsc::Push::push. Please usereserveif you need to block:q.reserve().await.push(data); -
lilosno longer depends onfutures, which may reduce your build times, and makes things easier to interpret in a debugger.
lilos now supports the stable toolchain!
-
Minimum supported Rust version now 1.69 for various fixes.
-
Cortex-M0 CPUs are now fully supported, with a worked example for the RP2040 in
examples/rp2040, and successful applications (not in this repo) on Nordic nRF52832 and STM32G0. (Note thatlilosis not multi-CPU aware, so the second core on the RP2040 can run code but notlilostasks. Because very fewlilostypes areSend, it should be hard to screw this up without effort.) -
All SysTick timer support is now behind a feature,
systick, which is on by default. You can omit this if you're targeting a platform where the SysTick timer is stopped in the normal sleep/wait states, such as the Nordic nRF52832. Your application will need to use interrupts (including potentially interrupts from a replacement low-power timer) to handle all events. I'll post a worked example eventually. -
Upgraded to
cortex-m-rt0.7.1; earlier 0.6-seriescortex-m-rtcrates don't reliably preserve stack alignment on startup, and must be avoided. (It would be nice if Cargo had something louder than yank in this case.) This is a user-visible change because you're responsible for linking in thecortex-m-rtsetup code into yourmainroutine -- make sure you're not using 0.6.
-
New STM32H7 UART echo example -- similar to the STM32F407 example, but on hardware you can buy! (Edit: ...aaaaand it's out of stock)
-
Changes to ensure that RLS/rust-analyzer work in examples.
-
OS APIs have given up on
never_typeever stabilizing and switched to the awkwardly-named-but-stablecore::convert::Infallibletype. The main implication for programs: task futures should now have the typeasync fn my_task(arguments) -> Infallibleinstead of-> !. You can probably search-replace this. -
The
MutexAPI has changed to omit thelockoperation by default. I've decided the traditionallock/MutexGuardapproach in async code makes it too easy to accidentally write cancel-incorrect abstractions. You now have to opt into the guard-based operations on aMutex-by-Mutexbasis by replacingMutex<T>withMutex<CancelSafe<T>>-- but first try using theperformoperation instead. -
APIs relying on
core::time::Durationhave been switched over to a newlilos::time::Millistype, withDurationoptionally supported where it makes sense. It turns out thatDurationis internally structured such that essentially all operations require 64-bit (or 128-bit!) multiplication and/or division/remainder. This became really obvious on M0, which lacks any sort of division insruction. Switching away fromDurationcuts several kiB off the size of the OS (which, depending on which features you're using, can be as much as 50-60%). -
The OS timestamp type has been renamed from
lilos::time::Tickstolilos::time::TickTimebecause I kept feeling like "ticks" sounds like a measure of time duration, rather than a timestamp. With the introduction ofMillisit started to seem really ambiguous, so, I changed it. -
Two significant API changes to
PeriodicGate:-
PeriodicGateis now created usingfrominstead ofnew, which lets it take either a cheapMillisor an expensiveDuration. (This is the main change required to port simple applications to 0.3 in my experience.) -
Added
PeriodicGate::new_shiftfor setting up periodic timers out of phase with respect to one another. This is useful for e.g. scheduling a display refresh at 60 Hz, and scheduling serial communication to happen at exactly the same frequency but shifted so they don't compete (which was the motivating use case).
-
-
Notify::untilis now more powerful and can wait for aboolcondition (its original use case), or for an operation returningOptionto returnSome. In the latter case, it returns the contained value. As a result, the olduntil_somehas been removed -- change any uses of it to useuntil. -
All public types in the OS now have
Debugimpls for your debug-printing pleasure. -
TickTimecan now be converted to and from au64. -
The internal
atomicmodule, containing "polyfill" atomic operations for portability between bigger cores and Cortex-M0, is nowpubso applications can use it if desired.
mutexmacros now importPinso you don't have to (whoops!).
- A bunch of code size optimizations for small processors.
- Switch to Rust 2021.
- Fix some uses of deprecated
cortex-mAPI. - More aggressive warning settings.
- Use
unsafe_op_in_unsafe_fn, which should really be Rust's default. - The repo is no longer a workspace, because builds in workspaces with
.cargo/configfiles change behavior depending on which directory you're in, despite having all build targets available, and this keeps confusing me. - Example programs and OS internals have switched to the newly stabilized
core::pin::pin!macro instead ofpin_utils. You don't have to do this in your own programs, but I recommend it!
-
Add
handoff(rendezvous) type, which can be much cheaper than a fullspscqueue if you don't mind having the sender and receiver synchronize. -
No longer require the
inline-asmfeature fromcortex-m. This is a visible change since it may affect your feature unification, but should not be a breaking change.
-
It became apparent that the original
queuehad soundness issues. Replacedqueuewithspsc, a rewritten version that I'm more confident in. This has a very different API, so this is a breaking change.spsc(single-producer single-consumer) is intended to distinguish it from other kinds of queues in the future. -
Both
spsc(formerlyqueue) andmutexare now behind features, so you can opt into the parts of the system you need.
Initial public release of the "operating system" factored out of my LED controller project.
0.1.1 and 0.1.2 were only docs changes.