Skip to content

Commit a841614

Browse files
committed
gpu: nova-core: firmware: process and prepare the GSP firmware
The GSP firmware is a binary blob that is verified, loaded, and run by the GSP bootloader. Its presentation is a bit peculiar as the GSP bootloader expects to be given a DMA address to a 3-levels page table mapping the GSP firmware at address 0 of its own address space. Prepare such a structure containing the DMA-mapped firmware as well as the DMA-mapped page tables, and a way to obtain the DMA handle of the level 0 page table. Then, move the GSP firmware instance from the `Firmware` struct to the `start_gsp` method since it doesn't need to be kept after the GSP is booted. As we are performing the required ELF section parsing and radix3 page table building, remove these items from the TODO file. Acked-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/r/20250913-nova_firmware-v6-7-9007079548b0@nvidia.com Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
1 parent 3e5c968 commit a841614

5 files changed

Lines changed: 246 additions & 19 deletions

File tree

Documentation/gpu/nova/core/todo.rst

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -230,23 +230,6 @@ Rust abstraction for debugfs APIs.
230230
GPU (general)
231231
=============
232232

233-
Parse firmware headers
234-
----------------------
235-
236-
Parse ELF headers from the firmware files loaded from the filesystem.
237-
238-
| Reference: ELF utils
239-
| Complexity: Beginner
240-
| Contact: Abdiel Janulgue
241-
242-
Build radix3 page table
243-
-----------------------
244-
245-
Build the radix3 page table to map the firmware.
246-
247-
| Complexity: Intermediate
248-
| Contact: Abdiel Janulgue
249-
250233
Initial Devinit support
251234
-----------------------
252235

drivers/gpu/nova-core/firmware.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::gpu::Chipset;
1919

2020
pub(crate) mod booter;
2121
pub(crate) mod fwsec;
22+
pub(crate) mod gsp;
2223

2324
pub(crate) const FIRMWARE_VERSION: &str = "535.113.01";
2425

