mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
296 lines
12 KiB
Rust
296 lines
12 KiB
Rust
use core::num::{NonZeroU8, NonZeroU16};
|
|
use std::cell::RefCell;
|
|
|
|
use utralib::generated::*;
|
|
|
|
// To read out performance monitoring data, use the `usb_update.py` script as follows:
|
|
// ` python3 .\..\usb_update.py --dump v2p.txt --dump-file .\ring_aes_8.bin`
|
|
// where the `v2p.txt` file contains a virtual to physical mapping that is generated by the `perflib`
|
|
// framework and
|
|
|
|
pub const BUFLEN: usize = 1024 * 512;
|
|
|
|
/// ($pid:expr, $fid:expr, $meta:expr, $index:expr, $line:expr)
|
|
/// general format of a performance counter entry:
|
|
/// file ID | process ID | meta codes | index | line number
|
|
/// 31 29 28 24 23 21 20 13 12 0
|
|
/// This gives tracking across:
|
|
/// - 8 files
|
|
/// - 32 processes
|
|
/// - 8 "meta" codes
|
|
/// - a loop index of up to 255 times
|
|
/// - a file line number of up to 8192
|
|
#[macro_export]
|
|
macro_rules! perf_entry {
|
|
($pid:expr, $fid:expr, $meta:expr, $index:expr, $line:expr) => {
|
|
(($fid as u32) & 0x7) << 29
|
|
| (($pid as u32) & 0x1F) << 24
|
|
| (($meta as u32) & 0x7) << 21
|
|
| (($index as u32) & 0xFF) << 13
|
|
| (($line as u32) & 0x1FFF)
|
|
};
|
|
}
|
|
pub const PERFMETA_NONE: u32 = 0;
|
|
pub const PERFMETA_STARTBLOCK: u32 = 1;
|
|
pub const PERFMETA_ENDBLOCK: u32 = 2;
|
|
|
|
#[repr(C)]
|
|
pub struct PerfLogEntry {
|
|
code: u32,
|
|
ts: u32,
|
|
}
|
|
|
|
/// Pure client for performance monitoring. It is not capable of emptying the FIFO or
|
|
/// detecting an overflow, but it can register an event through a somewhat-abstracted interface.
|
|
pub struct PerfClient {
|
|
event_csr: AtomicCsr<u32>,
|
|
}
|
|
impl PerfClient {
|
|
pub fn new(event_csr: AtomicCsr<u32>) -> Self { Self { event_csr } }
|
|
|
|
#[inline(always)]
|
|
pub fn log_event_unchecked(&self, code: u32) {
|
|
// event_sourceN is identical across each block, so we can take advantage of the flexibility of
|
|
// UTRA to do a non-specific cross-crate register access pattern.
|
|
self.event_csr.wfo(utra::event_source1::PERFEVENT_CODE, code);
|
|
}
|
|
}
|
|
|
|
/// Manager interface for performance monitoring. This also includes a performance event port.
|
|
pub struct PerfMgr<'a> {
|
|
data: RefCell<&'a mut [PerfLogEntry]>,
|
|
perf_csr: AtomicCsr<u32>,
|
|
event_csr: AtomicCsr<u32>,
|
|
log_index: RefCell<usize>,
|
|
ts_cont: RefCell<u64>,
|
|
// these are expressed as raw values to be written to hardware
|
|
saturation_limit: u64,
|
|
prescaler: u16,
|
|
saturate: bool,
|
|
event_bit_width: u8,
|
|
// debug fields
|
|
dbg_buf_count: RefCell<u32>,
|
|
}
|
|
|
|
impl<'a> PerfMgr<'a> {
|
|
pub fn new(log_ptr: *mut u8, perf_csr: AtomicCsr<u32>, event_csr: AtomicCsr<u32>) -> Self {
|
|
let data = unsafe {
|
|
core::slice::from_raw_parts_mut(
|
|
log_ptr as *mut PerfLogEntry,
|
|
BUFLEN / core::mem::size_of::<PerfLogEntry>(),
|
|
)
|
|
};
|
|
// initialize the buffer region when the manager is created
|
|
for d in data.iter_mut() {
|
|
d.code = 0;
|
|
d.ts = 0;
|
|
}
|
|
Self {
|
|
data: RefCell::new(data),
|
|
perf_csr,
|
|
event_csr,
|
|
log_index: RefCell::new(0),
|
|
ts_cont: RefCell::new(0),
|
|
saturation_limit: 0xffff_ffff,
|
|
prescaler: 0, // 1 clock per sample
|
|
event_bit_width: 31, // 32 bits width
|
|
saturate: true,
|
|
dbg_buf_count: RefCell::new(0),
|
|
}
|
|
}
|
|
|
|
/// Sets the saturation limit for the performance counter. Units in clock cycles.
|
|
/// If `None`, the counter will freely rollover without stopping the performance counting process.
|
|
#[allow(dead_code)]
|
|
pub fn sat_limit(&mut self, limit: Option<u64>) {
|
|
if let Some(l) = limit {
|
|
self.saturation_limit = l;
|
|
self.saturate = true;
|
|
} else {
|
|
self.saturate = false;
|
|
self.saturation_limit = u64::MAX;
|
|
}
|
|
}
|
|
|
|
/// Zero is an invalid value for clocks per sample. Hence NonZeroU16 type.
|
|
#[allow(dead_code)]
|
|
pub fn clocks_per_sample(&mut self, cps: NonZeroU16) { self.prescaler = cps.get() - 1; }
|
|
|
|
/// Sets the width of the event code. This is enforced by hardware, software is free to pass in a code
|
|
/// that is too large. Excess bits will be ignored, starting from the MSB side. A bitwdith of 0 is
|
|
/// illegal. A bitwidth larger than 32 is set to 32.
|
|
#[allow(dead_code)]
|
|
pub fn code_bitwidth(&mut self, bitwidth: NonZeroU8) {
|
|
let bw = bitwidth.get();
|
|
if bw > 32 {
|
|
self.event_bit_width = 31;
|
|
} else {
|
|
self.event_bit_width = bw - 1;
|
|
}
|
|
}
|
|
|
|
pub fn stop_and_reset(&self) {
|
|
for d in self.data.borrow_mut().iter_mut() {
|
|
d.code = 0;
|
|
d.ts = 0;
|
|
}
|
|
self.perf_csr.wfo(utra::perfcounter::RUN_STOP, 1);
|
|
let mut first_i: Option<u32> = None;
|
|
let mut last_i = 0;
|
|
while self.perf_csr.rf(utra::perfcounter::STATUS_READABLE) == 1 {
|
|
let i = self.perf_csr.r(utra::perfcounter::EVENT_INDEX); // this advances the FIFO until it is empty
|
|
if first_i.is_none() {
|
|
first_i = Some(i);
|
|
} else {
|
|
last_i = i;
|
|
}
|
|
}
|
|
if let Some(i) = first_i {
|
|
log::warn!("FIFO was not drained before reset: indices [{}..{}]", i, last_i);
|
|
}
|
|
// stop the counter if it would rollover
|
|
self.perf_csr.wo(utra::perfcounter::SATURATE_LIMIT0, self.saturation_limit as u32);
|
|
self.perf_csr.wo(utra::perfcounter::SATURATE_LIMIT1, (self.saturation_limit >> 32) as u32);
|
|
|
|
// configure the system
|
|
self.perf_csr.wo(
|
|
utra::perfcounter::CONFIG,
|
|
self.perf_csr.ms(utra::perfcounter::CONFIG_PRESCALER, self.prescaler as u32)
|
|
| self.perf_csr.ms(utra::perfcounter::CONFIG_SATURATE, if self.saturate { 1 } else { 0 })
|
|
| self
|
|
.perf_csr
|
|
.ms(utra::perfcounter::CONFIG_EVENT_WIDTH_MINUS_ONE, self.event_bit_width as u32),
|
|
);
|
|
self.log_index.replace(0);
|
|
self.ts_cont.replace(0);
|
|
}
|
|
|
|
pub fn start(&self) { self.perf_csr.wfo(utra::perfcounter::RUN_RESET_RUN, 1); }
|
|
|
|
/// This function is convenient, but the overhead of the checking adds a lot of cache line noise to the
|
|
/// data If you are looking to get very cycle-accurate counts, use `log_event_unchecked` with manual
|
|
/// flush calls
|
|
///
|
|
/// returns Ok(()) if the event could be logged
|
|
/// returns an Err if the performance buffer would overflow
|
|
#[inline(always)]
|
|
pub fn log_event(&self, code: u32) -> Result<(), xous::Error> {
|
|
self.flush_if_full()?;
|
|
self.event_csr.wfo(utra::event_source1::PERFEVENT_CODE, code);
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[inline(always)]
|
|
pub fn log_event_unchecked(&self, code: u32) {
|
|
self.event_csr.wfo(utra::event_source1::PERFEVENT_CODE, code);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn flush(&self) -> Result<(), xous::Error> {
|
|
let mut oom = false;
|
|
// stop the counter
|
|
self.perf_csr.wfo(utra::perfcounter::RUN_STOP, 1);
|
|
// copy over the events to the long term buffer
|
|
let mut ts_offset = 0;
|
|
let mut expected_i = 0;
|
|
while self.perf_csr.rf(utra::perfcounter::STATUS_READABLE) == 1 {
|
|
if *self.log_index.borrow() < self.data.borrow().len() {
|
|
self.dbg_buf_count.replace(self.dbg_buf_count.take() + 1); // this tracks total entries copied into the perfbuf
|
|
(*self.data.borrow_mut())[*self.log_index.borrow()].code =
|
|
self.perf_csr.r(utra::perfcounter::EVENT_RAW0);
|
|
ts_offset = self.perf_csr.r(utra::perfcounter::EVENT_RAW1) as u32;
|
|
(*self.data.borrow_mut())[*self.log_index.borrow()].ts =
|
|
*self.ts_cont.borrow() as u32 + ts_offset;
|
|
|
|
let i = self.perf_csr.r(utra::perfcounter::EVENT_INDEX); // this advances the FIFO
|
|
if i != expected_i & 0xfff {
|
|
log::info!("i {} != expected_i {}", i, expected_i);
|
|
}
|
|
expected_i += 1;
|
|
self.log_index.replace(self.log_index.take() + 1);
|
|
} else {
|
|
oom = true;
|
|
break;
|
|
}
|
|
}
|
|
// update the timestamp continuation field with the last timestamp seen; the next line
|
|
// resets the timestamp counter to 0 again.
|
|
self.ts_cont.replace(self.ts_cont.take() + ts_offset as u64);
|
|
// restart the counter
|
|
if !oom {
|
|
// duplicate this code down here because we want the reset and log to be as close as possible to
|
|
// the return statement
|
|
self.perf_csr.wfo(utra::perfcounter::RUN_RESET_RUN, 1);
|
|
Ok(())
|
|
} else {
|
|
self.perf_csr.wfo(utra::perfcounter::RUN_RESET_RUN, 1);
|
|
Err(xous::Error::OutOfMemory)
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn flush_if_full(&self) -> Result<(), xous::Error> {
|
|
// check to see if the FIFO is full first. If so, drain it.
|
|
if self.perf_csr.rf(utra::perfcounter::STATUS_FULL) != 0 { self.flush() } else { Ok(()) }
|
|
}
|
|
|
|
/// Flushes any data in the FIFO to the performance buffer. Also stops the performance counter from
|
|
/// running. Returns the total number of entries in the buffer, or an OOM if the buffer is full
|
|
/// This will update the time stamp rollover counter, so, you could in theory restart after this without
|
|
/// losing state.
|
|
pub fn stop_and_flush(&self) -> Result<u32, xous::Error> {
|
|
let mut oom = false;
|
|
// stop the counter
|
|
self.perf_csr.wfo(utra::perfcounter::RUN_STOP, 1);
|
|
// copy over the events to the long term buffer
|
|
let mut ts_offset = 0;
|
|
let mut expected_i = 0;
|
|
while self.perf_csr.rf(utra::perfcounter::STATUS_READABLE) == 1 {
|
|
if *self.log_index.borrow() < self.data.borrow().len() {
|
|
self.dbg_buf_count.replace(self.dbg_buf_count.take() + 1); // this tracks total entries copied into the perfbuf
|
|
self.data.borrow_mut()[*self.log_index.borrow()].code =
|
|
self.perf_csr.r(utra::perfcounter::EVENT_RAW0);
|
|
ts_offset = self.perf_csr.r(utra::perfcounter::EVENT_RAW1) as u32;
|
|
self.data.borrow_mut()[*self.log_index.borrow()].ts =
|
|
*self.ts_cont.borrow() as u32 + ts_offset;
|
|
|
|
let i = self.perf_csr.r(utra::perfcounter::EVENT_INDEX); // this advances the FIFO
|
|
if i != expected_i {
|
|
log::info!("i {} != expected_i {}", i, expected_i);
|
|
}
|
|
expected_i += 1;
|
|
self.log_index.replace(self.log_index.take() + 1);
|
|
} else {
|
|
oom = true;
|
|
break;
|
|
}
|
|
}
|
|
self.ts_cont.replace(self.ts_cont.take() + ts_offset as u64);
|
|
log::info!("FIFO final flush had {} entries", if expected_i == 0 { 0 } else { expected_i - 1 });
|
|
log::info!("Had {} buffer entries", *self.dbg_buf_count.borrow()); // should be the same as above, but trying to chase down some minor issues...
|
|
if !oom { Ok(*self.dbg_buf_count.borrow()) } else { Err(xous::Error::OutOfMemory) }
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn print_page_table(&self) {
|
|
log::info!("Buf vmem loc: {:x}", self.data.borrow().as_ptr() as u32);
|
|
match xous::syscall::virt_to_phys(self.data.borrow().as_ptr() as usize) {
|
|
Ok(addr) => log::info!("got {}", addr),
|
|
Err(e) => log::info!("error: {:?}", e),
|
|
};
|
|
log::info!(
|
|
"Buf pmem loc: {:x}",
|
|
xous::syscall::virt_to_phys(self.data.borrow().as_ptr() as usize).unwrap_or(0)
|
|
);
|
|
log::info!("PerfLogEntry size: {}", core::mem::size_of::<PerfLogEntry>());
|
|
log::info!("Now printing the page table mapping for the performance buffer:");
|
|
for page in (0..BUFLEN).step_by(4096) {
|
|
log::info!(
|
|
"V|P {:x} {:x}",
|
|
self.data.borrow().as_ptr() as usize + page,
|
|
xous::syscall::virt_to_phys(self.data.borrow().as_ptr() as usize + page).unwrap_or(0),
|
|
);
|
|
}
|
|
}
|
|
}
|