blob: 4d258cf70de9f46b9b618e38c87d7b93f7efb254 [file] [edit]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! zfmt-based logging client and server utilities.
//!
//! This crate provides `IpcLogger` (the client) and `LogServer`/`LogBuffer` (the server)
//! to enable logging over Pigweed Maize kernel IPC channels.
#![cfg_attr(not(test), no_std)]
use util_error::ErrorCode;
#[cfg(not(test))]
use util_ipc::IpcHandle;
use util_ipc::{Instant, IpcChannel};
use util_types::Opcode;
use zerocopy::IntoBytes;
use zfmt::Logger;
/// IPC opcode to write a log event.
pub const OPCODE_LOG_WRITE: Opcode = Opcode::new(*b"WLOG");
/// IPC opcode to read a log event.
pub const OPCODE_LOG_READ: Opcode = Opcode::new(*b"RLOG");
/// IPC opcode to clear the user notification signal.
pub const OPCODE_CLEAR_NOTIFIER: Opcode = Opcode::new(*b"CLRN");
pub mod buffer;
mod clock;
pub mod messages;
pub mod render;
pub mod server;
pub use buffer::LogBuffer;
pub use server::LogServer;
pub use zfmt::events::StreamStart;
pub use zfmt::FixedBuf;
pub use zfmt::Write;
pub use zfmt::ZfmtU64;
/// Since we're using pigweed's `multi_process_app`s, we can't have any
/// per-process `.data` or `.bss` sections. It is required that all processes
/// use handle zero as their IPC channel to the logging server so we can
/// have a single const IpcLogger referring to channel zero.
///
/// TODO: investiage other options.
#[cfg(not(test))]
const UTIL_ZFMT_LOGGER: IpcLogger<IpcHandle> = IpcLogger::new(IpcHandle::new(0));
/// A client-side logger that sends events over an `IpcChannel`.
///
/// It implements the `zfmt::Logger` trait, allowing it to be used with
/// `zfmt` logging macros.
pub struct IpcLogger<IPC: IpcChannel> {
ipc: IPC,
}
impl<IPC: IpcChannel> IpcLogger<IPC> {
/// Creates a new `IpcLogger` wrapping the given IPC channel.
pub const fn new(ipc: IPC) -> Self {
Self { ipc }
}
/// Get the event at `cursor` from the logger. Returns the cursor and event.
/// Note: the returned cursor is the location from where the event was read. The client must
/// adjust their own cursor appropriately after processing the event payload.
pub fn get_event<'a>(
&self,
cursor: u64,
eventbuf: &'a mut [u8],
) -> Result<(u64, &'a [u8]), ErrorCode> {
let mut status = 0u32;
let mut log_cursor = 0u64;
let mut n = self
.ipc
.transact(
&[OPCODE_LOG_READ.as_bytes(), cursor.as_bytes()],
&mut [status.as_mut_bytes(), log_cursor.as_mut_bytes(), eventbuf],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
ErrorCode::check_status(status)?;
n = n.saturating_sub(core::mem::size_of_val(&status) + core::mem::size_of_val(&log_cursor));
let n = n.min(eventbuf.len());
Ok((log_cursor, &eventbuf[..n]))
}
/// Clear the user notification from the logger.
pub fn clear_notifier(&self) -> Result<(), ErrorCode> {
self.ipc
.transact(
OPCODE_CLEAR_NOTIFIER.as_bytes(),
&mut [0u8; 0],
Instant::MAX,
)
.map_err(ErrorCode::kernel_error)?;
Ok(())
}
}
impl<IPC: IpcChannel> Logger for IpcLogger<IPC> {
fn timestamp(&self) -> ZfmtU64 {
ZfmtU64::from_u64(0)
}
fn send_vectored(&self, bufs: &[&[u8]]) {
// Note: zfmt will invoke send_vectored with 2 slices.
// TODO: improve send_vectored to simply have the header and payload so we don't have to
// reconstruct the iovec slice.
const EMPTY: [u8; 0] = [];
let cmd = [
OPCODE_LOG_WRITE.as_bytes(),
*bufs.first().unwrap_or(&EMPTY.as_slice()),
*bufs.get(1).unwrap_or(&EMPTY.as_slice()),
*bufs.get(2).unwrap_or(&EMPTY.as_slice()),
];
let _ = self.ipc.transact(&cmd, &mut [0u8; 0], Instant::MAX);
}
}
/// Returns a reference to the global `IpcLogger` instance.
///
/// This logger is configured to use channel handle 0, which is the convention
/// for processes in the multi-process application to communicate with the log server.
#[cfg(not(test))]
#[inline(always)]
pub fn logger() -> &'static IpcLogger<IpcHandle> {
&UTIL_ZFMT_LOGGER
}
// Global logging macros (firmware only; use zfmt::log_info! directly in tests).
#[cfg(not(test))]
#[macro_export]
macro_rules! trace {
($event:expr) => {
zfmt::log_trace!(*$crate::logger(), $event);
};
}
#[cfg(not(test))]
#[macro_export]
macro_rules! debug {
($event:expr) => {
zfmt::log_debug!(*$crate::logger(), $event);
};
}
#[cfg(not(test))]
#[macro_export]
macro_rules! info {
($event:expr) => {
zfmt::log_info!(*$crate::logger(), $event);
};
}
#[cfg(not(test))]
#[macro_export]
macro_rules! warn {
($event:expr) => {
zfmt::log_warn!(*$crate::logger(), $event);
};
}
#[cfg(not(test))]
#[macro_export]
macro_rules! error {
($event:expr) => {
zfmt::log_error!(*$crate::logger(), $event);
};
}