-
Notifications
You must be signed in to change notification settings - Fork 297
Expand file tree
/
Copy pathqf_port.c
More file actions
440 lines (372 loc) · 15.6 KB
/
Copy pathqf_port.c
File metadata and controls
440 lines (372 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
//============================================================================
// QP/C Real-Time Event Framework (RTEF)
//
// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved.
//
// Q u a n t u m L e a P s
// ------------------------
// Modern Embedded Software
//
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial
//
// This software is dual-licensed under the terms of the open-source GNU
// General Public License (GPL) or under the terms of one of the closed-
// source Quantum Leaps commercial licenses.
//
// Redistributions in source code must retain this top-level comment block.
// Plagiarizing this software to sidestep the license obligations is illegal.
//
// NOTE:
// The GPL does NOT permit the incorporation of this code into proprietary
// programs. Please contact Quantum Leaps for commercial licensing options,
// which expressly supersede the GPL and are designed explicitly for
// closed-source distribution.
//
// Quantum Leaps contact information:
// <www.state-machine.com/licensing>
// <info@state-machine.com>
//============================================================================
// expose features from the 2008 POSIX standard (IEEE Standard 1003.1-2008)
#define _POSIX_C_SOURCE 200809L
#define QP_IMPL // this is QP implementation
#include "qp_port.h" // QP port
#include "qp_pkg.h" // QP package-scope interface
#include "qsafe.h" // QP Functional Safety (FuSa) Subsystem
#ifdef Q_SPY // QS software tracing enabled?
#include "qs_port.h" // QS port
#include "qs_pkg.h" // QS package-scope internal interface
#else
#include "qs_dummy.h" // disable the QS software tracing
#endif // Q_SPY
#include <limits.h> // for PTHREAD_STACK_MIN
#include <sys/mman.h> // for mlockall()
#include <sys/ioctl.h>
#include <time.h> // for clock_nanosleep()
#include <string.h> // for memcpy() and memset()
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
Q_DEFINE_THIS_MODULE("qf_port")
// Local objects =============================================================
static bool l_isRunning; // flag indicating when QF is running
static struct timespec l_tick; // structure for the clock tick
static int_t l_tickPrio; // priority of the ticker thread
#define NSEC_PER_SEC 1000000000L
#define DEFAULT_TICKS_PER_SEC 100L
//----------------------------------------------------------------------------
#ifdef __APPLE__
#define TIMER_ABSTIME 0
// emulate clock_nanosleep() for CLOCK_MONOTONIC and TIMER_ABSTIME
static inline int clock_nanosleep(clockid_t clockid, int flags,
const struct timespec* t,
struct timespec* remain)
{
Q_UNUSED_PAR(clockid);
Q_UNUSED_PAR(flags);
Q_UNUSED_PAR(remain);
struct timespec ts_delta;
clock_gettime(CLOCK_MONOTONIC, &ts_delta);
ts_delta.tv_sec = t->tv_sec - ts_delta.tv_sec;
ts_delta.tv_nsec = t->tv_nsec - ts_delta.tv_nsec;
if (ts_delta.tv_sec < 0) {
ts_delta.tv_sec = 0;
ts_delta.tv_nsec = 0;
}
else if (ts_delta.tv_nsec < 0) {
if (ts_delta.tv_sec == 0) {
ts_delta.tv_sec = 0;
ts_delta.tv_nsec = 0;
}
else {
ts_delta.tv_sec = ts_delta.tv_sec - 1;
ts_delta.tv_nsec = ts_delta.tv_nsec + NSEC_PER_SEC;
}
}
return nanosleep(&ts_delta, NULL);
}
#endif
//----------------------------------------------------------------------------
static void *ticker_thread(void *arg); // prototype
static void *ticker_thread(void *arg) { // for pthread_create()
Q_UNUSED_PAR(arg);
// system clock tick must be configured
QF_CRIT_STAT
QF_CRIT_ENTRY();
Q_REQUIRE_INCRIT(100, l_tick.tv_nsec != 0);
QF_CRIT_EXIT();
// get the absolute monotonic time for no-drift sleeping
static struct timespec next_tick;
clock_gettime(CLOCK_MONOTONIC, &next_tick);
// round down nanoseconds to the nearest configured period
next_tick.tv_nsec = (next_tick.tv_nsec / l_tick.tv_nsec) * l_tick.tv_nsec;
while (l_isRunning) { // the clock tick loop...
// advance to the next tick (absolute time)
next_tick.tv_nsec += l_tick.tv_nsec;
if (next_tick.tv_nsec >= NSEC_PER_SEC) {
next_tick.tv_nsec -= NSEC_PER_SEC;
next_tick.tv_sec += 1;
}
// sleep without drifting till next_tick (absolute), see NOTE03
if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME,
&next_tick, NULL) == 0) // success?
{
// clock tick callback (must call QTIMEEVT_TICK_X())
QF_onClockTick();
}
}
return (void *)0; // return success
}
//............................................................................
static void sigIntHandler(int dummy); // prototype
static void sigIntHandler(int dummy) {
Q_UNUSED_PAR(dummy);
QF_onCleanup();
exit(-1);
}
//============================================================================
QPSet QF_readySet_;
pthread_cond_t QF_condVar_; // cond.var. to signal events
//============================================================================
// QF functions
// NOTE: initialize the critical section mutex as non-recursive,
// but check that nesting of critical sections never occurs
// (see QF_enterCriticalSection_()/QF_leaveCriticalSection_()
static pthread_mutex_t l_critSectMutex_ = PTHREAD_MUTEX_INITIALIZER;
static int_t l_critSectNest; // critical section nesting up-down counter
//............................................................................
void QF_enterCriticalSection_(void) {
pthread_mutex_lock(&l_critSectMutex_);
Q_ASSERT_INCRIT(100, l_critSectNest == 0); // NO nesting of crit.sect!
++l_critSectNest;
}
//............................................................................
void QF_leaveCriticalSection_(void) {
Q_ASSERT_INCRIT(200, l_critSectNest == 1); // crit.sect. must balance!
if ((--l_critSectNest) == 0) {
pthread_mutex_unlock(&l_critSectMutex_);
}
}
//............................................................................
void QF_init(void) {
// init the global condition variable with the default initializer
pthread_cond_init(&QF_condVar_, NULL);
QPSet_setEmpty(&QF_readySet_);
// lock memory so we're never swapped out to disk
//mlockall(MCL_CURRENT | MCL_FUTURE); // un-comment when supported
QTimeEvt_init(); // initialize QTimeEvts
l_tick.tv_sec = 0;
l_tick.tv_nsec = NSEC_PER_SEC / DEFAULT_TICKS_PER_SEC; // default rate
l_tickPrio = sched_get_priority_min(SCHED_FIFO); // default ticker prio
// install the SIGINT (Ctrl-C) signal handler
struct sigaction sig_act;
memset(&sig_act, 0, sizeof(sig_act));
sig_act.sa_handler = &sigIntHandler;
sigaction(SIGINT, &sig_act, NULL);
}
//............................................................................
int QF_run(void) {
QF_CRIT_STAT
// system clock tick configured?
if ((l_tick.tv_sec != 0) || (l_tick.tv_nsec != 0)) {
pthread_attr_t attr;
pthread_attr_init(&attr);
// SCHED_FIFO corresponds to real-time preemptive priority-based
// scheduler.
// NOTE: This scheduling policy requires the superuser privileges
pthread_attr_setschedpolicy (&attr, SCHED_FIFO);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
struct sched_param param;
param.sched_priority = l_tickPrio;
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_t ticker;
int err = pthread_create(&ticker, &attr, &ticker_thread, 0);
if (err != 0) {
// Creating the p-thread with the SCHED_FIFO policy failed.
// Most probably this application has no superuser privileges,
// so we just fall back to the default SCHED_OTHER policy
// and priority 0.
pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
param.sched_priority = 0;
pthread_attr_setschedparam(&attr, ¶m);
err = pthread_create(&ticker, &attr, &ticker_thread, 0);
}
QF_CRIT_ENTRY();
Q_ASSERT_INCRIT(310, err == 0); // ticker thread must be created
QF_CRIT_EXIT();
//pthread_attr_getschedparam(&attr, ¶m);
//printf("param.sched_priority==%d\n", param.sched_priority);
pthread_attr_destroy(&attr);
}
QF_CRIT_ENTRY();
// produce the QS_QF_RUN trace record
QS_BEGIN_PRE(QS_QF_RUN, 0U)
QS_END_PRE()
l_isRunning = true; // QF is running
// Application callback: configure and enable individual interrupts.
// NOTE: called within critical section and returns also in
// critical section.
QF_onStartup();
// the combined event-loop and background-loop of the QV kernel
while (l_isRunning) {
// find the maximum priority AO ready to run
if (QPSet_notEmpty(&QF_readySet_)) {
uint_fast8_t p = QPSet_findMax(&QF_readySet_);
QActive *a = QActive_registry_[p];
// the active object 'a' must still be registered in QF
// (e.g., it must not be stopped)
Q_ASSERT_INCRIT(320, a != (QActive *)0);
QF_CRIT_EXIT();
QEvt const *e = QActive_get_(a); // queue not empty
QASM_DISPATCH(a, e, a->prio); // dispatch event (virtual call)
#if (QF_MAX_EPOOL > 0U)
QF_gc(e); // check if the event is garbage, and collect it if so
#endif
QF_CRIT_ENTRY();
if (a->eQueue.frontEvt.e == (QEvt *)0) { // empty queue?
QPSet_remove(&QF_readySet_, p);
}
}
else {
// the QV kernel in embedded systems calls here the QV_onIdle()
// callback. However, the POSIX-QV port does not do busy-waiting
// for events. Instead, the POSIX-QV port efficiently waits until
// QP events become available.
while (QPSet_isEmpty(&QF_readySet_)) {
Q_ASSERT_INCRIT(390, l_critSectNest == 1);
--l_critSectNest;
pthread_cond_wait(&QF_condVar_, &l_critSectMutex_);
Q_ASSERT_INCRIT(391, l_critSectNest == 0);
++l_critSectNest;
}
}
}
QF_CRIT_EXIT();
QF_onCleanup(); // cleanup callback
QS_EXIT(); // cleanup the QSPY connection
pthread_cond_destroy(&QF_condVar_); // cleanup the condition variable
pthread_mutex_destroy(&l_critSectMutex_); // cleanup the global mutex
return 0; // return success
}
//............................................................................
void QF_stop(void) {
l_isRunning = false; // terminate the main event-loop
// unblock the event-loop so it can terminate
QPSet_insert(&QF_readySet_, 1U);
pthread_cond_signal(&QF_condVar_);
}
//............................................................................
void QF_setTickRate(uint32_t ticksPerSec, int tickPrio) {
if (ticksPerSec != 0U) {
l_tick.tv_nsec = NSEC_PER_SEC / ticksPerSec;
}
else {
l_tick.tv_nsec = 0U; // means NO system clock tick
}
l_tickPrio = tickPrio;
}
// console access ============================================================
#ifdef QF_CONSOLE
#include <termios.h>
static struct termios l_tsav; // structure with saved terminal attributes
void QF_consoleSetup(void) {
struct termios tio; // modified terminal attributes
tcgetattr(0, &l_tsav); // save the current terminal attributes
tcgetattr(0, &tio); // obtain the current terminal attributes
tio.c_lflag &= (tcflag_t)~(ICANON | ECHO); // disable the canonical mode & echo
tcsetattr(0, TCSANOW, &tio); // set the new attributes
}
//............................................................................
void QF_consoleCleanup(void) {
tcsetattr(0, TCSANOW, &l_tsav); // restore the saved attributes
}
//............................................................................
int QF_consoleGetKey(void) {
int byteswaiting;
ioctl(0, FIONREAD, &byteswaiting);
if (byteswaiting > 0) {
char ch;
byteswaiting = read(0, &ch, 1);
return (int)ch;
}
return 0; // no input at this time
}
//............................................................................
int QF_consoleWaitForKey(void) {
return (int)getchar();
}
#endif // #ifdef QF_CONSOLE
// QActive functions =========================================================
void QActive_start(QActive * const me,
QPrioSpec const prioSpec,
QEvtPtr * const qSto, uint_fast16_t const qLen,
void * const stkSto, uint_fast16_t const stkSize,
void const * const par)
{
Q_UNUSED_PAR(stkSto);
Q_UNUSED_PAR(stkSize);
// no per-AO stack needed for this port
QF_CRIT_STAT
QF_CRIT_ENTRY();
Q_REQUIRE_INCRIT(800, stkSto == (void *)0);
QF_CRIT_EXIT();
QEQueue_init(&me->eQueue, qSto, qLen);
me->prio = (uint8_t)(prioSpec & 0xFFU); // QF-priority of the AO
me->pthre = 0U; // preemption-threshold (not used in this port)
QActive_register_(me); // register this AO
QASM_INIT(me, par, me->prio); // top-most initial tran. (virtual call)
QS_FLUSH(); // flush the QS trace buffer to the host
}
//............................................................................
#ifdef QACTIVE_CAN_STOP
void QActive_stop(QActive * const me) {
if (QActive_subscrList_ != (QSubscrList *)0) {
QActive_unsubscribeAll(me); // unsubscribe from all events
}
// make sure the AO is no longer in "ready set"
QF_CRIT_STAT
QF_CRIT_ENTRY();
QPSet_remove(&QF_readySet_, me->prio);
QF_CRIT_EXIT();
QActive_unregister_(me);
}
#endif
//............................................................................
void QActive_setAttr(QActive *const me, uint32_t attr1, void const *attr2) {
Q_UNUSED_PAR(me);
Q_UNUSED_PAR(attr1);
Q_UNUSED_PAR(attr2);
Q_ERROR_INCRIT(900); // should not be called in this QP port
}
//============================================================================
// NOTE01:
// In Linux, the scheduler policy closest to real-time is the SCHED_FIFO
// policy, available only with superuser privileges. QF_run() attempts to set
// this policy as well as to maximize its priority, so that the ticking
// occurs in the most timely manner (as close to an interrupt as possible).
// However, setting the SCHED_FIFO policy might fail, most probably due to
// insufficient privileges.
//
// NOTE03:
// Any blocking system call, such as clock_nanosleep() system call can
// be interrupted by a signal, such as ^C from the keyboard. In this case this
// QF port breaks out of the event-loop and returns to main() that exits and
// terminates all spawned p-threads.
//
// NOTE04:
// According to the man pages (for pthread_attr_setschedpolicy) the only value
// supported in the Linux p-threads implementation is PTHREAD_SCOPE_SYSTEM,
// meaning that the threads contend for CPU time with all processes running on
// the machine. In particular, thread priorities are interpreted relative to
// the priorities of all other processes on the machine.
//
// This is good, because it seems that if we set the priorities high enough,
// no other process (or thread running within) can gain control over the CPU.
//
// However, QF limits the number of priority levels to QF_MAX_ACTIVE.
// Assuming that a QF application will be real-time, this port reserves the
// three highest p-thread priorities for the ISR-like threads (e.g., I/O),
// and the remaining highest-priorities for the active objects.
//