Skip to content

Commit 0242ede

Browse files
committed
zephyr: Logging through Zephyr log
Implement a simplistic mechanism that is able to log through Zephyr's logging mechanism. This change does not attempt to demangle the #define nightmare that is Zephyr's logging, but just formats to a string, and sends that directly through a C helper function. As such, this performs at least one allocation/free, possibly with some reallocs, depending on how accurate the allocation estimate is, and whether adding a null terminator requires an additional allocation. Logs are also all recorded under a single registered log, in the rust main file. Signed-off-by: David Brown <[email protected]>
1 parent fc3bf01 commit 0242ede

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

main.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,38 @@
1010

1111
extern void rust_main(void);
1212

13+
// Logging, to see how things things are expanded.
14+
#include <zephyr/logging/log.h>
15+
LOG_MODULE_REGISTER(rust, 3);
16+
1317
int main(void)
1418
{
1519
rust_main();
1620
return 0;
1721
}
1822

23+
void rust_log_message(uint32_t level, char *msg) {
24+
// Ok. The log macros in Zephyr perform all kinds of macro stitching, etc, on the
25+
// arguments. As such, we can't just pass the level to something, but actually need to
26+
// expand things here. This puts the file and line information of the log in this file,
27+
// rather than where we came from.
28+
switch (level) {
29+
case LOG_LEVEL_ERR:
30+
LOG_ERR("%s", msg);
31+
break;
32+
case LOG_LEVEL_WRN:
33+
LOG_WRN("%s", msg);
34+
break;
35+
case LOG_LEVEL_INF:
36+
LOG_INF("%s", msg);
37+
break;
38+
case LOG_LEVEL_DBG:
39+
default:
40+
LOG_DBG("%s", msg);
41+
break;
42+
}
43+
}
44+
1945
/* On most arches, panic is entirely macros resulting in some kind of inline assembly. Create this
2046
* wrapper so the Rust panic handler can call the same kind of panic.
2147
*/

zephyr-sys/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ fn main() -> Result<()> {
7171
.allowlist_function("k_.*")
7272
.allowlist_function("gpio_.*")
7373
.allowlist_function("sys_.*")
74+
.allowlist_function("z_log.*")
7475
.allowlist_item("E.*")
7576
.allowlist_item("K_.*")
7677
.allowlist_item("ZR_.*")
78+
.allowlist_item("LOG_LEVEL_.*")
7779
// Deprecated
7880
.blocklist_function("sys_clock_timeout_end_calc")
7981
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))

zephyr-sys/wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ extern int errno;
3333
#include <zephyr/kernel.h>
3434
#include <zephyr/kernel/thread_stack.h>
3535
#include <zephyr/drivers/gpio.h>
36+
#include <zephyr/logging/log.h>
3637

3738
/*
3839
* bindgen will output #defined constant that resolve to simple numbers. There are some symbols

zephyr/src/logging.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ cfg_if::cfg_if! {
4141
{
4242
// Otherwise, if we have logging and allocation, and not minimal, we can use the Zephyr
4343
// logging backend. Doing this without allocation is currently a TODO:
44-
todo!("Zephyr logging not yet supported");
44+
mod impl_zlog;
45+
pub use impl_zlog::set_logger;
4546
} else {
4647
/// No lagging is possible, provide an empty handler that does nothing.
4748
///

zephyr/src/logging/impl_zlog.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//! Logging through Zephyr's log mechanism.
2+
//!
3+
//! This module implements a log handler for the [`log`] crate that logs messages through Zephyr's
4+
//! logging infrastructure.
5+
//!
6+
//! Zephyr's logging is heavily built around a lot of C assumptions, and involves a lot of macros.
7+
//! As such, this initial implemention will try to keep things simple, and format the message to an
8+
//! allocated string, and send that off to the logging infrastructure, formatted as a "%s" message.
9+
//! There are a lot of opportunities to improve this.
10+
11+
extern crate alloc;
12+
13+
use alloc::format;
14+
use core::ffi::c_char;
15+
16+
use log::{Level, Log, Metadata, Record, SetLoggerError};
17+
18+
use crate::raw;
19+
20+
struct ZlogLogger;
21+
22+
impl Log for ZlogLogger {
23+
// For now, just always print messages.
24+
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
25+
true
26+
}
27+
28+
/// Print out the log message.
29+
fn log(&self, record: &Record<'_>) {
30+
let level = match record.level() {
31+
Level::Error => raw::LOG_LEVEL_ERR,
32+
Level::Warn => raw::LOG_LEVEL_WRN,
33+
Level::Info => raw::LOG_LEVEL_INF,
34+
Level::Debug => raw::LOG_LEVEL_DBG,
35+
// Zephyr doesn't have a separate trace, so fold that into debug.
36+
Level::Trace => raw::LOG_LEVEL_DBG,
37+
};
38+
let mut msg = format!("{}: {}",
39+
record.target(),
40+
record.args());
41+
// Append a null so this is a valid C string. This lets us avoid an additional allocation
42+
// and copying.
43+
msg.push('\x00');
44+
unsafe {
45+
rust_log_message(level, msg.as_ptr() as *const c_char);
46+
}
47+
}
48+
49+
// Flush not needed.
50+
fn flush(&self) {
51+
}
52+
}
53+
54+
extern "C" {
55+
fn rust_log_message(level: u32, msg: *const c_char);
56+
}
57+
58+
static ZLOG_LOGGER: ZlogLogger = ZlogLogger;
59+
60+
/// Set the log handler to log messages through Zephyr's logging framework.
61+
///
62+
/// This is unsafe due to racy issues in the log framework on targets that do not support atomic
63+
/// pointers.
64+
pub unsafe fn set_logger() -> Result<(), SetLoggerError> {
65+
super::set_logger_internal(&ZLOG_LOGGER)
66+
}

0 commit comments

Comments
 (0)