Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
OSSelectWakeup.cxx
Go to the documentation of this file.
1
34#include "os/OSSelectWakeup.hxx"
35#include "utils/logging.h"
36#if defined(__MACH__)
37#define _DARWIN_C_SOURCE // pselect
38#endif
39
41{
42}
43
44int OSSelectWakeup::select(int nfds, fd_set *readfds,
45 fd_set *writefds, fd_set *exceptfds,
46 long long deadline_nsec)
47{
48 {
49 AtomicHolder l(this);
50 inSelect_ = true;
52 {
53 deadline_nsec = 0;
54 }
55 else
56 {
57#if OPENMRN_FEATURE_DEVICE_SELECT
58 // It is important that we do this select_clear() in the same
59 // critical section as checking pendingWakeup above. This ensures
60 // tht all wakeups before this clear cause sleep to be zero, and
61 // all wakeups after this clear will cause the event group bit to
62 // be set.
64#endif
65 }
66 }
67#if OPENMRN_FEATURE_DEVICE_SELECT
68 int ret =
69 Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec);
70 if (!ret && pendingWakeup_)
71 {
72 ret = -1;
73 errno = EINTR;
74 }
75#elif OPENMRN_HAVE_PSELECT
76 struct timespec timeout;
77 timeout.tv_sec = deadline_nsec / 1000000000;
78 timeout.tv_nsec = deadline_nsec % 1000000000;
79 int ret =
80 ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_);
81#elif OPENMRN_HAVE_SELECT
82#ifdef ESP_PLATFORM
83 fd_set newexcept;
84 if (!exceptfds)
85 {
86 FD_ZERO(&newexcept);
87 exceptfds = &newexcept;
88 }
89 FD_SET(vfsFd_, exceptfds);
90 if (vfsFd_ >= nfds)
91 {
92 nfds = vfsFd_ + 1;
93 }
94#endif // ESP_PLATFORM
95 struct timeval timeout;
96 // divide in two steps to avoid overflow on ESP32
97 timeout.tv_sec = (deadline_nsec / 1000) / 1000000LL;
98 timeout.tv_usec = (deadline_nsec / 1000) % 1000000LL;
99 int ret =
100 ::select(nfds, readfds, writefds, exceptfds, &timeout);
101#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED)
102 #error no select implementation in multi threaded OS.
103#else
104 // Single threaded OS: nothing to wake up.
105 int ret = 0;
106#endif
107 {
108 AtomicHolder l(this);
109 pendingWakeup_ = false;
110 inSelect_ = false;
111 }
112 return ret;
113}
114
115#ifdef ESP_PLATFORM
116#include "freertos_includes.h"
117
118#include <esp_system.h>
119#include <esp_vfs.h>
120#include <fcntl.h>
121#include <pthread.h>
122#include <sys/types.h>
123#include <sys/stat.h>
124
126static pthread_once_t vfs_init_once = PTHREAD_ONCE_INIT;
127
130static pthread_key_t select_wakeup_key;
131
136static constexpr int WAKEUP_VFS_FD = 0;
137
138
148static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds,
149 fd_set *exceptfds, esp_vfs_select_sem_t signal_sem, void **end_select_args)
150{
151 OSSelectWakeup *parent =
152 (OSSelectWakeup *)pthread_getspecific(select_wakeup_key);
153 HASSERT(parent);
154 LOG(VERBOSE, "esp start select %p (thr %p parent %p)", signal_sem.sem,
155 os_thread_self(), parent);
156
157 // Check if our VFS FD is included in exceptfds before tracking that we
158 // should possibly wake up early.
159 if (FD_ISSET(WAKEUP_VFS_FD, exceptfds))
160 {
161 parent->esp_start_select(readfds, writefds, exceptfds, signal_sem);
162 }
163 return ESP_OK;
164}
165
172static esp_err_t esp_end_select(void *arg)
173{
174 OSSelectWakeup *parent =
175 (OSSelectWakeup *)pthread_getspecific(select_wakeup_key);
176 HASSERT(parent);
177 LOG(VERBOSE, "esp end select (thr %p parent %p)", os_thread_self(),
178 parent);
179 parent->esp_end_select();
180 return ESP_OK;
181}
182
186void OSSelectWakeup::esp_start_select(fd_set *readfds, fd_set *writefds,
187 fd_set *exceptfds, esp_vfs_select_sem_t signal_sem)
188{
189 AtomicHolder h(this);
190 espSem_ = signal_sem;
191 // store the fd_set for the except set since this is guaranteed to be set
192 // in OSSelectWakeup::select. Other fd_sets are not guaranteed or necessary
193 // to be stored/checked here.
194 exceptFds_ = exceptfds;
195 exceptFdsOrig_ = *exceptfds;
196 FD_ZERO(exceptFds_);
197 if (pendingWakeup_)
198 {
199 // There is a race condition between the Executor deciding to run
200 // select, and the internal implementation of select() calling this
201 // function. Since we only get the semaphone in this call, the wakeup
202 // functions are noops if they hit during this window. If there was a
203 // missed wakeup, we repeat it.
204 esp_wakeup();
205 }
206}
207
210void OSSelectWakeup::esp_end_select()
211{
212 AtomicHolder h(this);
213 // zero out the copy so we don't unintentionally wake up when the semaphore
214 // is no longer valid.
215 FD_ZERO(&exceptFdsOrig_);
216}
217
220void OSSelectWakeup::esp_wakeup()
221{
222 AtomicHolder h(this);
223
224 // If our VFS FD is not set in the except fd_set we can exit early.
225 if (!FD_ISSET(WAKEUP_VFS_FD, &exceptFdsOrig_))
226 {
227 return;
228 }
229
230 // Mark the VFS implementation FD for the wakeup call. Note that this
231 // should not use vfsFd_ since the fd_set will contain the VFS specific FD
232 // and not the system global FD.
233 FD_SET(WAKEUP_VFS_FD, exceptFds_);
234
235 LOG(VERBOSE, "wakeup es %p %u", espSem_.sem, *(unsigned*)espSem_.sem);
236 esp_vfs_select_triggered(espSem_);
237}
238
241void OSSelectWakeup::esp_wakeup_from_isr()
242{
243 BaseType_t woken = pdFALSE;
244
245 // If our VFS FD is not set in the except fd_set we can exit early.
246 if (!FD_ISSET(WAKEUP_VFS_FD, &exceptFdsOrig_))
247 {
248 return;
249 }
250
251 // Mark the VFS implementation FD for the wakeup call. Note that this
252 // should not use vfsFd_ since the fd_set will contain the VFS specific FD
253 // and not the system global FD.
254 FD_SET(WAKEUP_VFS_FD, exceptFds_);
255
256 esp_vfs_select_triggered_isr(espSem_, &woken);
257
258 if (woken == pdTRUE)
259 {
260 portYIELD_FROM_ISR();
261 }
262}
263
264static int esp_wakeup_open(const char * path, int flags, int mode)
265{
266 // This virtual FS has only one fd, 0.
267 return WAKEUP_VFS_FD;
268}
269
270static void esp_vfs_init()
271{
272 esp_vfs_t vfs;
273 memset(&vfs, 0, sizeof(vfs));
274 vfs.flags = ESP_VFS_FLAG_DEFAULT;
275 vfs.start_select = esp_start_select;
276 vfs.end_select = esp_end_select;
277 vfs.open = esp_wakeup_open;
278 ESP_ERROR_CHECK(esp_vfs_register("/dev/wakeup", &vfs, nullptr));
279 HASSERT(0 == pthread_key_create(&select_wakeup_key, nullptr));
280}
281
282void OSSelectWakeup::esp_allocate_vfs_fd()
283{
284 HASSERT(0 == pthread_once(&vfs_init_once, &esp_vfs_init));
285 vfsFd_ = ::open("/dev/wakeup/0", 0, 0);
286 HASSERT(vfsFd_ >= 0);
287 HASSERT(0 == pthread_setspecific(select_wakeup_key, this));
288 LOG(VERBOSE, "VFSALLOC wakeup fd %d (thr %p test %p)", vfsFd_,
289 os_thread_self(), pthread_getspecific(select_wakeup_key));
290}
291
292void OSSelectWakeup::esp_deallocate_vfs_fd()
293{
294 if (vfsFd_ >= 0)
295 {
296 ::close(vfsFd_);
297 }
298 vfsFd_ = -1;
299}
300
301#endif // ESP_PLATFORM
void empty_signal_handler(int)
Signal handler that does nothing.
See OSMutexLock in os/OS.hxx.
Definition Atomic.hxx:153
Helper class that allows a select to be asynchronously woken up.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, long long deadline_nsec)
Portable call to a select that can be woken up asynchronously from a different thread or an ISR conte...
bool pendingWakeup_
True if there was a wakeup call since the previous select finished.
bool inSelect_
True during the duration of a select operation.
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int VERBOSE
Loglevel that is usually not printed, reporting debugging information.
Definition logging.h:59
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
OS_INLINE os_thread_t os_thread_self(void)
Return a handle to the calling thread.
Definition os.h:370
static int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, long long timeout)
POSIX select().
Definition Select.cxx:96
static void select_clear()
Clears the current thread's select bits.
Definition Select.cxx:78