Skip to content

Commit 68e2023

Browse files
committed
lib: rust: Add simple printk support to Rust
When `CONFIG_PRINTK` is enabled, provide macros `printk` and `printkln` within the `zephyr` crate that applications can use to print. This currently sends messages, with a small amount of buffering, using `k_str_out`. Until C functions are made generally available from the Rust side, we use a small wrapper function to call `k_str_out`, as it is implemented as an inline syscall. Signed-off-by: David Brown <[email protected]>
1 parent 1ed0aea commit 68e2023

File tree

3 files changed

+138
-0
lines changed

3 files changed

+138
-0
lines changed

lib/rust/main.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
/* This main is brought into the Rust application. */
7+
#include <zephyr/kernel.h>
78

89
#ifdef CONFIG_RUST
910

@@ -15,4 +16,14 @@ int main(void)
1516
return 0;
1617
}
1718

19+
#ifdef CONFIG_PRINTK
20+
/*
21+
* Until we have syscall support in Rust, wrap this syscall.
22+
*/
23+
void wrapped_str_out(char *c, size_t n)
24+
{
25+
k_str_out(c, n);
26+
}
27+
#endif
28+
1829
#endif

lib/rust/zephyr/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ include!(concat!(env!("OUT_DIR"), "/kconfig.rs"));
1515
#[cfg(not(CONFIG_RUST))]
1616
compile_error!("CONFIG_RUST must be set to build Rust in Zephyr");
1717

18+
// Printk is provided if it is configured into the build.
19+
#[cfg(CONFIG_PRINTK)]
20+
pub mod printk;
21+
1822
use core::panic::PanicInfo;
1923

2024
/// Override rust's panic. This simplistic initial version just hangs in a loop.
@@ -23,3 +27,9 @@ fn panic(_ :&PanicInfo) -> ! {
2327
loop {
2428
}
2529
}
30+
31+
/// Provide symbols used by macros in a crate-local namespace.
32+
#[doc(hidden)]
33+
pub mod _export {
34+
pub use core::format_args;
35+
}

lib/rust/zephyr/src/printk.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) 2024 Linaro LTD
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Printk implementation for Rust.
5+
//!
6+
//! This uses the `k_str_out` syscall, which is part of printk to output to the console.
7+
8+
use core::fmt::{
9+
Arguments,
10+
Result,
11+
Write,
12+
write,
13+
};
14+
15+
#[macro_export]
16+
macro_rules! printk {
17+
($($arg:tt)*) => {{
18+
$crate::printk::printk(format_args!($($arg)*));
19+
}};
20+
}
21+
22+
#[macro_export]
23+
macro_rules! printkln {
24+
($($arg:tt)*) => {{
25+
$crate::printk::printkln(format_args!($($arg)*));
26+
}};
27+
}
28+
29+
// This could readily be optimized for the configuration where we don't have userspace, as well as
30+
// when we do, and are not running in userspace. This initial implementation will always use a
31+
// string buffer, as it doesn't depend on static symbols in print.c.
32+
//
33+
// A consequence is that the CONFIG_PRINTK_SYNC is enabled, the synchonization will only occur at
34+
// the granularity of these buffers rather than at the print message level.
35+
36+
/// The buffer size for syscall. This is a tradeoff between efficiency (large buffers need fewer
37+
/// syscalls) and needing more stack space.
38+
const BUF_SIZE: usize = 32;
39+
40+
struct Context {
41+
// How many characters are used in the buffer.
42+
count: usize,
43+
// Bytes written.
44+
buf: [u8; BUF_SIZE],
45+
}
46+
47+
fn utf8_byte_length(byte: u8) -> usize {
48+
if byte & 0b1000_0000 == 0 {
49+
// Single byte (0xxxxxxx)
50+
1
51+
} else if byte & 0b1110_0000 == 0b1100_0000 {
52+
// Two-byte sequence (110xxxxx)
53+
2
54+
} else if byte & 0b1111_0000 == 0b1110_0000 {
55+
// Three-byte sequence (1110xxxx)
56+
3
57+
} else if byte & 0b1111_1000 == 0b1111_0000 {
58+
// Four-byte sequence (11110xxx)
59+
4
60+
} else {
61+
// Continuation byte or invalid (10xxxxxx)
62+
1
63+
}
64+
}
65+
66+
impl Context {
67+
fn add_byte(&mut self, b: u8) {
68+
// Ensure we have room for an entire UTF-8 sequence.
69+
if self.count + utf8_byte_length(b) > self.buf.len() {
70+
self.flush();
71+
}
72+
73+
self.buf[self.count] = b;
74+
self.count += 1;
75+
}
76+
77+
fn flush(&mut self) {
78+
if self.count > 0 {
79+
unsafe {
80+
wrapped_str_out(self.buf.as_ptr(), self.count);
81+
}
82+
self.count = 0;
83+
}
84+
}
85+
}
86+
87+
impl Write for Context {
88+
fn write_str(&mut self, s: &str) -> Result {
89+
for b in s.bytes() {
90+
self.add_byte(b);
91+
}
92+
Ok(())
93+
}
94+
}
95+
96+
pub fn printk(args: Arguments<'_>) {
97+
let mut context = Context {
98+
count: 0,
99+
buf: [0; BUF_SIZE],
100+
};
101+
write(&mut context, args).unwrap();
102+
context.flush();
103+
}
104+
105+
pub fn printkln(args: Arguments<'_>) {
106+
let mut context = Context {
107+
count: 0,
108+
buf: [0; BUF_SIZE],
109+
};
110+
write(&mut context, args).unwrap();
111+
context.add_byte(b'\n');
112+
context.flush();
113+
}
114+
115+
extern "C" {
116+
fn wrapped_str_out(buf: *const u8, len: usize);
117+
}

0 commit comments

Comments
 (0)