Skip to content

Commit b425547

Browse files
committed
[merge] Initial Rust support
Thanks to @d3zd3z: This adds the basics of Rust support for lk. Notably, it includes: - A pl011 driver written in Rust. - Interfaces to init, log, cbuf in lk. - A type 'LkOnce' that provides Once-like functionality but in a way that works with LK's init mechanism. - Rust code is built as a single build with cargo to generate a .a file linked into the build. The Rust support is not enabled by default, and can be enabled by passing USE_RUST=1 to the make invocation. Currently only qemu-virst-arm64-test is supported, and this has only been tested when using clang to compile the C code. PR #479
2 parents 9c0d9aa + e011fe6 commit b425547

File tree

40 files changed

+2478
-5
lines changed

40 files changed

+2478
-5
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: LK CI (Rust)
2+
3+
# Build targets that support `USE_RUST=1`
4+
5+
on:
6+
pull_request:
7+
push:
8+
branches-ignore:
9+
- 'wip/**'
10+
- 'docs/**' # Skip builds for documentation branches
11+
paths-ignore:
12+
- '**.md' # Skip builds when only markdown files change
13+
- 'docs/**' # Skip builds for docs directory changes
14+
15+
jobs:
16+
build:
17+
runs-on: ubuntu-24.04
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
toolchain-ver: [19]
22+
debug: [2, 0]
23+
project:
24+
# Currently, this is hard-coded here, but should probably be gathered
25+
# from the source.
26+
- qemu-virt-arm64-test
27+
env:
28+
PROJECT: ${{ matrix.project }}
29+
TOOLCHAIN_VER: ${{ matrix.toolchain-ver }}
30+
DEBUG: ${{ matrix.debug }}
31+
UBSAN: 0 # UBSan runtimes for baremetal are not part of the toolchain
32+
steps:
33+
- name: banner
34+
shell: bash
35+
run: |
36+
printf "Building with %d processors\n" "$(nproc)"
37+
grep -oP '(?<=model name\t: ).*' /proc/cpuinfo|head -n1
38+
echo PROJECT = $PROJECT
39+
echo TOOLCHAIN_VER = $TOOLCHAIN_VER
40+
echo DEBUG = $DEBUG
41+
echo UBSAN = $UBSAN
42+
43+
- name: checkout
44+
uses: actions/checkout@v4
45+
46+
# Install LLVM and set up the required environment variables
47+
- name: compute toolchain
48+
shell: bash
49+
run: |
50+
sudo apt-get install -y clang-${{ matrix.toolchain-ver }} lld-${{ matrix.toolchain-ver }}
51+
GCC_TOOLCHAIN_PREFIX=$(make list-toolchain | grep TOOLCHAIN_PREFIX | tail -1 | cut -d ' ' -f 3)
52+
# Map the GCC toolchain prefix to a clang --target argument:
53+
CLANG_TRIPLE=$(echo "${GCC_TOOLCHAIN_PREFIX}" | sed 's/-elf-/-unknown-elf/g')
54+
LLVM_BINDIR=/usr/lib/llvm-${{ matrix.toolchain-ver }}/bin
55+
echo "CC=${LLVM_BINDIR}/clang --target=${CLANG_TRIPLE}" >> $GITHUB_ENV
56+
echo "LD=${LLVM_BINDIR}/ld.lld" >> $GITHUB_ENV
57+
echo "OBJDUMP=${LLVM_BINDIR}/llvm-objdump" >> $GITHUB_ENV
58+
echo "OBJCOPY=${LLVM_BINDIR}/llvm-objcopy" >> $GITHUB_ENV
59+
echo "CPPFILT=${LLVM_BINDIR}/llvm-cxxfilt" >> $GITHUB_ENV
60+
echo "SIZE=${LLVM_BINDIR}/llvm-size" >> $GITHUB_ENV
61+
echo "NM=${LLVM_BINDIR}/llvm-nm" >> $GITHUB_ENV
62+
echo "STRIP=${LLVM_BINDIR}/llvm-strip" >> $GITHUB_ENV
63+
echo "TOOLCHAIN_PREFIX=/invalid/prefix/should/not/be/used" >> $GITHUB_ENV
64+
echo "LIBGCC=" >> $GITHUB_ENV
65+
cat "$GITHUB_ENV"
66+
67+
# Install Rust
68+
# Note that rustup is already included in the image, and we just need to add our target.
69+
- name: install rust
70+
shell: bash
71+
run: |
72+
rustup install nightly
73+
rustup component add rust-src --toolchain nightly
74+
rustup target add aarch64-unknown-linux-gnu
75+
rustup target add aarch64-unknown-none
76+
77+
# build it
78+
- name: build
79+
shell: bash
80+
run: |
81+
make -j $(nproc) USE_RUST=1
82+
# When LK is compiled with DEBUG=0, there's no console and no way for us
83+
# to read test output
84+
- name: qemu
85+
if: ${{ matrix.project == 'qemu-virt-arm64-test' }}
86+
shell: bash
87+
run: |
88+
env -i DEBIAN_FRONTEND=noninteractive sudo apt-get update
89+
env -i DEBIAN_FRONTEND=noninteractive sudo apt-get install -y qemu-system-arm
90+
- name: unittest
91+
if: ${{ matrix.project == 'qemu-virt-arm64-test' }}
92+
shell: bash
93+
run: |
94+
python3 scripts/unittest.py
95+
96+
# vim: ts=2 sw=2 expandtab

