Version: 1.0
Header: #include <ulmk/microkernel.h>
Status: Reflects the implemented API in include/ulmk/microkernel.h.
Purpose of this document: complete reference for all syscall wrappers exposed to userspace components. Consult this when writing a component or a board service. For the arch-internal contract see
docs/arch_api_spec.md. For the build system seedocs/build_system_spec.md.
- Model
- Types and Constants
- Error Codes
- Privilege Levels
- Capabilities
- Thread API
- IPC Endpoint API
- Notification API
- Memory API
- IRQ API
- Board Timer Service
- Syscall Number Table
- Capability Grant API
- Boot Entry Point
All public API functions are static inline syscall wrappers defined in
include/ulmk/microkernel.h. They issue a TriCore SYSCALL instruction; the
kernel router in kernel/syscall/syscall_router.c dispatches to the handler.
Calling convention (TriCore ABI):
SYSCALL #N D15 = N (syscall number / TIN)
D4 = arg0
D5 = arg1
D6 = arg2
D7 = arg3
D2 = return value (written by kernel before RFE)
Up to 4 arguments fit in registers. When more are needed, the caller allocates
a small struct on its stack and passes a pointer as one of the four arguments
(see ulmk_ep_reply_recv and ulmk_ep_recv_or_notif).
The kernel entry path (ulmk_arch_syscall_entry in arch/tricore/arch.c) raises
CCPN = 255 before calling the syscall router, disabling all hardware IRQs for
the duration of the syscall.
typedef uintptr_t ulmk_tid_t; /* opaque TCB handle (kernel pointer cast) */
typedef uintptr_t ulmk_ep_t; /* IPC endpoint handle — raw pointer */
typedef uintptr_t ulmk_notif_t; /* notification object handle — raw pointer */
#define ULMK_TID_INVALID ((ulmk_tid_t)0)
#define ULMK_EP_INVALID ((ulmk_ep_t)0)
#define ULMK_NOTIF_INVALID ((ulmk_notif_t)0)Handles are raw kernel pointers. They are valid only while the object exists; using a freed handle is undefined behaviour.
#define ULMK_MSG_WORDS 6
typedef struct {
uint32_t label; /* caller-defined message tag */
uint32_t words[ULMK_MSG_WORDS]; /* 24 bytes of inline payload */
} ulmk_msg_t;Total inline payload: 28 bytes per message (label + 6 words). This is the control-plane envelope only — the kernel stages it by pointer between the caller's and server's userspace buffers (one copy each way on the hot path).
For larger payloads, put a pointer/length (or buffer id) in words[] and let
userspace share the data region (ulmk_mem_grant() or a buffer both sides can
already reach). The kernel does not copy bulk data on IPC.
typedef struct {
const char *name; /* display name (up to 15 chars) */
void (*entry)(void *arg);
void *arg;
uint8_t priority; /* 0 = highest, 255 = lowest */
size_t stack_size; /* bytes; allocated from user_pool */
ulmk_privilege_t privilege; /* ULMK_PRIV_USER or ULMK_PRIV_DRIVER */
size_t heap_size; /* 0 = no per-thread heap; last for compat */
} ulmk_thread_attr_t;Always declare ulmk_thread_attr_t attr = {0} before setting individual fields so
that heap_size defaults safely to zero.
typedef struct {
uintptr_t base; /* start of the heap region within the slabAO */
size_t size; /* bytes; equals attr.heap_size at creation */
} ulmk_heap_info_t;Returned by ulmk_get_thread_heap().
#define ULMK_BOOT_MAX_MEM_REGIONS 4
typedef struct {
struct {
uintptr_t base;
size_t size;
} mem[ULMK_BOOT_MAX_MEM_REGIONS];
uint32_t mem_count;
uintptr_t csa_pool_base;
size_t csa_pool_size;
} ulmk_boot_info_t;Valid only for the duration of ulmk_root_thread(). Copy any fields needed
beyond bootstrap before spawning child threads.
#define ULMK_PERM_READ (1u << 0)
#define ULMK_PERM_WRITE (1u << 1)
#define ULMK_PERM_EXEC (1u << 2)
#define ULMK_PERM_USER (1u << 3)
typedef struct {
const char *name;
uintptr_t start;
uintptr_t end;
uint32_t perms;
} ulmk_domain_desc_t;| Constant | Value | Meaning |
|---|---|---|
ULMK_OK |
0 | Success |
ULMK_EINVAL |
−1 | Invalid argument |
ULMK_ENOMEM |
−2 | Out of memory / pool exhausted |
ULMK_EPERM |
−3 | Permission denied (missing capability or privilege) |
ULMK_ENOSPC |
−4 | No space in table |
ULMK_EDEADLK |
−5 | Would deadlock |
ULMK_ESRCH |
−6 | Thread or object not found |
ULMK_ETIMEOUT |
−7 | Timer deadline expired before operation completed |
typedef enum {
ULMK_PRIV_USER = 0, /* PSW.IO = 0: no peripheral access */
ULMK_PRIV_DRIVER = 1, /* PSW.IO = 1: peripheral access, restricted syscalls */
ULMK_PRIV_KERNEL = 2, /* PSW.IO = 2: supervisor — kernel-internal only */
} ulmk_privilege_t;The root thread starts at ULMK_PRIV_DRIVER. Most board services and driver
threads run at ULMK_PRIV_DRIVER. Untrusted application threads use
ULMK_PRIV_USER.
Capabilities are a bitmask in the TCB. Checked by the syscall router before privileged operations.
| Constant | Bit | Operation gated |
|---|---|---|
ULMK_CAP_SPAWN |
0 | ulmk_thread_create() |
ULMK_CAP_KILL |
1 | ulmk_thread_kill() |
ULMK_CAP_IRQ |
2 | ulmk_irq_bind(), ulmk_irq_enable(), ulmk_irq_disable(), ulmk_irq_ack() |
ULMK_CAP_MAP_PERIPH |
3 | ulmk_mem_map() with ULMK_MMAP_PERIPH |
ULMK_CAP_GRANT_CAP |
4 | ulmk_cap_grant() |
ULMK_CAP_ALL |
0xFF | All capabilities; initial value of the root thread |
ulmk_tid_t ulmk_thread_self(void);Returns the TID of the calling thread. Available at any privilege level.
int ulmk_thread_yield(void);Yields the CPU to the next runnable thread at the same priority (FIFO within
level). Does not time-slice across equal-priority threads — use explicit
blocking or periodic yields for fairness at the same level. Returns ULMK_OK.
ulmk_tid_t ulmk_thread_create(const ulmk_thread_attr_t *attr);Allocates a TCB and a stack of attr->stack_size bytes from the user pool,
initialises the CSA chain to start at attr->entry(attr->arg), and makes the
thread runnable.
Requires: ULMK_CAP_SPAWN.
Returns the new TID, or ULMK_TID_INVALID on failure (ULMK_ENOMEM, ULMK_ENOSPC,
ULMK_EPERM).
int ulmk_thread_kill(ulmk_tid_t tid);Terminates tid, frees its stack and CSA chain, and removes it from the
scheduler. If tid is blocked on an endpoint or notification, it is removed
from the wait list first.
Requires: ULMK_CAP_KILL.
Returns ULMK_OK, ULMK_ESRCH (TID not found), or ULMK_EPERM.
int ulmk_thread_suspend(ulmk_tid_t tid);
int ulmk_thread_resume(ulmk_tid_t tid);Suspend removes the thread from the ready queue without terminating it. Resume makes it runnable again. A suspended thread does not consume CPU.
Requires: ULMK_PRIV_DRIVER.
int ulmk_thread_priority_set(ulmk_tid_t tid, uint8_t prio);
int ulmk_thread_priority_get(ulmk_tid_t tid);Dynamic priority adjustment. Priority 0 is highest. Changes take effect at the next scheduler decision point.
__attribute__((noreturn)) void ulmk_thread_exit(void);Terminates the calling thread. Never returns. The ulmk_root_thread() function
must call this when bootstrapping is complete.
The IPC model is synchronous call-reply. The caller blocks until the server replies. Priority inheritance: the server runs at max(server_prio, caller_prio) while processing a call.
ulmk_ep_t ulmk_ep_create(void);Allocates an endpoint from the kernel pool. Returns ULMK_EP_INVALID if the
pool is exhausted. The endpoint is owned by the calling thread.
Important: create the endpoint before spawning the server thread. This
ensures clients can call the endpoint immediately after <name>_init() returns,
without a spin-wait.
int ulmk_ep_call(ulmk_ep_t ep, ulmk_msg_t *msg);Sends *msg to the endpoint and blocks until the server calls ulmk_ep_reply().
The reply overwrites *msg in place. The kernel keeps a pointer to msg while
the caller is blocked (no TCB bounce of the envelope on the slow path).
Returns ULMK_OK or ULMK_EINVAL (bad endpoint).
int ulmk_ep_recv(ulmk_ep_t ep, ulmk_msg_t *msg, ulmk_tid_t *sender);Blocks until a message arrives on ep. Fills *msg with the message and
*sender with the caller's TID. The server must call ulmk_ep_reply(sender, …)
to unblock the caller.
int ulmk_ep_reply(ulmk_tid_t sender, const ulmk_msg_t *reply);Sends *reply to the blocked sender and makes it runnable.
int ulmk_ep_reply_recv(ulmk_ep_t ep, ulmk_tid_t sender,
const ulmk_msg_t *reply,
ulmk_msg_t *next, ulmk_tid_t *next_sender);Atomic reply-and-receive: replies to sender, then immediately blocks on ep
for the next call. Avoids a round-trip through the scheduler compared with
separate ulmk_ep_reply + ulmk_ep_recv.
The four output pointers are packed into a stack-allocated ulmk_reply_recv_args_t
to stay within the 4-register argument limit.
int ulmk_ep_grant(ulmk_ep_t ep, ulmk_tid_t target);Grants access to ep to target. The target thread can then call ulmk_ep_call
on it. The endpoint owner retains full access.
int ulmk_ep_recv_or_notif(ulmk_ep_t ep, ulmk_notif_t notif,
uint32_t mask,
ulmk_msg_t *msg, ulmk_tid_t *sender,
uint32_t *notif_bits);Blocks on either an IPC call on ep or a notification signal on notif (with
bitmask mask), whichever arrives first. Useful for a server that must also
react to hardware events.
Returns ULMK_OK. On return, exactly one of *sender != ULMK_TID_INVALID (IPC
path) or *notif_bits != 0 (notification path) is true.
int ulmk_ep_destroy(ulmk_ep_t ep);Frees the endpoint. Any threads blocked on ep are unblocked with
ULMK_EINVAL.
Notifications are a lightweight one-to-many signalling primitive. 32 bits per object; each bit is an independent flag. Suitable for hardware IRQ delivery and event broadcasting.
ulmk_notif_t ulmk_notif_create(void);Allocates a notification object. Returns ULMK_NOTIF_INVALID if the pool is
exhausted.
int ulmk_notif_signal(ulmk_notif_t notif, uint32_t bits);Atomically OR bits into the notification state. If any thread is blocked
waiting for those bits, it is woken.
uint32_t ulmk_notif_poll(ulmk_notif_t notif, uint32_t mask);Returns the current set bits matching mask and clears them atomically.
Returns 0 if no bits matching mask are set. Does not block.
int ulmk_notif_wait(ulmk_notif_t notif, uint32_t mask, uint32_t *bits);Blocks until at least one bit matching mask is set. Clears and returns the
matching bits in *bits.
int ulmk_notif_destroy(ulmk_notif_t notif);Frees the notification object. Threads blocked on it are woken with
ULMK_EINVAL.
Each thread may carry a private heap allocated at creation time by setting
attr.heap_size > 0. The kernel allocates a contiguous slabAO
(stack_size + heap_size bytes) from user_pool and covers it with a single
MPU DPR. The TCB lives in a separate allocation so userspace cannot reach
kernel metadata through its DPR.
int ulmk_get_thread_heap(ulmk_heap_info_t *info);Populates *info with the heap base and size for the calling thread.
Returns ULMK_OK on success, ULMK_EPERM if the thread has no heap
(attr.heap_size == 0).
int ulmk_heap_extend(size_t size);Allocates size bytes from the global user_pool and adds the new region as
an additional MPU DPR for the calling thread. Requires ULMK_PRIV_DRIVER.
Returns ULMK_OK, ULMK_ENOMEM, ULMK_EPERM, or ULMK_ENOSPC (DPR limit
reached).
void *ulmk_mem_map(void *hint, size_t size, uint32_t perms, uint32_t flags);Maps a memory region. Flags:
| Flag | Meaning |
|---|---|
ULMK_MMAP_ANON |
Anonymous mapping from user_pool |
ULMK_MMAP_PERIPH |
Map a peripheral MMIO region (requires ULMK_CAP_MAP_PERIPH) |
int ulmk_mem_unmap(void *addr, size_t size);Unmaps a previously mapped region.
int ulmk_mem_grant(void *addr, size_t size, ulmk_tid_t target, uint32_t perms);Grants access to a memory region to target thread with the specified
permissions. Used to share large buffers between components without copying.
Requires ULMK_PRIV_DRIVER privilege and ULMK_CAP_IRQ capability.
int ulmk_irq_bind(uint8_t srpn, ulmk_notif_t notif, uint32_t bit);Binds hardware interrupt srpn to bit bit in notif. When the interrupt
fires, the kernel calls ulmk_notif_signal(notif, 1u << bit) from the ISR.
At most ULMK_CONFIG_MAX_IRQ_BINDINGS bindings can be active simultaneously.
int ulmk_irq_enable(uint8_t srpn);
int ulmk_irq_disable(uint8_t srpn);Enable or disable the SRC for srpn.
int ulmk_irq_ack(uint8_t srpn);Clear the pending flag in the SRC for srpn. Must be called after processing
a level-triggered interrupt to prevent immediate re-entry.
int ulmk_irq_bind_hw(uint8_t srpn, ulmk_notif_t notif, uint32_t bit,
uintptr_t src_reg_addr);Associate a fixed hardware SRC register (TriCore: absolute address of the
SRC_* register) with srpn and deliver bit in notif when the interrupt
fires. Used by board services (timer, UART, etc.) that own a specific SRC.
Requires: ULMK_CAP_IRQ and ULMK_PRIV_DRIVER.
The kernel has no system timer or sleep syscall. Timekeeping is board
policy: a driver thread maps the SoC timer block, binds its compare-match IRQ
via ulmk_irq_bind_hw(), and serves sleep requests over IPC.
QEMU reference: boards/qemu_tc3xx/board_timer.c (STM0 + SRPN 1).
ulmk_tid_t board_timer_start(const ulmk_boot_info_t *info);Spawn the timer server thread. Called once from board_services_init().
Returns the server TID.
void board_timer_sleep_us(uint32_t us);Block the caller until approximately us microseconds have elapsed (IPC to
the board timer server). Available after board_timer_start().
Real boards provide their own board_timer.c or a weak stub in
stub/board_timer_stub.c.
Defined in include/ulmk/syscall_nr.h. Single source of truth for both
userspace wrappers and the kernel router.
1–3 Memory (shared) (ULMK_SYS_MMAP, MUNMAP, MEM_GRANT)
4–6 [reserved] (former MALLOC/FREE/ALIGNED_ALLOC removed in slabAO model)
7–8 Per-thread heap (HEAP_EXTEND, GET_THREAD_HEAP)
10–14 Scheduling (YIELD, EXIT, [12–14 reserved — former kernel timer syscalls])
20 Thread query (THREAD_SELF)
30–37 IPC endpoints (EP_CREATE, CALL, RECV, REPLY, REPLY_RECV, GRANT,
RECV_OR_NOTIF, DESTROY)
40–44 Notifications (NOTIF_CREATE, SIGNAL, WAIT, POLL, DESTROY)
60–64 IRQ (IRQ_BIND, IRQ_ENABLE, IRQ_DISABLE, IRQ_ACK, IRQ_BIND_HW)
70–75 Thread mgmt (SPAWN, KILL, SUSPEND, RESUME, SET_PRIO, GET_PRIO)
80–84 Process mgmt (PROC_CREATE, DESTROY, ADD_REGION, GRANT_CAP, GRANT_IRQ)
Numbers are sparse on purpose — room for additions per group without renumbering.
Router upper bound: ULMK_SYS_MAX = 128.
int ulmk_cap_grant(ulmk_tid_t target, uint32_t caps);Grants the capability bits in caps to target. The caller must itself hold
ULMK_CAP_GRANT_CAP. The root thread starts with ULMK_CAP_ALL.
Typical boot pattern:
ulmk_tid_t driver = ulmk_thread_create(&driver_attr);
ulmk_cap_grant(driver, ULMK_CAP_IRQ | ULMK_CAP_MAP_PERIPH);void ulmk_root_thread(const ulmk_boot_info_t *info);User-provided strong symbol. Called by the kernel as the first and only
userspace context. Runs at ULMK_PRIV_DRIVER with ULMK_CAP_ALL.
Rules:
- Must never return. Call
ulmk_thread_exit()when bootstrapping is complete. - Valid for the duration of
ulmk_root_thread()only — copyinfofields before spawning threads. - Is the only thread in existence at entry; all other threads are created here or by threads created here.
See docs/component_spec.md for the component init convention and
docs/application_development_guide.md for a worked example.