mango-hal 0.2.1

Hardware Abstraction Layer for the mango operationg system.
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright (c) 2018-2023 Andre Richter <andre.o.richter@gmail.com>

//! Architectural synchronous and asynchronous exception handling.
//!
//! # Orientation
//!
//! Since arch modules are imported into generic modules using the path attribute, the path of this
//! file is:
//!
//! crate::exception::arch_exception

use crate::systems::rpi::{exception, memory};
use aarch64_cpu::{asm::barrier, registers::*};
use core::{arch::global_asm, cell::UnsafeCell, fmt};
use tock_registers::{
  interfaces::{Readable, Writeable},
  registers::InMemoryRegister,
};

// Assembly counterpart to this file.
global_asm!(include_str!("exception.s"),);

//--------------------------------------------------------------------------------------------------
// Private Definitions
//--------------------------------------------------------------------------------------------------

/// Wrapper structs for memory copies of registers.
#[repr(transparent)]
struct SpsrEL1(InMemoryRegister<u64, SPSR_EL1::Register>);
struct EsrEL1(InMemoryRegister<u64, ESR_EL1::Register>);

/// The exception context as it is stored on the stack on exception entry.
#[repr(C)]
struct ExceptionContext
{
  /// General Purpose Registers.
  gpr: [u64; 30],

  /// The link register, aka x30.
  lr: u64,

  /// Exception link register. The program counter at the time the exception happened.
  elr_el1: u64,

  /// Saved program status.
  spsr_el1: SpsrEL1,

  /// Exception syndrome register.
  esr_el1: EsrEL1,
}

//--------------------------------------------------------------------------------------------------
// Private Code
//--------------------------------------------------------------------------------------------------

/// Prints verbose information about the exception and then panics.
fn default_exception_handler(exc: &ExceptionContext)
{
  panic!(
    "CPU Exception!\n\n\
        {}",
    exc
  );
}

//------------------------------------------------------------------------------
// Current, EL0
//------------------------------------------------------------------------------

#[no_mangle]
extern "C" fn current_el0_synchronous(_e: &mut ExceptionContext)
{
  panic!("Should not be here. Use of SP_EL0 in EL1 is not supported.")
}

#[no_mangle]
extern "C" fn current_el0_irq(_e: &mut ExceptionContext)
{
  panic!("Should not be here. Use of SP_EL0 in EL1 is not supported.")
}

#[no_mangle]
extern "C" fn current_el0_serror(_e: &mut ExceptionContext)
{
  panic!("Should not be here. Use of SP_EL0 in EL1 is not supported.")
}

//------------------------------------------------------------------------------
// Current, ELx
//------------------------------------------------------------------------------

#[no_mangle]
extern "C" fn current_elx_synchronous(e: &mut ExceptionContext)
{
  #[cfg(feature = "test_build")]
  {
    const TEST_SVC_ID: u64 = 0x1337;

    if let Some(ESR_EL1::EC::Value::SVC64) = e.esr_el1.exception_class()
    {
      if e.esr_el1.iss() == TEST_SVC_ID
      {
        return;
      }
    }
  }

  default_exception_handler(e);
}

#[no_mangle]
extern "C" fn current_elx_irq(_e: &mut ExceptionContext)
{
  let token = unsafe { &exception::asynchronous::IRQContext::new() };
  exception::asynchronous::irq_manager().handle_pending_irqs(token);
}