app/rust_hello/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Rust sample hello world application crate.
2+
3+
[package]
4+
name = "rust_hello"
5+
version = "0.1.0"
6+
edition = "2024"
7+
description = """
8+
Rust LK hello world application.
9+
"""
10+
license = "MIT"
11+
12+
[dependencies.lk]
13+
version = "0.1.0"
14+
path = "../../rust/lk"
15+
16+
[dependencies.log]
17+
version = "0.4.27"

app/rust_hello/rules.mk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
LOCAL_DIR := $(GET_LOCAL_DIR)
2+
3+
RUST_CRATES += $(LOCAL_DIR)
4+
$(info "Adding rust crate $(LOCAL_DIR)")
5+
6+
MODULE := $(LOCAL_DIR)
7+
8+
MODULE_DEPS := lib/rust_support
9+
10+
include make/module.mk

app/rust_hello/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Sample application.
2+
3+
#![no_std]
4+
5+
extern crate alloc;
6+
7+
use alloc::boxed::Box;
8+
9+
use lk::{app::LkApp, lkapp};
10+
11+
pub fn must_link() {}
12+
13+
struct MyApp {
14+
count: usize,
15+
}
16+
17+
impl LkApp for MyApp {
18+
fn new() -> Option<Self> {
19+
Some(MyApp { count: 0 })
20+
}
21+
22+
fn main(&mut self) {
23+
self.count += 1;
24+
log::info!("App has been invoked {} times", self.count);
25+
}
26+
}
27+
28+
lkapp!(
29+
c"rust_hello",
30+
APP_RUST_HELLO,
31+
pub static MY_APP: MyApp);

arch/arm64/arm64-llvm.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"abi": "softfloat",
3+
"arch": "aarch64",
4+
"crt-objects-fallback": "false",
5+
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
6+
"disable-redzone": true,
7+
"features": "+v8a,+strict-align,-neon,-fp-armv8,+reserve-x18",
8+
"linker": "rust-lld",
9+
"linker-flavor": "gnu-lld",
10+
"llvm-target": "aarch64-unknown-none",
11+
"max-atomic-width": 128,
12+
"metadata": {
13+
"description": "Bare ARM64, softfloat",
14+
"host_tools": false,
15+
"std": false,
16+
"tier": 2
17+
},
18+
"panic-strategy": "abort",
19+
"relocation-model": "static",
20+
"stack-probes": {
21+
"kind": "inline"
22+
},
23+
"supported-sanitizers": [
24+
"kcfi",
25+
"kernel-address"
26+
],
27+
"target-pointer-width": 64
28+
}

