Kaynağa Gözat

Performance monitor extension

luojia65 3 yıl önce
ebeveyn
işleme
adb40d12ed
6 değiştirilmiş dosya ile 422 ekleme ve 3 silme
  1. 1 1
      Cargo.toml
  2. 26 2
      src/ecall.rs
  3. 111 0
      src/ecall/pmu.rs
  4. 1 0
      src/extension.rs
  5. 2 0
      src/lib.rs
  6. 281 0
      src/pmu.rs

+ 1 - 1
Cargo.toml

@@ -1,7 +1,7 @@
 [package]
 name = "rustsbi"
 description = "Minimal RISC-V's SBI implementation library in Rust"
-version = "0.2.0-alpha.4"
+version = "0.2.0-alpha.5"
 authors = ["luojia65 <[email protected]>"]
 repository = "https://github.com/rustsbi/rustsbi"
 documentation = "https://docs.rs/rustsbi"

+ 26 - 2
src/ecall.rs

@@ -8,6 +8,7 @@ mod legacy;
 mod srst;
 mod timer;
 mod rfence;
+mod pmu;
 
 pub const EXTENSION_BASE: usize = 0x10;
 pub const EXTENSION_TIMER: usize = 0x54494D45;
@@ -15,6 +16,7 @@ pub const EXTENSION_IPI: usize = 0x735049;
 pub const EXTENSION_RFENCE: usize = 0x52464E43;
 pub const EXTENSION_HSM: usize = 0x48534D;
 pub const EXTENSION_SRST: usize = 0x53525354;
+pub const EXTENSION_PMU: usize = 0x504D55;
 
 const LEGACY_SET_TIMER: usize = 0x0;
 const LEGACY_CONSOLE_PUTCHAR: usize = 0x01;
@@ -48,7 +50,7 @@ const LEGACY_SHUTDOWN: usize = 0x08;
 /// #[exception]
 /// fn handle_exception(ctx: &mut TrapFrame) {
 ///     if mcause::read().cause() == Trap::Exception(Exception::SupervisorEnvCall) {
-///         let params = [ctx.a0, ctx.a1, ctx.a2, ctx.a3, ctx.a4];
+///         let params = [ctx.a0, ctx.a1, ctx.a2, ctx.a3, ctx.a4, ctx.a5];
 ///         let ans = rustsbi::ecall(ctx.a7, ctx.a6, params);
 ///         ctx.a0 = ans.error;
 ///         ctx.a1 = ans.value;
@@ -61,7 +63,7 @@ const LEGACY_SHUTDOWN: usize = 0x08;
 /// Do not forget to advance `mepc` by 4 after an ecall is handled.
 /// This skips the `ecall` instruction itself which is 4-byte long in all conditions.
 #[inline]