#[no_mangle]
extern "C" fn current_elx_serror(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

//------------------------------------------------------------------------------
// Lower, AArch64
//------------------------------------------------------------------------------

#[no_mangle]
extern "C" fn lower_aarch64_synchronous(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

#[no_mangle]
extern "C" fn lower_aarch64_irq(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

#[no_mangle]
extern "C" fn lower_aarch64_serror(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

//------------------------------------------------------------------------------
// Lower, AArch32
//------------------------------------------------------------------------------

#[no_mangle]
extern "C" fn lower_aarch32_synchronous(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

#[no_mangle]
extern "C" fn lower_aarch32_irq(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

#[no_mangle]
extern "C" fn lower_aarch32_serror(e: &mut ExceptionContext)
{
  default_exception_handler(e);
}

//------------------------------------------------------------------------------
// Misc
//------------------------------------------------------------------------------

/// Human readable SPSR_EL1.
#[rustfmt::skip]
impl fmt::Display for SpsrEL1 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Raw value.
        writeln!(f, "SPSR_EL1: {:#010x}", self.0.get())?;

        let to_flag_str = |x| -> _ {
            if x { "Set" } else { "Not set" }
         };

        writeln!(f, "      Flags:")?;
        writeln!(f, "            Negative (N): {}", to_flag_str(self.0.is_set(SPSR_EL1::N)))?;
        writeln!(f, "            Zero     (Z): {}", to_flag_str(self.0.is_set(SPSR_EL1::Z)))?;
        writeln!(f, "            Carry    (C): {}", to_flag_str(self.0.is_set(SPSR_EL1::C)))?;
        writeln!(f, "            Overflow (V): {}", to_flag_str(self.0.is_set(SPSR_EL1::V)))?;

        let to_mask_str = |x| -> _ {
            if x { "Masked" } else { "Unmasked" }
        };

        writeln!(f, "      Exception handling state:")?;
        writeln!(f, "            Debug  (D): {}", to_mask_str(self.0.is_set(SPSR_EL1::D)))?;
        writeln!(f, "            SError (A): {}", to_mask_str(self.0.is_set(SPSR_EL1::A)))?;
        writeln!(f, "            IRQ    (I): {}", to_mask_str(self.0.is_set(SPSR_EL1::I)))?;
        writeln!(f, "            FIQ    (F): {}", to_mask_str(self.0.is_set(SPSR_EL1::F)))?;

        write!(f, "      Illegal Execution State (IL): {}",
            to_flag_str(self.0.is_set(SPSR_EL1::IL))
        )
    }
}

impl EsrEL1
{
  #[inline(always)]
  fn exception_class(&self) -> Option<ESR_EL1::EC::Value>
  {
    self.0.read_as_enum(ESR_EL1::EC)
  }

  #[cfg(feature = "test_build")]
  #[inline(always)]
  fn iss(&self) -> u64
  {
    self.0.read(ESR_EL1::ISS)
  }
}

/// Human readable ESR_EL1.
#[rustfmt::skip]
impl fmt::Display for EsrEL1 {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Raw print of whole register.
        writeln!(f, "ESR_EL1: {:#010x}", self.0.get())?;

        // Raw print of exception class.
        write!(f, "      Exception Class         (EC) : {:#x}", self.0.read(ESR_EL1::EC))?;

        // Exception class.
        let ec_translation = match self.exception_class() {
            Some(ESR_EL1::EC::Value::DataAbortCurrentEL) => "Data Abort, current EL",
            _ => "N/A",
        };
        writeln!(f, " - {}", ec_translation)?;

        // Raw print of instruction specific syndrome.
        write!(f, "      Instr Specific Syndrome (ISS): {:#x}", self.0.read(ESR_EL1::ISS))
    }
}

impl ExceptionContext
{
  #[inline(always)]
  fn exception_class(&self) -> Option<ESR_EL1::EC::Value>
  {
    self.esr_el1.exception_class()
  }

  #[inline(always)]
  fn fault_address_valid(&self) -> bool
  {
    use ESR_EL1::EC::Value::*;

    match self.exception_class()
    {
      None => false,
      Some(ec) => matches!(
        ec,
        InstrAbortLowerEL
          | InstrAbortCurrentEL
          | PCAlignmentFault
          | DataAbortLowerEL
          | DataAbortCurrentEL
          | WatchpointLowerEL
          | WatchpointCurrentEL
      ),
    }
  }
}

/// Human readable print of the exception context.
impl fmt::Display for ExceptionContext
{
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result
  {
    writeln!(f, "{}", self.esr_el1)?;

    if self.fault_address_valid()
    {
      writeln!(f, "FAR_EL1: {:#018x}", FAR_EL1.get() as usize)?;
    }

    writeln!(f, "{}", self.spsr_el1)?;
    writeln!(f, "ELR_EL1: {:#018x}", self.elr_el1)?;
    writeln!(f)?;
    writeln!(f, "General purpose register:")?;

    #[rustfmt::skip]
        let alternating = |x| -> _ {
            if x % 2 == 0 { "   " } else { "\n" }
        };

    // Print two registers per line.
    for (i, reg) in self.gpr.iter().enumerate()
    {
      write!(f, "      x{: <2}: {: >#018x}{}", i, reg, alternating(i))?;
    }
    write!(f, "      lr : {:#018x}", self.lr)
  }
}

//--------------------------------------------------------------------------------------------------
// Public Code
//--------------------------------------------------------------------------------------------------
use crate::systems::rpi::exception::PrivilegeLevel;

/// The processing element's current privilege level.
pub fn current_privilege_level() -> (PrivilegeLevel, &'static str)
{
  let el = CurrentEL.read_as_enum(CurrentEL::EL);
  match el
  {
    Some(CurrentEL::EL::Value::EL2) => (PrivilegeLevel::Hypervisor, "EL2"),
    Some(CurrentEL::EL::Value::EL1) => (PrivilegeLevel::Kernel, "EL1"),
    Some(CurrentEL::EL::Value::EL0) => (PrivilegeLevel::User, "EL0"),
    _ => (PrivilegeLevel::Unknown, "Unknown"),
  }
}

/// Init exception handling by setting the exception vector base address register.
///
/// # Safety
///
/// - Changes the HW state of the executing core.
/// - The vector table and the symbol `__exception_vector_table_start` from the linker script must
///   adhere to the alignment and size constraints demanded by the ARMv8-A Architecture Reference
///   Manual.
pub unsafe fn handling_init()
{
  // Provided by exception.S.
  extern "Rust" {
    static __exception_vector_start: UnsafeCell<()>;
  }

  VBAR_EL1.set(__exception_vector_start.get() as u64);

  // Force VBAR update to complete before next instruction.
  barrier::isb(barrier::SY);
}