Zephyr RTOS
Overview
Section titled “Overview”What is an RTOS?
Section titled “What is an RTOS?”A Real-Time Operating System (RTOS) is a lightweight OS designed to provide deterministic scheduling, minimal latency, and efficient resource management. Unlike general-purpose operating systems like Linux, an RTOS prioritizes real-time constraints, ensuring critical tasks meet strict timing deadlines. RTOSs are commonly used in embedded systems, microcontrollers, and simulation environments where fast boot times and efficient execution are essential.
Zephyr Use Cases
Section titled “Zephyr Use Cases”Zephyr provides a lightweight build flow and execution environment that supports running complex end-to-end workloads without the overhead of Linux.
Key benefits:
Multicore & Threading Support: Zephyr provides a task scheduler with multithreading and multicore support. Heterogeneous SoCs can be used via task pinning.
POSIX-like API: Zephyr provides POSIX APIs for threading and synchronization, useful for migrating Linux-based workloads.
Fast Boot Times: Zephyr has a significantly faster startup time in RTL simulations (~20K cycles for RocketConfig).
Small Footprint: Zephyr ELF files are 20-100KB (excluding application code), suitable for bringup and prototyping targets.
Simplified Virtual Memory: While Zephyr does not support full virtual memory like Linux, it does provide basic memory mapping and demand paging.
Installation and Basic Usage
Section titled “Installation and Basic Usage”Before beginning, ensure that your Chipyard RISCV toolchain is installed, and env.sh is sourced. Next, initialize the Zephyr submodule.
git submodule update --init software/zephyrproject/zephyr/west is Zephyr’s CMake-based build tool and dependency manager, designed to streamline project setup, compilation, and firmware deployment. It automates building Zephyr applications into ELF binaries and manages multiple repositories and submodules. In Chipyard, west is used to build ELF files for simulation on Spike, software RTL simulation, or FireSim.
Run the following commands to initialize the Zephyr workspace:
cd software/zephyrproject/zephyrwest init -l .west config manifest.file west-riscv.ymlwest updateNext, set the following environment variables:
export ZEPHYR_BASE=$(pwd)export ZEPHYR_TOOLCHAIN_VARIANT=cross-compileexport CROSS_COMPILE=$RISCV/bin/riscv64-unknown-elf-After initializing the workspace, you can build the Zephyr kernel and sample applications. For example, to build the hello_world sample application, run the following commands within the Zephyr directory:
west build -p -b spike_riscv64 ./samples/chipyard/hello_worldThis will generate a build directory with the compiled ELF file, with example output below:
[0/128] Preparing syscall dependency handling[128/128] Linking C executable zephyr/zephyr.elf
Memory region Used Size Region Size %age Used RAM: 36868 B 256 MB 0.01% IDT_LIST: 0 B 2 KB 0.00%You can run the ELF file on Spike using the following command:
spike ./build/zephyr/zephyr.elfThis should print the following output:
*** Booting Zephyr OS build 6c1e6f64895b ***Hello World! spike_riscv64/spike_virt_riscv64To simulate the Zephyr application in RTL simulation, follow the instrutions in the Simulation Guide. Use the path to the Zephyr ELF file as the BINARY argument to the RTL simulator.
Zephyr Core Concepts
Section titled “Zephyr Core Concepts”Below are useful concepts and terms to understand when working with Zephyr.
KConfig: Configuring Zephyr
Section titled “KConfig: Configuring Zephyr”Zephyr uses KConfig, a configuration system that allows developers to enable or disable features, select drivers, and tune system parameters. KConfig files are used to specify options that influence the build process.
Located in Kconfig files within the Zephyr source tree.
Used to enable hardware drivers (e.g., CONFIG_UART_HTIF=y for HTIF UART support).
Managed using the menuconfig or guiconfig tools.
Example:
config UART_HTIF bool "Enable HTIF UART driver" select SERIAL_HAS_DRIVER depends on RISCV
help Enable the HTIF (Host-Target Interface) UART driver for RISC-V Spike simulation.To modify configuration:
west build -t menuconfigThis launches an interactive menu to configure Zephyr features.
Device Trees: Hardware Description
Section titled “Device Trees: Hardware Description”Zephyr uses Device Tree Source (DTS) files to describe hardware components, memory layouts, and peripherals in a structured manner.
Key components:
Board-level DTS files (e.g., spike_riscv64.dts) define enabled devices.
SoC-level DTS files (e.g., virt-riscv.dtsi) provide shared hardware descriptions.
Bindings map devices to their respective drivers.
Device Drivers: Enabling Hardware Support
Section titled “Device Drivers: Enabling Hardware Support”Device drivers in Zephyr provide abstraction layers that interface with hardware components. Each driver is responsible for initialization, communication, and handling interrupts if applicable.
Drivers are located in:
zephyr/drivers/<subsystem>/ # e.g., serial/To register a driver: 1. Implement driver functions (e.g., poll_in, poll_out). 2. Define the DEVICE_DT_DEFINE() macro to initialize the driver. 3. Add the driver to CMakeLists.txt to be compiled when enabled in KConfig.
Driver Bindings: Connecting DTS to Drivers
Section titled “Driver Bindings: Connecting DTS to Drivers”Zephyr uses YAML bindings to map Device Tree nodes to their respective drivers. These bindings define required properties such as memory addresses, compatible strings, and configurations.
Adding a binding ensures that Zephyr correctly associates hardware definitions with driver implementations.
Zephyr Subsystems
Section titled “Zephyr Subsystems”Zephyr includes several subsystems for handling standard OS functionality, such as logging, input/output, and multi-threading.
Console: Standard Output Interface
Section titled “Console: Standard Output Interface”The console subsystem provides a standard output interface for logging and debugging.
To enable a UART device as the console:
chosen { zephyr,console = &htif;};Zephyr will automatically redirect printf-like output to the chosen console device.
Adding a New Zephyr Driver: HTIF UART
Section titled “Adding a New Zephyr Driver: HTIF UART”This tutorial guides you through the process of adding a Host-Target Interface (HTIF) UART driver to Zephyr. This driver enables serial output in Spike/FESVR simulations and can be used for debugging or system interaction. This driver has already been integrated; this guide provides an example of how to add a new driver to Zephyr.
Prerequisites
Section titled “Prerequisites”Before proceeding, ensure you have:
A working Zephyr workspace set up in Chipyard.
west installed and initialized.
Familiarity with Device Tree (DTS), CMake, and Zephyr driver configuration.
Define the HTIF UART in the Device Tree
Section titled “Define the HTIF UART in the Device Tree”To integrate HTIF as a serial device, update the Spike board’s Device Tree Source (DTS).
Edit boards/spike/riscv64/spike_riscv64.dts to enable HTIF:
/ { chosen { zephyr,console = &htif; zephyr,shell-uart = &htif; zephyr,sram = &ram0; };};
// Disable the default ns16550 UART&uart0 { status = "disabled";};
&htif { status = "okay";};In addition to enabling the HTIF device, this snippet sets the HTIF UART as the console and shell UART. The zephyr,console and zephyr,shell-uart properties specify the device node for the console and shell UART, respectively.
For the full file, refer to spike_riscv64.dts.
The HTIF device itself is fully defined in dts/riscv/spike/virt-riscv.dtsi, which provides a generic definition for the RISC-V “virt” machine used in Spike. This file includes:
htif: uart { compatible = "ucb,htif"; label = "HTIF_UART";};This defines the HTIF device as a UART-compatible peripheral, setting its compatible property to “ucb,htif”, which corresponds to the driver binding we will add later. The label property provides a human-readable name that can be referenced elsewhere in Zephyr’s configuration.
For the full file, see virt-riscv.dtsi.
Define Device Tree Binding
Section titled “Define Device Tree Binding”Add a binding file to dts/bindings/serial/ucb,htif-uart.yaml:
# SPDX-License-Identifier: Apache-2.0description: HTIF UART for Spike/FESVRcompatible: "ucb,htif"include: base.yamlproperties: label: type: string required: true description: Human-readable string describing the deviceThis file defines the HTIF UART device as a serial device with a label property. The compatible property matches the device tree entry in virt-riscv.dtsi.
For the complete file, see ucb,htif-uart.yaml.
Define HTIF Registers and Mutex in a Header
Section titled “Define HTIF Registers and Mutex in a Header”Create include/zephyr/drivers/htif.h to define HTIF constants and expose global variables:
#ifndef ZEPHYR_DRIVERS_HTIF_H#define ZEPHYR_DRIVERS_HTIF_H
#include <stdint.h>#include <zephyr/sys/mutex.h>
extern volatile uint64_t tohost;extern volatile uint64_t fromhost;extern struct k_mutex htif_lock;
#endif // ZEPHYR_DRIVERS_HTIF_HFor the complete header, see htif.h.
Implement the HTIF UART Driver
Section titled “Implement the HTIF UART Driver”Create drivers/serial/uart_htif.c, implementing poll_in and poll_out based on OpenSBI logic.
Key functions:
-
uart_htif_poll_out(): Transmits a character via HTIF.
-
uart_htif_poll_in(): Reads a character via HTIF.
static void uart_htif_poll_out(const struct device *dev, unsigned char out_char) { k_mutex_lock(&htif_lock, K_FOREVER); htif_wait_for_ready(); tohost = TOHOST_CMD(HTIF_DEV_CONSOLE, HTIF_CONSOLE_CMD_PUTC, out_char); k_mutex_unlock(&htif_lock);}
static int uart_htif_poll_in(const struct device *dev, unsigned char *p_char) { k_mutex_lock(&htif_lock, K_FOREVER); htif_wait_for_ready(); tohost = TOHOST_CMD(HTIF_DEV_CONSOLE, HTIF_CONSOLE_CMD_GETC, 0); while (fromhost == 0); *p_char = (char)(FROMHOST_DATA(fromhost) & 0xFF); fromhost = 0; k_mutex_unlock(&htif_lock); return 0;}Additionally, define the UART driver API and bind it to the HTIF device:
static const struct uart_driver_api uart_htif_driver_api = { .poll_in = uart_htif_poll_in, .poll_out = uart_htif_poll_out,};
DEVICE_DT_DEFINE(DT_NODELABEL(htif), uart_htif_init, NULL, NULL, NULL, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &uart_htif_driver_api);For the full implementation, see uart_htif.c.
Update the Linker Script
Section titled “Update the Linker Script”Ensure that tohost and fromhost are placed in a dedicated .htif section by modifying include/zephyr/arch/riscv/common/linker.ld:
.htif ALIGN(0x100) : { KEEP(*(.htif))}For the full linker script, see linker.ld.
Modify the CMake Build System
Section titled “Modify the CMake Build System”Zephyr’s build system needs to recognize the new driver. Update drivers/serial/CMakeLists.txt to include uart_htif.c when the CONFIG_UART_HTIF option is enabled:
zephyr_library_sources_ifdef(CONFIG_UART_HTIF uart_htif.c)For the full file, see CMakeLists.txt.
Add Kconfig Configuration for HTIF
Section titled “Add Kconfig Configuration for HTIF”Define a new Kconfig entry for enabling HTIF. Modify drivers/serial/Kconfig:
rsource "Kconfig.htif"Then, create a new Kconfig.htif file to define HTIF-specific options:
menuconfig UART_HTIF bool "Enable HTIF UART driver" select SERIAL_HAS_DRIVER depends on RISCV help Enable the HTIF (Host-Target Interface) UART driver for RISC-V Spike simulation.For the complete configuration, see Kconfig.htif.
You will now be able to enable the HTIF UART driver when building Zephyr applications.