mirror of https://github.com/torvalds/linux.git
408 lines
13 KiB
Rust
408 lines
13 KiB
Rust
// 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(())
|
|
}
|
|
}
|