Skip to main content

tipc_log/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 KylinSoft Co., Ltd. <https://www.kylinos.cn/>
3// See LICENSES for license details.
4
5//! Trusty simple logger backend (replaces `trusty-log`).
6//!
7//! Logs to stderr based on a compile-time configured log level.
8//! In `no_std` (TEE) builds, writes directly via the `writev` syscall
9//! without depending on `libc-trusty` or `libstd-rust`.
10#![allow(unsafe_op_in_unsafe_fn)]
11#![no_std]
12
13use core::sync::atomic::{AtomicBool, Ordering};
14
15use log::{Level, Log, Metadata, Record};
16
17// I/O backend
18
19mod io {
20    use core::ffi::c_void;
21
22    /// `iovec` struct matching the kernel's `struct iovec`.
23    #[repr(C)]
24    struct Iovec {
25        iov_base: *const c_void,
26        iov_len: usize,
27    }
28
29    /// `writev` syscall number.
30    ///
31    /// - LK/Trusty kernel: `0x01` (architecture-independent)
32    /// - x-kernel (aarch64 Linux): `66`
33    /// - x-kernel (x86_64 Linux): `20`
34    #[cfg(feature = "lk")]
35    const SYS_WRITEV: usize = 0x01;
36    #[cfg(all(not(feature = "lk"), target_arch = "aarch64"))]
37    const SYS_WRITEV: usize = 66;
38    #[cfg(all(not(feature = "lk"), target_arch = "x86_64"))]
39    const SYS_WRITEV: usize = 20;
40
41    /// Write `data` to file descriptor `fd` via the `writev` syscall.
42    ///
43    /// # Safety
44    ///
45    /// `data` must be a valid memory region of `data.len()` bytes.
46    #[cfg(target_arch = "aarch64")]
47    pub unsafe fn write(fd: i32, data: &[u8]) {
48        let iov = Iovec { iov_base: data.as_ptr() as *const c_void, iov_len: data.len() };
49        // SAFETY: `iov` is valid and `writev` is a trusted kernel syscall.
50        //
51        // LK uses `x12` for the syscall number; x-kernel uses `x8`
52        // (standard AArch64 Linux convention).
53        #[cfg(feature = "lk")]
54        core::arch::asm!(
55            "svc #0",
56            in("x12") SYS_WRITEV,
57            inlateout("x0") fd => _,
58            in("x1") &iov,
59            in("x2") 1usize,
60            options(nostack)
61        );
62        #[cfg(not(feature = "lk"))]
63        core::arch::asm!(
64            "svc #0",
65            in("x8") SYS_WRITEV,
66            inlateout("x0") fd => _,
67            in("x1") &iov,
68            in("x2") 1usize,
69            options(nostack)
70        );
71    }
72
73    /// Write `data` to file descriptor `fd` via the `writev` syscall.
74    ///
75    /// # Safety
76    ///
77    /// `data` must be a valid memory region of `data.len()` bytes.
78    #[cfg(target_arch = "x86_64")]
79    pub unsafe fn write(fd: i32, data: &[u8]) {
80        let iov = Iovec { iov_base: data.as_ptr() as *const c_void, iov_len: data.len() };
81        // SAFETY: `iov` is valid and `writev` is a trusted kernel syscall.
82        // x86_64 uses `rax` for the syscall number in both LK and x-kernel.
83        // `rcx` and `r11` are clobbered by the `syscall` instruction.
84        core::arch::asm!(
85            "syscall",
86            inlateout("rax") SYS_WRITEV => _,
87            in("rdi") fd as usize,
88            in("rsi") &iov,
89            in("rdx") 1usize,
90            out("rcx") _,
91            out("r11") _,
92            options(nostack)
93        );
94    }
95}
96
97// Logger
98
99/// Closure type for custom log formatting.
100pub type FormatFn = alloc::boxed::Box<dyn Fn(&Record) -> alloc::string::String + Sync + Send>;
101
102extern crate alloc;
103
104/// Logger configuration.
105pub struct TrustyLoggerConfig {
106    log_level: log::Level,
107    custom_format: Option<FormatFn>,
108}
109
110impl TrustyLoggerConfig {
111    pub const fn new() -> Self {
112        TrustyLoggerConfig { log_level: Level::Info, custom_format: None }
113    }
114
115    pub fn with_min_level(mut self, level: log::Level) -> Self {
116        self.log_level = level;
117        self
118    }
119
120    pub fn format<F>(mut self, format: F) -> Self
121    where
122        F: Fn(&Record) -> alloc::string::String + Sync + Send + 'static,
123    {
124        self.custom_format = Some(alloc::boxed::Box::new(format));
125        self
126    }
127}
128
129impl Default for TrustyLoggerConfig {
130    fn default() -> Self {
131        TrustyLoggerConfig::new()
132    }
133}
134
135/// The global logger instance.
136pub struct TrustyLogger {
137    config: TrustyLoggerConfig,
138}
139
140impl TrustyLogger {
141    pub const fn new(config: TrustyLoggerConfig) -> Self {
142        TrustyLogger { config }
143    }
144}
145
146impl Log for TrustyLogger {
147    fn enabled(&self, metadata: &Metadata) -> bool {
148        metadata.level() <= self.config.log_level
149    }
150
151    fn log(&self, record: &Record) {
152        if self.enabled(record.metadata()) {
153            let message = match &self.config.custom_format {
154                Some(fmt_fn) => fmt_fn(record),
155                None => alloc::format!("{} - {}\n", record.level(), record.args()),
156            };
157            // SAFETY: `message` is a valid byte slice; `writev` writes to stderr (fd=2).
158            unsafe {
159                io::write(2, message.as_bytes());
160            }
161        }
162    }
163
164    fn flush(&self) {}
165}
166
167// Global logger initialization
168
169static LOGGER_INITIALIZED: AtomicBool = AtomicBool::new(false);
170
171/// Initialize the global logger with default configuration (`Level::Info`).
172pub fn init() {
173    init_with_config(TrustyLoggerConfig::default());
174}
175
176/// Initialize the global logger with custom configuration.
177///
178/// If the logger is already initialized, this is a no-op (matching official
179/// `trusty-log` semantics via `OnceLock::get_or_init`).
180pub fn init_with_config(config: TrustyLoggerConfig) {
181    if LOGGER_INITIALIZED.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok()
182    {
183        let log_level_filter = config.log_level.to_level_filter();
184        // SAFETY: `Box::leak` extends the logger's lifetime to `'static`.
185        // This branch only runs once due to the `AtomicBool` guard above.
186        let logger = alloc::boxed::Box::leak(alloc::boxed::Box::new(TrustyLogger::new(config)));
187        log::set_logger(logger).expect("Could not set global logger");
188        log::set_max_level(log_level_filter);
189    }
190}