diff --git a/Documentation/gpu/nova/core/tlv.rst b/Documentation/gpu/nova/core/tlv.rst new file mode 100644 index 000000000000..3ce508e9545a --- /dev/null +++ b/Documentation/gpu/nova/core/tlv.rst @@ -0,0 +1,184 @@ +.. SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +================================== +TLV Tags in Nova Firmware Images +================================== + +Nova firmware images use a Type-Length-Value (TLV) format to encapsulate +firmware components and metadata. The TLV file begins with a 4-byte "magic" +header that contains the string "NVFW". Following the header is a sequence of +TLV blocks. + +Each block consists of a 4-byte tag of ASCII characters, a 4-byte length +encoded as a little-endian unsigned integer, and a sequence of bytes, the size +of which is equal to the length rounded up to the next multiple of 4. + +The driver code that reads the TLV and uses its contents is called the parser. +It is the responsibility of the parser to handle missing or malformed tags, +lengths, and values in the TLV. + +:: + + +------+------+------+------+ + | 'N' | 'V' | 'F' | 'W' | Magic header + +------+------+------+------+ + | Tag (4 bytes, ASCII) | TLV block 0 + +---------------------------+ + | Length (4 bytes, LE) | + +---------------------------+ + | | + | Value (length bytes, | + | padded to 4-byte align) | + | | + +---------------------------+ + | Tag (4 bytes, ASCII) | TLV block 1 + +---------------------------+ + | Length (4 bytes, LE) | + +---------------------------+ + | | + | Value (length bytes, | + | padded to 4-byte align) | + | | + +---------------------------+ + | ... | More TLV blocks + +---------------------------+ + +Tags and Length +=============== +TLV tags are always four-character words, with all letters being upper case. +Duplicate tags are not allowed. + +A TLV file may contain additional tags not described in this document. + +Values +====== +Values are one of four types. The type is not encoded in the format; rather, +the parser expects a given tag to have a value of a given type. + +1) Integers, encoded in 32-bit or 64-bit little-endian format. +2) Strings, encoded as-is and required to be only printable ASCII characters + and without a null terminator. +3) An array of bytes, for binary data. +4) Boolean, encoded as single byte, with a value of 0 for False or 1 for True. + +Common Tags +=========== +These tags are shared across firmware types and carry the same meaning +wherever they appear. Unlike the firmware-specific tags below, a common tag +is reserved: its meaning is fixed and may never be redefined for a particular +firmware type. + +``VERS`` (string) + Human-readable firmware version string. Present in all TLV files. + +A TLV image must contain either a single ``BLOB`` tag (firmware embedded +inline) or a ``SIZE``/``FILE`` pair (firmware stored in a separate file). + +``BLOB`` (bytes) + If the firmware microcode binary is stored in the TLV, this tag contains + the actual firmware image bytes. + +``FILE`` (string) + If the firmware binary is stored as a separate file, this tag contains the + name of that file, which is required to be in the same directory as the TLV, + so no paths are allowed in the filename. This tag is always paired with + ``SIZE``, so as to allow the driver to pre-allocate the buffer before + loading the file. + +``SIZE`` (u32) + Total size in bytes of the firmware image to be loaded from the companion + file named by ``FILE``. This tag is mandatory if ``FILE`` exists, so the + size of the firmware image must be known when the TLV is created. If the + firmware image is updated and its size changes, then the TLV must be + updated with it. + +GSP Firmware Tags +================= +``SIGN`` (bytes) + Cryptographic signature for the GSP firmware. + +``BLID`` (string) + The build ID, extracted from the ".note.gnu.build-id" section. + +Booter Firmware Tags +==================== +``DAOF`` (u32) - ``os_data_offset`` + OS data section offset within the firmware image (absolute byte offset). + Maps to the DMEM load source. + +``DASZ`` (u32) - ``os_data_size`` + OS data section size in bytes. + +``CDOF`` (u32) - ``os_code_offset`` + OS code section offset within the firmware image (absolute byte offset). + Maps to the non-secure IMEM load source. + +``CDSZ`` (u32) - ``os_code_size`` + OS code section size in bytes. + +``PLOC`` (u32) - ``patch_loc`` + Signature patch location -- byte offset within the firmware image where the + selected signature should be written. + +``FUSE`` (u32) - ``fuse_version`` + Fuse version of the firmware, used with the hardware fuse register to + select the correct signature index. + +``ENID`` (u32) - ``engine_id`` + Engine ID mask identifying the falcon engine this firmware targets. + +``UCID`` (u32) - ``ucode_id`` + Microcode ID used together with the engine ID to query hardware signature + fuse registers. + +``A0CO`` (u32) - ``app0_code_offset`` + App0 code offset -- start of the secure code region within the firmware + image. Used as the IMEM secure section source. + +``A0CS`` (u32) - ``app0_code_size`` + App0 code size in bytes. + +``NSIG`` (u32) - ``num_sigs`` + Number of signatures included in the ``SIGN`` tag. + +``SIGN`` (bytes) + Concatenated array of firmware signatures. The size of each signature is + the total length of the ``SIGN`` value divided by ``NSIG``. The correct + signature is selected using the fuse-version-derived index. + +Generic Bootloader Tags +======================= +``CDSZ`` (u32) - ``code_size`` + Size in bytes of the bootloader code to copy from the ``BLOB`` tag and + PIO-load into falcon IMEM. + +``STRT`` (u32) - ``start_tag`` + Start tag identifying the IMEM block where execution begins. The falcon + boot address is derived as ``start_tag << 8``. + +GSP Bootloader Tags +=================== +``CDOF`` (u32) - ``code_offset`` + Offset within the firmware image at which the code section starts. + +``DAOF`` (u32) - ``data_offset`` + Offset within the firmware image at which the data section starts. + +``MFOF`` (u32) - ``manifest_offset`` + Offset within the firmware image at which the manifest starts. + +``APPV`` (u32) - ``app_version`` + Application version of the firmware. + +FMC Firmware Tags +================= +``HASH`` (bytes) + SHA-384 hash of the FMC firmware, exactly 48 bytes long. + +``PKEY`` (bytes) + Public key used to verify the FMC firmware. At most 384 bytes (RSA-3072), + but may be shorter. + +``SIGN`` (bytes) + Signature of the FMC firmware. At most 384 bytes (RSA-3072), but may + be shorter. diff --git a/Documentation/gpu/nova/index.rst b/Documentation/gpu/nova/index.rst index 1783513cbd05..2afa58e8f08d 100644 --- a/Documentation/gpu/nova/index.rst +++ b/Documentation/gpu/nova/index.rst @@ -33,3 +33,4 @@ vGPU manager VFIO driver and the nova-drm driver. core/fsp core/fwsec core/falcon + core/tlv diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs index 20eff987c5d6..2075b68b364a 100644 --- a/drivers/gpu/nova-core/firmware.rs +++ b/drivers/gpu/nova-core/firmware.rs @@ -33,6 +33,7 @@ pub(crate) mod fwsec; pub(crate) mod gsp; pub(crate) mod riscv; +pub(crate) mod tlv; pub(crate) const FIRMWARE_VERSION: &str = "570.144"; diff --git a/drivers/gpu/nova-core/firmware/tlv.rs b/drivers/gpu/nova-core/firmware/tlv.rs new file mode 100644 index 000000000000..02150459c279 --- /dev/null +++ b/drivers/gpu/nova-core/firmware/tlv.rs @@ -0,0 +1,264 @@ +// 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`. +#[expect(dead_code)] +pub(crate) fn request_tlv( + dev: &device::Device, + chipset: gpu::Chipset, + name: &str, +) -> Result { + 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::(); + + /// Parses the first [`Self::SIZE`] bytes of `hdr` (caller may pass a longer slice). + fn parse(hdr: &[u8]) -> Option { + 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 { + 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], +} + +#[expect(dead_code)] +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 { + // 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 = 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> { + 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 { + 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) -> 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) + } +}