Syscalls

About basics

Sentry UAPI is implemented in Rust. In order to ensure compatibility with both Rust and C worlds, the UAPI specification is made using extern C external interface specification and types, and the crate defintion is automatically exported to C headers using cbindgen.

This allows a single root of trust for all UAPI definition, for both languages, in our case using a Rust definition that ensure struct typing usage.

The UAPI is design using the following:

  • a systypes package, that define all Sentry types shared with userspace

  • a uapi package, that hold the UAPI implementation in rust, depending on systypes

In the kernel counter part:

  • a sysgate package, that define all the kernel syscall implementation, and also depend on systypes

Syscall definition

sys_alarm

API definition

Rust UAPI for alarm syscall
mod uapi {
   fn alarm(timeout_ms: u32) -> Status
}
C UAPI for alarm syscall
enum Status sys_alarm(uint32_t timeout_ms);

Usage

Ask the kernel to emit a SIGNAL_ALARM signal in timeout_ms miliseconds to the current job.

This syscall is usefull when implementing userspace software-based timers. Although, there are some restrictions:

  • requested alarms can’t be removed before they expire

  • the system support a maximum number of concurrently configured alarms upto one alarm per task if all task requires an alarm in the same time

sample bare usage of sys_alarm
1if (sys_alarm(200) != STATUS_OKAY) {
2   // [...]
3}
4res = sys_wait_event([...]);
5/* alarm received 200ms later */

Note

as SIGNAL_ALARM can also be emitted by other tasks through the signal syscall, the alarm emitted by the kernel due to a alarm() request hold the current task identifier as signal source

Required capability

None.

Return values

  • STATUS_BUSY if the delay manager do not have any space for the requested alarm

  • STATUS_OK

sys_exit

API definition

Rust UAPI for exit syscall
mod uapi {
   fn exit(status: u32) -> Status
}
C UAPI for exit syscall
enum Status sys_exit(uint32_t status);

Usage

Terminate the current job with status code given in argument. Any non-zero status code is considered as an abnormal status code and is passed to job termination mechanism.

Required capability

None.

Return values

End the current job, never returns.

The exit status is used by the exitpoint symbol as lonely argument. This symbol is typically the _exit symbol of the libc that can execute post-execution triggers.

If the libc supports it, the application developer can define such a trigger so that it can be called by the _exit function in a post exit context in order to properly clean the task data. See the libShield documentation for more information.

See job termination chapter for more informations.

Note

The goal here is to support runtime-based termination call with potential application developper hooks, so that a unified central handler can react to various status code values, wherever the exit() call is made in the job implementation

sys_get_device_handle

API definition

Rust UAPI for get_task_handle syscall
mod uapi {
   fn get_device_handle(dev_label: u32) -> Status
}
C UAPI for get_task_handle syscall
enum Status sys_get_device_handle(uint32_t dev_label);

Usage

In Sentry, all devices are uniquely identified by ther handle. At bootup, the userspace task only hold the device label, which is generated using the dts file. This label is unique to the system and may vary depending on the project device tree file evolution.

In order to manipulate devices (for e.. to map or unmap it), a task must require the device handle from the kernel, using the device label as unique identifier.

As a consequence, before manipulating a device, a device driver (or the corresponding task holding it) must first get back the device handle. While got, the handle is valid for all the current OS lifecycle.

Note

the device identifier is stored in the devinfo_t const structure generated using the dts file

sample bare usage of sys_get_device_handle
1uint32_t my_dev_label = devinfo[MYDEVICE].id;
2devh_t my_dev_handle;
3if (sys_get_device_handle(my_dev_label) != STATUS_OK) {
4   // [...]
5}
6copy_to_user(&my_dev_handle, sizeof(devh_t));

Required capability

None in this syscall while the device is owned by the current task.

Warning

if the application do not hold the corresponding device capability, other device-related syscalls, such as sys_map_dev() will failed with STATUS_DENIED.

Return values

  • STATUS_INVALID if the label do not exist or is owned by another task

  • STATUS_OK

sys_get_shm_handle

API definition

Rust UAPI for get_shm_handle syscall
mod uapi {
   fn get_shm_handle(label: u32) -> Status
}
C UAPI for get_shm_handle syscall
enum Status sys_get_shm_handle(uint32_t label);

Usage

In Sentry, as explained in SHM model definition chapter, a shared memory is unikely identified by its label, in the same way as tasks. the shared memory handle is forged at boot time so that its value can’t be predefined at compile time. As a consequence, shared memory owner needs to ask for the SHM handle that correspond to a known labbel.

