Files
linux-stable-mirror/drivers/gpu/nova-core/gsp/cmdq.rs
T
Linus Torvalds 4b99990cdf Merge tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel
Pull drm updates from Dave Airlie:
 "Highlights:
   - xe: add initial CRI platform support
   - amdgpu: initial HDMI 2.1 FRL support
   - rust: add some new type concepts for device lifetimes
   - scheduler: moves to a fair algorithm and lots of cleanups

  But it's mostly the usual mountain of changes across the board.

  core:
   - add docbook for DRM_IOCTL_SYNCOBJ_EVENTFD
   - change signature of drm_connector_attach_hdr_output_metadata_property
   - dedup counter and timestamp retrieval in vblank code
   - parse AMD VSDB v3 in CTA extension blocks
   - add P230, Y7, XYYY2101010, T430, XVUY210101010 formats
   - don't call drop master on file close if not master
   - use drm_printf_indent in atomic / bridge
   - fix 32b format descriptions
   - docs: fix toctree
   - hdmi: add common TMDS character rates
   - fix drm_syncobj_find_fence leak

  rust:
   - introduce Higher-Ranked lifetime types
   - replace drvdata with scoped registration data
   - add GPUVM immediate mode abstraction for rust GPU drivers
   - introduce DeviceContext type state for drm::Device

  bridge:
   - clarify drm_bridge_get/put
   - create drm_get_bridge_by_endpoint and use it
   - analogix_dp: add panel probing
   - ite-it6211 - use drm audio hdmi helpers

  buddy:
   - add lockdep annotations

  dp:
   - add PR and VRR updates
   - mst: fix buffer overflows
   - add Adaptive Sync SDP decoding support
   - fix OOB reads in dp-mst

  ttm:
   - bump fpfn/lpfn to 64-bit

  scheduler:
   - change default to fair scheduler
   - map runqueue 1:1 with scheduler

  dma-buf:
   - port selftests to kunit
   - convert dma-buf system/heap allocators to module
   - add separate DMABUF_HEAPS_SYSTEM_CC_SHARED Kconfig

  udmabuf:
   - revert hugetlb support
   - fix error with CONFIG_DMA_API_DEBUG

  dma-fence:
   - fix tracepoints lifetime
   - remove unused signal on any support

  ras:
   - add clear error counter netlink command to drm ras

  gpusvm:
   - reject VMAs with VM_IO or VM_PFNMAP when creating SVM ranges
   - use IOVA allocations

  pagemap:
   - use IOVA allocations

  panels:
   - update to use ref counts
   - add support for CSW PNB601LS1-2, LGD LP116WHA-SPB1
   - add support for waveshare panels
   - CMN N116BCN-EA1, CMN N140HCA-EEK, IVO M140NWFQ R5,
   - IVO, R140NWFW R0, BOE NT140*, BOE NV133FHM-N4F,
   - AUO B140*, AUO B133HAN06.6 and AUO B116XTN02.3 eDP panels
   - Surface Pro 12 Panel

  xe:
   - add CRI PCI-IDs
   - debugfs add multi-lrc info
   - engine init cleanup
   - PF fair scheduling auto provisioning
   - system controller support for CRI/Xe3p
   - PXP state machine fixes
   - Reset/wedge/unload corner case fixes
   - Wedge path memory allocation fixes
   - PAT type cleanups
   - Reject unsafe PAT for CPU cached memory
   - OA improvements for CRI device memory
   - kernel doc syntax in xe headers
   - xe_drm.h documentation fixes
   - include guard cleanups
   - VF CCS memory pool
   - i915/xe step unification
   - Xe3p GT tuning fixes
   - forcewake cleanup in GT and GuC
   - admin-only PF mode
   - enable hwmon energy attributes for CRI
   - enable GT_MI_USER_INTERRUPT
   - refactor emit functions
   - oa workarounds
   - multi_queue: allow QUEUE_TIMESTAMP register
   - convert stolen memory to ttm range manager
   - use xe2 style blitter as a feature flag
   - make drm_driver const
   - add/use IRQ page to HW engine definition
   - fix oops when display disabled

  i915:
   - enable PIPEDMC_ERROR interrupt
   - more common display code refactoring
   - restructure DP/HDMI sink format handling
   - eliminate FB usage from lowlevel pinning code
   - panel replay bw optimization
   - integrate sharpness filter into the scaler
   - new fb_pin abstraction for xe/i915 fb transparent handling
   - skip inactive MST connectors on HDCP
   - start switching to display specific registers
   - use polling when irq unavailable
   - Adaptive-sync SDP prep

  amdgpu:
   - use drm_display_info for AMD VSDB data
   - Initial HDMI 2.1 FRL support
   - Initial DCN 4.2.1 support
   - GART fixes for non-4k pages
   - GC 11.5.6/SDMA 6.4.0/and other new IPs
   - GFX9/DCE6/Hawaii/SDMA4/GART/Userq fixes
   - Finish support for using multiple SDMA queues for TTM operations
   - SWSMU updates
   - GC 12.1 updates
   - SMU 15.0.8 updates
   - DCN 4.2 updates
   - DC type conversion fixes
   - Enable DC power module
   - Replay/PSR updates
   - SMU 13.x updates
   - Compute queue quantum MQD updates
   - ASPM fix
   - Align VKMS with common implementation
   - DC analog support fixes
   - UVD 3 fixes
   - TCC harvesting fixes for SI
   - GC 11 APU module reload fix
   - NBIO 6.3.2 support
   - IH 7.1 updates
   - DC cursor fixes
   - VCN/JPEG user fence fixes
   - DC support for connectors without DDC
   - Prefer ROM BAR for default VGA device
   - DC bandwidth fixes
   - Add PTL support for profiler
   - Introduce dc_plane_cm and migrate surface update color path
   - Add FRL registers for HDMI 2.1
   - Restructure VM state machine
   - Auxless ALPM support
   - GEM_OP locking/warning fixes
   - switch to system_dfl_wq

  amdkfd:
   - GPUVM TLB flush fix
   - Hotplug fix
   - Boundary check fixes
   - SVM fixes
   - CRIU fixes
   - add profiler API
   - MES 12.1 updates

  msm:
   - core:
     - fix shrinker documentation
     - IFPC enabled for gen8
     - PERFCNTR_CONFIG ioctl support
   - GPU:
     - reworked UBWC handling
     - a810 support
   - MDSS:
     - add support for Milos platform
     - reworked UBWC handling
   - DisplayPort:
     - reworked HPD handling as prep for MST
   - DPU:
     - Milos platform support
     - reworked UBWC handling
   - DSI:
     - Milos platform support

  nova:
   - Hopper/Blackwell enablement (GH100/GB100/GB202)
     - FSP support
     - 32-bit firmware support
     - HAL functions
   - refactor GSP boot/unload
   - GA100 support
   - VBIOS hardening/refactoring
   - Adopt higher order lifetime types

  tyr:
   - define register blocks
   - add shmem backed GEM objects
   - adopt higher order lifetime types
   - move clock cleanup into Drop

  radeon:
   - Hawaii SMU fixes
   - CS parser fix
   - use struct drm_edid instead of edid

  amdxdna:
   - export per-client BO memory via fdinfo
   - AIE4 device support
   - support medium/lower power modes
   - expandable device heap support
   - revert read-only user-pointer BO mappings

  ivpu:
   - support frequency limiting

  panthor:
   - enable GEM shrinker support
   - add eviction and reclaim info to fdinfo

  v3d:
   - enable runtime PM

  mgag200:
   - support XRGB1555 + C8

  ast:
   - support XRGB1555 + C8
   - use constants for lots of registers
   - fix register handling

  imagination:
   - fence handling refactoring

  nouveau:
   - fix sched double call
   - expose VBIOS on GSP-RM systems
   - add GA100 support

  virtio:
   - add VIRTIO_GPU_F_BLOB_ALIGNMENT flag
   - add deferred mapping support

  gud:
   - add RCade Display Adapter

  hibmc:
   - fix no connectors usage

  mediatek:
   - hdmi: convert error handling
   - simplify mtk_crtc allocation

  exynos:
   - move fbdev emulation to drm client buffers
   - use drm format helpers for geometry/size
   - adopt core DMA tracking
   - fix framebuffer offset handling

  renesas:
   - add RZ/T2H SOC support

  versilicon:
   - add cursor plane support

  tegra:
   - use drm client for framebuffer"

* tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel: (1731 commits)
  dma-buf: move system_cc_shared heap under separate Kconfig
  accel/amdxdna: Clear sva pointer after unbind
  agp/amd64: Fix broken error propagation in agp_amd64_probe()
  accel/amdxdna: Require carveout when PASID and force_iova are disabled
  drm/amdkfd: always resume_all after suspend_all
  drm/amdgpu/gfx: move fault and EOP IRQ get/put to hw_init/hw_fini
  drm/amd/display: Consult MCCS FreeSync cap only if requested & supported
  drm/amd/pm: Use strscpy in profile mode parsing
  drm/amdkfd: Fix infinite loop parsing CRAT with zero subtype length
  drm/amdkfd: fix sysfs topology prop length on buffer truncation
  drm/amdgpu: drop retry loop in amdgpu_hmm_range_get_pages
  drm/amd/pm: bound OD parameter parsing to stack array size
  drm/amd/pm: Stop pp_od_clk_voltage emit at PAGE_SIZE
  drm/amdkfd: Unwind debug trap enable on copy_to_user failure
  drm/amdgpu: validate the mes firmware version for gfx12.1
  drm/amdgpu: validate the mes firmware version for gfx12
  drm/amdgpu: compare MES firmware version ucode for gfx11
  drm/amdkfd: Add bounds check for AMDKFD_IOC_WAIT_EVENTS
  drm/amdgpu: restart the CS if some parts of the VM are still invalidated
  drm/amd/display: use unsigned types for local pipe and REG_GET counters
  ...
