unified: Add support for semaphore groups
Semaphore groups are enabled by default. Disabling them will both
decrease the footprint as well as improve the performance of the
k_sem_give() routine.
Change-Id: If6c1b0e2e1f71afd43e620f05f17068039d12b05
Signed-off-by: Peter Mitsis <peter.mitsis@windriver.com>
diff --git a/include/kernel.h b/include/kernel.h
index 74dcd2f..c779301 100644
--- a/include/kernel.h
+++ b/include/kernel.h
@@ -644,10 +644,53 @@
return sem->count;
}
-extern struct k_sem *k_sem_group_take(struct k_sem **sem_array,
- int32_t timeout);
-extern void k_sem_group_give(struct k_sem **sem_array);
-extern void k_sem_group_reset(struct k_sem **sem_array);
+#ifdef CONFIG_SEMAPHORE_GROUPS
+/**
+ * @brief Take the first available semaphore
+ *
+ * Given a list of semaphore pointers, this routine will attempt to take one
+ * of them, waiting up to a maximum of @a timeout ms to do so. The taken
+ * semaphore is identified by @a sem (set to NULL on error).
+ *
+ * Be aware that the more semaphores specified in the group, the more stack
+ * space is required by the waiting thread.
+ *
+ * @param sem_array Array of semaphore pointers terminated by a K_END entry
+ * @param sem Identifies the semaphore that was taken
+ * @param timeout Maximum number of milliseconds to wait
+ *
+ * @retval 0 A semaphore was successfully taken
+ * @retval -EBUSY No semaphore was available (@a timeout = K_NO_WAIT)
+ * @retval -EAGAIN Time out occurred while waiting for semaphore
+ */
+
+extern int k_sem_group_take(struct k_sem *sem_array[], struct k_sem **sem,
+ int32_t timeout);
+
+/**
+ * @brief Give all the semaphores in the group
+ *
+ * This routine will give each semaphore in the array of semaphore pointers.
+ *
+ * @param sem_array Array of semaphore pointers terminated by a K_END entry
+ *
+ * @return N/A
+ */
+extern void k_sem_group_give(struct k_sem *sem_array[]);
+
+/**
+ * @brief Reset the count to zero on each semaphore in the array
+ *
+ * This routine resets the count of each semaphore in the group to zero.
+ * Note that it does NOT have any impact on any thread that might have
+ * been previously pending on any of the semaphores.
+ *
+ * @param sem_array Array of semaphore pointers terminated by a K_END entry
+ *
+ * @return N/A
+ */
+extern void k_sem_group_reset(struct k_sem *sem_array[]);
+#endif
#define K_SEM_INITIALIZER(obj, initial_count, count_limit) \
{ \
diff --git a/include/legacy.h b/include/legacy.h
index b16fb68..0759e32 100644
--- a/include/legacy.h
+++ b/include/legacy.h
@@ -273,15 +273,21 @@
#define task_sem_reset k_sem_reset
#define task_sem_count_get k_sem_count_get
+#ifdef CONFIG_SEMAPHORE_GROUPS
typedef ksem_t *ksemg_t;
static inline ksem_t task_sem_group_take(ksemg_t group, int32_t timeout)
{
- return k_sem_group_take(group, _ticks_to_ms(timeout));
+ struct k_sem *sem;
+
+ (void)k_sem_group_take(group, &sem, _ticks_to_ms(timeout));
+
+ return sem;
}
#define task_sem_group_give k_sem_group_give
#define task_sem_group_reset k_sem_group_reset
+#endif
#define DEFINE_SEMAPHORE(name) \
K_SEM_DEFINE(_k_sem_obj_##name, 0, UINT_MAX); \
diff --git a/kernel/unified/Kconfig b/kernel/unified/Kconfig
index 5db434e..39cbadc 100644
--- a/kernel/unified/Kconfig
+++ b/kernel/unified/Kconfig
@@ -277,4 +277,14 @@
not subject to time slicing.
endmenu
+
+config SEMAPHORE_GROUPS
+ bool "Enable semaphore groups"
+ default y
+ help
+ This option enables support for semaphore groups. Threads that use
+ semaphore groups require more stack space. Disabling this option will
+ both decrease the footprint as well as improve the performance of
+ the k_sem_give() routine.
+
endmenu
diff --git a/kernel/unified/sem.c b/kernel/unified/sem.c
index 43ef159..b142059 100644
--- a/kernel/unified/sem.c
+++ b/kernel/unified/sem.c
@@ -35,6 +35,19 @@
#include <misc/dlist.h>
#include <sched.h>
+#ifdef CONFIG_SEMAPHORE_GROUPS
+struct _sem_desc {
+ sys_dnode_t semg_node; /* Node in list of semaphores */
+ struct k_thread *thread; /* Thread waiting for semaphores */
+ struct k_sem *sem; /* Semaphore on which to wait */
+};
+
+struct _sem_thread {
+ struct tcs_base dummy;
+ struct _sem_desc desc;
+};
+#endif
+
void k_sem_init(struct k_sem *sem, unsigned int initial_count,
unsigned int limit)
{
@@ -46,29 +59,233 @@
SYS_TRACING_OBJ_INIT(nano_sem, sem);
}
-void k_sem_give(struct k_sem *sem)
+#ifdef CONFIG_SEMAPHORE_GROUPS
+int k_sem_group_take(struct k_sem *sem_array[], struct k_sem **sem,
+ int32_t timeout)
{
- int key = irq_lock();
- struct tcs *first_pending_thread = _unpend_first_thread(&sem->wait_q);
+ unsigned int key;
+ struct k_sem *item = *sem_array;
+ int num = 0;
- if (first_pending_thread) {
- _timeout_abort(first_pending_thread);
- _ready_thread(first_pending_thread);
+ __ASSERT(sem_array[0] != K_END, "Empty semaphore list");
- _set_thread_return_value(first_pending_thread, 0);
+ key = irq_lock();
- if (!_is_in_isr() && _must_switch_threads()) {
- _Swap(key);
- return;
+ do {
+ if (item->count > 0) {
+ item->count--; /* Available semaphore found */
+ irq_unlock(key);
+ *sem = item;
+ return 0;
}
- } else {
- if (likely(sem->count != sem->limit)) {
- sem->count++;
- }
+ num++;
+ item = sem_array[num];
+ } while (item != K_END);
+
+ if (timeout == K_NO_WAIT) {
+ irq_unlock(key);
+ *sem = NULL;
+ return -EBUSY;
}
+ struct _sem_thread wait_objects[num];
+ int32_t priority = k_thread_priority_get(_current);
+ sys_dlist_t list;
+
+ sys_dlist_init(&list);
+ _current->swap_data = &list;
+
+ for (int i = 0; i < num; i++) {
+ wait_objects[i].dummy.flags = K_DUMMY;
+ wait_objects[i].dummy.prio = priority;
+
+ _timeout_tcs_init((struct k_thread *) &wait_objects[i].dummy);
+
+ sys_dlist_append(&list, &wait_objects[i].desc.semg_node);
+ wait_objects[i].desc.thread = _current;
+ wait_objects[i].desc.sem = sem_array[i];
+
+ _pend_thread((struct k_thread *)&wait_objects[i].dummy,
+ &sem_array[i]->wait_q, timeout);
+ }
+
+ /* Pend the current thread on a dummy wait queue */
+
+ _wait_q_t wait_q;
+
+ sys_dlist_init(&wait_q);
+ _pend_current_thread(&wait_q, timeout);
+
+ if (_Swap(key) != 0) {
+ *sem = NULL;
+ return -EAGAIN;
+ }
+
+ /* The accepted semaphore is the only one left on the list */
+
+ struct _sem_desc *desc = (struct _sem_desc *)sys_dlist_get(&list);
+
+ *sem = desc->sem;
+ return 0;
+}
+
+/**
+ * @brief Cancel all but specified semaphore in list if part of a semphore group
+ *
+ * Interrupts are locked prior to calling this routine
+ *
+ * @return 0 if not part of semaphore group, 1 if it is
+ */
+static int handle_sem_group(struct k_sem *sem, struct k_thread *thread)
+{
+ struct _sem_thread *dummy = (struct _sem_thread *)thread;
+ struct _sem_thread *sem_thread;
+ struct _sem_desc *desc = NULL;
+ sys_dlist_t *list;
+ sys_dnode_t *node;
+ sys_dnode_t *next;
+
+ if (!(thread->flags & K_DUMMY)) {
+ /*
+ * The awakened thread is a real thread and thus was not
+ * involved in a semaphore group operation.
+ */
+ return 0;
+ }
+
+ /*
+ * The awakened thread is a dummy thread and thus was involved
+ * in a semaphore group operation.
+ */
+
+ list = (sys_dlist_t *)dummy->desc.thread->swap_data;
+ node = sys_dlist_peek_head(list);
+
+ __ASSERT(node != NULL, "");
+
+ do {
+ next = sys_dlist_peek_next(list, node);
+
+ desc = (struct _sem_desc *)node;
+
+ if (desc->sem != sem) {
+ sem_thread = CONTAINER_OF(desc, struct _sem_thread,
+ desc);
+
+ _timeout_abort((struct k_thread *)&sem_thread->dummy);
+ _unpend_thread((struct k_thread *)&sem_thread->dummy);
+
+ sys_dlist_remove(node);
+ }
+ node = next;
+ } while (node != NULL);
+
+ /*
+ * If 'desc' is NULL, then the user-supplied 'sem_array' had only
+ * one semaphore in it. This is considered a user error as
+ * k_sem_give() should have been called instead.
+ */
+
+ __ASSERT(desc != NULL, "");
+
+ /*
+ * As this code may be executed several times by a semaphore group give
+ * operation, it is important to ensure that the attempt to ready the
+ * master thread is done only once.
+ */
+
+ if (!_is_thread_ready(desc->thread)) {
+ _reset_thread_states(desc->thread, K_PENDING | K_TIMING);
+ _timeout_abort(desc->thread);
+ if (_is_thread_ready(desc->thread)) {
+ _add_thread_to_ready_q(desc->thread);
+ }
+ }
+ _set_thread_return_value(desc->thread, 0);
+
+ return 1;
+}
+
+#else
+#define handle_sem_group(sem, thread) 0
+#endif
+
+/**
+ * @brief Common semaphore give code
+ *
+ * @return true if _Swap() will need to be invoked; false if not
+ */
+static bool sem_give_common(struct k_sem *sem)
+{
+ struct k_thread *thread;
+
+ thread = _unpend_first_thread(&sem->wait_q);
+ if (!thread) {
+ /*
+ * No thread is waiting on the semaphore.
+ * Increment the semaphore's count unless
+ * its limit has already been reached.
+ */
+ sem->count += (sem->count != sem->limit);
+ return false;
+ }
+
+ _timeout_abort(thread);
+
+ if (!handle_sem_group(sem, thread)) {
+ /* Handle the non-group case */
+ _ready_thread(thread);
+ _set_thread_return_value(thread, 0);
+ }
+
+ return !_is_in_isr() && _must_switch_threads();
+}
+
+#ifdef CONFIG_SEMAPHORE_GROUPS
+void k_sem_group_give(struct k_sem *sem_array[])
+{
+ unsigned int key;
+ bool swap_needed = false;
+
+ __ASSERT(sem_array[0] != K_END, "Empty semaphore list");
+
+ key = irq_lock();
+
+ for (int i = 0; sem_array[i] != K_END; i++) {
+ swap_needed |= sem_give_common(sem_array[i]);
+ }
+
+ if (swap_needed) {
+ _Swap(key);
+ } else {
+ irq_unlock(key);
+ }
+}
+
+void k_sem_group_reset(struct k_sem *sem_array[])
+{
+ unsigned int key;
+
+ key = irq_lock();
+ for (int i = 0; sem_array[i] != K_END; i++) {
+ sem_array[i]->count = 0;
+ }
irq_unlock(key);
}
+#endif
+
+void k_sem_give(struct k_sem *sem)
+{
+ unsigned int key;
+
+ key = irq_lock();
+
+ if (sem_give_common(sem)) {
+ _Swap(key);
+ } else {
+ irq_unlock(key);
+ }
+}
int k_sem_take(struct k_sem *sem, int32_t timeout)
{