// SPDX-License-Identifier: GPL-2.0

//! GSP Sequencer implementation for Pre-hopper GSP boot sequence.

use core::{
    array,
    mem::{
        size_of,
        size_of_val, //
    },
};

use kernel::{
    device,
    io::poll::read_poll_timeout,
    prelude::*,
    time::{
        delay::fsleep,
        Delta, //
    },
    transmute::FromBytes,
    types::ARef, //
};

use crate::{
    driver::Bar0,
    falcon::{
        gsp::Gsp,
        sec2::Sec2,
        Falcon, //
    },
    gsp::{
        cmdq::{
            Cmdq,
            MessageFromGsp, //
        },
        fw,
    },
    num::FromSafeCast,
    sbuffer::SBufferIter,
};

/// GSP Sequencer information containing the command sequence and data.
struct GspSequence {
    /// Current command index for error reporting.
    cmd_index: u32,
    /// Command data buffer containing the sequence of commands.
    cmd_data: KVec<u8>,
}

impl MessageFromGsp for GspSequence {
    const FUNCTION: fw::MsgFunction = fw::MsgFunction::GspRunCpuSequencer;
    type InitError = Error;
    type Message = fw::RunCpuSequencer;

    fn read(
        msg: &Self::Message,
        sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
    ) -> Result<Self, Self::InitError> {
        let cmd_data = sbuffer.flush_into_kvec(GFP_KERNEL)?;
        Ok(GspSequence {
            cmd_index: msg.cmd_index(),
            cmd_data,
        })
    }
}

const CMD_SIZE: usize = size_of::<fw::SequencerBufferCmd>();

/// GSP Sequencer Command types with payload data.
/// Commands have an opcode and an opcode-dependent struct.
#[allow(clippy::enum_variant_names)]
pub(crate) enum GspSeqCmd {
    RegWrite(fw::RegWritePayload),
    RegModify(fw::RegModifyPayload),
    RegPoll(fw::RegPollPayload),
    DelayUs(fw::DelayUsPayload),
    RegStore(fw::RegStorePayload),
    CoreReset,
    CoreStart,
    CoreWaitForHalt,
    CoreResume,
}

impl GspSeqCmd {
    /// Creates a new `GspSeqCmd` from raw data returning the command and its size in bytes.
    pub(crate) fn new(data: &[u8], dev: &device::Device) -> Result<(Self, usize)> {
        let fw_cmd = fw::SequencerBufferCmd::from_bytes(data).ok_or(EINVAL)?;
        let opcode_size = core::mem::size_of::<u32>();

        let (cmd, size) = match fw_cmd.opcode()? {
            fw::SeqBufOpcode::RegWrite => {
                let payload = fw_cmd.reg_write_payload()?;
                let size = opcode_size + size_of_val(&payload);
                (GspSeqCmd::RegWrite(payload), size)
            }
            fw::SeqBufOpcode::RegModify => {
                let payload = fw_cmd.reg_modify_payload()?;
                let size = opcode_size + size_of_val(&payload);
                (GspSeqCmd::RegModify(payload), size)
            }
            fw::SeqBufOpcode::RegPoll => {
                let payload = fw_cmd.reg_poll_payload()?;
                let size = opcode_size + size_of_val(&payload);
                (GspSeqCmd::RegPoll(payload), size)
            }
            fw::SeqBufOpcode::DelayUs => {
                let payload = fw_cmd.delay_us_payload()?;
                let size = opcode_size + size_of_val(&payload);
                (GspSeqCmd::DelayUs(payload), size)
            }
            fw::SeqBufOpcode::RegStore => {
                let payload = fw_cmd.reg_store_payload()?;
                let size = opcode_size + size_of_val(&payload);
                (GspSeqCmd::RegStore(payload), size)
            }
            fw::SeqBufOpcode::CoreReset => (GspSeqCmd::CoreReset, opcode_size),
            fw::SeqBufOpcode::CoreStart => (GspSeqCmd::CoreStart, opcode_size),
            fw::SeqBufOpcode::CoreWaitForHalt => (GspSeqCmd::CoreWaitForHalt, opcode_size),
            fw::SeqBufOpcode::CoreResume => (GspSeqCmd::CoreResume, opcode_size),
        };

        if data.len() < size {
            dev_err!(dev, "Data is not enough for command");
            return Err(EINVAL);
        }

        Ok((cmd, size))
    }
}