When the shared memory is shared with another task by the SHM owner, both tasks (owner and user) can ask for the handle that correspond to the label. This allows two sharing model:

  • the owner task voluntary share the label with the user task (using IPC for example)

  • owner and user task share the label since compile time, using config-based or hard-coded label

This syscall returns the handle corresponding to the label declared in the device-tree. this handle until the next reboot.

sample bare usage of sys_get_shm_handle
1uint32_t my_peer_label=0xbabe;
2taskh_t my_peer_handle;
3if (sys_get_shm_handle(my_shm_label) != STATUS_OK) {
4   // [...]
5}
6copy_to_user(&my_peer_handle, sizeof(shmh_t));

Required capability

None.

Return values

  • STATUS_INVALID if the SHM do not exist or is not owned or used by the calling task

  • STATUS_OK

sys_get_random

API definition

Rust UAPI for get_random syscall
mod uapi {
   fn get_random() -> Status
}
C UAPI for get_random syscall
enum Status sys_get_random(void);

Usage

Receive a 32bit length random value from the kernel RNG source in the svc_exchange area.

In devices that support hardware-based random source, the random source is using the hardware source and respects the FIPS requirements on random generators. This value can be used as a random seed for userspace-based cryptographic implemention.

sample bare usage of sys_log
1uint32_t seed = 0;
2if (sys_get_random() != STATUS_OK) {
3   // [...]
4}
5memcpy(&seed, _s_svc_exchange, sizeof(uint32_t));

Note

the libShield libc can typically delivers PRNG rand() API seeded by this syscall. other cryptographic libraries such as libecc can also use this syscall as random source

Required capability

CAP_CRY_KRNG

Return values

  • STATUS_DENIED if the task do not own the capability

  • STATUS_INVALID if the random source failed to delivers a FIPS-compliant random value

  • STATUS_OK

sys_get_task_handle

API definition

Rust UAPI for get_task_handle syscall
mod uapi {
   fn get_task_handle(label: u32) -> Status
}
C UAPI for get_task_handle syscall
enum Status sys_get_task_handle(uint32_t label);

Usage

In Sentry, as explained in Task terminology chapter, a task is unikely identified by its label, but can spawn sucessive jobs. Each of these jobs is being a dedicated instance of the same task, but at different moments of the system lifecycle.

In order to communicate with another task without any confusion and in order to be sure that the starting point of the communication, end to the finishing point of the communication stays with the very same remote job instance, communication API is using a per-job unique identifier, based on the task label, but with complementary fields.

As a consequence, before communicating with a remote task, knowing the remote task label, must ask the kernel for the currently remote job instance identifier of that task. This identifier is a task handle, and will be used for all communication.

If the remote job terminates (whatever the reason is), the task handle will automatically be invalid for next communication requests, even if a new job has been respawned for the very same task. This is an easy way to detect remote failure or termination.

This syscall returns the currently uptodate valid handle associated with the task uniquely identified by label on the system, and can be called multiple time if needed.

sample bare usage of sys_get_handle
1uint32_t my_peer_label=0xbabe;
2taskh_t my_peer_handle;
3if (sys_get_handle(my_peer_label) != STATUS_OK) {
4   // [...]
5}
6copy_to_user(&my_peer_handle, sizeof(taskh_t));
7sys_send_signal(my_peer_handle, SIGNAL_POLL);

Required capability

None.

Return values

  • STATUS_INVALID if the target task do not exist in the current task domain

  • STATUS_OK

sys_gpio_get

API definition

Rust UAPI for gpio_get syscall
mod uapi {
   fn gpio_get(device: devh_t, io_identifier: u8) -> Status
}
C UAPI for gpio_get syscall
enum Status sys_gpio_get(devh_t device, uint8_t io_identifier);

Usage

Getting a given I/O of a given device.

Any I/O (including standalone I/Os such as buttons, leds, external interrupt lines…) are always declared as a device in the device tree, which always generate a dedicated device handle to which the I/O is associated. When the device is a SoC-device that requires I/O configuration, the very same mechanisms is used, through the standard definition and usage of pinctrl attribute.

1button0: button_0 {
2         compatible = "gpio-button";
3 outpost,owner = <0xbabe>;
4 pinctrl-0 = <&button_pa4>;
5 status = "okay";
6 };

If the I/O exists in the given device and if the device is owned by the application, this function get back the current GPIO value into the svc_exchange area. The GPIO value is written into the fist byte of the svc_echange.

getting I/O 0 from button
1if (sys_gpio_get(myhandle, 0) != STATUS_OK) {
2   // [...]
3}
4copy_to_user(uint8_t *button_value, sizeof(uint8_t));

Required capability