@@ -39,7 +40,6 @@ fn request_firmware(
3940
#[expect(dead_code)]
4041
pub(crate) struct Firmware {
4142
bootloader: firmware::Firmware,
42-
gsp: firmware::Firmware,
4343
}
4444

4545
impl Firmware {
@@ -48,7 +48,6 @@ impl Firmware {
4848

4949
Ok(Firmware {
5050
bootloader: request("bootloader")?,
51-
gsp: request("gsp")?,
5251
})
5352
}
5453
}
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use core::mem::size_of_val;
4+
5+
use kernel::device;
6+
use kernel::dma::{DataDirection, DmaAddress};
7+
use kernel::kvec;
8+
use kernel::prelude::*;
9+
use kernel::scatterlist::{Owned, SGTable};
10+
11+
use crate::dma::DmaObject;
12+
use crate::gpu::{Architecture, Chipset};
13+
use crate::gsp::GSP_PAGE_SIZE;
14+
15+
/// Ad-hoc and temporary module to extract sections from ELF images.
16+
///
17+
/// Some firmware images are currently packaged as ELF files, where sections names are used as keys
18+
/// to specific and related bits of data. Future firmware versions are scheduled to move away from
19+
/// that scheme before nova-core becomes stable, which means this module will eventually be
20+
/// removed.
21+
mod elf {
22+
use core::mem::size_of;
23+
24+
use kernel::bindings;
25+
use kernel::str::CStr;
26+
use kernel::transmute::FromBytes;
27+
28+
/// Newtype to provide a [`FromBytes`] implementation.
29+
#[repr(transparent)]
30+
struct Elf64Hdr(bindings::elf64_hdr);
31+
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
32+
unsafe impl FromBytes for Elf64Hdr {}
33+
34+
#[repr(transparent)]
35+
struct Elf64SHdr(bindings::elf64_shdr);
36+
// SAFETY: all bit patterns are valid for this type, and it doesn't use interior mutability.
37+
unsafe impl FromBytes for Elf64SHdr {}
38+
39+
/// Tries to extract section with name `name` from the ELF64 image `elf`, and returns it.
40+
pub(super) fn elf64_section<'a, 'b>(elf: &'a [u8], name: &'b str) -> Option<&'a [u8]> {
41+
let hdr = &elf
42+
.get(0..size_of::<bindings::elf64_hdr>())
43+
.and_then(Elf64Hdr::from_bytes)?
44+
.0;
45+
46+
// Get all the section headers.
47+
let mut shdr = {
48+
let shdr_num = usize::from(hdr.e_shnum);
49+
let shdr_start = usize::try_from(hdr.e_shoff).ok()?;
50+
let shdr_end = shdr_num
51+
.checked_mul(size_of::<Elf64SHdr>())
52+
.and_then(|v| v.checked_add(shdr_start))?;
53+
54+
elf.get(shdr_start..shdr_end)
55+
.map(|slice| slice.chunks_exact(size_of::<Elf64SHdr>()))?
56+
};
57+
58+
// Get the strings table.
59+
let strhdr = shdr
60+
.clone()
61+
.nth(usize::from(hdr.e_shstrndx))
62+
.and_then(Elf64SHdr::from_bytes)?;
63+
64+
// Find the section which name matches `name` and return it.
65+
shdr.find(|&sh| {
66+
let Some(hdr) = Elf64SHdr::from_bytes(sh) else {
67+
return false;
68+
};
69+
70+
let Some(name_idx) = strhdr
71+
.0
72+
.sh_offset
73+
.checked_add(u64::from(hdr.0.sh_name))
74+
.and_then(|idx| usize::try_from(idx).ok())
75+
else {
76+
return false;
77+
};
78+
79+
// Get the start of the name.
80+
elf.get(name_idx..)
81+
// Stop at the first `0`.
82+
.and_then(|nstr| nstr.get(0..=nstr.iter().position(|b| *b == 0)?))
83+
// Convert into CStr. This should never fail because of the line above.
84+
.and_then(|nstr| CStr::from_bytes_with_nul(nstr).ok())
85+
// Convert into str.
86+
.and_then(|c_str| c_str.to_str().ok())
87+
// Check that the name matches.
88+
.map(|str| str == name)
89+
.unwrap_or(false)
90+
})
91+
// Return the slice containing the section.
92+
.and_then(|sh| {
93+
let hdr = Elf64SHdr::from_bytes(sh)?;
94+
let start = usize::try_from(hdr.0.sh_offset).ok()?;
95+
let end = usize::try_from(hdr.0.sh_size)
96+
.ok()
97+
.and_then(|sh_size| start.checked_add(sh_size))?;
98+
99+
elf.get(start..end)
100+
})
101+
}
102+
}
103+
104+
/// GSP firmware with 3-level radix page tables for the GSP bootloader.
105+
///
106+
/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
107+
/// space:
108+
///
109+
/// ```text
110+
/// Level 0: 1 page, 1 entry -> points to first level 1 page
111+
/// Level 1: Multiple pages/entries -> each entry points to a level 2 page
112+
/// Level 2: Multiple pages/entries -> each entry points to a firmware page
113+
/// ```
114+
///
115+
/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
116+
/// Also known as "Radix3" firmware.
117+
#[pin_data]
118+
pub(crate) struct GspFirmware {
119+
/// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
120+
#[pin]
121+
fw: SGTable<Owned<VVec<u8>>>,
122+
/// Level 2 page table whose entries contain DMA addresses of firmware pages.
123+
#[pin]
124+
level2: SGTable<Owned<VVec<u8>>>,
125+
/// Level 1 page table whose entries contain DMA addresses of level 2 pages.
126+
#[pin]
127+
level1: SGTable<Owned<VVec<u8>>>,
128+
/// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
129+
level0: DmaObject,
130+
/// Size in bytes of the firmware contained in [`Self::fw`].
131+
size: usize,
132+
/// Device-mapped GSP signatures matching the GPU's [`Chipset`].
133+
signatures: DmaObject,
134+
}
135+
136+
impl GspFirmware {
137+
/// Loads the GSP firmware binaries, map them into `dev`'s address-space, and creates the page
138+
/// tables expected by the GSP bootloader to load it.
139+
pub(crate) fn new<'a, 'b>(
140+
dev: &'a device::Device<device::Bound>,
141+
chipset: Chipset,
142+
ver: &'b str,
143+
) -> Result<impl PinInit<Self, Error> + 'a> {
144+
let fw = super::request_firmware(dev, chipset, "gsp", ver)?;
145+
146+
let fw_section = elf::elf64_section(fw.data(), ".fwimage").ok_or(EINVAL)?;
147+
148+
let sigs_section = match chipset.arch() {
149+
Architecture::Ampere => ".fwsignature_ga10x",
150+
_ => return Err(ENOTSUPP),
151+
};
152+
let signatures = elf::elf64_section(fw.data(), sigs_section)
153+
.ok_or(EINVAL)
154+
.and_then(|data| DmaObject::from_data(dev, data))?;
155+
156+
let size = fw_section.len();
157+
158+
// Move the firmware into a vmalloc'd vector and map it into the device address
159+
// space.
160+
let fw_vvec = VVec::with_capacity(fw_section.len(), GFP_KERNEL)
161+
.and_then(|mut v| {
162+
v.extend_from_slice(fw_section, GFP_KERNEL)?;
163+
Ok(v)
164+
})
165+
.map_err(|_| ENOMEM)?;
166+
167+
Ok(try_pin_init!(Self {
168+
fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),
169+
level2 <- {
170+
// Allocate the level 2 page table, map the firmware onto it, and map it into the
171+
// device address space.
172+
VVec::<u8>::with_capacity(
173+
fw.iter().count() * core::mem::size_of::<u64>(),
174+
GFP_KERNEL,
175+
)
176+
.map_err(|_| ENOMEM)
177+
.and_then(|level2| map_into_lvl(&fw, level2))
178+
.map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
179+
},
180+
level1 <- {
181+
// Allocate the level 1 page table, map the level 2 page table onto it, and map it
182+
// into the device address space.
183+
VVec::<u8>::with_capacity(
184+
level2.iter().count() * core::mem::size_of::<u64>(),
185+
GFP_KERNEL,
186+
)
187+
.map_err(|_| ENOMEM)
188+
.and_then(|level1| map_into_lvl(&level2, level1))
189+
.map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
190+
},
191+
level0: {
192+
// Allocate the level 0 page table as a device-visible DMA object, and map the
193+
// level 1 page table onto it.
194+
195+
// Level 0 page table data.
196+
let mut level0_data = kvec![0u8; GSP_PAGE_SIZE]?;
197+
198+
// Fill level 1 page entry.
199+
#[allow(clippy::useless_conversion)]
200+
let level1_entry = u64::from(level1.iter().next().unwrap().dma_address());
201+
let dst = &mut level0_data[..size_of_val(&level1_entry)];
202+
dst.copy_from_slice(&level1_entry.to_le_bytes());
203+
204+
// Turn the level0 page table into a [`DmaObject`].
205+
DmaObject::from_data(dev, &level0_data)?
206+
},
207+
size,
208+
signatures,
209+
}))
210+
}
211+
212+
#[expect(unused)]
213+
/// Returns the DMA handle of the radix3 level 0 page table.
214+
pub(crate) fn radix3_dma_handle(&self) -> DmaAddress {
215+
self.level0.dma_handle()
216+
}
217+
}
218+
219+
/// Build a page table from a scatter-gather list.
220+
///
221+
/// Takes each DMA-mapped region from `sg_table` and writes page table entries
222+
/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
223+
/// 4 consecutive page table entries.
224+
fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
225+
for sg_entry in sg_table.iter() {
226+
// Number of pages we need to map.
227+
let num_pages = (sg_entry.dma_len() as usize).div_ceil(GSP_PAGE_SIZE);
228+
229+
for i in 0..num_pages {
230+
let entry = sg_entry.dma_address() + (i as u64 * GSP_PAGE_SIZE as u64);
231+
dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
232+
}
233+
}
234+
235+
Ok(dst)
236+
}

