Stage exclusion lock

The STAge eXclusion lock (aka stax) serializes in-band vs out-of-band thread activities for accessing an arbitrary resource. Such lock can be shared so that multiple threads which run on the same execution stage may ‘own’ the stax guarding the resource, excluding any access from the converse stage until the last owner drops the innermost lock. In other words, at any point in time, the resource guarded by a stax is either owned by out-of-band threads exclusively, or by in-band threads exclusively, or by no thread at all.

A stax is useful for implementing “phased sharing” of a resource between threads which belong to different execution stages, which normally access it during distinct phases of the overall process. For instance, a device (re)configuration phase may involve in-band operations such as DMA channel preparation, virtual memory allocation and page pinning, whilst a device operational phase may involve out-of-band operations exclusively to convey traffic in and out. Both phases should never overlap, but there has to be a way to guarantee this, otherwise things might end with extreme prejudice if they do.

A stax is such a sanity mechanism which guarantees that threads running on different stages can never access the guarded resource concurrently. By design, a stax cannot guarantee bounded low latency to out-of-band threads though: if the application allows threads from both stages to compete for the stax, the out-of-band threads may be delayed until no in-band activity runs in the guarded section.

Sleeping locks like the EVL kernel mutexes cannot be used for synchronizing in-band threads, and conversely regular kernel mutexes cannot be used for synchronizing out-of-band threads. However, since we may assume that there will be no stage concurrency when accessing the resource guarded by a stax, as a consequence we may rely on stage-specific serializers to enforce mutual exclusion among them while they own the stax, without having to care further about threads running on the converse stage. For instance, if the current kernel thread is running out-of-band within a section of code guarded by a stax, it knows beforehand that it will never compete with in-band threads while there, therefore only concurrency with other out-of-band threads remains to be addressed. This solves a tricky issue about sharing the implementation of a common driver between in-band and out-of-band users in a safe way.

Stage exclusion locking

void evl_init_stax(struct evl_stax *stax)

Initialize an EVL stax.

  • stax

    A stax descriptor is constructed by evl_init_stax(), which contains ancillary information other calls will need.

  • void evl_destroy_stax(struct evl_stax *stax)

    Delete an EVL stax. Any thread sleeping on the out-of-band stage for the stax to become available is woken up by this call, receiving a ‘resource removed’ status (-EIDRM).

  • stax

    The descriptor of the stax to be destroyed.

  • int evl_lock_stax(struct evl_stax *stax)

    Lock the stax for the current stage. The first thread which is granted access to the stax enables all threads running on the same stage to enter the section concurrently, locking out any thread which runs on the converse stage.

  • stax

    The stax descriptor.

  • Returns zero on success, otherwise:

    • if the caller runs in-band on entry:

      • -ERESTARTSYS if the caller was interrupted by a signal while waiting for access (i.e. while one or more out-of-band threads were running in the guarded section).
    • if the caller runs out-of-band on entry:

      • -EINTR if the caller was forcibly unblocked while sleeping (e.g. by a call to evl_unblock_thread()).

      • -EIDRM if the stax was deleted while the caller was sleeping on it. When this status is returned, the stax must be considered stale and should not be accessed anymore.

    int evl_trylock_stax(struct evl_stax *stax)

    Attempt to lock the stax. This is a variant of evl_lock_stax()) which does not block the caller if access cannot be immediately granted on entry.

    Returns zero on success, otherwise -EAGAIN if the stax was already locked for the converse stage on entry.

    void evl_unlock_stax(struct evl_stax *stax)

    Unlock the stax, dropping a lock previously acquired by a successful call to evl_lock_stax()) or evl_trylock_stax()). Once all locks currently held by threads which belong to the same execution stage (i.e. in-band or out-of-band) have been released, the stax becomes available anew for the converse stage to acquire it.

  • stax

    The stax descriptor.

  • Like most calls in this API, unlocking a stax is sensitive to the current execution stage of the caller (in-band or out-of-band). Do NOT switch stage while holding a stax lock, ever.

    Last modified: Sun, 23 Jul 2023 09:49:44 +0200