CAPA_DEV_IO is required for autonomous GPIO-based devices. For other devices, each device hold its own capability. devices that hold pinmux are motly buses, that require the CAPA_DEV_BUSES.

Return values

  • STATUS_INVALID if the pin definition do not exist

  • STATUS_OK

sys_gpio_reset

API definition

Rust UAPI for gpio_reset syscall
mod uapi {
   fn gpio_reset(device: devh_t, io_identifier: u8) -> Status
}
C UAPI for gpio_reset syscall
enum Status sys_gpio_reset(devh_t device, uint8_t io_identifier);

Usage

Resetting the value of a given I/O of a given device.

Any I/O (including standalone I/Os such as buttons, leds, external interrupt lines…) are always declared as a device in the device tree, which always generate a dedicated device handle to which the I/O is associated. When the device is a SoC-device that requires I/O configuration, the very same mechanisms is used, through the standard definition and usage of pinctrl attribute.

1led0: led_0 {
2         compatible = "gpio-leds";
3 outpost,owner = <0xbabe>;
4 pinctrl-0 = <&led_pc7>;
5 status = "okay";
6 };

If the I/O exists in the given device and if the device is owned by the application, this function reset the current GPIO value into the svc_exchange area if the I/O is in output mode.

getting I/O 0 from button
1if (sys_gpio_reset(myhandle, 0) != STATUS_OK) {
2   // [...]
3}

Required capability

CAPA_DEV_IO is required for autonomous GPIO-based devices. For other devices, each device hold its own capability. devices that hold pinmux are motly buses, that require the CAPA_DEV_BUSES.

Return values

  • STATUS_INVALID if the pin definition do not exist

  • STATUS_OK

sys_gpio_set

API definition

Rust UAPI for gpio_set syscall
mod uapi {
   fn gpio_set(device: devh_t, io_identifier: u8, value: bool) -> Status
}
C UAPI for gpio_set syscall
enum Status sys_gpio_set(devh_t device, uint8_t io_identifier, bool value);

Usage

Setting a given I/O of a given device.

Any I/O (including standalone I/Os such as buttons, leds, external interrupt lines…) are always declared as a device in the device tree, which always generate a dedicated device handle to which the I/O is associated. When the device is a SoC-device that requires I/O configuration, the very same mechanisms is used, through the standard definition and usage of pinctrl attribute.

1led0: led_0 {
2         compatible = "gpio-leds";
3 outpost,owner = <0xbabe>;
4 pinctrl-0 = <&led_pc7>;
5 status = "okay";
6 };

If the I/O exists in the given device and if the device is owned by the application, this function set the GPIO value to the value given, while the GPIO is configured in output mode.

setting I/O 0 (fist element of the pinctrl)
1if (sys_gpio_set(myhandle, 0, 1) != STATUS_OK) {
2   // [...]
3}

Required capability

CAPA_DEV_IO is required for autonomous GPIO-based devices. For other devices, each device hold its own capability. devices that hold pinmux are motly buses, that require the CAPA_DEV_BUSES.

Return values

  • STATUS_INVALID if the pin definition do not exist or is in input mode

  • STATUS_OK

sys_gpio_toggle

API definition

Rust UAPI for gpio_toggle syscall
mod uapi {
   fn gpio_toggle(device: devh_t, io_identifier: u8) -> Status
}
C UAPI for gpio_set syscall
enum Status sys_gpio_toggle(devh_t device, uint8_t io_identifier);

Usage

Toggling a given I/O of a given device.

Any I/O (including standalone I/Os such as buttons, leds, external interrupt lines…) are always declared as a device in the device tree, which always generate a dedicated device handle to which the I/O is associated. When the device is a SoC-device that requires I/O configuration, the very same mechanisms is used, through the standard definition and usage of pinctrl attribute.

1led0: led_0 {
2         compatible = "gpio-leds";
3 outpost,owner = <0xbabe>;
4 pinctrl-0 = <&led_pc7>;
5 status = "okay";
6 };

If the I/O exists in the given device and if the device is owned by the application, this function set the GPIO value to the value given, while the GPIO is configured in output mode.

toggling I/O 0 (fist element of the pinctrl)
1if (sys_gpio_toggle(myhandle, 0) != STATUS_OK) {
2   // [...]
3}

Required capability

CAPA_DEV_IO is required for autonomous GPIO-based devices. For other devices, each device hold its own capability. devices that hold pinmux are motly buses, that require the CAPA_DEV_BUSES.

Return values

  • STATUS_INVALID if the pin definition do not exist or is in input mode

  • STATUS_OK

sys_irq_acknowledge

API definition

Rust UAPI for irq_acknowledge syscall
mod uapi {
   fn irq_acknowledge(irq: u16) -> Status
 }
