Module work

Source
Expand description

Zephyr Work Queues

§Zephyr Work Queues and Work

Zephyr has a mechanism called a Workqueue.

Each workqueue is backed by a single Zephyr thread, and has its own stack. The work queue consists of a FIFO queue of work items that will be run consecutively on that thread. The underlying types are k_work_q for the work queue itself, and k_work for the worker.

In addition to the simple schedulable work, Zephyr also has two additional types of work: k_work_delayable which can be scheduled to run in the future, and k_work_poll, described as triggered work in the docs. This can be scheduled to run when various items within Zephyr become available. This triggered work also has a timeout. In this sense the triggered work is a superset of the other types of work. Both delayable and triggered work are implemented by having the k_work embedded in their structure, and Zephyr schedules the work when the given reason happens.

At this time, only the basic work queue type is supported.

Zephyr’s work queues can be used in different ways:

  • Work can be scheduled as needed. For example, an IRQ handler can queue a work item to process data it has received from a device.
  • Work can be scheduled periodically.

As most C use of Zephyr statically allocates things like work, these are typically rescheduled when the work is complete. The work queue scheduling functions are designed, and intended, for a given work item to be able to reschedule itself, and such usage is common.

§Ownership

The remaining challenge with implementing k_work for Rust is that of ownership. The model taken here is that the work items are held in a Box that is effectively owned by the work itself. When the work item is scheduled to Zephyr, ownership of that box is effectively handed off to C, and then when the work item is called, the Box re-constructed. This repeats until the work is no longer needed, at which point the work will be dropped.

There are two common ways the lifecycle of work can be managed in an embedded system:

  • A set of Future’s are allocated once at the start, and these never return a value. Work Futures inside of this (which correspond to .await in async code) can have lives and return values, but the main loops will not return values, or be dropped. Embedded Futures will typically not be boxed.

One consequence of the ownership being passed through to C code is that if the work cancellation mechanism is used on a work queue, the work items themselves will be leaked.

These work items are also Pin, to ensure that the work actions are not moved.

§The work queues themselves

Workqueues themselves are built using WorkQueueBuilder. This needs a statically defined stack. Typical usage will be along the lines of:

kobj_define! {
  WORKER_STACK: ThreadStack<2048>;
}
// ...
let main_worker = Box::new(
    WorkQueueBuilder::new()
        .set_priority(2).
        .set_name(c"mainloop")
        .set_no_yield(true)
        .start(MAIN_LOOP_STACK.init_once(()).unwrap())
    );

let _ = zephyr::kio::spawn(
    mainloop(), // Async or function returning Future.
    &main_worker,
    c"w:mainloop",
);

...

// Leak the Box so that the worker is never freed.
let _ = Box::leak(main_worker);

It is important that WorkQueues never be dropped. It has a Drop implementation that invokes panic. Zephyr provides no mechanism to stop work queue threads, so dropping would result in undefined behavior.

§Current Status

Although Zephyr has 3 types of work queues, the k_work_poll is sufficient to implement all of the behavior, and this implementation only implements this type. Non Future work could be built around the other work types.

As such, this means that manually constructed work is still built using Future. The _async primitives throughout this crate can be used just as readily by hand-written Futures as by async code. Notable, the use of Signal will likely be common, along with possible timeouts.

Structs§

Signal
A Rust wrapper for k_poll_signal.
Work
A basic Zephyr work item.
WorkQueue
A running work queue thread.
WorkQueueBuilder
A builder for work queues themselves.

Enums§

SubmitResult
Possible returns from work queue submission.

Traits§

SimpleAction
A simple action that just does something with its data.

Functions§

get_current_workq
Retrieve the current work queue, if we are running within one.