Skip to content

Commit 035a501

Browse files
authored
Merge pull request #184 from f9micro/posix
Add POSIX PSE51/PSE52 compatibility layer
2 parents 8e6a823 + 8283605 commit 035a501

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+6868
-542
lines changed

.github/workflows/build.yml

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,20 @@ jobs:
4343
4444
test:
4545
runs-on: ubuntu-24.04
46-
needs: build
47-
46+
timeout-minutes: 15
47+
strategy:
48+
fail-fast: false
49+
matrix:
50+
app: [tests, posix]
51+
include:
52+
- app: tests
53+
name: "Kernel Test Suite"
54+
config: USER_APP_TESTS
55+
- app: posix
56+
name: "POSIX Compliance (PSE51+PSE52)"
57+
config: USER_APP_POSIX
58+
59+
name: test (${{ matrix.app }})
4860
steps:
4961
- uses: actions/checkout@v6
5062

@@ -56,25 +68,61 @@ jobs:
5668
- name: Install QEMU
5769
run: sudo apt-get update && sudo apt-get install -y qemu-system-arm
5870

59-
- name: Configure for netduinoplus2
60-
run: make netduinoplus2_defconfig
61-
62-
- name: Enable test suite
71+
- name: Configure for netduinoplus2 with ${{ matrix.name }}
6372
run: |
64-
# Enable test suite, disable conflicting apps
65-
sed -i 's/^CONFIG_PINGPONG=y/# CONFIG_PINGPONG is not set/' .config
66-
echo "CONFIG_TESTS=y" >> .config
73+
make netduinoplus2_defconfig
74+
# Enable QEMU mode and selected test suite
75+
python3 tools/kconfig/setconfig.py \
76+
QEMU=y \
77+
${{ matrix.config }}=y
6778
python3 tools/kconfig/genconfig.py --header-path include/autoconf.h Kconfig
6879
69-
- name: Build kernel with tests
80+
- name: Verify configuration
81+
run: |
82+
echo "=== Build Configuration ==="
83+
grep -E "^CONFIG_(QEMU|USER_APP_|BOARD_)" .config | grep "=y"
84+
85+
- name: Build
7086
run: make
7187

72-
- name: Run test suite
88+
- name: Run tests
7389
run: make run-tests
7490

75-
- name: Run MPU fault test (expected to fail on QEMU)
91+
- name: Run MPU fault test
92+
if: matrix.app == 'tests'
7693
run: make run-tests FAULT=mpu
77-
continue-on-error: true
94+
continue-on-error: true # MPU not fully emulated in QEMU
7895

7996
- name: Run stack canary fault test
97+
if: matrix.app == 'tests'
8098
run: make run-tests FAULT=canary
99+
100+
compile-hw:
101+
runs-on: ubuntu-24.04
102+
strategy:
103+
fail-fast: false
104+
matrix:
105+
app: [tests, posix]
106+
include:
107+
- app: tests
108+
config: USER_APP_TESTS
109+
- app: posix
110+
config: USER_APP_POSIX
111+
112+
name: compile-hw (${{ matrix.app }})
113+
steps:
114+
- uses: actions/checkout@v6
115+
116+
- name: Install ARM toolchain
117+
uses: carlosperate/arm-none-eabi-gcc-action@v1
118+
with:
119+
release: '15.2.Rel1'
120+
121+
- name: Configure for discoveryf4 (hardware target)
122+
run: |
123+
make discoveryf4_defconfig
124+
python3 tools/kconfig/setconfig.py ${{ matrix.config }}=y
125+
python3 tools/kconfig/genconfig.py --header-path include/autoconf.h Kconfig
126+
127+
- name: Build (compile-only, no QEMU)
128+
run: make

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,55 @@ while adding advanced features from industrial RTOSes.
4444
- Profiling: Thread uptime, stack usage, memory fragmentation analysis
4545
- Test Suite: Automated regression tests with QEMU integration
4646

