// SPDX-License-Identifier: GPL-2.0

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

use core::array;

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

use crate::{
    driver::Bar0,
    falcon::{
        gsp::Gsp,
        sec2::Sec2,
        Falcon, //
    },
    gsp::{
        cmdq::{
            Cmdq,
            MessageFromGsp, //
        },
        fw,
        GspBootContext,
        LibosMemoryRegionInitArgument, //
    },
    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)]
#[derive(Debug)]
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\n");
            return Err(EINVAL);
        }

        Ok((cmd, size))
    }
}

/// GSP Sequencer for executing firmware commands during boot.
pub(crate) struct GspSequencer<'a> {
    /// `Bar0` for register access.
    bar: Bar0<'a>,
    /// SEC2 falcon for core operations.
    sec2_falcon: &'a Falcon<'a, Sec2>,
    /// GSP falcon for core operations.
    gsp_falcon: &'a Falcon<'a, Gsp>,
    /// LibOS memory region init arguments.
    libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
    /// Bootloader application version.
    bootloader_app_version: u32,
    /// Device for logging.
    dev: &'a device::Device,
}

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

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

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

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

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

impl 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.gsp_falcon.dma_reset();
                Ok(())
            }
            GspSeqCmd::CoreStart => {
                seq.gsp_falcon.start()?;
                Ok(())
            }
            GspSeqCmd::CoreWaitForHalt => {
                seq.gsp_falcon.wait_till_halted()?;
                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()?;

                let libos_dma_address = seq.libos.dma_address();

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

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

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

                // Verify SEC2 completed successfully by checking its mailbox for errors.
                let mbox0 = seq.sec2_falcon.read_mailbox0();
                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.bootloader_app_version);

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

/// Iterator over GSP sequencer commands.
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: &'a device::Device,
}

impl<'a> GspSeqIter<'a> {
    fn new(seq: &'a GspSequence, dev: &'a device::Device) -> Self {
        Self {
            cmd_data: &seq.cmd_data,
            current_offset: 0,
            total_cmds: seq.cmd_index,
            cmds_processed: 0,
            dev,
        }
    }
}

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 {}\n", offset);
                None
            },
            |(cmd, size)| {
                self.current_offset += size;
                self.cmds_processed += 1;
                Some(Ok(cmd))
            },
        )
    }
}

impl<'a> GspSequencer<'a> {
    pub(crate) fn run(
        cmdq: &Cmdq,
        ctx: &'a GspBootContext<'_, '_>,
        libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
        bootloader_app_version: u32,
    ) -> Result {
        let seq_info = loop {
            match cmdq.receive_msg::<GspSequence>(Cmdq::RECEIVE_TIMEOUT) {
                Ok(seq_info) => break seq_info,
                Err(ERANGE) => continue,
                Err(e) => return Err(e),
            }
        };

        let sequencer = GspSequencer {
            bar: ctx.bar,
            sec2_falcon: ctx.sec2_falcon,
            gsp_falcon: ctx.gsp_falcon,
            libos,
            bootloader_app_version,
            dev: ctx.dev(),
        };

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

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

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