1 /*
2 * Copyright (c) 2010-2016 Wind River Systems, Inc.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /**
8 * @file
9 *
10 * @brief Kernel semaphore object.
11 *
12 * The semaphores are of the 'counting' type, i.e. each 'give' operation will
13 * increment the internal count by 1, if no thread is pending on it. The 'init'
14 * call initializes the count to 'initial_count'. Following multiple 'give'
15 * operations, the same number of 'take' operations can be performed without
16 * the calling thread having to pend on the semaphore, or the calling task
17 * having to poll.
18 */
19
20 #include <kernel.h>
21 #include <kernel_structs.h>
22
23 #include <toolchain.h>
24 #include <wait_q.h>
25 #include <sys/dlist.h>
26 #include <ksched.h>
27 #include <init.h>
28 #include <syscall_handler.h>
29 #include <tracing/tracing.h>
30 #include <sys/check.h>
31
32 /* We use a system-wide lock to synchronize semaphores, which has
33 * unfortunate performance impact vs. using a per-object lock
34 * (semaphores are *very* widely used). But per-object locks require
35 * significant extra RAM. A properly spin-aware semaphore
36 * implementation would spin on atomic access to the count variable,
37 * and not a spinlock per se. Useful optimization for the future...
38 */
39 static struct k_spinlock lock;
40
z_impl_k_sem_init(struct k_sem * sem,unsigned int initial_count,unsigned int limit)41 int z_impl_k_sem_init(struct k_sem *sem, unsigned int initial_count,
42 unsigned int limit)
43 {
44 /*
45 * Limit cannot be zero and count cannot be greater than limit
46 */
47 CHECKIF(limit == 0U || limit > K_SEM_MAX_LIMIT || initial_count > limit) {
48 SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, -EINVAL);
49
50 return -EINVAL;
51 }
52
53 sem->count = initial_count;
54 sem->limit = limit;
55
56 SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, 0);
57
58 z_waitq_init(&sem->wait_q);
59 #if defined(CONFIG_POLL)
60 sys_dlist_init(&sem->poll_events);
61 #endif
62 z_object_init(sem);
63
64 return 0;
65 }
66
67 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_init(struct k_sem * sem,unsigned int initial_count,unsigned int limit)68 int z_vrfy_k_sem_init(struct k_sem *sem, unsigned int initial_count,
69 unsigned int limit)
70 {
71 Z_OOPS(Z_SYSCALL_OBJ_INIT(sem, K_OBJ_SEM));
72 return z_impl_k_sem_init(sem, initial_count, limit);
73 }
74 #include <syscalls/k_sem_init_mrsh.c>
75 #endif
76
handle_poll_events(struct k_sem * sem)77 static inline void handle_poll_events(struct k_sem *sem)
78 {
79 #ifdef CONFIG_POLL
80 z_handle_obj_poll_events(&sem->poll_events, K_POLL_STATE_SEM_AVAILABLE);
81 #else
82 ARG_UNUSED(sem);
83 #endif
84 }
85
z_impl_k_sem_give(struct k_sem * sem)86 void z_impl_k_sem_give(struct k_sem *sem)
87 {
88 k_spinlock_key_t key = k_spin_lock(&lock);
89 struct k_thread *thread;
90
91 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, give, sem);
92
93 thread = z_unpend_first_thread(&sem->wait_q);
94
95 if (thread != NULL) {
96 arch_thread_return_value_set(thread, 0);
97 z_ready_thread(thread);
98 } else {
99 sem->count += (sem->count != sem->limit) ? 1U : 0U;
100 handle_poll_events(sem);
101 }
102
103 z_reschedule(&lock, key);
104
105 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, give, sem);
106 }
107
108 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_give(struct k_sem * sem)109 static inline void z_vrfy_k_sem_give(struct k_sem *sem)
110 {
111 Z_OOPS(Z_SYSCALL_OBJ(sem, K_OBJ_SEM));
112 z_impl_k_sem_give(sem);
113 }
114 #include <syscalls/k_sem_give_mrsh.c>
115 #endif
116
z_impl_k_sem_take(struct k_sem * sem,k_timeout_t timeout)117 int z_impl_k_sem_take(struct k_sem *sem, k_timeout_t timeout)
118 {
119 int ret = 0;
120
121 __ASSERT(((arch_is_in_isr() == false) ||
122 K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");
123
124 k_spinlock_key_t key = k_spin_lock(&lock);
125
126 SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, take, sem, timeout);
127
128 if (likely(sem->count > 0U)) {
129 sem->count--;
130 k_spin_unlock(&lock, key);
131 ret = 0;
132 goto out;
133 }
134
135 if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
136 k_spin_unlock(&lock, key);
137 ret = -EBUSY;
138 goto out;
139 }
140
141 SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_sem, take, sem, timeout);
142
143 ret = z_pend_curr(&lock, key, &sem->wait_q, timeout);
144
145 out:
146 SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, take, sem, timeout, ret);
147
148 return ret;
149 }
150
z_impl_k_sem_reset(struct k_sem * sem)151 void z_impl_k_sem_reset(struct k_sem *sem)
152 {
153 struct k_thread *thread;
154 k_spinlock_key_t key = k_spin_lock(&lock);
155
156 while (true) {
157 thread = z_unpend_first_thread(&sem->wait_q);
158 if (thread == NULL) {
159 break;
160 }
161 arch_thread_return_value_set(thread, -EAGAIN);
162 z_ready_thread(thread);
163 }
164 sem->count = 0;
165
166 SYS_PORT_TRACING_OBJ_FUNC(k_sem, reset, sem);
167
168 handle_poll_events(sem);
169
170 z_reschedule(&lock, key);
171 }
172
173 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_take(struct k_sem * sem,k_timeout_t timeout)174 static inline int z_vrfy_k_sem_take(struct k_sem *sem, k_timeout_t timeout)
175 {
176 Z_OOPS(Z_SYSCALL_OBJ(sem, K_OBJ_SEM));
177 return z_impl_k_sem_take((struct k_sem *)sem, timeout);
178 }
179 #include <syscalls/k_sem_take_mrsh.c>
180
z_vrfy_k_sem_reset(struct k_sem * sem)181 static inline void z_vrfy_k_sem_reset(struct k_sem *sem)
182 {
183 Z_OOPS(Z_SYSCALL_OBJ(sem, K_OBJ_SEM));
184 z_impl_k_sem_reset(sem);
185 }
186 #include <syscalls/k_sem_reset_mrsh.c>
187
z_vrfy_k_sem_count_get(struct k_sem * sem)188 static inline unsigned int z_vrfy_k_sem_count_get(struct k_sem *sem)
189 {
190 Z_OOPS(Z_SYSCALL_OBJ(sem, K_OBJ_SEM));
191 return z_impl_k_sem_count_get(sem);
192 }
193 #include <syscalls/k_sem_count_get_mrsh.c>
194
195 #endif
196