47+
## API Sets
48+
49+
F9 provides two API layers for application development:
50+
51+
### Native API (L4-style)
52+
The kernel exposes an L4-family system call interface derived from [L4Ka::Pistachio](https://github.com/l4ka/pistachio)
53+
and [seL4](https://sel4.systems/).
54+
Key syscalls:
55+
56+
| Syscall | Description |
57+
|---------|-------------|
58+
| `L4_Ipc` | Synchronous message passing between threads |
59+
| `L4_ThreadControl` | Create, configure, and delete threads |
60+
| `L4_Schedule` | Set thread scheduling parameters |
61+
| `L4_SpaceControl` | Configure address spaces |
62+
| `L4_ExchangeRegisters` | Read/write thread register state |
63+
| `L4_SystemClock` | Read system time (microseconds) |
64+
| `L4_KernelInterface` | Access Kernel Interface Page (KIP) |
65+
66+
Extensions for embedded real-time:
67+
- `L4_TimerNotify`: Hardware timer with notification delivery
68+
- `L4_NotifyWait` / `L4_NotifyPost` / `L4_NotifyClear`: Lightweight notification primitives
69+
70+
### POSIX API (PSE51/PSE52)
71+
A user-space compatibility layer implementing [IEEE Std 1003.13-2003](https://standards.ieee.org/ieee/1003.13/3322/)
72+
profiles for portable real-time applications:
73+
74+
| Profile | Description | Status |
75+
|---------|-------------|--------|
76+
| PSE51 | Minimal Realtime System | API Compliant |
77+
| PSE52 | Realtime Controller System | Partial |
78+
79+
Note: POSIX timer functions (`timer_create`, `timer_settime`) have limited functionality.
80+
Core threading, synchronization, and `clock_gettime`/`nanosleep` are fully operational.
81+
82+
Supported POSIX interfaces:
83+
84+
| Category | Functions |
85+
|----------|-----------|
86+
| Threads | `pthread_create`, `pthread_join`, `pthread_detach`, `pthread_self`, `pthread_equal`, `pthread_cancel`, `pthread_testcancel` |
87+
| Mutexes | `pthread_mutex_*` (normal, recursive, errorcheck), `pthread_mutex_timedlock` |
88+
| Condition Variables | `pthread_cond_wait`, `pthread_cond_signal`, `pthread_cond_broadcast`, `pthread_cond_timedwait` |
89+
| Spinlocks | `pthread_spin_init`, `pthread_spin_lock`, `pthread_spin_trylock`, `pthread_spin_unlock` |
90+
| Semaphores | `sem_init`, `sem_wait`, `sem_trywait`, `sem_timedwait`, `sem_post`, `sem_getvalue` |
91+
| Time | `clock_gettime`, `nanosleep` |
92+
93+
The POSIX layer is implemented entirely in user space atop the native notification system,
94+
requiring no kernel modifications. See [user/lib/posix](user/lib/posix) for implementation details.
95+
4796
## Documentation
4897

4998
Comprehensive documentation is available in the [Documentation/](Documentation/) directory:
@@ -81,7 +130,7 @@ Press `Ctrl+A` and then `X` to exit QEMU. Press `?` in KDB for debug menu (requi
81130
- STM32F4DISCOVERY (STM32F407VG)
82131
- STM32F429I-DISC1 (STM32F429ZI)
83132
- NUCLEO-F429ZI (STM32F429ZI)
84-
- Netduino Plus 2 (STM32F405RG) - QEMU emulated
133+
- Netduino Plus 2 (STM32F405RG) - QEMU only, used for automated testing
85134

86135
For detailed instructions including toolchain setup, serial configuration, and debugging,
87136
see [Documentation/quick-start.md](Documentation/quick-start.md).

include/ktimer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ struct tcb;
1313

1414
void ktimer_handler(void);
1515

16+
/* Get current kernel time in ticks since boot.
17+
* Returns 64-bit monotonically increasing tick count.
18+
* Used by SYS_SYSTEM_CLOCK syscall for userspace time queries.
19+
*/
20+
uint64_t ktimer_get_now(void);
21+
1622
/* Returns 0 if successfully handled
1723
* or number ticks if need to be rescheduled
1824
*/

include/l4/utcb.h

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,31 @@ struct utcb {
2525
uint32_t thread_word_1;
2626
uint32_t thread_word_2;
2727
/* +12w */
28-
/* Message Registers (MR) mapping with short message buffer:
29-
* MR0-MR7: Hardware registers R4-R11 (ctx.regs[0-7]) - 32 bytes
30-
* MR8-MR39: Short message buffer (tcb->msg_buffer[0-31]) - 128 bytes
31-
* MR40-MR47: UTCB overflow (mr[0-7]) - 32 bytes
28+
/* Message Registers (MR) storage:
3229
*
33-
* Total message capacity: 192 bytes (48 words)
34-
* Fastpath capacity: 160 bytes (40 words, MR0-MR39)
30+
* User-space perspective (via L4_LoadMR/L4_StoreMR):
31+
* - MR0-MR7: mr_low[0-7] (UTCB storage, marshaled to R4-R11 by L4_Ipc)
32+
* - MR8-MR39: tcb->msg_buffer[0-31] (kernel copies to receiver)
33+
* - MR40-MR47: mr[0-7] (UTCB overflow)
34+
*
35+
* Kernel perspective (ctx.regs[] = saved R4-R11):
36+
* - On SVC entry: kernel reads R4-R11 from exception frame
37+
* - On SVC exit: kernel restores R4-R11 to exception frame
38+
* - L4_Ipc loads mr_low→R4-R11 before SVC, stores after
39+
*
40+
* This decouples MRs from physical registers, preventing corruption
41+
* when C functions are called between L4_LoadMR and L4_Ipc.
3542
*/
43+
uint32_t mr_low[8]; /* MRs 0-7 (user-space cache, R4-R11 equivalent) */
44+
/* +20w */
3645
uint32_t mr[8]; /* MRs 40-47 (overflow beyond short buffer) */
37-
/* +20w */
38-
uint32_t br[8];
3946
/* +28w */
40-
uint32_t reserved[4];
41-
/* +32w */
47+
uint32_t br[8];
48+
/* +36w */
4249
};
4350

4451
typedef struct utcb utcb_t;
4552

46-
#define UTCB_SIZE 128
53+
#define UTCB_SIZE 144
4754

4855
#endif /* L4_UTCB_H_ */

include/notification.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,23 @@ uint32_t notification_get(tcb_t *tcb);
114114
*/
115115
uint32_t notification_read_clear(tcb_t *tcb, uint32_t mask);
116116

117+
/**
118+
* Wake thread blocked on SYS_NOTIFY_WAIT with proper semantics.
119+
*
120+
* Implements the full notification wake protocol:
121+
* 1. Check if thread is T_NOTIFY_BLOCKED (not T_RECV_BLOCKED)
122+
* 2. Check if signaled bits match thread's notify_mask
123+
* 3. Clear matched bits from notify_bits
124+
* 4. Write matched bits to thread's saved R0 (return value)
125+
* 5. Clear notify_mask and transition to T_RUNNABLE
126+
*
127+
* T_RECV_BLOCKED threads are NOT woken - they're waiting for IPC.
128+
*
129+
* @param thr Thread to potentially wake
130+
* @return 1 if thread was woken, 0 otherwise
131+
*/
132+
int notify_wake_thread(tcb_t *thr);
133+
117134
/**
118135
* Extended notification event structure.
119136
* Contains both notification bits and optional event data payload.

include/platform/ipc-fastpath.h

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,45 @@
4040
*
4141
* Copies MR0-MR{n_untyped} from sender to receiver:
4242
* - MR0-MR7: From saved_mrs to receiver->ctx.regs[0-7]
43-
* - MR8-MR39: From sender->msg_buffer to receiver->msg_buffer (NEW)
43+
* - MR8-MR39: From sender->msg_buffer to receiver->msg_buffer
4444
*
45-
* WCET: ~20 cycles (MR0-MR7) + ~100 cycles (MR8-MR39, if used)
45+
* WCET: ~16-24 cycles (MR0-MR7 via ldmia/stmia) + ~100 cycles (MR8-MR39)
4646
*/
4747
static inline void ipc_fastpath_copy_mrs(volatile uint32_t *saved_mrs,
4848
struct tcb *sender,
4949
struct tcb *receiver,
5050
int n_untyped)
5151
{
5252
int count = n_untyped + 1; /* +1 for tag in MR0 */
53-
int i;
5453

55-
/* Phase 1: Copy MR0-MR7 from saved registers (R4-R11) */
56-
for (i = 0; i < count && i < 8; i++)
57-
receiver->ctx.regs[i] = saved_mrs[i];
54+
/* MR0-MR7: Use ldmia/stmia for full 8-word copy (~16-24 cycles),
55+
* otherwise C loop for partial copy (~3-5 cycles/word).
56+
*
57+
* ldmia/stmia constraints:
58+
* - Base register must NOT be in the register list (UNPREDICTABLE)
59+
* - Must clobber r4-r11 and use "memory" barrier
60+
* - Both arrays are word-aligned (ctx.regs, __irq_saved_regs)
61+
*/
62+
if (count >= 8) {
63+
register uint32_t *src = (uint32_t *) saved_mrs;
64+
register uint32_t *dst = (uint32_t *) receiver->ctx.regs;
65+
__asm__ __volatile__(
66+
"ldmia %[src], {r4-r11}\n\t"
67+
"stmia %[dst], {r4-r11}\n\t"
68+
: [src] "+r"(src), [dst] "+r"(dst)
69+
:
70+
: "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "memory");
71+
} else {
72+
for (int i = 0; i < count; i++)
73+
receiver->ctx.regs[i] = saved_mrs[i];
74+
}
5875

59-
/* Phase 2: Copy MR8-MR39 from sender's msg_buffer (if needed) */
76+
/* MR8-MR39: C loop (ldmia/stmia not practical without spare registers) */
6077
if (count > 8) {
61-
int buf_count = count - 8; /* Number of words in buffer */
78+
int buf_count = count - 8;
6279
if (buf_count > 32)
63-
buf_count = 32; /* Clamp to buffer size */
64-
65-
for (i = 0; i < buf_count; i++)
80+
buf_count = 32;
81+
for (int i = 0; i < buf_count; i++)
6682
receiver->msg_buffer[i] = sender->msg_buffer[i];
6783
}
6884
}
@@ -152,20 +168,30 @@ static inline int ipc_fastpath_helper(struct tcb *caller,
152168
caller->timeout_event = 0;
153169
to_thr->timeout_event = 0;
154170

155-
/* Receiver becomes runnable with IPC priority boost */
171+
/* Receiver becomes runnable.
172+
* Only boost priority if receiver was waiting for ANY message.
173+
* If waiting for a specific reply, skip boost - thread was just
174+
* processing an IPC and will return to user code immediately.
175+
* This prevents priority inversion where reply receivers accumulate
176+
* priority 3 and starve lower-priority threads indefinitely.
177+
*/
156178
to_thr->state = T_RUNNABLE;
179+
if (to_thr->ipc_from == L4_ANYTHREAD)
180+
sched_set_priority(to_thr, SCHED_PRIO_IPC);
157181
to_thr->ipc_from = L4_NILTHREAD;
158-
sched_set_priority(to_thr, SCHED_PRIO_IPC);
159182
sched_enqueue(to_thr);
160183

161184
/* Caller continues (send-only, no reply expected)
162185
* Fastpath only handles from_tid==NILTHREAD (simple send).
163186
* For L4_Call (send+receive), slowpath handles blocking.
164187
*
165-
* Re-enqueue caller (was dequeued at SVC entry).
166-
* It's safe to enqueue current thread - sched has double-enqueue
167-
* protection.
188+
* Restore caller's base priority before re-enqueueing.
189+
* This mirrors slowpath behavior (thread_make_sender_runnable)
190+
* and prevents IPC priority boost from accumulating, which would
191+
* cause starvation of lower-priority threads.
168192
*/
193+
if (caller->priority != caller->base_priority)
194+
sched_set_priority(caller, caller->base_priority);
169195
caller->state = T_RUNNABLE;
170196
sched_enqueue(caller);
171197

@@ -196,9 +222,27 @@ static inline int ipc_fastpath_helper(struct tcb *caller,
196222
static inline int ipc_try_fastpath(struct tcb *caller, uint32_t *svc_param)
197223
{
198224
extern volatile uint32_t __irq_saved_regs[8];
225+
uint32_t local_mrs[8];
199226

200-
/* Read from global __irq_saved_regs saved by SVC_HANDLER */
201-
return ipc_fastpath_helper(caller, svc_param, __irq_saved_regs);
227+
/* Copy __irq_saved_regs to local buffer IMMEDIATELY to prevent
228+
* corruption from nested interrupts. A higher-priority IRQ could
229+
* overwrite the global before we finish reading, corrupting MR0-MR7.
230+
*
231+
* This is safe because SVC has the lowest exception priority on
232+
* Cortex-M, so we can't be interrupted by another SVC, but we
233+
* could be interrupted by higher-priority IRQs that also save
234+
* to __irq_saved_regs.
235+
*/
236+
local_mrs[0] = __irq_saved_regs[0];
237+
local_mrs[1] = __irq_saved_regs[1];
238+
local_mrs[2] = __irq_saved_regs[2];
239+
local_mrs[3] = __irq_saved_regs[3];
240+
local_mrs[4] = __irq_saved_regs[4];
241+
local_mrs[5] = __irq_saved_regs[5];
242+
local_mrs[6] = __irq_saved_regs[6];
243+
local_mrs[7] = __irq_saved_regs[7];
244+
245+
return ipc_fastpath_helper(caller, svc_param, local_mrs);
202246
}
203247

204248
#endif /* PLATFORM_IPC_FASTPATH_H_ */

include/syscall.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ typedef enum {
2020
SYS_PROCESSOR_CONTROL,
2121
SYS_MEMORY_CONTROL,
2222
SYS_TIMER_NOTIFY, /* Timer notification syscall */
23+
SYS_NOTIFY_WAIT, /* Wait for notification bits */
24+
SYS_NOTIFY_POST, /* Post notification bits to thread */
25+
SYS_NOTIFY_CLEAR, /* Clear notification bits (non-blocking) */
2326
} syscall_t;
2427

2528
void svc_handler(void);

include/thread.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ typedef enum {
6464
T_RUNNABLE,
6565
T_SVC_BLOCKED,
6666
T_RECV_BLOCKED,
67-
T_SEND_BLOCKED
67+
T_SEND_BLOCKED,
68+
T_NOTIFY_BLOCKED /* Blocked on SYS_NOTIFY_WAIT - distinct from IPC receive
69+
*/
6870
} thread_state_t;
6971

7072
typedef struct {
@@ -141,6 +143,11 @@ struct tcb {
141143
*/
142144
uint32_t notify_bits;
143145

146+
/* Wait mask for SYS_NOTIFY_WAIT syscall.
147+
* Thread blocks until (notify_bits & notify_mask) != 0.
148+
*/
149+
uint32_t notify_mask;
150+
144151
/* Optional event-specific data payload.
145152
* Used for extended notifications (e.g., IRQ number for high IRQs).
146153
* Set by notification_post() and retrieved by notification_get_extended().

0 commit comments

Comments
 (0)