Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device Tree Support #15

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

set(RUST_MODULE_DIR "${CMAKE_CURRENT_LIST_DIR}" CACHE INTERNAL "")

# Initially, we just have a single DT augment file.
set(DT_AUGMENTS "${CMAKE_CURRENT_LIST_DIR}/dt-rust.yaml" CACHE INTERNAL "")

# Zephyr targets are defined through Kconfig. We need to map these to
# an appropriate llvm target triple. This sets `RUST_TARGET` in the
# parent scope, or an error if the target is not yet supported by
Expand Down Expand Up @@ -133,6 +136,8 @@ ZEPHYR_DTS = \"${ZEPHYR_DTS}\"
INCLUDE_DIRS = \"${include_dirs}\"
INCLUDE_DEFINES = \"${include_defines}\"
WRAPPER_FILE = \"${WRAPPER_FILE}\"
BINARY_DIR_INCLUDE_GENERATED = \"${BINARY_DIR_INCLUDE_GENERATED}\"
DT_AUGMENTS = \"${DT_AUGMENTS}\"

[patch.crates-io]
${config_paths}
Expand All @@ -151,6 +156,8 @@ ${config_paths}
INCLUDE_DIRS="${include_dirs}"
INCLUDE_DEFINES="${include_defines}"
WRAPPER_FILE="${WRAPPER_FILE}"
DT_AUGMENTS="${DT_AUGMENTS}"
BINARY_DIR_INCLUDE_GENERATED="${BINARY_DIR_INCLUDE_GENERATED}"
cargo build
# TODO: release flag if release build
# --release
Expand Down
87 changes: 87 additions & 0 deletions dt-rust.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Description of how to augment the devicetree for Rust.
#
# Each entry describes an augmentation that will be added to matching nodes in the device tree.
# The full syntax is described (indirectly) in `zephyr-build/src/devicetree/config.rs`.

# Gpio controllers match for every node that has a `gpio-controller` property. This is one of the
# few instances were we can actually just match on a property.
- name: gpio-controller
rules:
- type: has_prop
value: gpio-controller
actions:
- type: instance
value:
raw:
type: myself
device: crate::device::gpio::Gpio

# The gpio-leds node will have #children nodes describing each led. We'll match on the parent
# having this compatible property. The nodes themselves are built out of the properties associated
# with each gpio.
- name: gpio-leds
rules:
- type: compatible
value:
names:
- gpio-leds
level: 1
actions:
- type: instance
value:
raw:
type: phandle
value: gpios
device: crate::device::gpio::GpioPin

# Flash controllers don't have any particular property to identify them, so we need a list of
# compatible values that should match.
- name: flash-controller
rules:
- type: compatible
value:
names:
- "nordic,nrf52-flash-controller"
- "nordic,nrf51-flash-controller"
- "raspberrypi,pico-flash-controller"
level: 0
actions:
- type: instance
value:
raw:
type: myself
device: crate::device::flash::FlashController

# Flash partitions exist as children of a node compatible with "soc-nv-flash" that itself is a child
# of the controller itself.
# TODO: Get the write and erase property from the DT if present.
- name: flash-partition
rules:
- type: compatible
value:
names:
- "fixed-partitions"
level: 1
- type: compatible
value:
names:
- "soc-nv-flash"
level: 2
actions:
- type: instance
value:
raw:
type: parent
value:
level: 3
args:
- type: reg
device: "crate::device::flash::FlashPartition"

# Generate a pseudo node that matches all of the labels across the tree with their nodes.
- name: labels
rules:
- type: root
actions:
- type: labels

2 changes: 0 additions & 2 deletions etc/platforms.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
-p mps2/an385
-p mps2/an521/cpu0
-p qemu_cortex_m0
-p qemu_cortex_m3
-p qemu_riscv32
Expand Down
7 changes: 7 additions & 0 deletions samples/blinky/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky)

rust_cargo_application()
20 changes: 20 additions & 0 deletions samples/blinky/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2024 Linaro LTD
# SPDX-License-Identifier: Apache-2.0

[package]
# This must be rustapp for now.
name = "rustapp"
version = "0.1.0"
edition = "2021"
description = "A sample hello world application in Rust"
license = "Apache-2.0 or MIT"

[lib]
crate-type = ["staticlib"]

[dependencies]
zephyr = "3.7.0"
log = "0.4.22"

[build-dependencies]
zephyr-build = "3.7.0"
97 changes: 97 additions & 0 deletions samples/blinky/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
.. zephyr:code-sample:: blinky
:name: Blinky
:relevant-api: gpio_interface

Blink an LED forever using the GPIO API.

Overview
********

The Blinky sample blinks an LED forever using the :ref:`GPIO API <gpio_api>`.

The source code shows how to:

#. Get a pin specification from the :ref:`devicetree <dt-guide>` as a
:c:struct:`gpio_dt_spec`
#. Configure the GPIO pin as an output
#. Toggle the pin forever

See :zephyr:code-sample:`pwm-blinky` for a similar sample that uses the PWM API instead.

