diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..23fd35f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0e2e5e6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "zlog-rs" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["staticlib"] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +incremental = false +lto = "fat" +codegen-units = 1 +opt-level = 3 + +[dependencies] +cty = "0.2.2" +heapless = "0.7.13" +log = "0.4.17" + + +[build-dependencies] +bindgen = "0.59.2" diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..709186f --- /dev/null +++ b/Kconfig @@ -0,0 +1,3 @@ + +config ZLOG_RS + bool "Enable logging through zlog-rs" diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..2599ed9 --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,35 @@ +# Test on local, build for embedded on NRF52 + +[tasks.cbindgen] +command = "cbindgen" +args = ["--config", "cbindgen.toml", "--crate", "zlog-rs", "--output", "include/generated/libzlog_rs.h"] + +[tasks.build-nrf52] +command = "cargo" +toolchain = "stable" +args = ["build", "--lib", "--target", "thumbv7em-none-eabihf", "--release"] +dependencies = ["cbindgen"] + +[tasks.build-nrf53] +command = "cargo" +toolchain = "stable" +args = ["build", "--lib", "--target", "thumbv8m.main-none-eabihf", "--release"] +dependencies = ["cbindgen"] + +[tasks.build] +command = "cargo" +toolchain = "stable" +args = ["build"] +dependencies = ["build-nrf52", "build-nrf53"] + +[tasks.test-release] +command = "cargo" +toolchain = "stable" +args = ["test", "--lib", "--release"] +dependencies = ["build"] + +[tasks.test] +command = "cargo" +toolchain = "stable" +args = ["test", "--lib"] +dependencies = ["test-release"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..781b078 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# ZLOG + +This repo provides a crate with a Rust Logger implementation for use on an embedded device with Zephyr RTOS. `bindgen` and `cbindgen` are used to bind Rust calls to Zephyr RTOS's log2 implementation + +## Build + +Include this crate in another crate that needs a logger implementation and make the usual log macro calls like `log::trace!("2.0 * 2.0 = {}", 2. * 2.);`. With a high initialization priority, the global logger is initialized to call the matching LOG_DBG, LOG_INF, LOG_WRN, or LOG_ERR macro in the bridging C. + +### Build Tools + +In order to build there are several tools to install + +* NRF Connect SDK + * v1.9.1 +* Rustup and Cargo + * stable + * v1.24.3 and v1.61.0 +* Cross compiling targets + * M4(F): `thumbv7em-none-eabihf` + * M33(F): `thumbv8m.main-none-eabihf` +* Tools and Subcommands + * `cargo install cargo-make,cbindgen` + * `brew install llvm` + * `llvm-config` needs to be in your `PATH` + * `echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> ~/.zshrc` + * `sudo xcode-select --install` + +If you have all of that, then `cargo make test` should work. + +## Example + +In the example, we have a `blinky` application compiled for NRF52 or NRF53. The CMakeLists.txt of the sample project adds zlog-rs as a static library, exposes the headers to sample app, and runs the build with cargo whenever you build. \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..a919421 --- /dev/null +++ b/build.rs @@ -0,0 +1,52 @@ +extern crate bindgen; + +use std::env; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + // Tell cargo to invalidate the built crate whenever the wrapper changes + println!("cargo:rerun-if-changed=include/bridge.h"); + + let llvm_config_path = Command::new("llvm-config") + .args(["--prefix"]) + .output() + .expect("`llvm-config` must be in PATH") + .stdout; + let llvm_config_path = + String::from_utf8(llvm_config_path).expect("`llvm-config --prefix` output must be UTF-8"); + + std::env::set_var( + "LLVM_CONFIG_PATH", + format!("{}/bin/llvm-config", llvm_config_path.trim()), + ); + + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("include/bridge.h") + .allowlist_function("zlog_rs_error_handler") + .allowlist_function("log_.*") + .allowlist_var("CONFIG_.*") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Tell cargo that bindings will be no_std + .use_core() + .ctypes_prefix("cty") + // Don't create layout tests that would only run on desktop + .layout_tests(false) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + // Write the bindings to the $OUT_DIR/bindings.rs file. + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..6737e9c --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,4 @@ +language = "C" +include_guard = "LIBZLOG_RS_INCLUDE_GENERATED_LIB_ZLOG_H" +include_version = true +autogen_warning = "/* DON'T TOUCH. THIS FILE IS AUTOGENERATED */" \ No newline at end of file diff --git a/include/bridge.h b/include/bridge.h new file mode 100644 index 0000000..36309fd --- /dev/null +++ b/include/bridge.h @@ -0,0 +1,23 @@ +#ifndef ZLOG_RS_INCLUDE_BRIDGE_H +#define ZLOG_RS_INCLUDE_BRIDGE_H + +#include +#include + +/// +/// ZEPHYR RTOS +/// + +// FATAL ERROR HANDLER + +__attribute__((__noreturn__)) void zlog_rs_error_handler(); + +// LOG + +void log_inf(const char *restrict msg); + +void log_wrn(const char *restrict msg); + +void log_err(const char *restrict msg); + +#endif \ No newline at end of file diff --git a/include/generated/libzlog_rs.h b/include/generated/libzlog_rs.h new file mode 100644 index 0000000..26eb159 --- /dev/null +++ b/include/generated/libzlog_rs.h @@ -0,0 +1,21 @@ +#ifndef LIBZLOG_RS_INCLUDE_GENERATED_LIB_ZLOG_H +#define LIBZLOG_RS_INCLUDE_GENERATED_LIB_ZLOG_H + +/* Generated with cbindgen:0.23.0 */ + +/* DON'T TOUCH. THIS FILE IS AUTOGENERATED */ + +#include +#include +#include +#include + +void zlog_init_error(void); + +void zlog_init_warn(void); + +void zlog_init_info(void); + +void zlog_init_trace(void); + +#endif /* LIBZLOG_RS_INCLUDE_GENERATED_LIB_ZLOG_H */ diff --git a/include/zlog.h b/include/zlog.h new file mode 100644 index 0000000..ba4cf72 --- /dev/null +++ b/include/zlog.h @@ -0,0 +1,8 @@ +#ifndef ZLOG_RS_INCLUDE_ZLOG_H +#define ZLOG_RS_INCLUDE_ZLOG_H + +#include "zephyr.h" + +#include "generated/libzlog_rs.h" + +#endif \ No newline at end of file diff --git a/module.yml b/module.yml new file mode 100644 index 0000000..326b264 --- /dev/null +++ b/module.yml @@ -0,0 +1,3 @@ +build: + cmake: zephyr + kconfig: zephyr/Kconfig \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..31578d3 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable" \ No newline at end of file diff --git a/src/bindings.rs b/src/bindings.rs new file mode 100644 index 0000000..428a22c --- /dev/null +++ b/src/bindings.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); \ No newline at end of file diff --git a/src/bridge.c b/src/bridge.c new file mode 100644 index 0000000..3b24939 --- /dev/null +++ b/src/bridge.c @@ -0,0 +1,73 @@ +#include "bridge.h" +#include "zlog.h" + +#include "autoconf.h" +#include "zephyr.h" +#include "kernel.h" +#include "fatal.h" +#include "init.h" +#include "logging/log.h" + +/// +/// ZEPHYR RTOS +/// + +void zlog_init(const struct device* _) +{ + switch (CONFIG_LOG_DEFAULT_LEVEL) + { + case LOG_LEVEL_ERR: + { + zlog_init_error(); + break; + } + case LOG_LEVEL_WRN: + { + zlog_init_warn(); + break; + } + case LOG_LEVEL_INF: + { + zlog_init_info(); + break; + } + case LOG_LEVEL_DBG: + { + zlog_init_trace(); + break; + } + default: + { + zlog_init_trace(); + break; + } + } +} + +SYS_INIT(zlog_init, POST_KERNEL, 10); + +// FATAL ERROR HANDLER +__attribute__((__noreturn__)) void zlog_rs_error_handler() +{ + k_sys_fatal_error_handler(0, NULL); + NVIC_SystemReset(); +} + +// LOG + +LOG_MODULE_REGISTER(rs, LOG_LEVEL_DBG); + +void log_inf(const char *restrict msg) +{ + LOG_INF("%s", msg); +} + +void log_wrn(const char *restrict msg) +{ + LOG_WRN("%s", msg); +} + +void log_err(const char *restrict msg) +{ + LOG_ERR("%s", msg); +} diff --git a/src/fatal.rs b/src/fatal.rs new file mode 100644 index 0000000..b6036eb --- /dev/null +++ b/src/fatal.rs @@ -0,0 +1,13 @@ +#![cfg(not(test))] + +use super::bindings::zlog_rs_error_handler; +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_panic: &PanicInfo<'_>) -> ! { + // Log is probably too long to print + unsafe { + zlog_rs_error_handler(); + } + unreachable!(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cae3e2a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,76 @@ +#![cfg_attr(not(test), no_std, no_main)] +#![cfg_attr(test, allow(unused))] + +#[cfg(test)] +#[macro_use] +extern crate std; +#[cfg(test)] +use std::prelude::*; + +use core::fmt::Write; +use heapless::String; +use log::*; + +mod bindings; +use bindings::*; +mod fatal; + +pub static LOGGER: ZLog = ZLog; + +pub struct ZLog; + +fn zlog_init(lvl: LevelFilter) { + log::set_logger(&LOGGER).unwrap(); + log::set_max_level(lvl); + log::info!("Initialized zlog") +} + +#[no_mangle] +pub extern "C" fn zlog_init_error() { + zlog_init(LevelFilter::Error); +} +#[no_mangle] +pub extern "C" fn zlog_init_warn() { + zlog_init(LevelFilter::Warn); +} +#[no_mangle] +pub extern "C" fn zlog_init_info() { + zlog_init(LevelFilter::Info); +} +#[no_mangle] +pub extern "C" fn zlog_init_trace() { + zlog_init(LevelFilter::Trace); +} + +impl Log for ZLog { + #[inline(always)] + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + #[inline(always)] + fn log(&self, record: &Record) { + let log_impl = match record.level() { + Level::Error => log_err, + Level::Warn => log_wrn, + _ => log_inf, + }; + let mut c_str = String::<256>::new(); + write!(c_str, "{}: {}\0", record.target(), record.args()).unwrap(); + unsafe { + log_impl(c_str.as_bytes().as_ptr() as *const u8 as *const cty::c_char); + } + } + + #[inline(always)] + fn flush(&self) {} +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +}