//! 系统多核配置和管理

use moa_bsp_define::smp::SecondaryData;
use moa_cpumask::{CpuId, Cpumask};

use crate::{
    bsp::{__irqchip_ipi_send_mask, __irqchip_ipi_send_single, __smp_define},
    println,
    processor::{PROCESSOR_CPU_ON, cpu_relax, nr_cpus, this_processor_id},
    sched::task::{get_idle_task_info, stack::THREAD_STACK_SIZE},
    smp::ipi::IPI_CPU_STOP,
    space::addr::Kaddr,
    time::{USEC_PER_SEC, delay::udelay},
};

pub(crate) mod ipi;
pub(crate) mod smp_call;

/// 上线处理器
pub static CPU_ONLINE: Cpumask = Cpumask::new();

pub(crate) fn smp_init() {
    unsafe extern "C" {
        fn secondary_entry();
    }

    #[cfg(aarch64_moana)]
    crate::drivers::psci::psci_init();

    println!("Bringing up secondary CPUs ...");
    let entry = Kaddr::from(secondary_entry as *const () as usize).to_phys().to_value();
    for cpu in 1..nr_cpus() {
        let (task, stack) = get_idle_task_info(cpu);
        SECONDARY_DATA.set_stack(stack + THREAD_STACK_SIZE);
        SECONDARY_DATA.set_task(task);
        unsafe {
            unsafe extern "C" {
                fn __flush_dcache_area(addr: usize, len: usize);
            }
            __flush_dcache_area(&raw const SECONDARY_DATA as usize, size_of::<SecondaryData>());
            PROCESSOR_CPU_ON(__smp_define().hwid(cpu), entry);
            while !CPU_ONLINE.test(CpuId::from_raw(cpu)) {
                cpu_relax();
            }
        }
    }

    println!("SMP: Total of {} processors activated.", CPU_ONLINE.weight());
}

/// 从核启动数据
#[unsafe(no_mangle)]
static SECONDARY_DATA: SecondaryData = SecondaryData::new();

/// 向一个处理器发送ipi中断
pub fn send_ipi_one(irqnr: u32, cpu: usize) {
    unsafe {
        __irqchip_ipi_send_single(irqnr, cpu);
    }
}

/// 向一组处理器发送ipi中断
pub fn send_ipi_cpumask(irqnr: u32, cpumask: &Cpumask) {
    unsafe {
        __irqchip_ipi_send_mask(irqnr, cpumask);
    }
}

/// 除当前处理器以外, 其他所有处理器停机
pub fn smp_send_stop() {
    if CPU_ONLINE.weight() > 1 {
        let mask = CPU_ONLINE.clone();
        mask.clear(this_processor_id());
        send_ipi_cpumask(IPI_CPU_STOP, &mask);
    }

    let mut timeout = USEC_PER_SEC;
    while CPU_ONLINE.weight() > 1 && timeout > 0 {
        udelay(1);
        timeout -= 1;
    }

    if CPU_ONLINE.weight() > 1 {
        println!("SMP: failed to stop secondary CPUs");
    }
}
