linux/drivers/gpu/nova-core/firmware/tlv.rs
Timur Tabi ad51578275 gpu: nova-core: transition booter to TLV images
Switch the booter firmware loader from the legacy binary format to the
TLV format.  This change requires the new TLV versions of the r570.144
firmware images.

The new TLV format has all of the metadata needed by Nova encoded as
separate tags, eliminating the need to parse legacy firmware headers
such as HsHeaderV2 and HsSignatureParams.  All of the structs and
code for parsing those headers is therefore deleted.

Signed-off-by: Timur Tabi <ttabi@nvidia.com>
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Tested-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://patch.msgid.link/20260731201017.2580713-5-ttabi@nvidia.com
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
2026-08-06 01:54:12 +02:00

263 lines
8.9 KiB
Rust

// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
use kernel::{
device,
firmware,
prelude::*,
str::CString, //
};
use crate::{
gpu,
num::*, //
};
/// Requests the GPU firmware TLV `name` suitable for `chipset`.
pub(crate) fn request_tlv(
dev: &device::Device,
chipset: gpu::Chipset,
name: &str,
) -> Result<firmware::Firmware> {
let chip_name = chipset.name();
let filename = CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{name}.tlv"))?;
dev_dbg!(dev, "loading firmware image {:?}\n", &filename);
firmware::Firmware::request(&filename, dev)
}
struct TlvBlock<'a> {
tag: [u8; 4],
value: &'a [u8],
}
/// On-wire TLV block header: 4-byte ASCII tag + little-endian payload length (bytes, excluding
/// padding to a 4-byte boundary).
struct TlvBlockHeader {
tag: [u8; 4],
length: usize,
}
impl TlvBlockHeader {
const SIZE: usize = size_of::<[u8; 4]>() + size_of::<u32>();
/// Parses the first [`Self::SIZE`] bytes of `hdr` (caller may pass a longer slice).
fn parse(hdr: &[u8]) -> Option<Self> {
let hdr = hdr.get(..Self::SIZE)?;
let tag = <[u8; 4]>::try_from(hdr.get(..4)?).ok()?;
if !tag.is_ascii() {
return None;
}
let len_arr = <[u8; 4]>::try_from(hdr.get(4..Self::SIZE)?).ok()?;
let length = u32_as_usize(u32::from_le_bytes(len_arr));
Some(Self { tag, length })
}
}
/// Iterator over the [`TlvBlock`]s of a [`Tlv`].
///
/// # Invariants
///
/// `pos` is a byte offset into `tlv.data` that always lies on a block boundary (in the sense
/// of the [`Tlv`] invariant): it is either the start of a well-formed block, or equal to
/// `tlv.data.len()` (end of iteration).
struct TlvIter<'tlv, 'a> {
tlv: &'tlv Tlv<'a>,
pos: usize,
}
impl<'tlv, 'a> Iterator for TlvIter<'tlv, 'a> {
type Item = TlvBlock<'a>;
/// Returns the block starting at `self.pos` and advances the cursor past it, or [`None`]
/// once the cursor reaches the end of the data or encounters an error.
///
/// Note that errors cannot actually occur because the data is validated in the constructor.
fn next(&mut self) -> Option<Self::Item> {
if self.pos >= self.tlv.data.len() {
return None;
}
let tail = self.tlv.data.get(self.pos..)?;
let hdr = tail.get(..TlvBlockHeader::SIZE)?;
let header = TlvBlockHeader::parse(hdr)?;
let stored_size = header.length.checked_next_multiple_of(4)?;
let advance = TlvBlockHeader::SIZE.checked_add(stored_size)?;
let payload_end = TlvBlockHeader::SIZE.checked_add(header.length)?;
let value = tail
.get(..advance)?
.get(TlvBlockHeader::SIZE..payload_end)?;
// INVARIANT: by the `Tlv` invariant the block at `self.pos` occupies exactly `advance`
// bytes, so `self.pos + advance` is the next block boundary (or `data.len()`).
self.pos = self.pos.checked_add(advance)?;
Some(TlvBlock {
tag: header.tag,
value,
})
}
}
/// The post-header part of a validated TLV (type, length, value) firmware image.
///
/// TLV firmware images start with a 4-byte "NVFW" magic header, followed by a sequence of
/// blocks. Each block has a 4-byte type tag, a 4-byte length field, and a data payload
/// (value) whose stored size is the length rounded up to the nearest multiple of 4.
///
/// [`Self::new`] checks the magic header and walks every block: tags must be ASCII,
/// lengths and padding must fit without overflow, and the byte stream after `NVFW` must
/// be exactly partitionable into blocks (no trailing partial header or slack). After
/// that, [`TlvIter`] only signals end-of-stream via [`None`], not parse failure.
///
/// Although the spec forbids duplicate tags, neither the constructor nor the iterator
/// enforces this restriction. Instead, duplicate tags are simply ignored.
///
/// # Invariants
///
/// `data` is a validated TLV payload (the bytes *after* the `NVFW` magic): it is the exact
/// concatenation of zero or more well-formed blocks, with no trailing partial header or slack.
/// Consequently, any offset `o` into `data` that is a block boundary and satisfies
/// `o < data.len()` is the start of a complete block whose header parses and whose stored
/// extent (`TlvBlockHeader::SIZE + header.length.next_multiple_of(4)` bytes) lies within
/// `data`. `data.len()` is itself a boundary.
pub(crate) struct Tlv<'a> {
data: &'a [u8],
}
impl<'a> Tlv<'a> {
const MAGIC: &'static [u8; 4] = b"NVFW";
/// Parses `data` as a TLV firmware image, returning [`EINVAL`] if the image is malformed.
pub(crate) fn new(data: &'a [u8]) -> Result<Self> {
// Verify that the magic bytes exist and are the correct value
let magic_len = Self::MAGIC.len();
if data
.get(..magic_len)
.is_none_or(|magic| magic != Self::MAGIC)
{
return Err(EINVAL);
}
// The payload is the contiguous sequence of TLV blocks after the magic.
let payload = data.get(magic_len..).ok_or(EINVAL)?;
// The spec says every TLV must have a VERS tag.
let mut has_vers = false;
let mut rest = payload;
while !rest.is_empty() {
// Validate and extract the header (type, length).
let Some(header): Option<TlvBlockHeader> = rest
.get(..TlvBlockHeader::SIZE)
.and_then(TlvBlockHeader::parse)
else {
return Err(EINVAL);
};
has_vers |= header.tag == *b"VERS";
// The `length` field of a TLV block contains the actual byte length of the
// value, but each TLV block is aligned to a 4-byte boundary.
let Some(stored_size) = header.length.checked_next_multiple_of(4) else {
return Err(EINVAL);
};
let length = TlvBlockHeader::SIZE
.checked_add(stored_size)
.ok_or(EINVAL)?;
rest = rest.split_at_checked(length).ok_or(EINVAL)?.1;
}
if !has_vers {
return Err(EINVAL);
}
// INVARIANT: the loop above walked `payload` block-by-block. For each block, the
// header is parsed (`TlvBlockHeader::parse` rejects non-ASCII tags), and the
// stored extent (`SIZE + length.next_multiple_of(4)`) is computed without
// overflow and split off `rest` only when it fits. The loop ends only when `rest`
// is empty, so the byte stream is an exact concatenation of blocks with no
// trailing partial header or slack.
Ok(Self { data: payload })
}
fn iter(&self) -> TlvIter<'_, 'a> {
// INVARIANT: 0 is a block boundary, either the start of the first block,
// or `data.len()` when `data` is empty.
TlvIter { tlv: self, pos: 0 }
}
fn find(&self, tag: &[u8; 4]) -> Result<TlvBlock<'a>> {
self.iter().find(|b| b.tag == *tag).ok_or(EINVAL)
}
/// Return a slice of bytes.
///
/// Returns `EINVAL` if the value is empty.
pub(crate) fn get_bytes(&self, tag: &[u8; 4]) -> Result<&'a [u8]> {
let tlv = self.find(tag)?;
// Treat empty value as an error, to avoid trying to parse nothing.
if tlv.value.is_empty() {
return Err(EINVAL); // TODO: Use ENODATA once available.
}
Ok(tlv.value)
}
/// Return a little-endian u32.
pub(crate) fn get_u32(&self, tag: &[u8; 4]) -> Result<u32> {
let tlv = self.find(tag)?;
tlv.value
.try_into()
.ok()
.map(u32::from_le_bytes)
.ok_or(EINVAL)
}
/// Return a string value.
pub(crate) fn get_string(&self, tag: &[u8; 4]) -> Result<&'a str> {
let tlv = self.find(tag)?;
let bytes = tlv.value;
// Strings can only contain printable ASCII characters.
if bytes.iter().any(|&b| !(32..127).contains(&b)) {
return Err(EINVAL);
}
core::str::from_utf8(bytes).map_err(|_| EINVAL)
}
/// Obtain the nth signature from a SIGN tag. If `index` is None,
/// then return the last signature.
pub(crate) fn get_signature(&self, index: Option<usize>) -> Result<&'a [u8]> {
let num_sigs: usize = match self.get_u32(b"NSIG")? {
0 => return Err(EINVAL),
n => n.into_safe_cast(),
};
let sig_bytes = self.get_bytes(b"SIGN")?;
// Ensure that sig_bytes can be divided evenly into chunks.
if sig_bytes.len() % num_sigs != 0 {
return Err(EINVAL);
}
// num_sigs cannot be 0, and sig_bytes cannot be empty, so this cannot panic.
let sig_size = sig_bytes.len() / num_sigs;
let index = index.unwrap_or(num_sigs - 1);
sig_bytes.chunks_exact(sig_size).nth(index).ok_or(EINVAL)
}
}