C UAPI for irq_acknowledge syscall
enum Status sys_irq_acknowledge(uint16_t IRQn);

Usage

Acknowledge (clear pending) given IRQ line.

This syscall is made in order to allow userspace driver to acknowledge a given IRQ when the IRQ handler is executed.

This requires the interrupt line to be owned by a device of the given task.

acknowledge given IRQ of an owned device
1int my_handler(uint16_t IRQn) {
2   // executing the handler
3   // [...]
4   if (sys_irq_acknowledge(myIRQn) != STATUS_OK) {
5      // [...]
6   }
7   // [...]
8}

Required capability

at least one CAP_DEV_xxx capa is required, as the IRQ acknowledgement is linked to a given device.

Return values

  • STATUS_INVALID if the IRQ is not owned or do not exists

  • STATUS_DENIED if the task do not hold any DEV capability

  • STATUS_OK

sys_irq_enable

API definition

Rust UAPI for irq_enable syscall
mod uapi {
   fn irq_enable(irq: u16) -> Status
 }
C UAPI for irq_enable syscall
enum Status sys_irq_enable(uint16_t IRQn);

Usage

Enable (unmask) given IRQ line at IRQ controller level.

Note

the controller assignation is not yet supported with this API, and would require a modification to support this

This syscall is made in order to allow userspace driver to enable a given IRQ once the device is properly configured.

This requires the interrupt line to be owned by a device of the given task.

enable given IRQ of an owned device
1// configure the device (and device-relative IRQ config)
2// [...]
3if (sys_irq_enable(myIRQn) != STATUS_OK) {
4      // [...]
5}

Required capability

at least one CAP_DEV_xxx capa is required, as the IRQ enabling is linked to a given device.

Return values

  • STATUS_INVALID if the IRQ is not owned or do not exists

  • STATUS_DENIED if the task do not hold any DEV capability

  • STATUS_OK

sys_irq_disable

API definition

Rust UAPI for irq_disable syscall
mod uapi {
   fn irq_disable(irq: u16) -> Status
 }
C UAPI for irq_disable syscall
enum Status sys_irq_disable(uint16_t IRQn);

Usage

Disable (mask) given IRQ line at IRQ controller level.

Note

the controller assignation is not yet supported with this API, and would require a modification to support this

This syscall is made in order to allow userspace driver to disable a given IRQ. This is useful when the IRQ was previously enabled, and need, for the driver own specific requirements, to be disabled. This syscall do not check that the IRQ was previously enabled.

This requires the interrupt line to be owned by a device of the given task.

disable given IRQ of an owned device
1if (sys_irq_disable(myIRQn) != STATUS_OK) {
2      // [...]
3}

Required capability

at least one CAP_DEV_xxx capa is required, as the IRQ disabling is linked to a given device.

Return values

  • STATUS_INVALID if the IRQ is not owned or do not exists

  • STATUS_DENIED if the task do not hold any DEV capability

  • STATUS_OK

sys_log

API definition

Rust UAPI for log syscall
mod uapi {
   fn log(length: usize) -> Status
}
C UAPI for log syscall
enum Status sys_log(size_t length);

Usage

Emit the given logs data length from the svc_exchange area toward the log output.

In release mode, the UAPI will just do nothing, avoiding any ifdef usage at application level. The kernel binary do not host any debug functionality and will simply ignore such requests

sample bare usage of sys_log
1enum Status res = STATUS_INVALID;
2if (len <=> CONFIG_SVC_EXCHANGE_AREA_LEN) {
3   memcpy(&_s_svc_exchange, data, len);
4   res = sys_log(len);
5}

Note

the libShield libc typically delivers a printf() implementation, while the UAPI delivers the rust println! macro to simplify the usage of the sys_log() function

Warning

the log syscall do not execute any parsing of the logged data. This allows binary transmission to the debug output if needed and requires upper layers to implement the format string parser. The syscall do not need any trailing zero (c string format)

Required capability

None.

Return values

  • STATUS_INVALID if length is bigger than CONFIG_SVC_EXCHANGE_AREA_LEN

  • STATUS_OK

sys_map_dev

API definition

Rust UAPI for device mapping syscall
mod uapi {
   fn map_dev(devh: dev_handle) -> Status
   fn unmap_dev(devh: dev_handle) -> Status
}
C UAPI for device mapping syscall
enum Status sys_map_dev(devh_t dev);
enum Status sys_unmap_dev(devh_t dev);

Usage

Map a given device into the task context. If the device has never been mapped before:

  • configure the device input clock(s).

  • enable interrupts line associated to the device if set in the device node