/// GSP Sequencer for executing firmware commands during boot.
pub(crate) struct GspSequencer<'a> {
    /// Sequencer information with command data.
    seq_info: GspSequence,
    /// `Bar0` for register access.
    bar: &'a Bar0,
    /// SEC2 falcon for core operations.
    sec2_falcon: &'a Falcon<Sec2>,
    /// GSP falcon for core operations.
    gsp_falcon: &'a Falcon<Gsp>,
    /// LibOS DMA handle address.
    libos_dma_handle: u64,
    /// Bootloader application version.
    bootloader_app_version: u32,
    /// Device for logging.
    dev: ARef<device::Device>,
}

/// Trait for running sequencer commands.
pub(crate) trait GspSeqCmdRunner {
    fn run(&self, sequencer: &GspSequencer<'_>) -> Result;
}

impl GspSeqCmdRunner for fw::RegWritePayload {
    fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
        let addr = usize::from_safe_cast(self.addr());

        sequencer.bar.try_write32(self.val(), addr)
    }
}

impl GspSeqCmdRunner for fw::RegModifyPayload {
    fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
        let addr = usize::from_safe_cast(self.addr());

        sequencer.bar.try_read32(addr).and_then(|val| {
            sequencer
                .bar
                .try_write32((val & !self.mask()) | self.val(), addr)
        })
    }
}

impl GspSeqCmdRunner for fw::RegPollPayload {
    fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
        let addr = usize::from_safe_cast(self.addr());

        // Default timeout to 4 seconds.
        let timeout_us = if self.timeout() == 0 {
            4_000_000
        } else {
            i64::from(self.timeout())
        };

        // First read.
        sequencer.bar.try_read32(addr)?;

        // Poll the requested register with requested timeout.
        read_poll_timeout(
            || sequencer.bar.try_read32(addr),
            |current| (current & self.mask()) == self.val(),
            Delta::ZERO,
            Delta::from_micros(timeout_us),
        )
        .map(|_| ())
    }
}

impl GspSeqCmdRunner for fw::DelayUsPayload {
    fn run(&self, _sequencer: &GspSequencer<'_>) -> Result {
        fsleep(Delta::from_micros(i64::from(self.val())));
        Ok(())
    }
}

impl GspSeqCmdRunner for fw::RegStorePayload {
    fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
        let addr = usize::from_safe_cast(self.addr());

        sequencer.bar.try_read32(addr).map(|_| ())
    }
}

impl GspSeqCmdRunner for GspSeqCmd {
    fn run(&self, seq: &GspSequencer<'_>) -> Result {
        match self {
            GspSeqCmd::RegWrite(cmd) => cmd.run(seq),
            GspSeqCmd::RegModify(cmd) => cmd.run(seq),
            GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
            GspSeqCmd::DelayUs(cmd) => cmd.run(seq),
            GspSeqCmd::RegStore(cmd) => cmd.run(seq),
            GspSeqCmd::CoreReset => {
                seq.gsp_falcon.reset(seq.bar)?;
                seq.gsp_falcon.dma_reset(seq.bar);
                Ok(())
            }
            GspSeqCmd::CoreStart => {
                seq.gsp_falcon.start(seq.bar)?;
                Ok(())
            }
            GspSeqCmd::CoreWaitForHalt => {
                seq.gsp_falcon.wait_till_halted(seq.bar)?;
                Ok(())
            }
            GspSeqCmd::CoreResume => {
                // At this point, 'SEC2-RTOS' has been loaded into SEC2 by the sequencer
                // but neither SEC2-RTOS nor GSP-RM is running yet. This part of the
                // sequencer will start both.

                // Reset the GSP to prepare it for resuming.
                seq.gsp_falcon.reset(seq.bar)?;

                // Write the libOS DMA handle to GSP mailboxes.
                seq.gsp_falcon.write_mailboxes(
                    seq.bar,
                    Some(seq.libos_dma_handle as u32),
                    Some((seq.libos_dma_handle >> 32) as u32),
                );

                // Start the SEC2 falcon which will trigger GSP-RM to resume on the GSP.
                seq.sec2_falcon.start(seq.bar)?;

                // Poll until GSP-RM reload/resume has completed (up to 2 seconds).
                seq.gsp_falcon
                    .check_reload_completed(seq.bar, Delta::from_secs(2))?;

                // Verify SEC2 completed successfully by checking its mailbox for errors.
                let mbox0 = seq.sec2_falcon.read_mailbox0(seq.bar);
                if mbox0 != 0 {
                    dev_err!(seq.dev, "Sequencer: sec2 errors: {:?}\n", mbox0);
                    return Err(EIO);
                }

                // Configure GSP with the bootloader version.
                seq.gsp_falcon
                    .write_os_version(seq.bar, seq.bootloader_app_version);

                // Verify the GSP's RISC-V core is active indicating successful GSP boot.
                if !seq.gsp_falcon.is_riscv_active(seq.bar) {
                    dev_err!(seq.dev, "Sequencer: RISC-V core is not active\n");
                    return Err(EIO);
                }
                Ok(())
            }
        }
    }
}

