Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

System call

In Zephyr, like any other operating system, a system call is a way for programs to interact with the operating system. It allows them to request services such as file operations, process management, and network communication that are not directly available within their own code.

System calls provide a bridge between user-level applications and the kernel of the operating system. They allow programs to access privileged instructions and resources that would otherwise be inaccessible from user space.

In most programming languages, system calls are exposed through an application programming interface (API). This API provides a set of functions or methods that developers can call to make use of the underlying system capabilities.

Some common examples of system calls include:

  • Opening and closing peripherals/files.
  • Reading from and writing to peripherals/files
  • Creating and managing processes
  • Allocating memory dynamically
  • Sending data over any transport (wireless/socket/I2C/SPI etc)

System calls often involve passing parameters or arguments to specify the desired behavior. These parameters can include file names, buffer sizes, process IDs, or other relevant information.

User mode

In Zephyr’s user mode, applications run with restricted privileges to ensure system stability and security. In this mode, applications have limited access to hardware resources and are isolated from other processes running in the system. This helps prevent malicious or buggy code from affecting critical system operations.

Supervisor mode

Supervisor mode, also known as Kernel mode, is a privileged execution environment in which the operating system kernel resides. In this mode, the kernel has unrestricted access to all hardware resources and can perform privileged operations such as managing memory, scheduling tasks, handling interrupts, and controlling device drivers. Only trusted code should execute in supervisor mode to maintain the integrity of the system.

Both user-mode applications and supervisor-mode kernel code coexist within the Zephyr operating system to provide a robust platform for developing embedded systems with stringent requirements on performance, reliability, and security.

Normally, when a program makes a system call, it transitions from user mode into supervisor mode where the requested operation is executed on behalf of the calling program. Once completed, control is returned back to the caller along with any results or error codes. The distinction from user mode and supervisor mode is generally made with some security design goals in mind.

Device driver invocation context

Understanding the invocation context of system calls is essential for developers who want to build low-level software components or interact directly with hardware devices. In the default builds provided by the nRF Connect SDK, most of the samples are built in the application working with CONFIG_USERSPACE not enabled. In this mode, all APIs just directly call the implementation function.

In a nutshell, the application accesses the driver API through the device struct pointer, which is obtained by using DEVICE_DT_GET(node_id).

Note that many of the generic APIs also have API-specific device driver structures and initializers, that encompass the device structure. For example, in the image below, the application uses the API-specific macro SPI_DT_SPEC_GET() to get the API-specific device structure struct spi_dt_spec. SPI_DT_SPEC_GET() uses DEVICE_DT_GET() to get the device structure, struct device bus, and SPI_CONFIG_DT() to get the device configuration, struct spi_config config.

With this information, we can start looking at the basic knowledge needed to create an API for the application to interact with the newly implemented device driver functionality.

Let us assume that the device driver offers a feature that takes two arguments and adds and prints the result. So the declaration of this functionality inside the device driver looks like below, and this has been plugged into the driver dev->api properly.

To be able to access this from the application, you need to create a translator API in a header file that looks like below.

There are two parts of logic in the above code snippet.

  • __syscall declaration of the function.
  • z_impl_xxx implementation needed by the generated code. With CONFIG_APPLICATION_DEFINED_SYSCALL set in your prj.conf, the script scripts/build/gen_syscalls.py will search for any declaration of system calls in your application folder. When it finds a declaration like the above, it parses it and converts it like the below in a generated header file with filename same as the file in which __syscall was declared.

As you can see, the generated code will only use z_impl_myapp_simple_addition when CONFIG_USERSPACE is not enabled. The implementation of z_impl_myapp_simple_addition is already done (which gets the device API pointer and dereferences it to the correct function pointer in the driver that implements this feature) at the same place we declared the __syscall.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.