//! Debugger Architecture Module
//!
//! This module contains the architecture specific implementations for the debugger.
//! These implementations are abstracted behind the DebuggerArch trait, which is
//! the architecture agnostic interface the rest of the debugger uses. The architecture
//! structs also implement the required GdbStub architecture traits for register
//! access.
//!
//! ## License
//!
//! Copyright (C) Microsoft Corporation.
//!
//! SPDX-License-Identifier: Apache-2.0
//!

use gdbstub::target::ext::breakpoints;
use patina_internal_cpu::interrupts::ExceptionContext;
use patina_paging::PageTable;

use crate::ExceptionInfo;

cfg_if::cfg_if! {
    if #[cfg(target_arch = "x86_64")] {
        #[coverage(off)]
        mod x64;
        pub type SystemArch = x64::X64Arch;
    } else if #[cfg(target_arch = "aarch64")] {
        #[coverage(off)]
        mod aarch64;
        pub type SystemArch = aarch64::Aarch64Arch;
    }
}

/// Trait for architecture specific debugger implementations.
///
/// This trait abstracts the architecture specifics for the debugger. As these
/// are abstracting processor state and instructions, all routines are expected
/// to be static.
///
pub trait DebuggerArch {
    const DEFAULT_EXCEPTION_TYPES: &'static [usize];
    const BREAKPOINT_INSTRUCTION: &'static [u8];
    const GDB_TARGET_XML: &'static str;
    const GDB_REGISTERS_XML: &'static str;

    type PageTable: PageTable;

    /// Executes a breakpoint instruction.
    fn breakpoint();

    /// Processes the entry into the debugger, doing any fixup needed to the
    /// CPU state of the system context.
    fn process_entry(exception_type: u64, context: &mut ExceptionContext) -> ExceptionInfo;

    /// Processes the exit from the debugger, doing any fixup needed to the
    /// CPU state of the system context.
    fn process_exit(exception_info: &mut ExceptionInfo);

    /// Enables the architecture specific single step.
    fn set_single_step(exception_info: &mut ExceptionInfo);

    /// Initializes the architecture specific state for the debugger.
    fn initialize();

    /// Adds a watchpoint to the provided address.
    fn add_watchpoint(address: u64, length: u64, access_type: breakpoints::WatchKind) -> bool;

    /// Removes a watchpoint from the provided address.
    fn remove_watchpoint(address: u64, length: u64, access_type: breakpoints::WatchKind) -> bool;

    /// Reboots the system.
    fn reboot();

    /// Gets the current page table.
    fn get_page_table() -> Result<Self::PageTable, ()>;

    /// Process architecture specific monitor commands.
    fn monitor_cmd(_tokens: &mut core::str::SplitWhitespace, _out: &mut dyn core::fmt::Write) {}

    /// Checks if the memory is accessible by attempting a read and handling faults.
    fn memory_poke_test(_address: u64) -> Result<(), ()>;

    /// Checks if a memory poke test is active, handling it and returning true
    /// if it is.
    fn check_memory_poke_test(_context: &mut ExceptionContext) -> bool;
}

pub trait UefiArchRegs: Sized {
    /// Initializes the register from a UEFI context structure.
    fn from_context(context: &ExceptionContext) -> Self;

    /// Writes the register to a UEFI context structure.
    fn write_to_context(&self, context: &mut ExceptionContext);

    /// Reads the register from a UEFI context structure.
    fn read_from_context(&mut self, context: &ExceptionContext) {
        *self = Self::from_context(context);
    }

    // Reads a single register from a UEFI context structure.
    fn read_register_from_context(
        context: &ExceptionContext,
        reg_id: <SystemArch as gdbstub::arch::Arch>::RegId,
        buf: &mut [u8],
    ) -> Result<usize, ()>;

    // Writes a single register to a UEFI context structure.
    fn write_register_to_context(
        context: &mut ExceptionContext,
        reg_id: <SystemArch as gdbstub::arch::Arch>::RegId,
        buf: &[u8],
    ) -> Result<(), ()>;
}
