1 /*
2  * Copyright (c) 2011-2016 Wind River Systems, Inc.
3  * Copyright (c) 2024, Meta
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include "posix_internal.h"
9 
10 #include <pthread.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 
15 #include <zephyr/sys/util.h>
16 #include <zephyr/logging/log.h>
17 
18 #ifdef CONFIG_THREAD_NAME
19 #define MAX_NAME_LEN CONFIG_THREAD_MAX_NAME_LEN
20 #else
21 #define MAX_NAME_LEN 1
22 #endif
23 
24 #define NUM_PHIL      CONFIG_POSIX_THREAD_THREADS_MAX
25 #define obj_init_type "POSIX"
26 #define fork_type_str "mutexes"
27 
28 BUILD_ASSERT(CONFIG_POSIX_THREAD_THREADS_MAX == CONFIG_MAX_PTHREAD_MUTEX_COUNT);
29 BUILD_ASSERT(CONFIG_DYNAMIC_THREAD_POOL_SIZE == CONFIG_POSIX_THREAD_THREADS_MAX);
30 
31 typedef pthread_mutex_t *fork_t;
32 
33 LOG_MODULE_REGISTER(posix_philosophers, LOG_LEVEL_INF);
34 
35 static pthread_mutex_t forks[NUM_PHIL];
36 static pthread_t threads[NUM_PHIL];
37 
fork_init(fork_t frk)38 static inline void fork_init(fork_t frk)
39 {
40 	int ret;
41 
42 	ret = pthread_mutex_init(frk, NULL);
43 	if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
44 		errno = ret;
45 		perror("pthread_mutex_init");
46 		__ASSERT(false, "Failed to initialize fork");
47 	}
48 }
49 
fork(size_t idx)50 static inline fork_t fork(size_t idx)
51 {
52 	return &forks[idx];
53 }
54 
take(fork_t frk)55 static inline void take(fork_t frk)
56 {
57 	int ret;
58 
59 	ret = pthread_mutex_lock(frk);
60 	if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
61 		errno = ret;
62 		perror("pthread_mutex_lock");
63 		__ASSERT(false, "Failed to lock mutex");
64 	}
65 }
66 
drop(fork_t frk)67 static inline void drop(fork_t frk)
68 {
69 	int ret;
70 
71 	ret = pthread_mutex_unlock(frk);
72 	if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
73 		errno = ret;
74 		perror("pthread_mutex_unlock");
75 		__ASSERT(false, "Failed to unlock mutex");
76 	}
77 }
78 
set_phil_state_pos(int id)79 static void set_phil_state_pos(int id)
80 {
81 	if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) {
82 		printk("\x1b[%d;%dH", id + 1, 1);
83 	}
84 }
85 
print_phil_state(int id,const char * fmt,int32_t delay)86 static void print_phil_state(int id, const char *fmt, int32_t delay)
87 {
88 	int ret;
89 	int prio;
90 	int policy;
91 	struct sched_param param;
92 
93 	ret = pthread_getschedparam(pthread_self(), &policy, &param);
94 	if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
95 		errno = ret;
96 		perror("pthread_getschedparam");
97 		__ASSERT(false, "Failed to get scheduler params");
98 	}
99 
100 	prio = posix_to_zephyr_priority(param.sched_priority, policy);
101 
102 	set_phil_state_pos(id);
103 
104 	printk("Philosopher %d [%s:%s%d] ", id, prio < 0 ? "C" : "P", prio < 0 ? "" : " ", prio);
105 
106 	if (delay) {
107 		printk(fmt, delay < 1000 ? " " : "", delay);
108 	} else {
109 		printk(fmt, "");
110 	}
111 
112 	printk("\n");
113 }
114 
get_random_delay(int id,int period_in_ms)115 static int32_t get_random_delay(int id, int period_in_ms)
116 {
117 	int32_t ms;
118 	int32_t delay;
119 	int32_t uptime;
120 	struct timespec ts;
121 
122 	/*
123 	 * The random delay is unit-less, and is based on the philosopher's ID
124 	 * and the current uptime to create some pseudo-randomness. It produces
125 	 * a value between 0 and 31.
126 	 */
127 	clock_gettime(CLOCK_MONOTONIC, &ts);
128 	uptime = ts.tv_sec * MSEC_PER_SEC + (ts.tv_nsec / NSEC_PER_MSEC);
129 	delay = (uptime / 100 * (id + 1)) & 0x1f;
130 
131 	/* add 1 to not generate a delay of 0 */
132 	ms = (delay + 1) * period_in_ms;
133 
134 	return ms;
135 }
136 
is_last_philosopher(int id)137 static inline int is_last_philosopher(int id)
138 {
139 	return id == (NUM_PHIL - 1);
140 }
141 
philosopher(void * arg)142 static void *philosopher(void *arg)
143 {
144 	fork_t my_fork1;
145 	fork_t my_fork2;
146 
147 	int my_id = POINTER_TO_INT(arg);
148 
149 	/* Djkstra's solution: always pick up the lowest numbered fork first */
150 	if (is_last_philosopher(my_id)) {
151 		my_fork1 = fork(0);
152 		my_fork2 = fork(my_id);
153 	} else {
154 		my_fork1 = fork(my_id);
155 		my_fork2 = fork(my_id + 1);
156 	}
157 
158 	while (1) {
159 		int32_t delay;
160 
161 		print_phil_state(my_id, "       STARVING       ", 0);
162 		take(my_fork1);
163 		print_phil_state(my_id, "   HOLDING ONE FORK   ", 0);
164 		take(my_fork2);
165 
166 		delay = get_random_delay(my_id, 25);
167 		print_phil_state(my_id, "  EATING  [ %s%d ms ] ", delay);
168 		usleep(delay * USEC_PER_MSEC);
169 
170 		drop(my_fork2);
171 		print_phil_state(my_id, "   DROPPED ONE FORK   ", 0);
172 		drop(my_fork1);
173 
174 		delay = get_random_delay(my_id, 25);
175 		print_phil_state(my_id, " THINKING [ %s%d ms ] ", delay);
176 		usleep(delay * USEC_PER_MSEC);
177 	}
178 
179 	return NULL;
180 }
181 
new_prio(int phil)182 static int new_prio(int phil)
183 {
184 	if (CONFIG_NUM_COOP_PRIORITIES > 0 && CONFIG_NUM_PREEMPT_PRIORITIES > 0) {
185 		if (IS_ENABLED(CONFIG_SAMPLE_SAME_PRIO)) {
186 			return 0;
187 		}
188 
189 		return -(phil - (NUM_PHIL / 2));
190 	}
191 
192 	if (CONFIG_NUM_COOP_PRIORITIES > 0) {
193 		return -phil - 2;
194 	}
195 
196 	if (CONFIG_NUM_PREEMPT_PRIORITIES > 0) {
197 		return phil;
198 	}
199 
200 	__ASSERT_NO_MSG("Unsupported scheduler configuration");
201 }
202 
init_objects(void)203 static void init_objects(void)
204 {
205 	ARRAY_FOR_EACH(forks, i) {
206 		LOG_DBG("Initializing fork %zu", i);
207 		fork_init(fork(i));
208 	}
209 }
210 
start_threads(void)211 static void start_threads(void)
212 {
213 	int ret;
214 	int prio;
215 	int policy;
216 	struct sched_param param;
217 
218 	ARRAY_FOR_EACH(forks, i) {
219 		LOG_DBG("Initializing philosopher %zu", i);
220 		ret = pthread_create(&threads[i], NULL, philosopher, INT_TO_POINTER(i));
221 		if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
222 			errno = ret;
223 			perror("pthread_create");
224 			__ASSERT(false, "Failed to create thread");
225 		}
226 
227 		prio = new_prio(i);
228 		param.sched_priority = zephyr_to_posix_priority(prio, &policy);
229 		ret = pthread_setschedparam(threads[i], policy, &param);
230 		if (IS_ENABLED(CONFIG_SAMPLE_ERROR_CHECKING) && ret != 0) {
231 			errno = ret;
232 			perror("pthread_setschedparam");
233 			__ASSERT(false, "Failed to set scheduler params");
234 		}
235 
236 		if (IS_ENABLED(CONFIG_THREAD_NAME)) {
237 			char tname[MAX_NAME_LEN];
238 
239 			snprintf(tname, sizeof(tname), "Philosopher %zu", i);
240 			pthread_setname_np(threads[i], tname);
241 		}
242 	}
243 }
244 
245 #define DEMO_DESCRIPTION                                                                           \
246 	"\x1b[2J\x1b[15;1H"                                                                        \
247 	"Demo Description\n"                                                                       \
248 	"----------------\n"                                                                       \
249 	"An implementation of a solution to the Dining Philosophers\n"                             \
250 	"problem (a classic multi-thread synchronization problem).\n"                              \
251 	"This particular implementation demonstrates the usage of multiple\n"                      \
252 	"preemptible and cooperative threads of differing priorities, as\n"                        \
253 	"well as %s %s and thread sleeping.\n",                                                    \
254 		obj_init_type, fork_type_str
255 
display_demo_description(void)256 static void display_demo_description(void)
257 {
258 	if (IS_ENABLED(CONFIG_SAMPLE_DEBUG_PRINTF)) {
259 		printk(DEMO_DESCRIPTION);
260 	}
261 }
262 
main(void)263 int main(void)
264 {
265 	display_demo_description();
266 
267 	init_objects();
268 	start_threads();
269 
270 	if (IS_ENABLED(CONFIG_COVERAGE)) {
271 		/* Wait a few seconds before main() exit, giving the sample the
272 		 * opportunity to dump some output before coverage data gets emitted
273 		 */
274 		sleep(5);
275 	}
276 
277 	return 0;
278 }
279