.. _blinky-sample-requirements:

Requirements
************

Your board must:

#. Have an LED connected via a GPIO pin (these are called "User LEDs" on many of
Zephyr's :ref:`boards`).
#. Have the LED configured using the ``led0`` devicetree alias.

Building and Running
********************

Build and flash Blinky as follows, changing ``reel_board`` for your board:

.. zephyr-app-commands::
:zephyr-app: samples/basic/blinky
:board: reel_board
:goals: build flash
:compact:

After flashing, the LED starts to blink and messages with the current LED state
are printed on the console. If a runtime error occurs, the sample exits without
printing to the console.

Build errors
************

You will see a build error at the source code line defining the ``struct
gpio_dt_spec led`` variable if you try to build Blinky for an unsupported
board.

On GCC-based toolchains, the error looks like this:

.. code-block:: none

error: '__device_dts_ord_DT_N_ALIAS_led_P_gpios_IDX_0_PH_ORD' undeclared here (not in a function)

Adding board support
********************

To add support for your board, add something like this to your devicetree:

.. code-block:: DTS

/ {
aliases {
led0 = &myled0;
};

leds {
compatible = "gpio-leds";
myled0: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
};
};
};

The above sets your board's ``led0`` alias to use pin 13 on GPIO controller
``gpio0``. The pin flags :c:macro:`GPIO_ACTIVE_HIGH` mean the LED is on when
the pin is set to its high state, and off when the pin is in its low state.

Tips:

- See :dtcompatible:`gpio-leds` for more information on defining GPIO-based LEDs
in devicetree.

- If you're not sure what to do, check the devicetrees for supported boards which
use the same SoC as your target. See :ref:`get-devicetree-outputs` for details.

- See :zephyr_file:`include/zephyr/dt-bindings/gpio/gpio.h` for the flags you can use
in devicetree.

- If the LED is built in to your board hardware, the alias should be defined in
your :ref:`BOARD.dts file <devicetree-in-out-files>`. Otherwise, you can
define one in a :ref:`devicetree overlay <set-devicetree-overlays>`.
3 changes: 3 additions & 0 deletions samples/blinky/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
zephyr_build::dt_cfgs();
}
10 changes: 10 additions & 0 deletions samples/blinky/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONFIG_GPIO=y

CONFIG_RUST=y
CONFIG_RUST_ALLOC=y

CONFIG_DEBUG=y
CONFIG_MAIN_STACK_SIZE=8192

# Verify that userspace builds work.
# CONFIG_USERSPACE=y
12 changes: 12 additions & 0 deletions samples/blinky/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
sample:
name: Blinky Sample
tests:
sample.basic.blinky:
tags:
- LED
- gpio
filter: dt_enabled_alias_with_parent_compat("led0", "gpio-leds")
depends_on: gpio
harness: led
integration_platforms:
- frdm_k64f
77 changes: 77 additions & 0 deletions samples/blinky/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2024 Linaro LTD
// SPDX-License-Identifier: Apache-2.0

#![no_std]

// Sigh. The check config system requires that the compiler be told what possible config values
// there might be. This is completely impossible with both Kconfig and the DT configs, since the
// whole point is that we likely need to check for configs that aren't otherwise present in the
// build. So, this is just always necessary.
#![allow(unexpected_cfgs)]

use log::warn;

use core::ffi::c_void;

use zephyr::raw::GPIO_OUTPUT_ACTIVE;
use zephyr::time::{ Duration, sleep };

#[no_mangle]
extern "C" fn rust_main() {
unsafe { zephyr::set_logger().unwrap(); }

warn!("Starting blinky");
// println!("Blinky!");
// Invoke "blink" as a user thread.
// blink();
if false {
unsafe {
zephyr::raw::k_thread_user_mode_enter
(Some(blink),
core::ptr::null_mut(),
core::ptr::null_mut(),
core::ptr::null_mut());
}
} else {
unsafe {
blink(core::ptr::null_mut(),
core::ptr::null_mut(),
core::ptr::null_mut());
}
}
}

// fn blink() {
unsafe extern "C" fn blink(_p1: *mut c_void, _p2: *mut c_void, _p3: *mut c_void) {
// Just call a "safe" rust function.
do_blink();
}

#[cfg(dt = "aliases::led0")]
fn do_blink() {
warn!("Inside of blinky");

let mut led0 = zephyr::devicetree::aliases::led0::get_instance().unwrap();
let mut gpio_token = unsafe { zephyr::device::gpio::GpioToken::get_instance().unwrap() };

if !led0.is_ready() {
warn!("LED is not ready");
loop {
}
// return;
}

unsafe { led0.configure(&mut gpio_token, GPIO_OUTPUT_ACTIVE); }
let duration = Duration::millis_at_least(500);
loop {
unsafe { led0.toggle_pin(&mut gpio_token); }
sleep(duration);
}
}

#[cfg(not(dt = "aliases::led0"))]
fn do_blink() {
warn!("No leds configured");
loop {
}
}
Loading