/// Iterator over GSP sequencer commands.
pub(crate) struct GspSeqIter<'a> {
    /// Command data buffer.
    cmd_data: &'a [u8],
    /// Current position in the buffer.
    current_offset: usize,
    /// Total number of commands to process.
    total_cmds: u32,
    /// Number of commands processed so far.
    cmds_processed: u32,
    /// Device for logging.
    dev: ARef<device::Device>,
}

impl<'a> Iterator for GspSeqIter<'a> {
    type Item = Result<GspSeqCmd>;

    fn next(&mut self) -> Option<Self::Item> {
        // Stop if we've processed all commands or reached the end of data.
        if self.cmds_processed >= self.total_cmds || self.current_offset >= self.cmd_data.len() {
            return None;
        }

        // Check if we have enough data for opcode.
        if self.current_offset + core::mem::size_of::<u32>() > self.cmd_data.len() {
            return Some(Err(EIO));
        }

        let offset = self.current_offset;

        // Handle command creation based on available data,
        // zero-pad if necessary (since last command may not be full size).
        let mut buffer = [0u8; CMD_SIZE];
        let copy_len = if offset + CMD_SIZE <= self.cmd_data.len() {
            CMD_SIZE
        } else {
            self.cmd_data.len() - offset
        };
        buffer[..copy_len].copy_from_slice(&self.cmd_data[offset..offset + copy_len]);
        let cmd_result = GspSeqCmd::new(&buffer, &self.dev);

        cmd_result.map_or_else(
            |_err| {
                dev_err!(self.dev, "Error parsing command at offset {}", offset);
                None
            },
            |(cmd, size)| {
                self.current_offset += size;
                self.cmds_processed += 1;
                Some(Ok(cmd))
            },
        )
    }
}

impl<'a> GspSequencer<'a> {
    fn iter(&self) -> GspSeqIter<'_> {
        let cmd_data = &self.seq_info.cmd_data[..];

        GspSeqIter {
            cmd_data,
            current_offset: 0,
            total_cmds: self.seq_info.cmd_index,
            cmds_processed: 0,
            dev: self.dev.clone(),
        }
    }
}

/// Parameters for running the GSP sequencer.
pub(crate) struct GspSequencerParams<'a> {
    /// Bootloader application version.
    pub(crate) bootloader_app_version: u32,
    /// LibOS DMA handle address.
    pub(crate) libos_dma_handle: u64,
    /// GSP falcon for core operations.
    pub(crate) gsp_falcon: &'a Falcon<Gsp>,
    /// SEC2 falcon for core operations.
    pub(crate) sec2_falcon: &'a Falcon<Sec2>,
    /// Device for logging.
    pub(crate) dev: ARef<device::Device>,
    /// BAR0 for register access.
    pub(crate) bar: &'a Bar0,
}

impl<'a> GspSequencer<'a> {
    pub(crate) fn run(cmdq: &mut Cmdq, params: GspSequencerParams<'a>) -> Result {
        let seq_info = loop {
            match cmdq.receive_msg::<GspSequence>(Delta::from_secs(10)) {
                Ok(seq_info) => break seq_info,
                Err(ERANGE) => continue,
                Err(e) => return Err(e),
            }
        };

        let sequencer = GspSequencer {
            seq_info,
            bar: params.bar,
            sec2_falcon: params.sec2_falcon,
            gsp_falcon: params.gsp_falcon,
            libos_dma_handle: params.libos_dma_handle,
            bootloader_app_version: params.bootloader_app_version,
            dev: params.dev,
        };

        dev_dbg!(sequencer.dev, "Running CPU Sequencer commands");

        for cmd_result in sequencer.iter() {
            match cmd_result {
                Ok(cmd) => cmd.run(&sequencer)?,
                Err(e) => {
                    dev_err!(
                        sequencer.dev,
                        "Error running command at index {}",
                        sequencer.seq_info.cmd_index
                    );
                    return Err(e);
                }
            }
        }

        dev_dbg!(
            sequencer.dev,
            "CPU Sequencer commands completed successfully"
        );
        Ok(())
    }
}
