blob: 208f64649455b547331263ad893ee392e1df0abc [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Enable the feature `dev` to get features that are only useful for
//! development and testing purposes.
use crate::registers::Register;
use crate::Error;
use crate::Status;
use core::cell::UnsafeCell;
use core::mem::size_of;
static ERROR: SyncUnsafeCell<Error> = SyncUnsafeCell::new(Error::None);
// TODO(dcallagh): replace with the real SyncUnsafeCell after
// https://github.com/rust-lang/rust/issues/95439
#[repr(transparent)]
struct SyncUnsafeCell<T>(UnsafeCell<T>);
impl<T> SyncUnsafeCell<T> {
const fn new(value: T) -> Self {
SyncUnsafeCell(UnsafeCell::new(value))
}
}
unsafe impl<T> Sync for SyncUnsafeCell<T> {}
// Compile-time assertion that Error will fit into a single load/store.
// This is an important assumption for the unsafe code below.
const _: () = {
if size_of::<Error>() > 4 {
panic!("The Error type must be small enough to load/store with a single instruction");
}
};
/// Store an error in the global error state. This is intended to be called from the NMI handler
/// context, where there is no way to access normal RTIC-managed resources. Prefer to call
/// CommonHostInterface::report_error() instead.
pub fn report_global_error(error: Error) {
// We only keep track of the first error reported. This is quite likely
// to be the cause of any subsequent errors, so is the most important
// one to track.
//
// Safety: This function is called from an exception handler, which can preempt anything else,
// so there is a race window between reading the ERROR pointer and writing the new ERROR value.
// However, because we only ever set a non-None error, and the presence of any non-None error
// is considered fatal (FAULT bit will be reported), if there is a race we are guaranteed that
// one or the other error value will end up being stored. This assumption is valid because
// writing the ERROR value compiles down to a single store instruction.
unsafe {
if *ERROR.0.get() == Error::None {
*ERROR.0.get() = error;
}
}
}
pub struct CommonHostInterface {
error: Error,
}
// TODO(dcallagh): put #[default] on Error after Rust 1.62
impl Default for CommonHostInterface {
fn default() -> Self {
CommonHostInterface { error: Error::None }
}
}
impl CommonHostInterface {
pub fn new() -> Self {
Self::default()
}
pub fn read_register(&self, register: Register) -> Option<u16> {
Some(match register {
Register::Magic => crate::hps_magic_code(),
Register::Error => self.get_error().into(),
Register::SystemStatus => {
if self.get_error() != Error::None {
Status::FAULT.bits()
} else {
Status::OK.bits()
}
}
_ => return self.read_dev_register(register),
})
}
pub fn report_error(&mut self, error: Error) {
if self.error == Error::None {
self.error = error;
}
}
pub fn get_error(&self) -> Error {
// Safety: reading the ERROR value compiles to a single load instruction, so we
// are guaranteed to always read a valid value even in the presence of preemption.
let global_error = unsafe { *ERROR.0.get() };
if global_error != Error::None {
global_error
} else {
self.error
}
}
#[cfg(feature = "dev")]
fn read_dev_register(&self, register: Register) -> Option<u16> {
Some(match register {
Register::SlowRead => dev::fib(u16::from(self.get_error()).count_ones() as u16 + 20),
_ => return None,
})
}
#[cfg(not(feature = "dev"))]
fn read_dev_register(&self, _: Register) -> Option<u16> {
None
}
}
#[cfg(feature = "dev")]
mod dev {
/// Computes a fibonacci number. This is just intended to be a somewhat slow
/// operation that the compiler hopefully won't optimize away.
pub(crate) fn fib(n: u16) -> u16 {
match n {
0 => 0,
1 => 1,
n => fib(n - 1) + fib(n - 2),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_reporting() {
let mut interface = CommonHostInterface::new();
assert_eq!(interface.read_register(Register::Error), Some(0));
assert_eq!(
interface.read_register(Register::SystemStatus),
Some(Status::OK.bits())
);
interface.report_error(Error::HostI2cOverrun);
assert_eq!(
interface.read_register(Register::Error),
Some(u16::from(Error::HostI2cOverrun))
);
// Reporting a second error has no effect, since only the first error
// reported is known.
interface.report_error(Error::HostI2cUnderrun);
assert_eq!(
interface.read_register(Register::Error),
Some(u16::from(Error::HostI2cOverrun))
);
}
#[test]
fn test_magic() {
assert_eq!(
CommonHostInterface::new().read_register(Register::Magic),
Some(40434)
);
}
}