/*
 * 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;
	bool sync;
	struct k_sem done_sem;

	/* reserved for implementation */
	union {
		struct rbnode rbnode;
		sys_dlist_t dlnode;
	};
	struct k_thread *thread;
	struct k_p4wq *queue;
};

#define K_P4WQ_QUEUE_PER_THREAD		BIT(0)
#define K_P4WQ_DELAYED_START		BIT(1)
#define K_P4WQ_USER_CPU_MASK		BIT(2)

/**
 * @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;

	/* K_P4WQ_* flags above */
	uint32_t flags;
};

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;
	uint32_t flags;
};

/**
 * @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 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,						\
		.flags = 0,						\
	}

/**
 * @brief Statically initialize an array of P4 Work Queues
 *
 * Statically defines an array of struct k_p4wq objects 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 array that will be defined
 * @param n_threads Number of threads and work queues
 * @param stack_sz Requested stack size of each thread, in bytes
 * @param flg Flags
 */
#define K_P4WQ_ARRAY_DEFINE(name, n_threads, stack_sz, flg)		\
	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[n_threads];				\
	static const 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,						\
		.flags = K_P4WQ_QUEUE_PER_THREAD | flg,			\
	}

/**
 * @brief Initialize P4 Queue
 *
 * Initializes a P4 Queue object.  These objects must 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);

/**
 * @brief Regain ownership of the work item, wait for completion if it's synchronous
 */
int k_p4wq_wait(struct k_p4wq_work *work, k_timeout_t timeout);

void k_p4wq_enable_static_thread(struct k_p4wq *queue, struct k_thread *thread,
				 uint32_t cpu_mask);

#endif /* ZEPHYR_INCLUDE_SYS_P4WQ_H_ */