2026-06-17 10:21:00 +01:00

850 lines
32 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
mod continuation;
use core::mem;
use kernel::{
device,
dma::{
Coherent,
DmaAddress, //
},
dma_write,
io::{
poll::read_poll_timeout,
Io, //
},
new_mutex,
prelude::*,
ptr,
sync::{
aref::ARef,
Mutex, //
},
time::Delta,
transmute::{
AsBytes,
FromBytes, //
},
};
use continuation::{
ContinuationRecord,
SplitState, //
};
use pin_init::pin_init_scope;
use crate::{
driver::Bar0,
gsp::{
fw::{
GspMsgElement,
MsgFunction,
MsgqRxHeader,
MsgqTxHeader,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
GSP_PAGE_SHIFT,
GSP_PAGE_SIZE, //
},
num,
regs,
sbuffer::SBufferIter, //
};
/// Marker type representing the absence of a reply for a command. Commands using this as their
/// reply type are sent using [`Cmdq::send_command_no_wait`].
pub(crate) struct NoReply;
/// Trait implemented by types representing a command to send to the GSP.
///
/// The main purpose of this trait is to provide [`Cmdq`] with the information it needs to send
/// a given command.
///
/// [`CommandToGsp::init`] in particular is responsible for initializing the command directly
/// into the space reserved for it in the command queue buffer.
///
/// Some commands may be followed by a variable-length payload. For these, the
/// [`CommandToGsp::variable_payload_len`] and [`CommandToGsp::init_variable_payload`] need to be
/// defined as well.
pub(crate) trait CommandToGsp {
/// Function identifying this command to the GSP.
const FUNCTION: MsgFunction;
/// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
type Command: FromBytes + AsBytes;
/// Type of the reply expected from the GSP, or [`NoReply`] for commands that don't
/// have a reply.
type Reply;
/// Error type returned by [`CommandToGsp::init`].
type InitError;
/// In-place command initializer responsible for filling the command in the command queue
/// buffer.
fn init(&self) -> impl Init<Self::Command, Self::InitError>;
/// Size of the variable-length payload following the command structure generated by
/// [`CommandToGsp::init`].
///
/// Most commands don't have a variable-length payload, so this is zero by default.
fn variable_payload_len(&self) -> usize {
0
}
/// Method initializing the variable-length payload.
///
/// The command buffer is circular, which means that we may need to jump back to its beginning
/// while in the middle of a command. For this reason, the variable-length payload is
/// initialized using a [`SBufferIter`].
///
/// This method will receive a buffer of the length returned by
/// [`CommandToGsp::variable_payload_len`], and must write every single byte of it. Leaving
/// unwritten space will lead to an error.
///
/// Most commands don't have a variable-length payload, so this does nothing by default.
fn init_variable_payload(
&self,
_dst: &mut SBufferIter<core::array::IntoIter<&mut [u8], 2>>,
) -> Result {
Ok(())
}
/// Total size of the command (including its variable-length payload) without the
/// [`GspMsgElement`] header.
fn size(&self) -> usize {
size_of::<Self::Command>() + self.variable_payload_len()
}
}
/// Trait representing messages received from the GSP.
///
/// This trait tells [`Cmdq::receive_msg`] how it can receive a given type of message.
pub(crate) trait MessageFromGsp: Sized {
/// Function identifying this message from the GSP.
const FUNCTION: MsgFunction;
/// Error type returned by [`MessageFromGsp::read`].
type InitError;
/// Type containing the raw message to be read from the message queue.
type Message: FromBytes;
/// Method reading the message from the message queue and returning it.
///
/// From a `Self::Message` and a [`SBufferIter`], constructs an instance of `Self` and returns
/// it.
fn read(
msg: &Self::Message,
sbuffer: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>,
) -> Result<Self, Self::InitError>;
}
/// Number of GSP pages making the [`Msgq`].
pub(crate) const MSGQ_NUM_PAGES: u32 = 0x3f;
/// Circular buffer of a [`Msgq`].
///
/// This area of memory is to be shared between the driver and the GSP to exchange commands or
/// messages.
#[repr(C, align(0x1000))]
#[derive(Debug)]
struct MsgqData {
data: [[u8; GSP_PAGE_SIZE]; num::u32_as_usize(MSGQ_NUM_PAGES)],
}
// Annoyingly we are forced to use a literal to specify the alignment of
// `MsgqData`, so check that it corresponds to the actual GSP page size here.
static_assert!(align_of::<MsgqData>() == GSP_PAGE_SIZE);
/// Unidirectional message queue.
///
/// Contains the data for a message queue, that either the driver or GSP writes to.
///
/// Note that while the write pointer of `tx` corresponds to the `msgq` of the same instance, the
/// read pointer of `rx` actually refers to the `Msgq` owned by the other side.
/// This design ensures that only the driver or GSP ever writes to a given instance of this struct.
#[repr(C)]
// There is no struct defined for this in the open-gpu-kernel-source headers.
// Instead it is defined by code in `GspMsgQueuesInit()`.
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
pub(super) struct Msgq {
/// Header for sending messages, including the write pointer.
pub(super) tx: MsgqTxHeader,
/// Header for receiving messages, including the read pointer.
pub(super) rx: MsgqRxHeader,
/// The message queue proper.
msgq: MsgqData,
}
/// Structure shared between the driver and the GSP and containing the command and message queues.
#[repr(C)]
// TODO: Revert to private once `IoView` projections replace the `gsp_mem` module.
pub(super) struct GspMem {
/// Self-mapping page table entries.
ptes: PteArray<{ Self::PTE_ARRAY_SIZE }>,
/// CPU queue: the driver writes commands here, and the GSP reads them. It also contains the
/// write and read pointers that the CPU updates. This means that the read pointer here is an
/// index into the GSP queue.
///
/// This member is read-only for the GSP.
pub(super) cpuq: Msgq,
/// GSP queue: the GSP writes messages here, and the driver reads them. It also contains the
/// write and read pointers that the GSP updates. This means that the read pointer here is an
/// index into the CPU queue.
///
/// This member is read-only for the driver.
pub(super) gspq: Msgq,
}
impl GspMem {
const PTE_ARRAY_SIZE: usize = GSP_PAGE_SIZE / size_of::<u64>();
}
// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl AsBytes for GspMem {}
// SAFETY: These structs don't meet the no-padding requirements of FromBytes but
// that is not a problem because they are not used outside the kernel.
unsafe impl FromBytes for GspMem {}
/// Wrapper around [`GspMem`] to share it with the GPU using a [`Coherent`].
///
/// This provides the low-level functionality to communicate with the GSP, including allocation of
/// queue space to write messages to and management of read/write pointers.
///
/// This is shared with the GSP, with clear ownership rules regarding the command queues:
///
/// * The driver owns (i.e. can write to) the part of the CPU message queue between the CPU write
/// pointer and the GSP read pointer. This region is returned by [`Self::driver_write_area`].
/// * The driver owns (i.e. can read from) the part of the GSP message queue between the CPU read
/// pointer and the GSP write pointer. This region is returned by [`Self::driver_read_area`].
struct DmaGspMem(Coherent<GspMem>);
impl DmaGspMem {
/// Allocate a new instance and map it for `dev`.
fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::<Msgq>() }>();
const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();
let gsp_mem = Coherent::<GspMem>::zeroed(dev, GFP_KERNEL)?;
let start = gsp_mem.dma_handle();
// Write values one by one to avoid an on-stack instance of `PteArray`.
for i in 0..GspMem::PTE_ARRAY_SIZE {
dma_write!(gsp_mem, .ptes.0[build: i], PteArray::<0>::entry(start, i)?);
}
dma_write!(
gsp_mem,
.cpuq.tx,
MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES)
);
dma_write!(gsp_mem, .cpuq.rx, MsgqRxHeader::new());
Ok(Self(gsp_mem))
}
/// Returns the region of the CPU message queue that the driver is currently allowed to write
/// to.
///
/// As the message queue is a circular buffer, the region may be discontiguous in memory. In
/// that case the second slice will have a non-zero length.
fn driver_write_area(&mut self) -> (&mut [[u8; GSP_PAGE_SIZE]], &mut [[u8; GSP_PAGE_SIZE]]) {
let tx = self.cpu_write_ptr();
let rx = self.gsp_read_ptr();
// Pointer to the first entry of the CPU message queue.
let data = ptr::project!(mut self.0.as_mut_ptr(), .cpuq.msgq.data[build: 0]);
let (tail_end, wrap_end) = if rx == 0 {
// The write area is non-wrapping, and stops at the second-to-last entry of the command
// queue (to leave the last one empty).
(MSGQ_NUM_PAGES - 1, 0)
} else if rx <= tx {
// The write area wraps and continues until `rx - 1`.
(MSGQ_NUM_PAGES, rx - 1)
} else {
// The write area doesn't wrap and stops at `rx - 1`.
(rx - 1, 0)
};
// SAFETY:
// - `data` was created from a valid pointer, and `rx` and `tx` are in the
// `0..MSGQ_NUM_PAGES` range per the invariants of `cpu_write_ptr` and `gsp_read_ptr`,
// thus the created slices are valid.
// - The area starting at `tx` and ending at `rx - 2` modulo `MSGQ_NUM_PAGES`,
// inclusive, belongs to the driver for writing and is not accessed concurrently by
// the GSP.
// - The caller holds a reference to `self` for as long as the returned slices are live,
// meaning the CPU write pointer cannot be advanced and thus that the returned area
// remains exclusive to the CPU for the duration of the slices.
// - The created slices point to non-overlapping sub-ranges of `data` in all
// branches (in the `rx <= tx` case, the second slice ends at `rx - 1` which is strictly
// less than `tx` where the first slice starts; in the other cases the second slice is
// empty), so creating two `&mut` references from them does not violate aliasing rules.
unsafe {
(
core::slice::from_raw_parts_mut(
data.add(num::u32_as_usize(tx)),
num::u32_as_usize(tail_end - tx),
),
core::slice::from_raw_parts_mut(data, num::u32_as_usize(wrap_end)),
)
}
}
/// Returns the size of the region of the CPU message queue that the driver is currently allowed
/// to write to, in bytes.
fn driver_write_area_size(&self) -> usize {
let tx = self.cpu_write_ptr();
let rx = self.gsp_read_ptr();
// `rx` and `tx` are both in `0..MSGQ_NUM_PAGES` per the invariants of `gsp_read_ptr` and
// `cpu_write_ptr`. The minimum value case is where `rx == 0` and `tx == MSGQ_NUM_PAGES -
// 1`, which gives `0 + MSGQ_NUM_PAGES - (MSGQ_NUM_PAGES - 1) - 1 == 0`.
let slots = (rx + MSGQ_NUM_PAGES - tx - 1) % MSGQ_NUM_PAGES;
num::u32_as_usize(slots) * GSP_PAGE_SIZE
}
/// Returns the region of the GSP message queue that the driver is currently allowed to read
/// from.
///
/// As the message queue is a circular buffer, the region may be discontiguous in memory. In
/// that case the second slice will have a non-zero length.
fn driver_read_area(&self) -> (&[[u8; GSP_PAGE_SIZE]], &[[u8; GSP_PAGE_SIZE]]) {
let tx = self.gsp_write_ptr();
let rx = self.cpu_read_ptr();
// Pointer to the first entry of the GSP message queue.
let data = ptr::project!(self.0.as_ptr(), .gspq.msgq.data[build: 0]);
let (tail_end, wrap_end) = if rx <= tx {
// Read area is non-wrapping and stops right before `tx`.
(tx, 0)
} else {
// Read area is wrapping and stops right before `tx`.
(MSGQ_NUM_PAGES, tx)
};
// SAFETY:
// - `data` was created from a valid pointer, and `rx` and `tx` are in the
// `0..MSGQ_NUM_PAGES` range per the invariants of `gsp_write_ptr` and `cpu_read_ptr`,
// thus the created slices are valid.
// - The area starting at `rx` and ending at `tx - 1` modulo `MSGQ_NUM_PAGES`,
// inclusive, belongs to the driver for reading and is not accessed concurrently by
// the GSP.
// - The caller holds a reference to `self` for as long as the returned slices are live,
// meaning the CPU read pointer cannot be advanced and thus that the returned area
// remains exclusive to the CPU for the duration of the slices.
unsafe {
(
core::slice::from_raw_parts(
data.add(num::u32_as_usize(rx)),
num::u32_as_usize(tail_end - rx),
),
core::slice::from_raw_parts(data, num::u32_as_usize(wrap_end)),
)
}
}
/// Allocates a region on the command queue that is large enough to send a command of `size`
/// bytes, waiting for space to become available based on the provided timeout.
///
/// This returns a [`GspCommand`] ready to be written to by the caller.
///
/// # Errors
///
/// - `EMSGSIZE` if the command is larger than [`GSP_MSG_QUEUE_ELEMENT_SIZE_MAX`].
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the command header is not properly aligned.
fn allocate_command(&mut self, size: usize, timeout: Delta) -> Result<GspCommand<'_>> {
if size_of::<GspMsgElement>() + size > GSP_MSG_QUEUE_ELEMENT_SIZE_MAX {
return Err(EMSGSIZE);
}
read_poll_timeout(
|| Ok(self.driver_write_area_size()),
|available_bytes| *available_bytes >= size_of::<GspMsgElement>() + size,
Delta::from_micros(1),
timeout,
)?;
// Get the current writable area as an array of bytes.
let (slice_1, slice_2) = {
let (slice_1, slice_2) = self.driver_write_area();
(slice_1.as_flattened_mut(), slice_2.as_flattened_mut())
};
// Extract area for the `GspMsgElement`.
let (header, slice_1) = GspMsgElement::from_bytes_mut_prefix(slice_1).ok_or(EIO)?;
// Create the contents area.
let (slice_1, slice_2) = if slice_1.len() > size {
// Contents fits entirely in `slice_1`.
(&mut slice_1[..size], &mut slice_2[0..0])
} else {
// Need all of `slice_1` and some of `slice_2`.
let slice_2_len = size - slice_1.len();
(slice_1, &mut slice_2[..slice_2_len])
};
Ok(GspCommand {
header,
contents: (slice_1, slice_2),
})
}
// Returns the index of the memory page the GSP will write the next message to.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn gsp_write_ptr(&self) -> u32 {
super::fw::gsp_mem::gsp_write_ptr(&self.0)
}
// Returns the index of the memory page the GSP will read the next command from.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn gsp_read_ptr(&self) -> u32 {
super::fw::gsp_mem::gsp_read_ptr(&self.0)
}
// Returns the index of the memory page the CPU can read the next message from.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn cpu_read_ptr(&self) -> u32 {
super::fw::gsp_mem::cpu_read_ptr(&self.0)
}
// Informs the GSP that it can send `elem_count` new pages into the message queue.
fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
super::fw::gsp_mem::advance_cpu_read_ptr(&self.0, elem_count)
}
// Returns the index of the memory page the CPU can write the next command to.
//
// # Invariants
//
// - The returned value is within `0..MSGQ_NUM_PAGES`.
fn cpu_write_ptr(&self) -> u32 {
super::fw::gsp_mem::cpu_write_ptr(&self.0)
}
// Informs the GSP that it can process `elem_count` new pages from the command queue.
fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
super::fw::gsp_mem::advance_cpu_write_ptr(&self.0, elem_count)
}
}
/// A command ready to be sent on the command queue.
///
/// This is the type returned by [`DmaGspMem::allocate_command`].
struct GspCommand<'a> {
// Writable reference to the header of the command.
header: &'a mut GspMsgElement,
// Writable slices to the contents of the command. The second slice is zero unless the command
// loops over the command queue.
contents: (&'a mut [u8], &'a mut [u8]),
}
/// A message ready to be processed from the message queue.
///
/// This is the type returned by [`Cmdq::wait_for_msg`].
struct GspMessage<'a> {
// Reference to the header of the message.
header: &'a GspMsgElement,
// Slices to the contents of the message. The second slice is zero unless the message loops
// over the message queue.
contents: (&'a [u8], &'a [u8]),
}
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
/// area.
#[pin_data]
pub(crate) struct Cmdq {
/// Inner mutex-protected state.
#[pin]
inner: Mutex<CmdqInner>,
/// DMA handle of the command queue's shared memory region.
pub(super) dma_handle: DmaAddress,
}
impl Cmdq {
/// Offset of the data after the PTEs.
const POST_PTE_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq);
/// Offset of command queue ring buffer.
pub(crate) const CMDQ_OFFSET: usize = core::mem::offset_of!(GspMem, cpuq)
+ core::mem::offset_of!(Msgq, msgq)
- Self::POST_PTE_OFFSET;
/// Offset of message queue ring buffer.
pub(crate) const STATQ_OFFSET: usize = core::mem::offset_of!(GspMem, gspq)
+ core::mem::offset_of!(Msgq, msgq)
- Self::POST_PTE_OFFSET;
/// Number of page table entries for the GSP shared region.
pub(crate) const NUM_PTES: usize = size_of::<GspMem>() >> GSP_PAGE_SHIFT;
/// Default timeout for receiving a message from the GSP.
pub(super) const RECEIVE_TIMEOUT: Delta = Delta::from_secs(5);
/// Creates a new command queue for `dev`.
pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
pin_init_scope(move || {
let gsp_mem = DmaGspMem::new(dev)?;
Ok(try_pin_init!(Self {
dma_handle: gsp_mem.0.dma_handle(),
inner <- new_mutex!(CmdqInner {
dev: dev.into(),
gsp_mem,
seq: 0,
}),
}))
})
}
/// Computes the checksum for the message pointed to by `it`.
///
/// A message is made of several parts, so `it` is an iterator over byte slices representing
/// these parts.
fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
let sum64 = it
.enumerate()
.map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
.fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));
((sum64 >> 32) as u32) ^ (sum64 as u32)
}
/// Notifies the GSP that we have updated the command queue pointers.
fn notify_gsp(bar: Bar0<'_>) {
bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
}
/// Sends `command` to the GSP and waits for the reply.
///
/// Messages with non-matching function codes are silently consumed until the expected reply
/// arrives.
///
/// The queue is locked for the entire send+receive cycle to ensure that no other command can
/// be interleaved.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available to send the command, or if the reply is
/// not received within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command and reply initializers are propagated as-is.
pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Reply>
where
M: CommandToGsp,
M::Reply: MessageFromGsp,
Error: From<M::InitError>,
Error: From<<M::Reply as MessageFromGsp>::InitError>,
{
let mut inner = self.inner.lock();
inner.send_command(bar, command)?;
loop {
match inner.receive_msg::<M::Reply>(Self::RECEIVE_TIMEOUT) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
}
}
}
/// Sends `command` to the GSP without waiting for a reply.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
pub(crate) fn send_command_no_wait<M>(&self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp<Reply = NoReply>,
Error: From<M::InitError>,
{
self.inner.lock().send_command(bar, command)
}
/// Receive a message from the GSP.
///
/// See [`CmdqInner::receive_msg`] for details.
pub(crate) fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
self.inner.lock().receive_msg(timeout)
}
}
/// Inner mutex protected state of [`Cmdq`].
struct CmdqInner {
/// Device this command queue belongs to.
dev: ARef<device::Device>,
/// Current command sequence number.
seq: u32,
/// Memory area shared with the GSP for communicating commands and messages.
gsp_mem: DmaGspMem,
}
impl CmdqInner {
/// Timeout for waiting for space on the command queue.
const ALLOCATE_TIMEOUT: Delta = Delta::from_secs(1);
/// Sends `command` to the GSP, without splitting it.
///
/// # Errors
///
/// - `EMSGSIZE` if the command exceeds the maximum queue element size.
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp,
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
let size_in_bytes = command.size();
let dst = self
.gsp_mem
.allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
// Extract area for the command itself. The GSP message header and the command header
// together are guaranteed to fit entirely into a single page, so it's ok to only look
// at `dst.contents.0` here.
let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
// Fill the header and command in-place.
let msg_element = GspMsgElement::init(self.seq, size_in_bytes, M::FUNCTION);
// SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
// fails.
unsafe {
msg_element.__init(core::ptr::from_mut(dst.header))?;
command.init().__init(core::ptr::from_mut(cmd))?;
}
// Fill the variable-length payload, which may be empty.
let mut sbuffer = SBufferIter::new_writer([&mut payload_1[..], &mut dst.contents.1[..]]);
command.init_variable_payload(&mut sbuffer)?;
if !sbuffer.is_empty() {
return Err(EIO);
}
drop(sbuffer);
// Compute checksum now that the whole message is ready.
dst.header
.set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
dst.header.as_bytes(),
dst.contents.0,
dst.contents.1,
])));
dev_dbg!(
&self.dev,
"GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
self.seq,
M::FUNCTION,
dst.header.length(),
);
// All set - update the write pointer and inform the GSP of the new command.
let elem_count = dst.header.element_count();
self.seq += 1;
self.gsp_mem.advance_cpu_write_ptr(elem_count);
Cmdq::notify_gsp(bar);
Ok(())
}
/// Sends `command` to the GSP.
///
/// The command may be split into multiple messages if it is large.
///
/// # Errors
///
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the variable payload requested by the command has not been entirely
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
fn send_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp,
Error: From<M::InitError>,
{
match SplitState::new(command)? {
SplitState::Single(command) => self.send_single_command(bar, command),
SplitState::Split(command, mut continuations) => {
self.send_single_command(bar, command)?;
while let Some(continuation) = continuations.next() {
// Turbofish needed because the compiler cannot infer M here.
self.send_single_command::<ContinuationRecord<'_>>(bar, continuation)?;
}
Ok(())
}
}
}
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
/// beyond the advertised length in its [`GspMsgElement`].
///
/// This method returns:
///
/// - A reference to the [`GspMsgElement`] of the message,
/// - Two byte slices with the contents of the message. The second slice is empty unless the
/// message loops across the message queue.
///
/// # Errors
///
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
/// message queue.
///
/// Error codes returned by the message constructor are propagated as-is.
fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
// Wait for a message to arrive from the GSP.
let (slice_1, slice_2) = read_poll_timeout(
|| Ok(self.gsp_mem.driver_read_area()),
|driver_area| !driver_area.0.is_empty(),
Delta::from_millis(1),
timeout,
)
.map(|(slice_1, slice_2)| (slice_1.as_flattened(), slice_2.as_flattened()))?;
// Extract the `GspMsgElement`.
let (header, slice_1) = GspMsgElement::from_bytes_prefix(slice_1).ok_or(EIO)?;
dev_dbg!(
&self.dev,
"GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
header.sequence(),
header.function(),
header.length(),
);
let payload_length = header.payload_length();
// Check that the driver read area is large enough for the message.
if slice_1.len() + slice_2.len() < payload_length {
return Err(EIO);
}
// Cut the message slices down to the actual length of the message.
let (slice_1, slice_2) = if slice_1.len() > payload_length {
// PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
(slice_1.split_at(payload_length).0, &slice_2[0..0])
} else {
(
slice_1,
// PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
// large as `payload_length`.
slice_2.split_at(payload_length - slice_1.len()).0,
)
};
// Validate checksum.
if Cmdq::calculate_checksum(SBufferIter::new_reader([
header.as_bytes(),
slice_1,
slice_2,
])) != 0
{
dev_err!(
&self.dev,
"GSP RPC: receive: Call {} - bad checksum\n",
header.sequence()
);
return Err(EIO);
}
Ok(GspMessage {
header,
contents: (slice_1, slice_2),
})
}
/// Receive a message from the GSP.
///
/// The expected message type is specified using the `M` generic parameter. If the pending
/// message has a different function code, `ERANGE` is returned and the message is consumed.
///
/// The read pointer is always advanced past the message, regardless of whether it matched.
///
/// # Errors
///
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if there was some inconsistency (e.g. message shorter than advertised) on the
/// message queue.
/// - `EINVAL` if the function code of the message was not recognized.
/// - `ERANGE` if the message had a recognized but non-matching function code.
///
/// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
fn receive_msg<M: MessageFromGsp>(&mut self, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
let message = self.wait_for_msg(timeout)?;
let function = message.header.function().map_err(|_| EINVAL)?;
// Extract the message. Store the result as we want to advance the read pointer even in
// case of failure.
let result = if function == M::FUNCTION {
let (cmd, contents_1) = M::Message::from_bytes_prefix(message.contents.0).ok_or(EIO)?;
let mut sbuffer = SBufferIter::new_reader([contents_1, message.contents.1]);
M::read(cmd, &mut sbuffer)
.map_err(|e| e.into())
.inspect(|_| {
if !sbuffer.is_empty() {
dev_warn!(
&self.dev,
"GSP message {:?} has unprocessed data\n",
function
);
}
})
} else {
Err(ERANGE)
};
// Advance the read pointer past this message.
self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
message.header.length().div_ceil(GSP_PAGE_SIZE),
)?);
result
}
}