Once returning from this syscall, the device is mapped at its corresponding address (io-mapped address) as declared in its device tree node.

The devh_t device handle value is a property of the device driver library that has been forged at build time and must be used as an opaque field.

sample bare usage of sys_map
1enum Status res = STATUS_INVALID;
2devh_t mydriver_handle = mydriver_get_handle();
3res = sys_map_dev(mydriver_handle);
4// manipulating device registers.....
5res = sys_unmap_dev(mydriver_handle);

Note

the libShield libc typically delivers a mmap() implementation with, for example, the following usage type:

addr = mmap(NULL, 0, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, devh, 0);

Required capability

The required capability depend on the concerned device, with the following capability mapping:

  • CAP_DEV_BUSES: All buses such as USART, SPI, CAN, I2C…

  • CAP_DEV_IO: bare general purpose I/O manipulation (e.g. LED or button)

  • CAP_DEV_DMA: DMA streams, and DMA masters such as GPU, DMA2D

  • CAP_DEV_ANALOG: analogic devices, such as DAC and ADC

  • CAP_DEV_TIMER: hardware timers

  • CAP_DEV_STORAGE: storage devices (MMIO, etc.)

  • CAP_DEV_CRYPTO: cryptographic accelerator (HMAC, CRYP, etc.)

  • CAP_DEV_CLOCK: real-time clock, GPS, when in-SoC

  • CAP_DEV_POWER: power-related devices

  • CAP_DEV_NEURAL: neural accelerators

Return values

  • STATUS_INVALID if length is bigger than CONFIG_SVC_EXCHANGE_AREA_LEN

  • STATUS_PERM if device or device capability capability is not owned

  • STATUS_DENIED if device or device capability capability is not owned

  • STATUS_ALREADY_MAPPED if device is already mapped

  • STATUS_OK if device was successfully mapped

sys_map_shm

API definition

Rust UAPI for SHM mapping syscall
mod uapi {
   fn map_shm(shmh: shmh_t) -> Status
   fn unmap_shm(shmh: shmh_t) -> Status
}
C UAPI for device mapping syscall
enum Status sys_map_shm(shmh_t shm);
enum Status sys_unmap_shm(shmh_t shm);

Usage

Map a given shared memory into the task context.

Once returning from this syscall, the shared-memory is mapped at its corresponding address as declared in its device tree node.

sample bare usage of sys_map_shm
1enum Status res = STATUS_INVALID;
2shmh_t shm;
3uint32_t shm_label = 0xf00UL;
4
5if (sys_get_shmhandle(shm_label) != STATUS_OK) {
6   // [...]
7}
8res = sys_map_shm(shm_label);

Note

the SHM credential must be set through sys_shm_set_credential() in order to be allowed to map the SHM.

Unmapping a shared memory is made using sys_shm_unmap(), in the very same way SHM is mapped. Unmapping the shared memory free the associated memory ressource of the task immediately.

Required capability

None.

Return values

For sys_shm_map():

  • STATUS_INVALID if the SHM handle do not exist or is not associated at all to the calling task

  • STATUS_DENIED if the SHM credential do not allow mapping

  • STATUS_ALREADY_MAPPED if SHM is already mapped

  • STATUS_OK

For sys_shm_unmap():

  • STATUS_INVALID if the SHM handle do not exist or is not mapped

  • STATUS_OK

sys_unmap_shm

API definition

Rust UAPI for SHM mapping syscall
mod uapi {
   fn map_shm(shmh: shmh_t) -> Status
   fn unmap_shm(shmh: shmh_t) -> Status
}
C UAPI for device mapping syscall
enum Status sys_map_shm(shmh_t shm);
enum Status sys_unmap_shm(shmh_t shm);

Usage

Map a given shared memory into the task context.

Once returning from this syscall, the shared-memory is mapped at its corresponding address as declared in its device tree node.

sample bare usage of sys_map_shm
1enum Status res = STATUS_INVALID;
2shmh_t shm;
3uint32_t shm_label = 0xf00UL;
4
5if (sys_get_shmhandle(shm_label) != STATUS_OK) {
6   // [...]
7}
8res = sys_map_shm(shm_label);

Note

the SHM credential must be set through sys_shm_set_credential() in order to be allowed to map the SHM.

Unmapping a shared memory is made using sys_shm_unmap(), in the very same way SHM is mapped. Unmapping the shared memory free the associated memory ressource of the task immediately.

Required capability

None.

Return values

For sys_shm_map():

  • STATUS_INVALID if the SHM handle do not exist or is not associated at all to the calling task

  • STATUS_DENIED if the SHM credential do not allow mapping

  • STATUS_ALREADY_MAPPED if SHM is already mapped

  • STATUS_OK

