// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved.
//
// StratoVirt is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//         http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

pub mod caps;

pub use self::caps::ArmCPUFeatures;
pub use self::caps::CpregListEntry;

use std::sync::{Arc, Mutex};

use anyhow::{Context, Result};
use kvm_bindings::{
    kvm_mp_state as MpState, kvm_regs as Regs, kvm_vcpu_events as VcpuEvents,
    KVM_MP_STATE_RUNNABLE as MP_STATE_RUNNABLE, KVM_MP_STATE_STOPPED as MP_STATE_STOPPED,
};

use crate::CPU;
use migration::{
    DeviceStateDesc, FieldDesc, MigrationError, MigrationHook, MigrationManager, StateTransfer,
};
use migration_derive::{ByteCode, Desc};
use util::byte_code::ByteCode;

// PSR (Processor State Register) bits.
// See: https://elixir.bootlin.com/linux/v5.6/source/arch/arm64/include/uapi/asm/ptrace.h#L34
#[allow(non_upper_case_globals)]
const PSR_MODE_EL1h: u64 = 0x0000_0005;
const PSR_F_BIT: u64 = 0x0000_0040;
const PSR_I_BIT: u64 = 0x0000_0080;
const PSR_A_BIT: u64 = 0x0000_0100;
const PSR_D_BIT: u64 = 0x0000_0200;
// MPIDR is Multiprocessor Affinity Register
// [40:63] bit reserved on AArch64 Architecture,
const UNINIT_MPIDR: u64 = 0xFFFF_FF00_0000_0000;

/// Interrupt ID for pmu.
/// See: https://developer.arm.com/documentation/den0094/b/
/// And: https://developer.arm.com/documentation/dai0492/b/
pub const PPI_BASE: u32 = 16;
pub const PMU_INTR: u32 = 7;

/// AArch64 CPU booting configure information
///
/// Before jumping into the kernel, primary CPU general-purpose
/// register `x0` need to setting to physical address of device
/// tree blob (dtb) in system RAM.
///
/// See: https://elixir.bootlin.com/linux/v5.6/source/Documentation/arm64/booting.rst
#[derive(Default, Copy, Clone, Debug)]
pub struct ArmCPUBootConfig {
    pub fdt_addr: u64,
    pub boot_pc: u64,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ArmRegsIndex {
    CoreRegs,
    MpState,
    VcpuEvents,
    CpregList,
    VtimerCount,
}

#[derive(Default, Copy, Clone, Debug)]
pub struct ArmCPUTopology {}

impl ArmCPUTopology {
    pub fn new() -> Self {
        ArmCPUTopology::default()
    }

    pub fn set_topology(self, _topology: (u8, u8, u8)) -> Self {
        self
    }
}

/// AArch64 CPU architect information
#[repr(C)]
#[derive(Copy, Clone, Desc, ByteCode)]
#[desc_version(compat_version = "0.1.0")]
pub struct ArmCPUState {
    /// The vcpu id, `0` means primary CPU.
    pub apic_id: u32,
    /// MPIDR register value of this vcpu,
    /// The MPIDR provides an additional processor identification mechanism
    /// for scheduling purposes.
    pub mpidr: u64,
    /// Vcpu core registers.
    pub core_regs: Regs,
    /// Vcpu cpu events register.
    pub cpu_events: VcpuEvents,
    /// Vcpu mpstate register.
    pub mp_state: MpState,
    /// The length of Cpreg.
    pub cpreg_len: usize,
    /// The list of Cpreg.
    pub cpreg_list: [CpregListEntry; 512],
    /// Vcpu features
    pub features: ArmCPUFeatures,
    /// Virtual timer count.
    pub vtimer_cnt: u64,
}

impl ArmCPUState {
    /// Allocates a new `ArmCPUState`.
    ///
    /// # Arguments
    ///
    /// * `vcpu_id` - ID of this `CPU`.
    pub fn new(vcpu_id: u32) -> Self {
        let mp_state = MpState {
            mp_state: if vcpu_id == 0 {
                MP_STATE_RUNNABLE
            } else {
                MP_STATE_STOPPED
            },
        };

        ArmCPUState {
            apic_id: vcpu_id,
            mpidr: UNINIT_MPIDR,
            mp_state,
            ..Default::default()
        }
    }

    pub fn set(&mut self, cpu_state: &Arc<Mutex<ArmCPUState>>) {
        let locked_cpu_state = cpu_state.lock().unwrap();
        self.apic_id = locked_cpu_state.apic_id;
        self.mpidr = locked_cpu_state.mpidr;
        self.core_regs = locked_cpu_state.core_regs;
        self.cpu_events = locked_cpu_state.cpu_events;
        self.mp_state = locked_cpu_state.mp_state;
        self.cpreg_len = locked_cpu_state.cpreg_len;
        self.cpreg_list = locked_cpu_state.cpreg_list;
        self.features = locked_cpu_state.features;
    }

    /// Set cpu topology
    ///
    /// # Arguments
    ///
    /// * `topology` - ARM CPU Topology
    pub fn set_cpu_topology(&mut self, _topology: &ArmCPUTopology) -> Result<()> {
        Ok(())
    }

    /// Get mpidr value.
    pub fn mpidr(&self) -> u64 {
        self.mpidr
    }

    /// Get core_regs value.
    pub fn core_regs(&self) -> Regs {
        self.core_regs
    }

    pub fn set_core_reg(&mut self, boot_config: &ArmCPUBootConfig) {
        // Set core regs.
        self.core_regs.regs.pstate = PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1h;
        self.core_regs.regs.regs[1] = 0;
        self.core_regs.regs.regs[2] = 0;
        self.core_regs.regs.regs[3] = 0;

        // Configure boot ip and device tree address, prepare for kernel setup
        if self.apic_id == 0 {
            self.core_regs.regs.regs[0] = boot_config.fdt_addr;
            self.core_regs.regs.pc = boot_config.boot_pc;
        }
    }

    /// Get cpu features.
    pub fn get_features(&self) -> &ArmCPUFeatures {
        &self.features
    }
}

impl StateTransfer for CPU {
    fn get_state_vec(&self) -> Result<Vec<u8>> {
        self.hypervisor_cpu
            .get_regs(self.arch_cpu.clone(), ArmRegsIndex::CoreRegs)?;
        self.hypervisor_cpu
            .get_regs(self.arch_cpu.clone(), ArmRegsIndex::MpState)?;
        self.hypervisor_cpu
            .get_regs(self.arch_cpu.clone(), ArmRegsIndex::CpregList)?;
        self.hypervisor_cpu
            .get_regs(self.arch_cpu.clone(), ArmRegsIndex::VcpuEvents)?;

        Ok(self.arch_cpu.lock().unwrap().as_bytes().to_vec())
    }

    fn set_state(&self, state: &[u8]) -> Result<()> {
        let cpu_state = *ArmCPUState::from_bytes(state)
            .with_context(|| MigrationError::FromBytesError("CPU"))?;

        let mut cpu_state_locked = self.arch_cpu.lock().unwrap();
        *cpu_state_locked = cpu_state;
        drop(cpu_state_locked);

        Ok(())
    }

    fn get_device_alias(&self) -> u64 {
        MigrationManager::get_desc_alias(&ArmCPUState::descriptor().name).unwrap_or(!0)
    }
}

impl MigrationHook for CPU {}