// SPDX-License-Identifier: MIT OR Apache-2.0

//! Multi-processor management protocols.
//!
//! On any system with more than one logical processor we can categorize them as:
//!
//! * BSP — bootstrap processor, executes modules that are necessary for booting the system
//! * AP — application processor, any processor other than the bootstrap processor
//!
//! This module contains protocols that provide a generalized way of performing the following tasks on these logical processors:
//!
//! * retrieving information of multi-processor environment and MP-related status of specific processors
//! * dispatching user-provided function to APs
//! * maintaining MP-related processor status

use crate::data_types::Event;
use crate::proto::unsafe_protocol;
use crate::{Result, Status, StatusExt};
use bitflags::bitflags;
use core::ffi::c_void;
use core::ptr;
use core::time::Duration;

/// Callback to be called on the AP.
pub type Procedure = extern "efiapi" fn(*mut c_void);

bitflags! {
    /// Flags indicating if the processor is BSP or AP,
    /// if the processor is enabled or disabled, and if
    /// the processor is healthy.
    #[repr(transparent)]
    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
    struct StatusFlag: u32 {
        /// Processor is playing the role of BSP.
        const PROCESSOR_AS_BSP_BIT = 1;
        /// Processor is enabled.
        const PROCESSOR_ENABLED_BIT = 1 << 1;
        /// Processor is healthy.
        const PROCESSOR_HEALTH_STATUS_BIT = 1 << 2;
    }
}

/// Information about number of logical processors on the platform.
#[derive(Default, Debug)]
pub struct ProcessorCount {
    /// Total number of processors (including BSP).
    pub total: usize,
    /// Number of processors (including BSP) that are currently enabled.
    pub enabled: usize,
}

/// Information about processor on the platform.
#[repr(C)]
#[derive(Default, Debug)]
pub struct ProcessorInformation {
    /// Unique processor ID determined by system hardware.
    pub processor_id: u64,
    /// Flags indicating BSP, enabled and healthy status.
    status_flag: StatusFlag,
    /// Physical location of the processor.
    pub location: CpuPhysicalLocation,
}

impl ProcessorInformation {
    /// Returns `true` if the processor is playing the role of BSP.
    #[must_use]
    pub const fn is_bsp(&self) -> bool {
        self.status_flag.contains(StatusFlag::PROCESSOR_AS_BSP_BIT)
    }

    /// Returns `true` if the processor is enabled.
    #[must_use]
    pub const fn is_enabled(&self) -> bool {
        self.status_flag.contains(StatusFlag::PROCESSOR_ENABLED_BIT)
    }

    /// Returns `true` if the processor is healthy.
    #[must_use]
    pub const fn is_healthy(&self) -> bool {
        self.status_flag
            .contains(StatusFlag::PROCESSOR_HEALTH_STATUS_BIT)
    }
}

/// Information about physical location of the processor.
#[repr(C)]
#[derive(Default, Debug)]
pub struct CpuPhysicalLocation {
    /// Zero-based physical package number that identifies
    /// the cartridge of the processor.
    pub package: u32,
    /// Zero-based physical core number within package of the processor.
    pub core: u32,
    /// Zero-based logical thread number within core of the processor.
    pub thread: u32,
}

/// MP Services [`Protocol`].
///
/// Protocol that provides services needed for multi-processor management.
///
/// [`Protocol`]: uefi::proto::Protocol
#[derive(Debug)]
#[repr(C)]
#[unsafe_protocol("3fdda605-a76e-4f46-ad29-12f4531b3d08")]
pub struct MpServices {
    get_number_of_processors: extern "efiapi" fn(
        this: *const Self,
        number_of_processors: *mut usize,
        number_of_enabled_processors: *mut usize,
    ) -> Status,
    get_processor_info: extern "efiapi" fn(
        this: *const Self,
        processor_number: usize,
        processor_info_buffer: *mut ProcessorInformation,
    ) -> Status,
    startup_all_aps: extern "efiapi" fn(
        this: *const Self,
        procedure: Procedure,
        single_thread: bool,
        wait_event: *mut c_void,
        timeout_in_micro_seconds: usize,
        procedure_argument: *mut c_void,
        failed_cpu_list: *mut *mut usize,
    ) -> Status,
    startup_this_ap: extern "efiapi" fn(
        this: *const Self,
        procedure: Procedure,
        processor_number: usize,
        wait_event: *mut c_void,
        timeout_in_micro_seconds: usize,
        procedure_argument: *mut c_void,
        finished: *mut bool,
    ) -> Status,
    switch_bsp: extern "efiapi" fn(
        this: *const Self,
        processor_number: usize,
        enable_old_bsp: bool,
    ) -> Status,
    enable_disable_ap: extern "efiapi" fn(
        this: *const Self,
        processor_number: usize,
        enable_ap: bool,
        health_flag: *const u32,
    ) -> Status,
    who_am_i: extern "efiapi" fn(this: *const Self, processor_number: *mut usize) -> Status,
}