For sys_shm_unmap():

  • STATUS_INVALID if the SHM handle do not exist or is not mapped

  • STATUS_OK

sys_pm_clock_set

API definition

Rust UAPI for pm_clock_gate syscall
mod uapi {
   fn pm_clock_set(clk_reg_offset: u32, clk_reg_value: u32) -> Status
}
C UAPI for pm_clock_set syscall
enum Status sys_pm_clock_set(uint32_t reg_offset, uint32_t reg_value);

Usage

Set a given RCC clock register to a given value. This syscall is required only in specific devices usage such as MIPI-DSI bridges, that require successive consecutive input PLL configuration.

Required capability

CAPA_SYS_POWER is required as this impact the overall system power configuration.

Return Values

  • STATUS_DENIED if the calling task do not hold the CAPA_SYS_POWER capability

  • STATUS_INVALID if the given register offset is not supported for reconfiguration

  • STATUS_OK if the power configuration update is made properly

sys_send_ipc

API definition

Rust UAPI for send_ipc syscall
mod uapi {
    fn send_ipc(target: taskh_t, len: u32) -> Status
}
C UAPI for send_ipc syscall
enum Status sys_send_ipc(taskh_t target, uint32_t len);

Usage

Sending an Inter-Process Communication message toward the target job identified by the handle taskh_t.

IPC are peace of data that are emitted between task’s jobs. The data content type is not considered at kernel level, allowing jobs to emit any type of content, while short enough to be transmitted between svc echange zones.

Emitting an IPC is always a blocking event. The job is preempted and is awakened only when:

  • the IPC target job read the IPC content

  • the IPC target job exists without reading the IPC (whatever the cause)

IPC kernel implementation is a one-copy mechanism implementation. The effective IPC data copy is not done at send time but instead at receive time, by the target (see sys_recv_event for more information).

Sentry IPC support direct and indirect deadlock detection, and thus allows to avoid any potential cycles that may generate user-space communication automaton locks. This is done by checking IPC cycles between tasks each time an IPC send syscall is executed.

IPC do not require specific capability, but use task handles as target, requiring each task to know the target task label identifier before communicating.

Warning

IPC between tasks of different domains is forbidden

The way IPC are executed use the following pseudocode (Ada-based pseudo-code):

procedure send_ipc
    (length: in u32; target: in taskh_t)
is
begin
    -- check IPC syscall inputs
    if length not in 1 .. MAX_IPC_LEN then
        current.syscall_return_value := ERROR_INVAL;
        return;
    end if;
    if job_do_not_exist(target) = TRUE then
        current.syscall_return_value := ERROR_NOENT;
        return;
    end if;

    -- check for direct or indirect deadlocks
    if ensure_no_deadlock(target, current) = FALSE then
        current.syscall_return_value := ERROR_DEADLOCK;
        return;
    end if;

    -- flag target IPC input that current task has emit an IPC. Non-zero is a trigger
    target.ipcs(current).length := len;

    -- awake target, if possible
    if awakable_for_ipc(target) then
        awake(target);
    end if;

    -- set current task job as unschedulable
    current.state := STATE_WAITFORIPC;

    -- elect a new job
    sched_elect;
    -- preemption here, until asynchronous event rise:
    -- - target read the IPC (valid awakening, no error)
    -- - target exit before IPC is read (invalid awakening: ERROR_BROKENPIPE)
end send_ipc;

Note

If reaching the elect line, the syscall return value is asynchronously updated at handler level, before moving back to userspace, using the current.syscall_return_value

Note

IPCs are considered as a slow path. For high performance exchanges, use signals or shared memories

Return values

STATUS_OK: IPC has been emitted and received (read) by peer. STATUS_INVALID: The IPC arguments are not valid. STATUS_DEADLK: emitting this IPC would generate an inter-task deadlock. Please check your own input IPC before emitting one.

sys_send_signal

API definition

Rust UAPI for send_signal syscall
mod uapi {
   fn send_signal(target: taskh_t, signal: uapi::Signal) -> Status
}
C UAPI for send_signal syscall
enum Status sys_send_signal(taskh_t target, enum Signal signal);

Usage

Emit a signal to the target identified by the target opaque, as received by the sys_get_process_handle() syscall.

If the target exists and is running, the signal is added to its input signal queue. The syscall is a non-blocking, synchronous syscall and do not generate any scheduling impact. The signal management is an asynchronous communication mechanism, meaning that the syscall returns before that the target do actually receive the signal.

Warning

