blob: 6b5b1acfabd5f5c1ce8bbbe43d1c19a2ab14288d [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use core::cell::UnsafeCell;
use core::sync::atomic::Ordering;
use crate::{EventType, Record};
/// A single entry in the trace buffer.
#[allow(dead_code)]
#[repr(C)]
struct RecordEntry([u32; 4]);
impl RecordEntry {
pub const fn new() -> Self {
Self([0u32; 4])
}
}
/// A circular buffer for storing trace records.
///
/// The buffer uses atomic operations to manage a write position, allowing multiple
/// producers to write concurrently without locking.
///
/// # Type Parameters
/// * `AtomicUsize`: The atomic type used for the position counter (e.g., `pw_atomic::AtomicUsize`).
/// * `NUM_ENTRIES`: The number of entries in the buffer.
pub struct Buffer<AtomicUsize: pw_atomic::AtomicUsize, const NUM_ENTRIES: usize> {
records: UnsafeCell<[RecordEntry; NUM_ENTRIES]>,
pos: AtomicUsize,
}
/// Safety: The buffer is safe to share across threads because:
/// 1. `pos` is an `AtomicUsize` which handles concurrent updates safely.
/// 2. `records` is accessed via `UnsafeCell`, but the protocol for writing ensures
/// that each slot is effectively owned by a single writer at a time (barring wraparound collisions).
unsafe impl<AtomicUsize: pw_atomic::AtomicUsize, const NUM_ENTRIES: usize> Sync
for Buffer<AtomicUsize, NUM_ENTRIES>
{
}
impl<AtomicUsize: pw_atomic::AtomicUsize, const NUM_ENTRIES: usize>
Buffer<AtomicUsize, NUM_ENTRIES>
{
/// Creates a new, empty buffer.
#[must_use]
pub const fn new() -> Self {
Self {
records: UnsafeCell::new([const { RecordEntry::new() }; NUM_ENTRIES]),
pos: AtomicUsize::ZERO,
}
}
/// Returns the total size of the buffer in bytes.
pub const fn buffer_len(&self) -> usize {
NUM_ENTRIES * core::mem::size_of::<RecordEntry>()
}
/// Returns a raw pointer to the underlying buffer storage.
///
/// # Safety
/// This provides direct access to the raw buffer storage. The caller must ensure
/// that they do not violate any safety guarantees, especially if reading while
/// other threads are writing.
pub const unsafe fn buffer(&self) -> *const () {
self.records.get() as *const ()
}
/// Adds a record to the buffer.
///
/// This method reserves a slot in the circular buffer using atomic operations
/// and writes the record data.
///
/// # Arguments
/// * `time_stamp`: The timestamp of the event.
/// * `event_type`: The type of event.
/// * `event_data`: The raw payload of the event.
///
/// # Example
/// ```
/// use pw_kernel_tracing::{Buffer, EventType, ContextSwitchEvent};
/// use core::sync::atomic::AtomicUsize;
///
/// let buffer = Buffer::<AtomicUsize, 4>::new();
/// let event = ContextSwitchEvent {
/// old_thread_id: 1,
/// new_thread_id: 2,
/// old_thread_state: 3,
/// };
/// buffer.add_record(0x1234, EventType::ContextSwitch, event.encode());
/// ```
pub fn add_record(&self, time_stamp: u32, event_type: EventType, event_data: [u32; 3]) {
let pos = loop {
let pos = self.pos.load(Ordering::SeqCst);
let next_pos = (pos + 1) % NUM_ENTRIES;
if self
.pos
.compare_exchange(pos, next_pos, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
break pos;
}
};
let record = Record {
event_type,
time_stamp,
event_data,
};
// Safety:
// * The offset into the records array is guaranteed to be in bounds
// because it is bounds checked above.
// * "Reasonable" exclusive access can be assumed because entries written
// as soon as their storage is reserved. This makes the likelihood of
// the trace buffer wrapping before the entry is re-written extremely
// low.
// * In the unlikely event that the buffer wraps and an entry is corrupted,
// the reader of the trace buffer is expected to handle the corrupted
// entry.
unsafe {
let entry = self.records.get().cast::<RecordEntry>().add(pos);
*entry = RecordEntry(record.encode());
}
}
}
// These tests require an implementation of core::atomic.
#[cfg(all(test, feature = "builtin_atomics"))]
mod tests {
use core::sync::atomic::AtomicUsize as StdAtomicUsize;
use unittest::test;
use super::*;
// Helper to access private records for testing
impl<AtomicUsize: pw_atomic::AtomicUsize, const NUM_ENTRIES: usize>
Buffer<AtomicUsize, NUM_ENTRIES>
{
fn get_record_entry(&self, index: usize) -> &RecordEntry {
unsafe { &(*self.records.get())[index] }
}
}
#[test]
fn add_record_writes_to_buffer() -> unittest::Result<()> {
let buffer = Buffer::<StdAtomicUsize, 4>::new();
buffer.add_record(0x123456, EventType::ContextSwitch, [1, 2, 3]);
let entry = buffer.get_record_entry(0);
let header = entry.0[0];
let timestamp = header & 0x03ff_ffff;
let event_type_val = (header >> 26) & 0x3f;
unittest::assert_eq!(timestamp, 0x123456);
unittest::assert_eq!(event_type_val, EventType::ContextSwitch as u32);
unittest::assert_eq!(entry.0[1], 1);
unittest::assert_eq!(entry.0[2], 2);
unittest::assert_eq!(entry.0[3], 3);
Ok(())
}
#[test]
fn buffer_wraps_around() -> unittest::Result<()> {
let buffer = Buffer::<StdAtomicUsize, 2>::new();
buffer.add_record(0x100, EventType::ContextSwitch, [1, 1, 1]);
buffer.add_record(0x200, EventType::ContextSwitch, [2, 2, 2]);
buffer.add_record(0x300, EventType::ContextSwitch, [3, 3, 3]); // Should overwrite index 0
// Check index 0 (should be [3,3,3])
let entry0 = buffer.get_record_entry(0);
unittest::assert_eq!(entry0.0[1], 3);
// Check index 1 (should be [2,2,2])
let entry1 = buffer.get_record_entry(1);
unittest::assert_eq!(entry1.0[1], 2);
Ok(())
}
#[test]
fn buffer_allocates_slots_correctly() -> unittest::Result<()> {
let buffer = Buffer::<StdAtomicUsize, 4>::new();
// internal position should be 0
unittest::assert_eq!(buffer.pos.load(Ordering::SeqCst), 0);
buffer.add_record(0x0, EventType::ContextSwitch, [0; 3]);
// internal position should be 1
unittest::assert_eq!(buffer.pos.load(Ordering::SeqCst), 1);
buffer.add_record(0x1, EventType::ContextSwitch, [0; 3]);
buffer.add_record(0x2, EventType::ContextSwitch, [0; 3]);
buffer.add_record(0x3, EventType::ContextSwitch, [0; 3]);
// Should wrap to 0
unittest::assert_eq!(buffer.pos.load(Ordering::SeqCst), 0);
Ok(())
}
}