impl MpServices {
    /// Retrieves the number of logical processors and the number of enabled logical processors in the system.
    pub fn get_number_of_processors(&self) -> Result<ProcessorCount> {
        let mut total: usize = 0;
        let mut enabled: usize = 0;
        (self.get_number_of_processors)(self, &mut total, &mut enabled)
            .to_result_with_val(|| ProcessorCount { total, enabled })
    }

    /// Gets detailed information on the requested processor at the instant this call is made.
    pub fn get_processor_info(&self, processor_number: usize) -> Result<ProcessorInformation> {
        let mut pi: ProcessorInformation = Default::default();
        (self.get_processor_info)(self, processor_number, &mut pi).to_result_with_val(|| pi)
    }

    /// Executes provided function on all APs.
    pub fn startup_all_aps(
        &self,
        single_thread: bool,
        procedure: Procedure,
        procedure_argument: *mut c_void,
        event: Option<Event>,
        timeout: Option<Duration>,
    ) -> Result {
        let timeout_arg = match timeout {
            Some(timeout) => timeout.as_micros().try_into().unwrap(),
            None => 0,
        };

        let event_arg = match event {
            Some(event) => event.as_ptr(),
            None => ptr::null_mut(),
        };

        (self.startup_all_aps)(
            self,
            procedure,
            single_thread,
            event_arg,
            timeout_arg,
            procedure_argument,
            ptr::null_mut(),
        )
        .to_result()
    }

    /// Executes provided function on a specific AP in blocking mode.
    pub fn startup_this_ap(
        &self,
        processor_number: usize,
        procedure: Procedure,
        procedure_argument: *mut c_void,
        event: Option<Event>,
        timeout: Option<Duration>,
    ) -> Result {
        let timeout_arg = match timeout {
            Some(timeout) => timeout.as_micros().try_into().unwrap(),
            None => 0,
        };

        let event_arg = match event {
            Some(event) => event.as_ptr(),
            None => ptr::null_mut(),
        };

        (self.startup_this_ap)(
            self,
            procedure,
            processor_number,
            event_arg,
            timeout_arg,
            procedure_argument,
            ptr::null_mut(),
        )
        .to_result()
    }

    /// Switches the requested AP to be the BSP from that point onward.
    pub fn switch_bsp(&self, processor_number: usize, enable_old_bsp: bool) -> Result {
        (self.switch_bsp)(self, processor_number, enable_old_bsp).to_result()
    }

    /// Enables or disables an AP from this point onward.
    ///
    /// The `healthy` argument can be used to specify the new health status of the AP.
    pub fn enable_disable_ap(
        &self,
        processor_number: usize,
        enable_ap: bool,
        healthy: Option<bool>,
    ) -> Result {
        let health_flag_raw: u32;
        let health_flag_ptr = match healthy {
            Some(healthy) => {
                let mut sf = StatusFlag::empty();
                sf.set(StatusFlag::PROCESSOR_HEALTH_STATUS_BIT, healthy);
                health_flag_raw = sf.bits();
                &health_flag_raw
            }
            None => ptr::null(),
        };
        (self.enable_disable_ap)(self, processor_number, enable_ap, health_flag_ptr).to_result()
    }

    /// Gets the handle number of the caller processor.
    pub fn who_am_i(&self) -> Result<usize> {
        let mut processor_number: usize = 0;
        (self.who_am_i)(self, &mut processor_number).to_result_with_val(|| processor_number)
    }
}