-pub fn handle_ecall(extension: usize, function: usize, param: [usize; 5]) -> SbiRet {
+pub fn handle_ecall(extension: usize, function: usize, param: [usize; 6]) -> SbiRet {
     match extension {
         EXTENSION_RFENCE => rfence::handle_ecall_rfence(function, param[0], param[1], param[2], param[3], param[4]),
         EXTENSION_TIMER => match () {
@@ -74,6 +76,12 @@ pub fn handle_ecall(extension: usize, function: usize, param: [usize; 5]) -> Sbi
         EXTENSION_BASE => base::handle_ecall_base(function, param[0]),
         EXTENSION_HSM => hsm::handle_ecall_hsm(function, param[0], param[1], param[2]),
         EXTENSION_SRST => srst::handle_ecall_srst(function, param[0], param[1]),
+        EXTENSION_PMU => match () {
+            #[cfg(target_pointer_width = "64")]
+            () => pmu::handle_ecall_pmu_64(function, param[0], param[1], param[2], param[3], param[4]),
+            #[cfg(target_pointer_width = "32")]
+            () => pmu::handle_ecall_pmu_32(function, param[0], param[1], param[2], param[3], param[4], param[5]),
+        },
         LEGACY_SET_TIMER => match () {
             #[cfg(target_pointer_width = "64")]
             () => legacy::set_timer_64(param[0]),
@@ -107,6 +115,8 @@ const SBI_ERR_INVALID_PARAM: usize = usize::from_ne_bytes(isize::to_ne_bytes(-3)
 // const SBI_ERR_DENIED: usize = usize::from_ne_bytes(isize::to_ne_bytes(-4));
 const SBI_ERR_INVALID_ADDRESS: usize = usize::from_ne_bytes(isize::to_ne_bytes(-5));
 const SBI_ERR_ALREADY_AVAILABLE: usize = usize::from_ne_bytes(isize::to_ne_bytes(-6));
+const SBI_ERR_ALREADY_STARTED: usize = usize::from_ne_bytes(isize::to_ne_bytes(-7));
+const SBI_ERR_ALREADY_STOPPED: usize = usize::from_ne_bytes(isize::to_ne_bytes(-8));
 
 impl SbiRet {
     /// Return success SBI state with given value.
@@ -155,6 +165,20 @@ impl SbiRet {
             value: 0,
         }
     }
+    /// SBI call failed for the target resource is already started, e.g. target performance counter is started.
+    pub fn already_started() -> SbiRet {
+        SbiRet {
+            error: SBI_ERR_ALREADY_STARTED,
+            value: 0,
+        }
+    }
+    /// SBI call failed for the target resource is already stopped, e.g. target performance counter is stopped.
+    pub fn already_stopped() -> SbiRet {
+        SbiRet {
+            error: SBI_ERR_ALREADY_STOPPED,
+            value: 0,
+        }
+    }
     pub(crate) fn legacy_ok(legacy_value: usize) -> SbiRet {
         SbiRet {
             error: legacy_value,

+ 111 - 0
src/ecall/pmu.rs

@@ -0,0 +1,111 @@
+//! pmu extension
+use super::SbiRet;
+use crate::pmu;
+
+const FUNCTION_PMU_NUM_COUNTERS: usize = 0x0;
+const FUNCTION_PMU_COUNTER_GET_INFO: usize = 0x1;
+const FUNCTION_PMU_COUNTER_CONFIG_MATCHING: usize = 0x2;
+const FUNCTION_PMU_COUNTER_START: usize = 0x3;
+const FUNCTION_PMU_COUNTER_STOP: usize = 0x4;
+const FUNCTION_PMU_COUNTER_FW_READ: usize = 0x5;
+
+#[inline]
+#[cfg(target_pointer_width = "64")]
+pub fn handle_ecall_pmu_64(function: usize, param0: usize, param1: usize, param2: usize, param3: usize, param4: usize) -> SbiRet {
+    match function {
+        FUNCTION_PMU_NUM_COUNTERS => pmu::num_counters(),
+        FUNCTION_PMU_COUNTER_GET_INFO => pmu::counter_get_info(param0),
+        FUNCTION_PMU_COUNTER_CONFIG_MATCHING => counter_config_matching_64(param0, param1, param2, param3, param4),
+        FUNCTION_PMU_COUNTER_START => counter_start_64(param0, param1, param2, param3),
+        FUNCTION_PMU_COUNTER_STOP => pmu::counter_stop(param0, param1, param2),
+        FUNCTION_PMU_COUNTER_FW_READ => pmu::counter_fw_read(param0),
+        _ => SbiRet::not_supported(),
+    }
+}
+
+#[inline]
+#[cfg(target_pointer_width = "32")]
+pub fn handle_ecall_pmu_32(function: usize, param0: usize, param1: usize, param2: usize, param3: usize, param4: usize, param5: usize) -> SbiRet {
+    match function {
+        FUNCTION_PMU_NUM_COUNTERS => pmu::num_counters(),
+        FUNCTION_PMU_COUNTER_GET_INFO => pmu::counter_get_info(param0),
+        FUNCTION_PMU_COUNTER_CONFIG_MATCHING => counter_config_matching_32(param0, param1, param2, param3, param4, param5),
+        FUNCTION_PMU_COUNTER_START => counter_start_32(param0, param1, param2, param3, param4),
+        FUNCTION_PMU_COUNTER_STOP => pmu::counter_stop(param0, param1, param2),
+        FUNCTION_PMU_COUNTER_FW_READ => pmu::counter_fw_read(param0),
+        _ => SbiRet::not_supported(),
+    }
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline] 
+fn counter_config_matching_64(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    config_flags: usize,
+    event_idx: usize,
+    event_data: usize
+) -> SbiRet {
+    pmu::counter_config_matching(
+        counter_idx_base,
+        counter_idx_mask,
+        config_flags,
+        event_idx,
+        event_data as u64
+    )
+}
+
+
+#[cfg(target_pointer_width = "32")]
+#[inline] 
+fn counter_config_matching_32(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    config_flags: usize,
+    event_idx: usize,
+    event_data_lo: usize,
+    event_data_hi: usize,
+) -> SbiRet {
+    let event_data = (event_data_lo as u64) + ((event_data_hi as u64) << 32);
+    pmu::counter_config_matching(
+        counter_idx_base,
+        counter_idx_mask,
+        config_flags,
+        event_idx,
+        event_data
+    )
+}
+
+#[cfg(target_pointer_width = "64")]
+#[inline] 
+fn counter_start_64(
+    counter_idx_base: usize, 
+    counter_idx_mask: usize, 
+    start_flags: usize, 
+    initial_value: usize
+) -> SbiRet {
+    pmu::counter_start(
+        counter_idx_base, 
+        counter_idx_mask,
+        start_flags,
+        initial_value as u64
+    )
+}
+
+#[cfg(target_pointer_width = "32")]
+#[inline] 
+fn counter_start_32(
+    counter_idx_base: usize, 
+    counter_idx_mask: usize, 
+    start_flags: usize, 
+    initial_value_lo: usize,
+    initial_value_hi: usize,
+) -> SbiRet {
+    let initial_value = (initial_value_lo as u64) + ((initial_value_hi as u64) << 32);
+    pmu::counter_start(
+        counter_idx_base, 
+        counter_idx_mask,
+        start_flags,
+        initial_value
+    )
+}

+ 1 - 0
src/extension.rs

@@ -9,6 +9,7 @@ pub fn probe_extension(extension: usize) -> bool {
         EXTENSION_RFENCE => crate::rfence::probe_rfence(),
         EXTENSION_SRST => crate::reset::probe_reset(),
         EXTENSION_HSM => crate::hsm::probe_hsm(),
+        EXTENSION_PMU => crate::pmu::probe_pmu(),
         // new extensions should be added here to be probed
         _ => false,
     }

+ 2 - 0
src/lib.rs

@@ -155,6 +155,7 @@ mod privileged;
 pub mod reset;
 mod timer;
 mod rfence;
+mod pmu;
 
 const SBI_SPEC_MAJOR: usize = 0;
 const SBI_SPEC_MINOR: usize = 2;
@@ -182,5 +183,6 @@ pub use privileged::enter_privileged;
 pub use reset::{init_reset, Reset};
 pub use timer::{init_timer, Timer};
 pub use rfence::{init_rfence as init_remote_fence, Rfence as Fence};
+pub use pmu::{init_pmu, Pmu};
 #[doc(hidden)]
 pub use legacy_stdio::{legacy_stdio_getchar, legacy_stdio_putchar};

+ 281 - 0
src/pmu.rs

@@ -0,0 +1,281 @@
+use crate::ecall::SbiRet;
+
+/// Performance Monitoring Unit Extension
+///
+/// The RISC-V hardware performance counters such as `mcycle`, `minstret`, and `mhpmcounterX` CSRs 
+/// are accessible as read-only from supervisor-mode using `cycle`, `instret`, and `hpmcounterX` CSRs. 
+/// The SBI performance monitoring unit (PMU) extension is an interface for supervisor-mode to configure 
+/// and use the RISC-V hardware performance counters with assistance from the machine-mode (or hypervisor-mode). 
+/// These hardware performance counters can only be started, stopped, or configured from machine-mode 
+/// using `mcountinhibit` and `mhpmeventX` CSRs. 
+/// Due to this, a machine-mode SBI implementation may choose to disallow SBI PMU extension 
+/// if `mcountinhibit` CSR is not implemented by the RISC-V platform.
+/// 
+/// A RISC-V platform generally supports monitoring of various hardware events using a limited number 
+/// of hardware performance counters which are up to 64 bits wide. 
+/// In addition, a SBI implementation can also provide firmware performance counters which can monitor firmware events 
+/// such as number of misaligned load/store instructions, number of RFENCEs, number of IPIs, etc. 
+/// The firmware counters are always 64 bits wide.
+/// 
+/// The SBI PMU extension provides:
+/// 
+/// 1. An interface for supervisor-mode software to discover and configure per-HART hardware/firmware counters
+/// 2. A typical perf compatible interface for hardware/firmware performance counters and events
+/// 3. Full access to microarchitecture’s raw event encodings
+/// 
+/// To define SBI PMU extension calls, we first define important entities `counter_idx`, `event_idx`, and `event_data`. 
+/// The `counter_idx` is a logical number assigned to each hardware/firmware counter. 
+/// The `event_idx `represents a hardware (or firmware) event whereas 
+/// the `event_data` is 64 bits wide and represents additional configuration (or parameters) for 
+/// a hardware (or firmware) event.
+///
+/// The event_idx is a 20 bits wide number encoded as follows:
+///
+/// ```rust
+///    event_idx[19:16] = type;
+///    event_idx[15:0] = code;
+/// ```
+pub trait Pmu: Send {
+    /// Returns the number of counters (both hardware and firmware) in return `SbiRet.value`
+    /// and always returns SBI_SUCCESS in `SbiRet.error`.
+    fn num_counters(&mut self) -> SbiRet;
+    /// Get details about the specified counter such as underlying CSR number, width of the counter, 
+    /// type of counter hardware/firmware, etc.
+    ///
+    /// The `counter_info` returned by this SBI call is encoded as follows:
+    ///
+    /// ```rust
+    ///     counter_info[11:0] = CSR; // (12bit CSR number)
+    ///     counter_info[17:12] = Width; // (One less than number of bits in CSR)
+    ///     counter_info[XLEN-2:18] = Reserved; // Reserved for future use
+    ///     counter_info[XLEN-1] = Type; // (0 = hardware and 1 = firmware)
+    /// ```
+    /// If `counter_info.type` == `1` then `counter_info.csr` and `counter_info.width` should be ignored.
+    ///
+    /// # Return value
+    ///
+    /// Returns the `counter_info` described above in `SbiRet.value`.
+    ///
+    /// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+    /// 
+    /// | Return code             | Description 
+    /// |:------------------------|:----------------------------------------------
+    /// | SBI_SUCCESS             | `counter_info` read successfully.
+    /// | SBI_ERR_INVALID_PARAM   | `counter_idx` points to an invalid counter.
+    fn counter_get_info(&mut self, counter_idx: usize) -> SbiRet;
+    /// Find and configure a counter from a set of counters which is not started (or enabled) 
+    /// and can monitor the specified event. 
+    /// 
+    /// # Parameters
+    ///
+    /// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters,
+    /// whereas the `event_idx` represent the event to be monitored 
+    /// and `event_data` represents any additional event configuration.
+    ///
+    /// The `config_flags` parameter represent additional counter configuration and filter flags. 
+    /// The bit definitions of the `config_flags` parameter are shown in the table below:
+    /// 
+    /// | Flag Name                    | Bits       | Description
+    /// |:-----------------------------|:-----------|:------------
+    /// | SBI_PMU_CFG_FLAG_SKIP_MATCH  | 0:0        | Skip the counter matching
+    /// | SBI_PMU_CFG_FLAG_CLEAR_VALUE | 1:1        | Clear (or zero) the counter value in counter configuration
+    /// | SBI_PMU_CFG_FLAG_AUTO_START  | 2:2        | Start the counter after configuring a matching counter
+    /// | SBI_PMU_CFG_FLAG_SET_VUINH   | 3:3        | Event counting inhibited in VU-mode
+    /// | SBI_PMU_CFG_FLAG_SET_VSINH   | 4:4        | Event counting inhibited in VS-mode
+    /// | SBI_PMU_CFG_FLAG_SET_UINH    | 5:5        | Event counting inhibited in U-mode
+    /// | SBI_PMU_CFG_FLAG_SET_SINH    | 6:6        | Event counting inhibited in S-mode
+    /// | SBI_PMU_CFG_FLAG_SET_MINH    | 7:7        | Event counting inhibited in M-mode
+    /// | *RESERVED*                   | 8:(XLEN-1) | All non-zero values are reserved for future use.
+    ///
+    /// *NOTE:* When *SBI_PMU_CFG_FLAG_SKIP_MATCH* is set in `config_flags`, the
+    /// SBI implementation will unconditionally select the first counter from the
+    /// set of counters specified by the `counter_idx_base` and `counter_idx_mask`. 
+    /// 
+    /// *NOTE:* The *SBI_PMU_CFG_FLAG_AUTO_START* flag in `config_flags` has no
+    /// impact on the counter value.    
+    /// 
+    /// *NOTE:* The `config_flags[3:7]` bits are event filtering hints so these
+    /// can be ignored or overridden by the SBI implementation for security concerns
+    /// or due to lack of event filtering support in the underlying RISC-V platform.
+    /// 
+    /// # Return value
+    /// 
+    /// Returns the `counter_idx` in `sbiret.value` upon success.
+    /// 
+    /// In case of failure, the possible error codes returned in `sbiret.error` are shown in the table below:    
+    /// 
+    /// | Return code           | Description 
+    /// |:----------------------|:----------------------------------------------
+    /// | SBI_SUCCESS           | counter found and configured successfully.
+    /// | SBI_ERR_INVALID_PARAM | set of counters has an invalid counter.
+    /// | SBI_ERR_NOT_SUPPORTED | none of the counters can monitor specified event.
+    fn counter_config_matching(
+        &mut self,
+        counter_idx_base: usize,
+        counter_idx_mask: usize,
+        config_flags: usize,
+        event_idx: usize,
+        event_data: u64
+    ) -> SbiRet;
+    /// Start or enable a set of counters on the calling HART with the specified initial value. 
+    ///
+    /// # Parameters
+    /// 
+    /// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters.
+    /// whereas the `initial_value` parameter specifies the initial value of the counter.
+    ///
+    /// The bit definitions of the `start_flags` parameter are shown in the table below:
+    /// 
+    /// | Flag Name                    | Bits       | Description
+    /// |:-----------------------------|:-----------|:------------
+    /// | SBI_PMU_START_SET_INIT_VALUE | 0:0        | Set the value of counters based on the `initial_value` parameter.
+    /// | *RESERVED*                   | 1:(XLEN-1) | All non-zero values are reserved for future use.
+    ///
+    /// *NOTE*: When `SBI_PMU_START_SET_INIT_VALUE` is not set in `start_flags`, the counter value will 
+    /// not be modified and event counting will start from current counter value.
+    /// 
+    /// # Return value
+    ///
+    /// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+    /// 
+    /// | Return code             | Description 
+    /// |:------------------------|:----------------------------------------------
+    /// | SBI_SUCCESS             | counter started successfully.
+    /// | SBI_ERR_INVALID_PARAM   | some of the counters specified in parameters are invalid.
+    /// | SBI_ERR_ALREADY_STARTED | some of the counters specified in parameters are already started.
+    fn counter_start(
+        &mut self, 
+        counter_idx_base: usize, 
+        counter_idx_mask: usize, 
+        start_flags: usize, 
+        initial_value: u64
+    ) -> SbiRet;
+    /// Stop or disable a set of counters on the calling HART. 
+    ///
+    /// # Parameters
+    /// 
+    /// The `counter_idx_base` and `counter_idx_mask` parameters represent the set of counters. 
+    /// The bit definitions of the `stop_flags` parameter are shown in the table below:
+    /// 
+    /// | Flag Name               | Bits       | Description
+    /// |:------------------------|:-----------|:------------
+    /// | SBI_PMU_STOP_FLAG_RESET | 0:0        | Reset the counter to event mapping.
+    /// | *RESERVED*              | 1:(XLEN-1) | All non-zero values are reserved for future use.
+    /// 
+    /// # Return value
+    ///
+    /// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+    /// 
+    /// | Return code             | Description 
+    /// |:------------------------|:----------------------------------------------
+    /// | SBI_SUCCESS             | counter stopped successfully.
+    /// | SBI_ERR_INVALID_PARAM   | some of the counters specified in parameters are invalid.
+    /// | SBI_ERR_ALREADY_STOPPED | some of the counters specified in parameters are already stopped.
+    fn counter_stop(&mut self, counter_idx_base: usize, counter_idx_mask: usize, stop_flags: usize) -> SbiRet;
+    /// Provide the current value of a firmware counter in `SbiRet.value`.
+    ///
+    /// # Parameters
+    /// 
+    /// This function should be only used to read a firmware counter. It will return an error
+    /// when user provides a hardware counter in `counter_idx` parameter.
+    ///
+    /// # Return value
+    ///
+    /// The possible return error codes returned in `SbiRet.error` are shown in the table below:    
+    /// 
+    /// | Return code             | Description 
+    /// |:------------------------|:----------------------------------------------
+    /// | SBI_SUCCESS             | firmware counter read successfully.
+    /// | SBI_ERR_INVALID_PARAM   | `counter_idx` points to a hardware counter or an invalid counter.
+    fn counter_fw_read(&mut self, counter_idx: usize) -> SbiRet;
+}
+
+// TODO: all the events here
+
+use alloc::boxed::Box;
+use spin::Mutex;
+
+lazy_static::lazy_static! {
+    static ref PMU: Mutex<Option<Box<dyn Pmu>>> =
+        Mutex::new(None);
+}
+
+#[doc(hidden)] // use through a macro or a call from implementation
+pub fn init_pmu<T: Pmu + Send + 'static>(hsm: T) {
+    *PMU.lock() = Some(Box::new(hsm));
+}
+
+#[inline]
+pub(crate) fn probe_pmu() -> bool {
+    PMU.lock().as_ref().is_some()
+}
+
+#[inline] 
+pub(crate) fn num_counters() -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.num_counters();
+    }
+    SbiRet::not_supported()
+}
+
+#[inline] 
+pub(crate) fn counter_get_info(counter_idx: usize) -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.counter_get_info(counter_idx);
+    }
+    SbiRet::not_supported()
+}
+
+#[inline] 
+pub(crate) fn counter_config_matching(
+    counter_idx_base: usize,
+    counter_idx_mask: usize,
+    config_flags: usize,
+    event_idx: usize,
+    event_data: u64
+) -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.counter_config_matching(
+            counter_idx_base,
+            counter_idx_mask,
+            config_flags,
+            event_idx,
+            event_data
+        );
+    }
+    SbiRet::not_supported()
+}
+
+#[inline] 
+pub(crate) fn counter_start(
+    counter_idx_base: usize, 
+    counter_idx_mask: usize, 
+    start_flags: usize, 
+    initial_value: u64
+) -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.counter_start(
+            counter_idx_base, 
+            counter_idx_mask,
+            start_flags,
+            initial_value
+        );
+    }
+    SbiRet::not_supported()
+}
+
+#[inline] 
+pub(crate) fn counter_stop(counter_idx_base: usize, counter_idx_mask: usize, stop_flags: usize) -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.counter_stop(counter_idx_base, counter_idx_mask, stop_flags);
+    }
+    SbiRet::not_supported()
+}
+
+#[inline] 
+pub(crate) fn counter_fw_read(counter_idx: usize) -> SbiRet {
+    if let Some(obj) = &mut *PMU.lock() {
+        return obj.counter_fw_read(counter_idx);
+    }
+    SbiRet::not_supported()
+}