drivers/gpu/nova-core/gsp.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ mod boot;
44

55
use kernel::prelude::*;
66

7+
pub(crate) const GSP_PAGE_SHIFT: usize = 12;
8+
pub(crate) const GSP_PAGE_SIZE: usize = 1 << GSP_PAGE_SHIFT;
9+
710
/// GSP runtime data.
811
///
912
/// This is an empty pinned placeholder for now.

drivers/gpu/nova-core/gsp/boot.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::fb::FbLayout;
1010
use crate::firmware::{
1111
booter::{BooterFirmware, BooterKind},
1212
fwsec::{FwsecCommand, FwsecFirmware},
13+
gsp::GspFirmware,
1314
FIRMWARE_VERSION,
1415
};
1516
use crate::gpu::Chipset;
@@ -112,6 +113,11 @@ impl super::Gsp {
112113

113114
let bios = Vbios::new(dev, bar)?;
114115

116+
let _gsp_fw = KBox::pin_init(
117+
GspFirmware::new(dev, chipset, FIRMWARE_VERSION)?,
118+
GFP_KERNEL,
119+
)?;
120+
115121
let fb_layout = FbLayout::new(chipset, bar)?;
116122
dev_dbg!(dev, "{:#x?}\n", fb_layout);
117123

0 commit comments

Comments
 (0)