Skip to content

Commit 47c4846

Browse files
Joel FernandesDanilo Krummrich
authored andcommitted
gpu: nova-core: vbios: Add support for FWSEC ucode extraction
Using the support for navigating the VBIOS, add support to extract vBIOS ucode data required for GSP to boot. The main data extracted from the vBIOS is the FWSEC-FRTS firmware which runs on the GSP processor. This firmware runs in high secure mode, and sets up the WPR2 (Write protected region) before the Booter runs on the SEC2 processor. Tested on my Ampere GA102 and boot is successful. Cc: Alexandre Courbot <[email protected]> Cc: John Hubbard <[email protected]> Cc: Shirish Baskaran <[email protected]> Cc: Alistair Popple <[email protected]> Cc: Timur Tabi <[email protected]> Cc: Ben Skeggs <[email protected]> Signed-off-by: Joel Fernandes <[email protected]> [ [email protected]: remove now-unneeded Devres acquisition ] Signed-off-by: Alexandre Courbot <[email protected]> Link: https://lore.kernel.org/r/[email protected] [ Re-format and use markdown in comments. - Danilo ] Signed-off-by: Danilo Krummrich <[email protected]>
1 parent dc70c6a commit 47c4846

File tree

2 files changed

+304
-11
lines changed

2 files changed

+304
-11
lines changed

