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