1 /*
2  * Copyright (c) 2023, Meta
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "posix_internal.h"
8 
9 #include <zephyr/init.h>
10 #include <zephyr/kernel.h>
11 #include <zephyr/posix/pthread.h>
12 #include <zephyr/sys/bitarray.h>
13 
14 union _spinlock_storage {
15 	struct k_spinlock lock;
16 	uint8_t byte;
17 };
18 #if !defined(CONFIG_CPP) && !defined(CONFIG_SMP) && !defined(CONFIG_SPIN_VALIDATE)
19 BUILD_ASSERT(sizeof(struct k_spinlock) == 0,
20 	     "please remove the _spinlock_storage workaround if, at some point, k_spinlock is no "
21 	     "longer zero bytes when CONFIG_SMP=n && CONFIG_SPIN_VALIDATE=n");
22 #endif
23 
24 static union _spinlock_storage posix_spinlock_pool[CONFIG_MAX_PTHREAD_SPINLOCK_COUNT];
25 static k_spinlock_key_t posix_spinlock_key[CONFIG_MAX_PTHREAD_SPINLOCK_COUNT];
26 SYS_BITARRAY_DEFINE_STATIC(posix_spinlock_bitarray, CONFIG_MAX_PTHREAD_SPINLOCK_COUNT);
27 
28 /*
29  * We reserve the MSB to mark a pthread_spinlock_t as initialized (from the
30  * perspective of the application). With a linear space, this means that
31  * the theoretical pthread_spinlock_t range is [0,2147483647].
32  */
33 BUILD_ASSERT(CONFIG_MAX_PTHREAD_SPINLOCK_COUNT < PTHREAD_OBJ_MASK_INIT,
34 	"CONFIG_MAX_PTHREAD_SPINLOCK_COUNT is too high");
35 
posix_spinlock_to_offset(struct k_spinlock * l)36 static inline size_t posix_spinlock_to_offset(struct k_spinlock *l)
37 {
38 	return (union _spinlock_storage *)l - posix_spinlock_pool;
39 }
40 
to_posix_spinlock_idx(pthread_spinlock_t lock)41 static inline size_t to_posix_spinlock_idx(pthread_spinlock_t lock)
42 {
43 	return mark_pthread_obj_uninitialized(lock);
44 }
45 
get_posix_spinlock(pthread_spinlock_t * lock)46 static struct k_spinlock *get_posix_spinlock(pthread_spinlock_t *lock)
47 {
48 	size_t bit;
49 	int actually_initialized;
50 
51 	if (lock == NULL) {
52 		return NULL;
53 	}
54 
55 	/* if the provided spinlock does not claim to be initialized, its invalid */
56 	bit = to_posix_spinlock_idx(*lock);
57 	if (!is_pthread_obj_initialized(*lock)) {
58 		return NULL;
59 	}
60 
61 	/* Mask off the MSB to get the actual bit index */
62 	if (sys_bitarray_test_bit(&posix_spinlock_bitarray, bit, &actually_initialized) < 0) {
63 		return NULL;
64 	}
65 
66 	if (actually_initialized == 0) {
67 		/* The spinlock claims to be initialized but is actually not */
68 		return NULL;
69 	}
70 
71 	return (struct k_spinlock *)&posix_spinlock_pool[bit];
72 }
73 
pthread_spin_init(pthread_spinlock_t * lock,int pshared)74 int pthread_spin_init(pthread_spinlock_t *lock, int pshared)
75 {
76 	int ret;
77 	size_t bit;
78 
79 	if (lock == NULL ||
80 	    !(pshared == PTHREAD_PROCESS_PRIVATE || pshared == PTHREAD_PROCESS_SHARED)) {
81 		/* not specified as part of POSIX but this is the Linux behavior */
82 		return EINVAL;
83 	}
84 
85 	ret = sys_bitarray_alloc(&posix_spinlock_bitarray, 1, &bit);
86 	if (ret < 0) {
87 		return ENOMEM;
88 	}
89 
90 	*lock = mark_pthread_obj_initialized(bit);
91 
92 	return 0;
93 }
94 
pthread_spin_destroy(pthread_spinlock_t * lock)95 int pthread_spin_destroy(pthread_spinlock_t *lock)
96 {
97 	int err;
98 	size_t bit;
99 	struct k_spinlock *l;
100 
101 	l = get_posix_spinlock(lock);
102 	if (l == NULL) {
103 		/* not specified as part of POSIX but this is the Linux behavior */
104 		return EINVAL;
105 	}
106 
107 	bit = posix_spinlock_to_offset(l);
108 	err = sys_bitarray_free(&posix_spinlock_bitarray, 1, bit);
109 	__ASSERT_NO_MSG(err == 0);
110 
111 	return 0;
112 }
113 
pthread_spin_lock(pthread_spinlock_t * lock)114 int pthread_spin_lock(pthread_spinlock_t *lock)
115 {
116 	size_t bit;
117 	struct k_spinlock *l;
118 
119 	l = get_posix_spinlock(lock);
120 	if (l == NULL) {
121 		/* not specified as part of POSIX but this is the Linux behavior */
122 		return EINVAL;
123 	}
124 
125 	bit = posix_spinlock_to_offset(l);
126 	posix_spinlock_key[bit] = k_spin_lock(l);
127 
128 	return 0;
129 }
130 
pthread_spin_trylock(pthread_spinlock_t * lock)131 int pthread_spin_trylock(pthread_spinlock_t *lock)
132 {
133 	size_t bit;
134 	struct k_spinlock *l;
135 
136 	l = get_posix_spinlock(lock);
137 	if (l == NULL) {
138 		/* not specified as part of POSIX but this is the Linux behavior */
139 		return EINVAL;
140 	}
141 
142 	bit = posix_spinlock_to_offset(l);
143 	return k_spin_trylock(l, &posix_spinlock_key[bit]);
144 }
145 
pthread_spin_unlock(pthread_spinlock_t * lock)146 int pthread_spin_unlock(pthread_spinlock_t *lock)
147 {
148 	size_t bit;
149 	struct k_spinlock *l;
150 
151 	l = get_posix_spinlock(lock);
152 	if (l == NULL) {
153 		/* not specified as part of POSIX but this is the Linux behavior */
154 		return EINVAL;
155 	}
156 
157 	bit = posix_spinlock_to_offset(l);
158 	k_spin_unlock(l, posix_spinlock_key[bit]);
159 
160 	return 0;
161 }
162