Only one signal at a time is supported by a peer for a given source. If a source send a new signal to a peer that did not already received the previous one, the send_signal syscall will return a STATUS_BUSY flag

The Sentry supported list of signals are defined in UAPI model definition.

sample bare usage of sys_send_signal
1uint32_t seed = 0;
2if (sys_send_signal(target, SIGNAL_USR1) != STATUS_OK) {
3   // [...]
4}

Note

If a previously working signal request starts to fail with an invalid return, this is typically the consequence of a target respawn or termination

Note

See get_task_handle() UAPI specification to learn about how to forge the target variable value

Required capability

None.

Return values

  • STATUS_BUSY if the target has its input signal queue full

  • STATUS_INVALID if the target do not exist in the current job domain

  • STATUS_OK

sys_shm_set_credential

API definition

Rust UAPI for shm_set_crendential syscall
mod uapi {
   fn shm_set_credential(shm: shm_t, target: taskh_t, shm_perm: u32) -> Status
}
C UAPI for shm_set_crendential syscall
enum Status sys_get_shm_handle(shmh_t shm, taskh_t target, uint32_t perms);

Usage

In Sentry, as explained in SHM model definition chapter, a shared memory hold credential for its owner and user task.

These credentials need to be set at bootup by the owner task. Only the owner task is allowed to set credentials of both itself and any other user task.

Setting credentials is made using the shm_set_credential() syscall, targetting a given task handle.

The task handle can be:

  • the owner itself, meaning that the configured credential is associated with the owner

  • another task handle, meaning that the configured credential is associated with another task

If the target task is declared for the first time, it become the user task with which the SHM is shared.

An owner can’t set credential for a target that currently maps the SHM. This means that credential must be set for a target that currently do not map it. This is required to keep coherency between the currently mapped ressources and the kernel configuration.

If not mapped and the target task hande changes, the previously set job associated with the previous task handle with which the shared memory is shared can’t map the SHM any more.

The following credential flags exist:

  • SHM_PERMISSION_MAP: the shared memory is mappable by the credential target task

  • SHM_PERMISSION_READ: the shared memory is readable when mapped. On MCU devices, it is always true

  • SHM_PERMISSION_WRITE: the shared memory is writeable when mapped

  • SHM_PERMISSION_TRANSFER: the shared memory user can be transferable to another task

These flags are ORed so that multiple flags can be set if needed.

It is to note that the SHM_PERMISSION_MAP is ignored if the outpost,no-map attribute in the device tree is set (see SHM general description of Sentry concept, for more information).

sample bare usage of sys_shm__set_credential
 1uint32_t my_shm_label=0xf00UL;
 2taskh_t myself;
 3if (sys_get_task_handle(myself_label) != STATUS_OK) {
 4   // [...]
 5}
 6copy_to_user(&myself, sizeof(taskh_t));
 7if (sys_get_shm_handle(my_shm_label) != STATUS_OK) {
 8   // [...]
 9}
10copy_to_user(&my_shm_handle, sizeof(shmh_t));
11
12sys_shm_set_credential(my_shm_handle, myself, SHM_PERMISSION_MAP | SHM_PERMISSION_WRITE);

Required capability

None.

Return values

  • STATUS_INVALID if the SHM do not exist, target do not exist or is not owned or used by the calling task

  • STATUS_DENIED if the calling task is the user, not the owner

  • STATUS_BUSY if the target associated with the credential has the SHM mapped

  • STATUS_OK

sys_shm_get_infos

API definition

Rust UAPI for shm_get_infos syscall
mod uapi {
   fn shm_get_infos(shm: shm_t) -> Status
}
C UAPI for shm_get_infos syscall
enum Status sys_shm_get_infos(shmh_t shm);

Usage

In Sentry, as explained in SHM model definition chapter, a shared memory holds credential for its owner and user task. It also hold some memory-related shm_get_infos such as base address and size.

These information set is useful to get back in user-space without requiring any DTS forge at application level.

These informations can be received through this syscall, by fulfilling the shm_infos_t structure declared by the UAPI in the SVC Exchange area. The shm_infos_t is defined in the UAPI <types.h> header in C or through the uapi.systypes Rust mod.

shm_infos_t structure definition
1/* SHM informations data structure */
2typedef struct shm_infos {
3  shmh_t   handle;  /*< SHM handle */
4  uint32_t label;   /*< SHM label */
5  size_t   base;    /*< SHM base address */
6  size_t   len;     /*< SHM length in bytes */
7  uint32_t perms;   /*< SHM permissions (mask of SHMPermission) */
8} shm_infos_t;

The only input required for this syscall is the SHM handle, as given by the sys_get_shmhandle() syscall.

