zephyr/include/sys/p4wq.h
Andy Ross d2eadfa162 lib/os: P4 Work Queue: Pooled Parallel Preemptible Priority-based
This adds a somewhat special purpose IPC mechanism.  It's intended for
applications which have a "work queue" like architecture of discrete
callback items, but which need the ability to schedule those items
independently in separate threads across multiple CPUs.  So P4 Work
items:

1. Can run at any Zephyr scheduler priority and with any deadline
   (this feature assumes EDF scheduling is enabled)

2. Can be submitted at any time and from any context, including being
   resubmitted from within their own handler.

3. Will preempt any lower priority work as soon as they are runnable,
   according to the standard rules of Zephyr priority scheduling.

4. Run from a pool of worker threads that can be allocated efficiently
   (i.e. you need as many as the number of CPUs plus the number of
   preempted in-progress items, but no more).

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2021-01-15 11:35:50 -05:00

164 lines
4.8 KiB
C

/*
* Copyright (c) 2020 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_SYS_P4WQ_H_
#define ZEPHYR_INCLUDE_SYS_P4WQ_H_
#include <kernel.h>
/* Zephyr Pooled Parallel Preemptible Priority-based Work Queues */
struct k_p4wq_work;
/**
* P4 Queue handler callback
*/
typedef void (*k_p4wq_handler_t)(struct k_p4wq_work *work);
/**
* @brief P4 Queue Work Item
*
* User-populated struct representing a single work item. The
* priority and deadline fields are interpreted as thread scheduling
* priorities, exactly as per k_thread_priority_set() and
* k_thread_deadline_set().
*/
struct k_p4wq_work {
/* Filled out by submitting code */
int32_t priority;
int32_t deadline;
k_p4wq_handler_t handler;
/* reserved for implementation */
union {
struct rbnode rbnode;
sys_dlist_t dlnode;
};
struct k_thread *thread;
};
/**
* @brief P4 Queue
*
* Kernel pooled parallel preemptible priority-based work queue
*/
struct k_p4wq {
struct k_spinlock lock;
/* Pending threads waiting for work items
*
* FIXME: a waitq isn't really the right data structure here.
* Wait queues are priority-sorted, but we don't want that
* sorting overhead since we're effectively doing it ourselves
* by directly mutating the priority when a thread is
* unpended. We just want "blocked threads on a list", but
* there's no clean scheduler API for that.
*/
_wait_q_t waitq;
/* Work items waiting for processing */
struct rbtree queue;
/* Work items in progress */
sys_dlist_t active;
};
struct k_p4wq_initparam {
uint32_t num;
uintptr_t stack_size;
struct k_p4wq *queue;
struct k_thread *threads;
struct z_thread_stack_element *stacks;
};
/**
* @brief Statically initialize a P4 Work Queue
*
* Statically defines a struct k_p4wq object with the specified number
* of threads which will be initialized at boot and ready for use on
* entry to main().
*
* @param name Symbol name of the struct k_p4wq that will be defined
* @param n_threads Number of threads in the work queue pool
* @param stack_sz Requested stack size of each thread, in bytes
*/
#define K_P4WQ_DEFINE(name, n_threads, stack_sz) \
static K_THREAD_STACK_ARRAY_DEFINE(_p4stacks_##name, \
n_threads, stack_sz); \
static struct k_thread _p4threads_##name[n_threads]; \
static struct k_p4wq name; \
static const Z_STRUCT_SECTION_ITERABLE(k_p4wq_initparam, \
_init_##name) = { \
.num = n_threads, \
.stack_size = stack_sz, \
.threads = _p4threads_##name, \
.stacks = &(_p4stacks_##name[0][0]), \
.queue = &name, \
}
/**
* @brief Initialize P4 Queue
*
* Initializes a P4 Queue object. These objects much be initialized
* via this function (or statically using K_P4WQ_DEFINE) before any
* other API calls are made on it.
*
* @param queue P4 Queue to initialize
*/
void k_p4wq_init(struct k_p4wq *queue);
/**
* @brief Dynamically add a thread object to a P4 Queue pool
*
* Adds a thread to the pool managed by a P4 queue. The thread object
* must not be in use. If k_thread_create() has previously been
* called on it, it must be aborted before being given to the queue.
*
* @param queue P4 Queue to which to add the thread
* @param thread Uninitialized/aborted thread object to add
* @param stack Thread stack memory
* @param stack_size Thread stack size
*/
void k_p4wq_add_thread(struct k_p4wq *queue, struct k_thread *thread,
k_thread_stack_t *stack,
size_t stack_size);
/**
* @brief Submit work item to a P4 queue
*
* Submits the specified work item to the queue. The caller must have
* already initialized the relevant fields of the struct. The queue
* will execute the handler when CPU time is available and when no
* higher-priority work items are available. The handler may be
* invoked on any CPU.
*
* The caller must not mutate the struct while it is stored in the
* queue. The memory should remain unchanged until k_p4wq_cancel() is
* called or until the entry to the handler function.
*
* @note This call is a scheduling point, so if the submitted item (or
* any other ready thread) has a higher priority than the current
* thread and the current thread has a preemptible priority then the
* caller will yield.
*
* @param queue P4 Queue to which to submit
* @param item P4 work item to be submitted
*/
void k_p4wq_submit(struct k_p4wq *queue, struct k_p4wq_work *item);
/**
* @brief Cancel submitted P4 work item
*
* Cancels a previously-submitted work item and removes it from the
* queue. Returns true if the item was found in the queue and
* removed. If the function returns false, either the item was never
* submitted, has already been executed, or is still running.
*
* @return true if the item was successfully removed, otherwise false
*/
bool k_p4wq_cancel(struct k_p4wq *queue, struct k_p4wq_work *item);
#endif /* ZEPHYR_INCLUDE_SYS_P4WQ_H_ */