drivers/gpu/nova-core/firmware.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ impl Firmware {
4444
/// Structure used to describe some firmwares, notably FWSEC-FRTS.
4545
#[repr(C)]
4646
#[derive(Debug, Clone)]
47-
#[allow(dead_code)] // Temporary, will be removed in later patch.
4847
pub(crate) struct FalconUCodeDescV3 {
4948
/// Header defined by `NV_BIT_FALCON_UCODE_DESC_HEADER_VDESC*` in OpenRM.
5049
hdr: u32,
@@ -77,7 +76,6 @@ pub(crate) struct FalconUCodeDescV3 {
7776

7877
impl FalconUCodeDescV3 {
7978
/// Returns the size in bytes of the header.
80-
#[expect(dead_code)] // Temporary, will be removed in later patch.
8179
pub(crate) fn size(&self) -> usize {
8280
const HDR_SIZE_SHIFT: u32 = 16;
8381
const HDR_SIZE_MASK: u32 = 0xffff0000;

drivers/gpu/nova-core/vbios.rs

Lines changed: 304 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
#![expect(dead_code)]
77

88
use crate::driver::Bar0;
9+
use crate::firmware::FalconUCodeDescV3;
910
use core::convert::TryFrom;
11+
use kernel::device;
1012
use kernel::error::Result;
1113
use kernel::pci;
1214
use kernel::prelude::*;
@@ -195,8 +197,8 @@ impl Vbios {
195197
pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> {
196198
// Images to extract from iteration
197199
let mut pci_at_image: Option<PciAtBiosImage> = None;
198-
let mut first_fwsec_image: Option<FwSecBiosImage> = None;
199-
let mut second_fwsec_image: Option<FwSecBiosImage> = None;
200+
let mut first_fwsec_image: Option<FwSecBiosBuilder> = None;
201+
let mut second_fwsec_image: Option<FwSecBiosBuilder> = None;
200202

201203
// Parse all VBIOS images in the ROM
202204
for image_result in VbiosIterator::new(pdev, bar0)? {
@@ -230,12 +232,14 @@ impl Vbios {
230232
}
231233

232234
// Using all the images, setup the falcon data pointer in Fwsec.
233-
// These are temporarily unused images and will be used in later patches.
234-
if let (Some(second), Some(_first), Some(_pci_at)) =
235+
if let (Some(mut second), Some(first), Some(pci_at)) =
235236
(second_fwsec_image, first_fwsec_image, pci_at_image)
236237
{
238+
second
239+
.setup_falcon_data(pdev, &pci_at, &first)
240+
.inspect_err(|e| dev_err!(pdev.as_ref(), "Falcon data setup failed: {:?}\n", e))?;
237241
Ok(Vbios {
238-
fwsec_image: second,
242+
fwsec_image: second.build(pdev)?,
239243
})
240244
} else {
241245
dev_err!(
@@ -245,6 +249,10 @@ impl Vbios {
245249
Err(EINVAL)
246250
}
247251
}
252+
253+
pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage {
254+
&self.fwsec_image
255+
}
248256
}
249257

250258
/// PCI Data Structure as defined in PCI Firmware Specification
@@ -677,7 +685,7 @@ bios_image! {
677685
PciAt: PciAtBiosImage, // PCI-AT compatible BIOS image
678686
Efi: EfiBiosImage, // EFI (Extensible Firmware Interface)
679687
Nbsi: NbsiBiosImage, // NBSI (Nvidia Bios System Interface)
680-
FwSec: FwSecBiosImage, // FWSEC (Firmware Security)
688+
FwSec: FwSecBiosBuilder, // FWSEC (Firmware Security)
681689
}
682690

683691
/// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain.
@@ -699,9 +707,29 @@ struct NbsiBiosImage {
699707
// NBSI-specific fields can be added here in the future.
700708
}
701709

702-
struct FwSecBiosImage {
710+
struct FwSecBiosBuilder {
711+
base: BiosImageBase,
712+
/// These are temporary fields that are used during the construction of the
713+
/// [`FwSecBiosBuilder`].
714+
///
715+
/// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new
716+
/// [`FwSecBiosImage`].
717+
///
718+
/// The offset of the Falcon data from the start of Fwsec image.
719+
falcon_data_offset: Option<usize>,
720+
/// The [`PmuLookupTable`] starts at the offset of the falcon data pointer.
721+
pmu_lookup_table: Option<PmuLookupTable>,
722+
/// The offset of the Falcon ucode.
723+
falcon_ucode_offset: Option<usize>,
724+
}
725+
726+
/// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode.
727+
///
728+
/// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode.
729+
pub(crate) struct FwSecBiosImage {
703730
base: BiosImageBase,
704-
// FWSEC-specific fields can be added here in the future.
731+
/// The offset of the Falcon ucode.
732+
falcon_ucode_offset: usize,
705733
}
706734

707735
// Convert from BiosImageBase to BiosImage
@@ -713,7 +741,12 @@ impl TryFrom<BiosImageBase> for BiosImage {
713741
0x00 => Ok(BiosImage::PciAt(base.try_into()?)),
714742
0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })),
715743
0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })),
716-
0xE0 => Ok(BiosImage::FwSec(FwSecBiosImage { base })),
744+
0xE0 => Ok(BiosImage::FwSec(FwSecBiosBuilder {
745+
base,
746+
falcon_data_offset: None,
747+
pmu_lookup_table: None,
748+
falcon_ucode_offset: None,
749+
})),
717750
_ => Err(EINVAL),
718751
}
719752
}
@@ -857,3 +890,265 @@ impl TryFrom<BiosImageBase> for PciAtBiosImage {
857890
})
858891
}
859892
}
893+
894+
/// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`].
895+
///
896+
/// See the [`PmuLookupTable`] description for more information.
897+
#[expect(dead_code)]
898+
struct PmuLookupTableEntry {
899+
application_id: u8,
900+
target_id: u8,
901+
data: u32,
902+
}
903+
904+
impl PmuLookupTableEntry {
905+
fn new(data: &[u8]) -> Result<Self> {
906+
if data.len() < 5 {
907+
return Err(EINVAL);
908+
}
909+
910+
Ok(PmuLookupTableEntry {
911+
application_id: data[0],
912+
target_id: data[1],
913+
data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?),
914+
})
915+
}
916+
}
917+
918+
/// The [`PmuLookupTableEntry`] structure is used to find the [`PmuLookupTableEntry`] for a given
919+
/// application ID.
920+
///
921+
/// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to
922+
/// locate the Falcon Ucode.
923+
#[expect(dead_code)]
924+
struct PmuLookupTable {
925+
version: u8,
926+
header_len: u8,
927+
entry_len: u8,
928+
entry_count: u8,
929+
table_data: KVec<u8>,
930+
}
931+
932+
impl PmuLookupTable {
933+
fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> {
934+
if data.len() < 4 {
935+
return Err(EINVAL);
936+
}
937+
938+
let header_len = data[1] as usize;
939+
let entry_len = data[2] as usize;
940+
let entry_count = data[3] as usize;
941+
942+
let required_bytes = header_len + (entry_count * entry_len);
943+
944+
if data.len() < required_bytes {
945+
dev_err!(
946+
pdev.as_ref(),
947+
"PmuLookupTable data length less than required\n"
948+
);
949+
return Err(EINVAL);
950+
}
951+
952+
// Create a copy of only the table data
953+
let table_data = {
954+
let mut ret = KVec::new();
955+
ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?;
956+
ret
957+
};
958+
959+
// Debug logging of entries (dumps the table data to dmesg)
960+
for i in (header_len..required_bytes).step_by(entry_len) {
961+
dev_dbg!(
962+
pdev.as_ref(),
963+
"PMU entry: {:02x?}\n",
964+
&data[i..][..entry_len]
965+
);
966+
}
967+
968+
Ok(PmuLookupTable {
969+
version: data[0],
970+
header_len: header_len as u8,
971+
entry_len: entry_len as u8,
972+
entry_count: entry_count as u8,
973+
table_data,
974+
})
975+
}
976+
977+
fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> {
978+
if idx >= self.entry_count {
979+
return Err(EINVAL);
980+
}
981+
982+
let index = (idx as usize) * self.entry_len as usize;
983+
PmuLookupTableEntry::new(&self.table_data[index..])
984+
}
985+
986+
// find entry by type value
987+
fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> {
988+
for i in 0..self.entry_count {
989+
let entry = self.lookup_index(i)?;
990+
if entry.application_id == entry_type {
991+
return Ok(entry);
992+
}
993+
}
994+
995+
Err(EINVAL)
996+
}
997+
}
998+
999+
impl FwSecBiosBuilder {
1000+
fn setup_falcon_data(
1001+
&mut self,
1002+
pdev: &pci::Device,
1003+
pci_at_image: &PciAtBiosImage,
1004+
first_fwsec: &FwSecBiosBuilder,
1005+
) -> Result {
1006+
let mut offset = pci_at_image.falcon_data_ptr(pdev)? as usize;
1007+
let mut pmu_in_first_fwsec = false;
1008+
1009+
// The falcon data pointer assumes that the PciAt and FWSEC images
1010+
// are contiguous in memory. However, testing shows the EFI image sits in
1011+
// between them. So calculate the offset from the end of the PciAt image
1012+
// rather than the start of it. Compensate.
1013+
offset -= pci_at_image.base.data.len();
1014+
1015+
// The offset is now from the start of the first Fwsec image, however
1016+
// the offset points to a location in the second Fwsec image. Since
1017+
// the fwsec images are contiguous, subtract the length of the first Fwsec
1018+
// image from the offset to get the offset to the start of the second
1019+
// Fwsec image.
1020+
if offset < first_fwsec.base.data.len() {
1021+
pmu_in_first_fwsec = true;
1022+
} else {
1023+
offset -= first_fwsec.base.data.len();
1024+
}
1025+
1026+
self.falcon_data_offset = Some(offset);
1027+
1028+
if pmu_in_first_fwsec {
1029+
self.pmu_lookup_table =
1030+
Some(PmuLookupTable::new(pdev, &first_fwsec.base.data[offset..])?);
1031+
} else {
1032+
self.pmu_lookup_table = Some(PmuLookupTable::new(pdev, &self.base.data[offset..])?);
1033+
}
1034+
1035+
match self
1036+
.pmu_lookup_table
1037+
.as_ref()
1038+
.ok_or(EINVAL)?
1039+
.find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD)
1040+
{
1041+
Ok(entry) => {
1042+
let mut ucode_offset = entry.data as usize;
1043+
ucode_offset -= pci_at_image.base.data.len();
1044+
if ucode_offset < first_fwsec.base.data.len() {
1045+
dev_err!(pdev.as_ref(), "Falcon Ucode offset not in second Fwsec.\n");
1046+
return Err(EINVAL);
1047+
}
1048+
ucode_offset -= first_fwsec.base.data.len();
1049+
self.falcon_ucode_offset = Some(ucode_offset);
1050+
}
1051+
Err(e) => {
1052+
dev_err!(
1053+
pdev.as_ref(),
1054+
"PmuLookupTableEntry not found, error: {:?}\n",
1055+
e
1056+
);
1057+
return Err(EINVAL);
1058+
}
1059+
}
1060+
Ok(())
1061+
}
1062+
1063+
/// Build the final FwSecBiosImage from this builder
1064+
fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> {
1065+
let ret = FwSecBiosImage {
1066+
base: self.base,
1067+
falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?,
1068+
};
1069+
1070+
if cfg!(debug_assertions) {
1071+
// Print the desc header for debugging
1072+
let desc = ret.header(pdev.as_ref())?;
1073+
dev_dbg!(pdev.as_ref(), "PmuLookupTableEntry desc: {:#?}\n", desc);
1074+
}
1075+
1076+
Ok(ret)
1077+
}
1078+
}
1079+
1080+
impl FwSecBiosImage {
1081+
/// Get the FwSec header ([`FalconUCodeDescV3`]).
1082+
pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> {
1083+
// Get the falcon ucode offset that was found in setup_falcon_data.
1084+
let falcon_ucode_offset = self.falcon_ucode_offset;
1085+
1086+
// Make sure the offset is within the data bounds.
1087+
if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() {
1088+
dev_err!(dev, "fwsec-frts header not contained within BIOS bounds\n");
1089+
return Err(ERANGE);
1090+
}
1091+
1092+
// Read the first 4 bytes to get the version.
1093+
let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4]
1094+
.try_into()
1095+
.map_err(|_| EINVAL)?;
1096+
let hdr = u32::from_le_bytes(hdr_bytes);
1097+
let ver = (hdr & 0xff00) >> 8;
1098+
1099+
if ver != 3 {
1100+
dev_err!(dev, "invalid fwsec firmware version: {:?}\n", ver);
1101+
return Err(EINVAL);
1102+
}
1103+
1104+
// Return a reference to the FalconUCodeDescV3 structure.
1105+
//
1106+
// SAFETY: We have checked that `falcon_ucode_offset + size_of::<FalconUCodeDescV3>` is
1107+
// within the bounds of `data`. Also, this data vector is from ROM, and the `data` field
1108+
// in `BiosImageBase` is immutable after construction.
1109+
Ok(unsafe {
1110+
&*(self
1111+
.base
1112+
.data
1113+
.as_ptr()
1114+
.add(falcon_ucode_offset)
1115+
.cast::<FalconUCodeDescV3>())
1116+
})
1117+
}
1118+
1119+
/// Get the ucode data as a byte slice
1120+
pub(crate) fn ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
1121+
let falcon_ucode_offset = self.falcon_ucode_offset;
1122+
1123+
// The ucode data follows the descriptor.
1124+
let ucode_data_offset = falcon_ucode_offset + desc.size();
1125+
let size = (desc.imem_load_size + desc.dmem_load_size) as usize;
1126+
1127+
// Get the data slice, checking bounds in a single operation.
1128+
self.base
1129+
.data
1130+
.get(ucode_data_offset..ucode_data_offset + size)
1131+
.ok_or(ERANGE)
1132+
.inspect_err(|_| dev_err!(dev, "fwsec ucode data not contained within BIOS bounds\n"))
1133+
}
1134+
1135+
/// Get the signatures as a byte slice
1136+
pub(crate) fn sigs(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> {
1137+
const SIG_SIZE: usize = 96 * 4;
1138+
1139+
// The signatures data follows the descriptor.
1140+
let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>();
1141+
let size = desc.signature_count as usize * SIG_SIZE;
1142+
1143+
// Make sure the data is within bounds.
1144+
if sigs_data_offset + size > self.base.data.len() {
1145+
dev_err!(
1146+
dev,
1147+
"fwsec signatures data not contained within BIOS bounds\n"
1148+
);
1149+
return Err(ERANGE);
1150+
}
1151+
1152+
Ok(&self.base.data[sigs_data_offset..sigs_data_offset + size])
1153+
}
1154+
}

0 commit comments

Comments
 (0)