Syscall table
The syscall number is the SVC immediate; arguments pass in r0–r3 and the result returns in r0, with negative values meaning error (see Syscall ABI for the convention and error codes for the full list). Every kernel object -port, notification, shared-memory region, device, task- is named by a handle, a small per-process slot index; userspace never holds a raw kernel pointer.
The table
Task lifecycle (0x00–0x0B)
| Number | Name | Signature |
|---|---|---|
0x00 | pquit | (status) -> never returns |
0x01 | yield | () -> 0 |
0x03 | wait | (pid, &status) -> 0 or -err |
0x04 | getpid | () -> pid |
0x05 | sleep | (ms) -> 0 |
0x06 | pspawn | (name) -> 0 or -err |
0x07 | kickstart | (args*) -> 0 or -err |
0x08 | pkill | (task_handle) -> 0 or -err |
0x09 | tmake | (entry, user_sp, arg) -> tid |
0x0A | tjoin | (tid) -> exit_status or -err |
0x0B | tquit | (status) -> never returns |
Messaging (0x10–0x17)
| Number | Name | Signature |
|---|---|---|
0x10 | send | (port, w1, w2, w3) -> 0 or -err |
0x11 | recv | (port, timeout_ms) -> sender; r1–r3 payload |
0x12 | call | (port, w1, w2, w3) -> r0–r3 reply |
0x13 | reply | (r0, w1, w2, w3) -> 0 or -err |
0x14 | lsend | (port, len) -> 0 or -err |
0x15 | lcall | (port, len) -> {0, recv_len} |
0x16 | lreply | (reply_handle, len) -> 0 or -err |
0x17 | waitany | (handles*, count, timeout, result*) -> 0 or -err |
Handles & capabilities (0x20–0x26)
| Number | Name | Signature |
|---|---|---|
0x20 | port_create | () -> handle or -err |
0x21 | ntfn_create | () -> handle or -err |
0x22 | dev_query | (name/class, out*) -> handle or -err |
0x23 | grant | (handle, pid) -> 0 or -err |
0x24 | destroy | (handle) -> 0 or -err |
0x25 | ntfn_signal | (ntfn_handle, bits) -> 0 or -err |
0x26 | ntfn_wait | (ntfn_handle, timeout_ms) -> bits or -err |
Memory (0x30–0x38)
| Number | Name | Signature |
|---|---|---|
0x30 | memmap | (handle | HANDLE_ANON, size, prot, flags) -> va or -err |
0x31 | memunmap | (addr) -> 0 or -err |
0x32 | shm_create | (size) -> handle or -err |
0x37 | memprotect | (addr, size, prot) -> 0 or -err |
0x38 | asinject | (args*) -> 0 or -err |
Interrupts (0x40–0x42)
| Number | Name | Signature |
|---|---|---|
0x41 | irq_bind | (dev_handle, ntfn_handle) -> 0 or -err |
0x42 | irq_done | (dev_handle) -> 0 or -err |
Syscall descriptions
pquit
Terminate the calling process.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | status | Exit status stored in the PCB, retrievable by the parent via wait |
Returns
Does not return.
Errors
None. This syscall always succeeds.
Pitfalls
Do not use pquit to exit a thread unless you want the thread to terminate the entire process. Use tquit for a single thread.
yield
Voluntarily reschedule the process away.
Returns
r0 = 0.
Errors
None. This syscall always succeeds.
wait
Wait for a child process to exit.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | pid | PID of the process to wait on |
r1 | &status | Pointer to a buffer the kernel fills with the child's exit status |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Negative PID |
ERR_NOENT | No process with that PID exists |
ERR_BADPTR | Status pointer is invalid |
getpid
Get the PID of the current process.
Returns
PID of the process.
sleep
Voluntarily block the process for a fixed duration.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | ms | Sleep time in milliseconds |
Returns
0. There is no infinite sleep and no polling form.
pspawn
Create a frozen child process.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | name | Pointer to the name string |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Failed to copy the name |
ERR_NOMEM | Failed to create/fill the PCB, or the handle vector is full |
ERR_BADPTR | String pointer is invalid |
Pitfalls
This creates a FROZEN process that will not be scheduled. To fill its address space, the init process must call asinject with parsed ELF data, then kickstart it.
kickstart
Mark a FROZEN process as schedulable.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | args | Pointer to the argument struct |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Struct copy or handle lookup failed, the task is not frozen, or there is no such task |
ERR_BADPTR | Struct pointer is invalid |
Pitfalls
Do not kickstart a process whose address space has not been filled by asinject, or it takes a prefetch abort and is killed. Ordinary processes cannot call asinject; to spawn a child, message the init process.
pkill
Terminate a child process.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | task_handle | Slot index of the task handle |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | The handle doesn't exist, isn't a task handle, or the process doesn't exist |
Pitfalls
You cannot kill threads this way since pkill operates on whole processes.
tmake
Create a new thread in the calling process.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | entry | Entry point (must be void (*entry)(void *)) |
r1 | user_sp | User stack pointer (base) |
r2 | arg | Argument pointer passed to the entry function |
Returns
Thread ID (TID) of the new thread.
Errors
| Code | Condition |
|---|---|
ERR_BADPTR | Any of the three pointers are invalid |
ERR_NOMEM | Failed to allocate the kernel stack or TCB |
tjoin
Wait for a thread to exit.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | tid | TID of the thread to wait for |
Returns
Exit code of the thread.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | The calling process doesn't own that thread |
tquit
Terminate the calling thread. If it is the last thread, behaves like pquit.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | status | Thread exit code |
Returns
Does not return.
Errors
None. This syscall always succeeds.
send
Send a three-word message to a port and block until a receiver takes it (synchronous rendezvous).
Arguments
| Register | Name | Description |
|---|---|---|
r0 | port | Handle of the destination port |
r1–r3 | w1–w3 | The three payload words |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADHANDLE | Handle names no live port |
ERR_BADTYPE | Handle is not a port |
ERR_DEAD | The peer is gone |
recv
Block waiting for a message on a port. Returns the sender and payload once a sender rendezvouses.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | port | Handle of the port to receive on |
r1 | timeout_ms | TIMEOUT_POLL (0) for non-blocking, TIMEOUT_INFINITE to block indefinitely, otherwise a deadline in ms |
Returns
r0 = sender PID; r1–r3 = the payload words.
Errors
| Code | Condition |
|---|---|
ERR_TIMEOUT | Deadline expired, or a poll found nothing waiting |
ERR_BADHANDLE | Handle names no live port |
call
Atomic send-then-receive: deliver a message and block for the reply on the same port. This is the request/response fast path, cheaper than a separate send + recv.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | port | Handle of the server port |
r1–r3 | w1–w3 | Request payload words |
Returns
r0–r3 = the reply words.
Errors
ERR_BADHANDLE, ERR_BADTYPE, or ERR_DEAD as for send.
reply
Reply to the client currently blocked in a call on this server.
Arguments
| Register | Name | Description |
|---|---|---|
r0–r3 | reply words | The four reply words returned to the caller |
Returns
0 on success.
Errors
ERR_DEAD if the caller is gone.
lsend
Send a large message. The bytes live in the caller's lmsg buffer; only the length is passed. Blocks like send.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | port | Handle of the destination port |
r1 | len | Byte count in the lmsg buffer, at most 512 |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_OVERFLOW | len exceeds 512 bytes |
ERR_BADHANDLE / ERR_BADTYPE | Bad or non-port handle |
lcall
Large-message request/response: send from the lmsg buffer and block for a reply written back into it.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | port | Handle of the server port |
r1 | len | Request byte count in the lmsg buffer |
Returns
On success, r0 = 0 and r1 = the reply length in the lmsg buffer.
Errors
ERR_OVERFLOW, ERR_BADHANDLE, ERR_BADTYPE, ERR_DEAD.
lreply
Reply to a large-message caller. Unlike reply, it takes an explicit reply handle, allowing a server to answer out of order.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | reply_handle | Handle identifying the blocked caller |
r1 | len | Reply byte count in the lmsg buffer |
Returns
0 on success.
Errors
ERR_OVERFLOW, ERR_BADHANDLE, ERR_DEAD.
waitany
Block until any one of several handles (ports or notifications) becomes ready. This is how a server multiplexes many clients and IRQ sources in a single loop.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | handles | Pointer to an array of handles to watch |
r1 | count | Number of handles in the array |
r2 | timeout | 0 to poll, UINT32_MAX for infinite, otherwise a deadline in ms |
r3 | result | Pointer to the result struct; its first field is a caller-set size |
Returns
0 on success, with the ready handle and its event written into result.
Errors
| Code | Condition |
|---|---|
ERR_TIMEOUT | Deadline expired, or a poll found nothing ready |
ERR_BADPTR | Handles or result pointer invalid |
ERR_BADARG | Bad count or result size |
port_create
Create a new port (message endpoint) owned by the caller.
Returns
A port handle.
Errors
ERR_NOMEM if the object or a handle slot cannot be allocated.
ntfn_create
Create a new notification object: a lightweight, non-blocking signal carrier used for IRQ delivery and simple wakeups.
Returns
A notification handle.
Errors
ERR_NOMEM if the object or a handle slot cannot be allocated.
dev_query
Look up a device by name or class and obtain a handle to it, along with its metadata (such as its IRQ). This is the gate to mapping device registers with memmap and binding its interrupt with irq_bind.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | name/class | Device name or class selector |
r1 | out | Pointer to a buffer for device metadata |
Returns
A device handle.
Errors
| Code | Condition |
|---|---|
ERR_NOENT | No matching device |
ERR_NOPERM | Caller may not claim this device |
ERR_BADPTR | Output pointer invalid |
grant
Transfer a handle of any type to another process, the generic capability-transfer primitive. The handle is inserted into the target's table.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | handle | Handle to transfer |
r1 | pid | PID of the receiving process |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADHANDLE | Source handle names no live entry |
ERR_NOENT | No such target process |
ERR_NOMEM | Target handle table is full |
destroy
Destroy the object behind a handle. Dispatches on the handle's type, so it replaces the old per-type destroy calls.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | handle | Handle of the object to destroy |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADHANDLE | Handle names no live entry |
ERR_BADTYPE | Handle is a REPLY or TASK type, which destroy refuses |
ERR_BUSY | Object is still mapped somewhere |
Pitfalls
Unmap a shared-memory or device region with memunmap before destroying its handle, or you get ERR_BUSY.
ntfn_signal
Set bits on a notification object, waking any waiter. Bits accumulate until consumed.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | ntfn_handle | Handle of the notification |
r1 | bits | Bitmask to OR in (31-bit; bit 31 is reserved and rejected) |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Bit 31 was set |
ERR_BADHANDLE / ERR_BADTYPE | Bad or non-notification handle |
ntfn_wait
Block until a notification has bits set, then read and clear them.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | ntfn_handle | Handle of the notification |
r1 | timeout_ms | 0 to poll, UINT32_MAX for infinite, otherwise a deadline in ms |
Returns
The accumulated bits (31-bit; bit 31 is reserved so a negative return is always an error).
Errors
ERR_TIMEOUT on a deadline or empty poll; ERR_BADHANDLE / ERR_BADTYPE for a bad handle.
memmap
Map memory into the caller. The first argument selects what backs it: HANDLE_ANON for fresh anonymous pages, or a shared-memory / device handle for an existing object. This one call replaces the old attach and mapdev.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | handle | Object handle, or HANDLE_ANON for anonymous memory |
r1 | size | Anonymous: > 0 and ≤ 32 MB. Device/SHM: must be 0 (the object knows its size) |
r2 | prot | VM_PROT_READ/WRITE/EXEC only; W^X enforced; EXEC rejected on device |
r3 | flags | Must be 0 |
Returns
The virtual address of the mapping.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Bad size, W+X prot, EXEC on a device, or non-zero flags |
ERR_BADHANDLE / ERR_BADTYPE | Handle is not a mappable object |
ERR_NOMEM | Out of physical frames or virtual address space |
Pitfalls
The kernel ORs in VM_PROT_USER itself. a process cannot mint a kernel-privileged mapping through prot. See Memory for the full model.
memunmap
Unmap a whole region by its base address.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | addr | Base address of the region to unmap |
Returns
0 on success.
Errors
ERR_BADARG if addr is not the base of a mapped region.
Pitfalls
Whole-region only — you cannot unmap part of a mapping. Partial unmap is a planned additive call (memunmap_range) in a later 1.x kernel.
shm_create
Create a shared-memory object and receive a handle to it. Map it with memmap; hand it to a peer with grant.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | size | Size of the shared region |
Returns
A shared-memory handle.
Errors
ERR_BADARG for a bad size; ERR_NOMEM if frames or a handle slot cannot be allocated.
memprotect
Change the protection of an existing mapping.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | addr | Base of the region |
r1 | size | Length to reprotect |
r2 | prot | New VM_PROT_* bits (W^X enforced) |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADARG | Region is PINNED or GUARD, request spans regions, or W^X violated |
ERR_NOPERM | EXEC requested on MMIO |
asinject
Fill a frozen process's address space from parsed ELF data. This is the privileged step between pspawn and kickstart.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | args | Pointer to the injection struct; its first field is a caller-set size |
Returns
0 on success.
Errors
ERR_NOPERM for a non-privileged caller; ERR_BADPTR / ERR_BADARG for a bad struct.
Pitfalls
Only the init process may call this. Ordinary processes spawn children by messaging init, not by calling asinject directly.
irq_bind
Bind a device's interrupt to a notification object. When the IRQ fires, the kernel masks the line and signals the notification; the driver waits on it with ntfn_wait or waitany. This folds in the old irq_claim.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | dev_handle | Handle of the device (from dev_query) |
r1 | ntfn_handle | Notification to signal on each interrupt |
Returns
0 on success.
Errors
| Code | Condition |
|---|---|
ERR_BADHANDLE / ERR_BADTYPE | Bad device or notification handle |
ERR_BUSY | Another process already owns that IRQ |
irq_done
Signal that the device has been serviced. The kernel unmasks the IRQ line so it can fire again.
Arguments
| Register | Name | Description |
|---|---|---|
r0 | dev_handle | Handle of the device |
Returns
0 on success.
Errors
ERR_BADHANDLE / ERR_BADTYPE for a bad handle.
Pitfalls
Do not call irq_done until the device has actually been serviced and has deasserted the line, or you provoke an interrupt storm.