dev/uart/pl011/include/dev/uart/pl011.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
*/
88
#pragma once
99

10-
#include <dev/uart.h>
1110
#include <stdint.h>
1211

1312
// Set this flag if the UART is a debug UART, which routes input

engine.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ TARGET :=
141141
PLATFORM :=
142142
ARCH :=
143143
ALLMODULES :=
144+
RUST_CRATES :=
144145

145146
# add any external module dependencies
146147
MODULES := $(EXTERNAL_MODULES)

lib/rust_support/Cargo.toml.in

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Rust meta crate
2+
3+
# This crate brings in the other dependencies needed so that cargo builds all of the Rust code that
4+
# we need.
5+
6+
[package]
7+
name = "rust_support"
8+
version = "0.1.0"
9+
edition = "2024"
10+
description = "Rust support meta crate for lk"
11+
license = "MIT"
12+
13+
[lib]
14+
crate-type = ["staticlib"]
15+
16+
[dependencies.lk]
17+
version = "0.1.0"
18+
path = "@BUILDROOT@/rust/lk" # Relative to build directory.
19+
20+
[profile.release]
21+
debug-assertions = true
22+
overflow-checks = true
23+
debug = true
24+
codegen-units = 1
25+
26+
[profile.dev]
27+
opt-level = 2
28+
codegen-units = 1
29+
30+
@DEPCRATES@

lib/rust_support/expand.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#! /usr/bin/env python3
2+
3+
import fileinput
4+
import os
5+
6+
# This script reads a template from standard input or the named file, and
7+
# outputs this with some template insertions, based on some values coming from
8+
# the make invocation.
9+
10+
class Substr():
11+
def __init__(self):
12+
self.subs = []
13+
14+
def add(self, name):
15+
value = os.environ.get(name)
16+
if value is None:
17+
raise Exception(f"Environment variable {name} not set")
18+
self.subs.append( (f"@{name}@", value) )
19+
20+
if name == "BUILDROOT":
21+
self.buildroot = value
22+
23+
# Generate a substitution rules for a list of crates.
24+
def add_depcrates(self, name):
25+
crates = os.environ.get(name)
26+
if crates is None:
27+
raise Exception(f"Environment variable {name} not set")
28+
value = ""
29+
for crate in crates.split():
30+
base = os.path.basename(crate)
31+
value += f"[dependencies.{base}]\npath = \"{self.buildroot}/{crate}\"\n"
32+
# TODO: Get the crate version from the crate's Cargo.toml file.
33+
value += f"version = \"0.1.0\"\n\n"
34+
self.subs.append( ("@DEPCRATES@", value) )
35+
36+
def add_deplinks(self, name):
37+
crates = os.environ.get(name)
38+
if crates is None:
39+
raise Exception(f"Environment variable {name} not set")
40+
value = ""
41+
for crate in crates.split():
42+
base = os.path.basename(crate)
43+
base = base.replace("-", "_")
44+
if value != "":
45+
value += " "
46+
value += f"{base}::must_link();"
47+
self.subs.append( (f"@DEPLINKS@", value) )
48+
49+
def sub(self, line):
50+
for (k,v) in self.subs:
51+
line = line.replace(k, v)
52+
return line
53+
54+
def subst(name):
55+
value = os.environ.get(name)
56+
57+
subber = Substr();
58+
59+
subber.add("BUILDROOT")
60+
subber.add_depcrates("RUST_CRATES")
61+
subber.add_deplinks("RUST_CRATES")
62+
63+
for line in fileinput.input():
64+
line = subber.sub(line.rstrip())
65+
print(line)

0 commit comments

Comments
 (0)