When a given SHM credentials set for the current job is updated, the sys_shm_get_infos() returns an up-to-date content synchronously.

sample bare usage of sys_shm_get_infos
 1uint32_t my_shm_label=0xf00UL;
 2taskh_t myself;
 3shm_infos_t infos;
 4if (sys_get_task_handle(myself_label) != STATUS_OK) {
 5   // [...]
 6}
 7copy_to_user(&myself, sizeof(taskh_t));
 8if (sys_get_shm_handle(my_shm_label) != STATUS_OK) {
 9   // [...]
10}
11copy_to_user(&my_shm_handle, sizeof(shmh_t));
12
13if (sys_shm_get_infos(my_shm_handle)) {
14  // [...]
15}
16copy_to_user(&infos, sizeof(shm_infos_t));
17printf("SHM base address is %lx\n", infos.base);

Required capability

None.

Return values

  • STATUS_INVALID if the SHM do not exist, target do not exist or is not owned or used by the calling task

  • STATUS_OK

sys_wait_for_event

API definition

Rust UAPI for wait_for_event syscall
mod uapi {
   fn wait_for_event(mask: u8, timeout: i32) -> Status
}
C UAPI for wait_for_event syscall
typedef enum EventType {
  EVENT_TYPE_NONE = 0,
  EVENT_TYPE_IPC = 1,
  EVENT_TYPE_SIGNAL = 2,
  EVENT_TYPE_IRQ = 4,
  EVENT_TYPE_ALL = 7,
} EventType;

enum Status sys_wait_for_event(uint8_t mask, int32_t timeout);

Usage

This syscall is built in order to be a blocking point of the calling thread. This syscall handles external events blocking reception (IPCs, signals or interrupt).

In order to wait for specific event(s) only, the mask argument is used in order to filter some specific inputs events using logical OR between waited event(s).

The timeout argument is used to define the temporal behavior:

  • if timeout is -1, the syscall synchronously return to the job, with STATUS_AGAIN of no event is received

  • if timeout is 0, the job is preempted until an event is received

  • if timeout is positive, the job waits upto timeout ms. In case of timeout reached, STATUS_TIMEOUT is returned

In order to help with the timeout flag usage, C macros as been written to hide the field numeric value:

wait_for_event timeout helpers
#define WFE_WAIT_NO      (-1)
#define WFE_WAIT_FOREVER (0)
#define WFE_WAIT_UPTO(x) (x)

Any received event is delivered with a TLV basic content in the svc_exchange area:

` [T:u8][L:u8][magic:u16][source:u32][data...] `

The T field is keeping the enumerate EventMask encoding, in order to identify the type of received event.

Note

Only one type of event per call is returned, meaning that only EVENT_IPC, EVENT_SIGNAL or EVENT_IRQ is used for the T field

The L field always specify the exact data size. This means that:

  • An IPC event L field is equal to the effective received bytes

  • A signal event L field is always equal to 4 (signal value length)

  • An IRQ fevent L field hold one or more IRQ numbers. Each IRQ value being encoded on 16 bits values, meaning that the number of received IRQ is equal to L/2. In that last case, the IRQ are stored in the chronologically received order

The magic field is used in order to detect invalid exchange content easily, to prevent invalid data values access from userspace upper layers.

The exchange event is an UAPI types that is defined as the following:

wait_for_event helper struct
typedef struct exchange_event {
    uint8_t type;   /*< event type, as defined in uapi/types.h */
    uint8_t length; /*< event data length, depending on event */
    uint16_t magic; /*< event TLV magic, specific to input event reception */
    uint32_t source; /*< event source (task handle value). 0 means the kernel */
    uint8_t data[]; /*< event data, varies depending on length field */
} exchange_event_t;

This make the syscall usage easier:

Typicall wait_for_event usage
exchange_event_t * event = NULL;
status = wait_for_event(EVENT_TYPE_IPC | EVENT_TYPE_SIGNAL, WFE_WAIT_NO);
switch (status) {
   case STATUS_OKAY:
      /* an IPC or signal is received */
      event = &_s_svcexchange;
      switch (event->type) {
         case EVENT_TYPE_IPC:
            /* handle IPC */
            break;
         case EVENT_TYPE_SIGNAL:
            /* handle signal */
            break;
         default:
            break;
      }
      break;
   case STATUS_AGAIN:
      break;
   default:
      /* others are errors that should be handled */
      break;
}

Note

The wait_for_event() API is typically manipulated through the msgrcv() POSIX API implemented in libshield

Warning

Not that svc_exhchange area content is ephemeral upto the next syscall. The developper should copy its content to a safe area or manipulate it withtout any syscall in the between (including sys_log())