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 <zephyr/kernel.h>
21 #include <zephyr/kernel_structs.h>
22 
23 #include <zephyr/toolchain.h>
24 #include <wait_q.h>
25 #include <zephyr/sys/dlist.h>
26 #include <ksched.h>
27 #include <zephyr/init.h>
28 #include <zephyr/internal/syscall_handler.h>
29 #include <zephyr/tracing/tracing.h>
30 #include <zephyr/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 
41 #ifdef CONFIG_OBJ_CORE_SEM
42 static struct k_obj_type obj_type_sem;
43 #endif /* CONFIG_OBJ_CORE_SEM */
44 
z_impl_k_sem_init(struct k_sem * sem,unsigned int initial_count,unsigned int limit)45 int z_impl_k_sem_init(struct k_sem *sem, unsigned int initial_count,
46 		      unsigned int limit)
47 {
48 	/*
49 	 * Limit cannot be zero and count cannot be greater than limit
50 	 */
51 	CHECKIF(limit == 0U || initial_count > limit) {
52 		SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, -EINVAL);
53 
54 		return -EINVAL;
55 	}
56 
57 	sem->count = initial_count;
58 	sem->limit = limit;
59 
60 	SYS_PORT_TRACING_OBJ_FUNC(k_sem, init, sem, 0);
61 
62 	z_waitq_init(&sem->wait_q);
63 #if defined(CONFIG_POLL)
64 	sys_dlist_init(&sem->poll_events);
65 #endif /* CONFIG_POLL */
66 	k_object_init(sem);
67 
68 #ifdef CONFIG_OBJ_CORE_SEM
69 	k_obj_core_init_and_link(K_OBJ_CORE(sem), &obj_type_sem);
70 #endif /* CONFIG_OBJ_CORE_SEM */
71 
72 	return 0;
73 }
74 
75 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_init(struct k_sem * sem,unsigned int initial_count,unsigned int limit)76 int z_vrfy_k_sem_init(struct k_sem *sem, unsigned int initial_count,
77 		      unsigned int limit)
78 {
79 	K_OOPS(K_SYSCALL_OBJ_INIT(sem, K_OBJ_SEM));
80 	return z_impl_k_sem_init(sem, initial_count, limit);
81 }
82 #include <zephyr/syscalls/k_sem_init_mrsh.c>
83 #endif /* CONFIG_USERSPACE */
84 
handle_poll_events(struct k_sem * sem)85 static inline bool handle_poll_events(struct k_sem *sem)
86 {
87 #ifdef CONFIG_POLL
88 	return z_handle_obj_poll_events(&sem->poll_events, K_POLL_STATE_SEM_AVAILABLE);
89 #else
90 	ARG_UNUSED(sem);
91 	return false;
92 #endif /* CONFIG_POLL */
93 }
94 
z_impl_k_sem_give(struct k_sem * sem)95 void z_impl_k_sem_give(struct k_sem *sem)
96 {
97 	k_spinlock_key_t key = k_spin_lock(&lock);
98 	struct k_thread *thread;
99 	bool resched;
100 
101 	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, give, sem);
102 
103 	thread = z_unpend_first_thread(&sem->wait_q);
104 
105 	if (unlikely(thread != NULL)) {
106 		arch_thread_return_value_set(thread, 0);
107 		z_ready_thread(thread);
108 		resched = true;
109 	} else {
110 		sem->count += (sem->count != sem->limit) ? 1U : 0U;
111 		resched = handle_poll_events(sem);
112 	}
113 
114 	if (unlikely(resched)) {
115 		z_reschedule(&lock, key);
116 	} else {
117 		k_spin_unlock(&lock, key);
118 	}
119 
120 	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, give, sem);
121 }
122 
123 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_give(struct k_sem * sem)124 static inline void z_vrfy_k_sem_give(struct k_sem *sem)
125 {
126 	K_OOPS(K_SYSCALL_OBJ(sem, K_OBJ_SEM));
127 	z_impl_k_sem_give(sem);
128 }
129 #include <zephyr/syscalls/k_sem_give_mrsh.c>
130 #endif /* CONFIG_USERSPACE */
131 
z_impl_k_sem_take(struct k_sem * sem,k_timeout_t timeout)132 int z_impl_k_sem_take(struct k_sem *sem, k_timeout_t timeout)
133 {
134 	int ret;
135 
136 	__ASSERT(((arch_is_in_isr() == false) ||
137 		  K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");
138 
139 	k_spinlock_key_t key = k_spin_lock(&lock);
140 
141 	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_sem, take, sem, timeout);
142 
143 	if (likely(sem->count > 0U)) {
144 		sem->count--;
145 		k_spin_unlock(&lock, key);
146 		ret = 0;
147 		goto out;
148 	}
149 
150 	if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
151 		k_spin_unlock(&lock, key);
152 		ret = -EBUSY;
153 		goto out;
154 	}
155 
156 	SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_sem, take, sem, timeout);
157 
158 	ret = z_pend_curr(&lock, key, &sem->wait_q, timeout);
159 
160 out:
161 	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_sem, take, sem, timeout, ret);
162 
163 	return ret;
164 }
165 
z_impl_k_sem_reset(struct k_sem * sem)166 void z_impl_k_sem_reset(struct k_sem *sem)
167 {
168 	struct k_thread *thread;
169 	k_spinlock_key_t key = k_spin_lock(&lock);
170 	bool resched = false;
171 
172 	while (true) {
173 		thread = z_unpend_first_thread(&sem->wait_q);
174 		if (thread == NULL) {
175 			break;
176 		}
177 		resched = true;
178 		arch_thread_return_value_set(thread, -EAGAIN);
179 		z_ready_thread(thread);
180 	}
181 	sem->count = 0;
182 
183 	SYS_PORT_TRACING_OBJ_FUNC(k_sem, reset, sem);
184 
185 	resched = handle_poll_events(sem) || resched;
186 
187 	if (resched) {
188 		z_reschedule(&lock, key);
189 	} else {
190 		k_spin_unlock(&lock, key);
191 	}
192 }
193 
194 #ifdef CONFIG_USERSPACE
z_vrfy_k_sem_take(struct k_sem * sem,k_timeout_t timeout)195 static inline int z_vrfy_k_sem_take(struct k_sem *sem, k_timeout_t timeout)
196 {
197 	K_OOPS(K_SYSCALL_OBJ(sem, K_OBJ_SEM));
198 	return z_impl_k_sem_take(sem, timeout);
199 }
200 #include <zephyr/syscalls/k_sem_take_mrsh.c>
201 
z_vrfy_k_sem_reset(struct k_sem * sem)202 static inline void z_vrfy_k_sem_reset(struct k_sem *sem)
203 {
204 	K_OOPS(K_SYSCALL_OBJ(sem, K_OBJ_SEM));
205 	z_impl_k_sem_reset(sem);
206 }
207 #include <zephyr/syscalls/k_sem_reset_mrsh.c>
208 
z_vrfy_k_sem_count_get(struct k_sem * sem)209 static inline unsigned int z_vrfy_k_sem_count_get(struct k_sem *sem)
210 {
211 	K_OOPS(K_SYSCALL_OBJ(sem, K_OBJ_SEM));
212 	return z_impl_k_sem_count_get(sem);
213 }
214 #include <zephyr/syscalls/k_sem_count_get_mrsh.c>
215 
216 #endif /* CONFIG_USERSPACE */
217 
218 #ifdef CONFIG_OBJ_CORE_SEM
init_sem_obj_core_list(void)219 static int init_sem_obj_core_list(void)
220 {
221 	/* Initialize semaphore object type */
222 
223 	z_obj_type_init(&obj_type_sem, K_OBJ_TYPE_SEM_ID,
224 			offsetof(struct k_sem, obj_core));
225 
226 	/* Initialize and link statically defined semaphores */
227 
228 	STRUCT_SECTION_FOREACH(k_sem, sem) {
229 		k_obj_core_init_and_link(K_OBJ_CORE(sem), &obj_type_sem);
230 	}
231 
232 	return 0;
233 }
234 
235 SYS_INIT(init_sem_obj_core_list, PRE_KERNEL_1,
236 	 CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);
237 #endif /* CONFIG_OBJ_CORE_SEM */
238