Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <acourbot@nvidia.com>
Cc: John Hubbard <jhubbard@nvidia.com>
Cc: Shirish Baskaran <sbaskaran@nvidia.com>
Cc: Alistair Popple <apopple@nvidia.com>
Cc: Timur Tabi <ttabi@nvidia.com>
Cc: Ben Skeggs <bskeggs@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
[ acourbot@nvidia.com: remove now-unneeded Devres acquisition ]
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://lore.kernel.org/r/20250619-nova-frts-v6-19-ecf41ef99252@nvidia.com
[ Re-format and use markdown in comments. - Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>

authored by

Joel Fernandes and committed by
Danilo Krummrich
47c4846e dc70c6ae

+304 -11
-2
drivers/gpu/nova-core/firmware.rs
··· 44 44 /// Structure used to describe some firmwares, notably FWSEC-FRTS. 45 45 #[repr(C)] 46 46 #[derive(Debug, Clone)] 47 - #[allow(dead_code)] // Temporary, will be removed in later patch. 48 47 pub(crate) struct FalconUCodeDescV3 { 49 48 /// Header defined by `NV_BIT_FALCON_UCODE_DESC_HEADER_VDESC*` in OpenRM. 50 49 hdr: u32, ··· 76 77 77 78 impl FalconUCodeDescV3 { 78 79 /// Returns the size in bytes of the header. 79 - #[expect(dead_code)] // Temporary, will be removed in later patch. 80 80 pub(crate) fn size(&self) -> usize { 81 81 const HDR_SIZE_SHIFT: u32 = 16; 82 82 const HDR_SIZE_MASK: u32 = 0xffff0000;
+304 -9
drivers/gpu/nova-core/vbios.rs
··· 6 6 #![expect(dead_code)] 7 7 8 8 use crate::driver::Bar0; 9 + use crate::firmware::FalconUCodeDescV3; 9 10 use core::convert::TryFrom; 11 + use kernel::device; 10 12 use kernel::error::Result; 11 13 use kernel::pci; 12 14 use kernel::prelude::*; ··· 197 195 pub(crate) fn new(pdev: &pci::Device, bar0: &Bar0) -> Result<Vbios> { 198 196 // Images to extract from iteration 199 197 let mut pci_at_image: Option<PciAtBiosImage> = None; 200 - let mut first_fwsec_image: Option<FwSecBiosImage> = None; 201 - let mut second_fwsec_image: Option<FwSecBiosImage> = None; 198 + let mut first_fwsec_image: Option<FwSecBiosBuilder> = None; 199 + let mut second_fwsec_image: Option<FwSecBiosBuilder> = None; 202 200 203 201 // Parse all VBIOS images in the ROM 204 202 for image_result in VbiosIterator::new(pdev, bar0)? { ··· 232 230 } 233 231 234 232 // Using all the images, setup the falcon data pointer in Fwsec. 235 - // These are temporarily unused images and will be used in later patches. 236 - if let (Some(second), Some(_first), Some(_pci_at)) = 233 + if let (Some(mut second), Some(first), Some(pci_at)) = 237 234 (second_fwsec_image, first_fwsec_image, pci_at_image) 238 235 { 236 + second 237 + .setup_falcon_data(pdev, &pci_at, &first) 238 + .inspect_err(|e| dev_err!(pdev.as_ref(), "Falcon data setup failed: {:?}\n", e))?; 239 239 Ok(Vbios { 240 - fwsec_image: second, 240 + fwsec_image: second.build(pdev)?, 241 241 }) 242 242 } else { 243 243 dev_err!( ··· 248 244 ); 249 245 Err(EINVAL) 250 246 } 247 + } 248 + 249 + pub(crate) fn fwsec_image(&self) -> &FwSecBiosImage { 250 + &self.fwsec_image 251 251 } 252 252 } 253 253 ··· 685 677 PciAt: PciAtBiosImage, // PCI-AT compatible BIOS image 686 678 Efi: EfiBiosImage, // EFI (Extensible Firmware Interface) 687 679 Nbsi: NbsiBiosImage, // NBSI (Nvidia Bios System Interface) 688 - FwSec: FwSecBiosImage, // FWSEC (Firmware Security) 680 + FwSec: FwSecBiosBuilder, // FWSEC (Firmware Security) 689 681 } 690 682 691 683 /// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain. ··· 707 699 // NBSI-specific fields can be added here in the future. 708 700 } 709 701 710 - struct FwSecBiosImage { 702 + struct FwSecBiosBuilder { 711 703 base: BiosImageBase, 712 - // FWSEC-specific fields can be added here in the future. 704 + /// These are temporary fields that are used during the construction of the 705 + /// [`FwSecBiosBuilder`]. 706 + /// 707 + /// Once FwSecBiosBuilder is constructed, the `falcon_ucode_offset` will be copied into a new 708 + /// [`FwSecBiosImage`]. 709 + /// 710 + /// The offset of the Falcon data from the start of Fwsec image. 711 + falcon_data_offset: Option<usize>, 712 + /// The [`PmuLookupTable`] starts at the offset of the falcon data pointer. 713 + pmu_lookup_table: Option<PmuLookupTable>, 714 + /// The offset of the Falcon ucode. 715 + falcon_ucode_offset: Option<usize>, 716 + } 717 + 718 + /// The [`FwSecBiosImage`] structure contains the PMU table and the Falcon Ucode. 719 + /// 720 + /// The PMU table contains voltage/frequency tables as well as a pointer to the Falcon Ucode. 721 + pub(crate) struct FwSecBiosImage { 722 + base: BiosImageBase, 723 + /// The offset of the Falcon ucode. 724 + falcon_ucode_offset: usize, 713 725 } 714 726 715 727 // Convert from BiosImageBase to BiosImage ··· 741 713 0x00 => Ok(BiosImage::PciAt(base.try_into()?)), 742 714 0x03 => Ok(BiosImage::Efi(EfiBiosImage { base })), 743 715 0x70 => Ok(BiosImage::Nbsi(NbsiBiosImage { base })), 744 - 0xE0 => Ok(BiosImage::FwSec(FwSecBiosImage { base })), 716 + 0xE0 => Ok(BiosImage::FwSec(FwSecBiosBuilder { 717 + base, 718 + falcon_data_offset: None, 719 + pmu_lookup_table: None, 720 + falcon_ucode_offset: None, 721 + })), 745 722 _ => Err(EINVAL), 746 723 } 747 724 } ··· 888 855 bit_header, 889 856 bit_offset, 890 857 }) 858 + } 859 + } 860 + 861 + /// The [`PmuLookupTableEntry`] structure is a single entry in the [`PmuLookupTable`]. 862 + /// 863 + /// See the [`PmuLookupTable`] description for more information. 864 + #[expect(dead_code)] 865 + struct PmuLookupTableEntry { 866 + application_id: u8, 867 + target_id: u8, 868 + data: u32, 869 + } 870 + 871 + impl PmuLookupTableEntry { 872 + fn new(data: &[u8]) -> Result<Self> { 873 + if data.len() < 5 { 874 + return Err(EINVAL); 875 + } 876 + 877 + Ok(PmuLookupTableEntry { 878 + application_id: data[0], 879 + target_id: data[1], 880 + data: u32::from_le_bytes(data[2..6].try_into().map_err(|_| EINVAL)?), 881 + }) 882 + } 883 + } 884 + 885 + /// The [`PmuLookupTableEntry`] structure is used to find the [`PmuLookupTableEntry`] for a given 886 + /// application ID. 887 + /// 888 + /// The table of entries is pointed to by the falcon data pointer in the BIT table, and is used to 889 + /// locate the Falcon Ucode. 890 + #[expect(dead_code)] 891 + struct PmuLookupTable { 892 + version: u8, 893 + header_len: u8, 894 + entry_len: u8, 895 + entry_count: u8, 896 + table_data: KVec<u8>, 897 + } 898 + 899 + impl PmuLookupTable { 900 + fn new(pdev: &pci::Device, data: &[u8]) -> Result<Self> { 901 + if data.len() < 4 { 902 + return Err(EINVAL); 903 + } 904 + 905 + let header_len = data[1] as usize; 906 + let entry_len = data[2] as usize; 907 + let entry_count = data[3] as usize; 908 + 909 + let required_bytes = header_len + (entry_count * entry_len); 910 + 911 + if data.len() < required_bytes { 912 + dev_err!( 913 + pdev.as_ref(), 914 + "PmuLookupTable data length less than required\n" 915 + ); 916 + return Err(EINVAL); 917 + } 918 + 919 + // Create a copy of only the table data 920 + let table_data = { 921 + let mut ret = KVec::new(); 922 + ret.extend_from_slice(&data[header_len..required_bytes], GFP_KERNEL)?; 923 + ret 924 + }; 925 + 926 + // Debug logging of entries (dumps the table data to dmesg) 927 + for i in (header_len..required_bytes).step_by(entry_len) { 928 + dev_dbg!( 929 + pdev.as_ref(), 930 + "PMU entry: {:02x?}\n", 931 + &data[i..][..entry_len] 932 + ); 933 + } 934 + 935 + Ok(PmuLookupTable { 936 + version: data[0], 937 + header_len: header_len as u8, 938 + entry_len: entry_len as u8, 939 + entry_count: entry_count as u8, 940 + table_data, 941 + }) 942 + } 943 + 944 + fn lookup_index(&self, idx: u8) -> Result<PmuLookupTableEntry> { 945 + if idx >= self.entry_count { 946 + return Err(EINVAL); 947 + } 948 + 949 + let index = (idx as usize) * self.entry_len as usize; 950 + PmuLookupTableEntry::new(&self.table_data[index..]) 951 + } 952 + 953 + // find entry by type value 954 + fn find_entry_by_type(&self, entry_type: u8) -> Result<PmuLookupTableEntry> { 955 + for i in 0..self.entry_count { 956 + let entry = self.lookup_index(i)?; 957 + if entry.application_id == entry_type { 958 + return Ok(entry); 959 + } 960 + } 961 + 962 + Err(EINVAL) 963 + } 964 + } 965 + 966 + impl FwSecBiosBuilder { 967 + fn setup_falcon_data( 968 + &mut self, 969 + pdev: &pci::Device, 970 + pci_at_image: &PciAtBiosImage, 971 + first_fwsec: &FwSecBiosBuilder, 972 + ) -> Result { 973 + let mut offset = pci_at_image.falcon_data_ptr(pdev)? as usize; 974 + let mut pmu_in_first_fwsec = false; 975 + 976 + // The falcon data pointer assumes that the PciAt and FWSEC images 977 + // are contiguous in memory. However, testing shows the EFI image sits in 978 + // between them. So calculate the offset from the end of the PciAt image 979 + // rather than the start of it. Compensate. 980 + offset -= pci_at_image.base.data.len(); 981 + 982 + // The offset is now from the start of the first Fwsec image, however 983 + // the offset points to a location in the second Fwsec image. Since 984 + // the fwsec images are contiguous, subtract the length of the first Fwsec 985 + // image from the offset to get the offset to the start of the second 986 + // Fwsec image. 987 + if offset < first_fwsec.base.data.len() { 988 + pmu_in_first_fwsec = true; 989 + } else { 990 + offset -= first_fwsec.base.data.len(); 991 + } 992 + 993 + self.falcon_data_offset = Some(offset); 994 + 995 + if pmu_in_first_fwsec { 996 + self.pmu_lookup_table = 997 + Some(PmuLookupTable::new(pdev, &first_fwsec.base.data[offset..])?); 998 + } else { 999 + self.pmu_lookup_table = Some(PmuLookupTable::new(pdev, &self.base.data[offset..])?); 1000 + } 1001 + 1002 + match self 1003 + .pmu_lookup_table 1004 + .as_ref() 1005 + .ok_or(EINVAL)? 1006 + .find_entry_by_type(FALCON_UCODE_ENTRY_APPID_FWSEC_PROD) 1007 + { 1008 + Ok(entry) => { 1009 + let mut ucode_offset = entry.data as usize; 1010 + ucode_offset -= pci_at_image.base.data.len(); 1011 + if ucode_offset < first_fwsec.base.data.len() { 1012 + dev_err!(pdev.as_ref(), "Falcon Ucode offset not in second Fwsec.\n"); 1013 + return Err(EINVAL); 1014 + } 1015 + ucode_offset -= first_fwsec.base.data.len(); 1016 + self.falcon_ucode_offset = Some(ucode_offset); 1017 + } 1018 + Err(e) => { 1019 + dev_err!( 1020 + pdev.as_ref(), 1021 + "PmuLookupTableEntry not found, error: {:?}\n", 1022 + e 1023 + ); 1024 + return Err(EINVAL); 1025 + } 1026 + } 1027 + Ok(()) 1028 + } 1029 + 1030 + /// Build the final FwSecBiosImage from this builder 1031 + fn build(self, pdev: &pci::Device) -> Result<FwSecBiosImage> { 1032 + let ret = FwSecBiosImage { 1033 + base: self.base, 1034 + falcon_ucode_offset: self.falcon_ucode_offset.ok_or(EINVAL)?, 1035 + }; 1036 + 1037 + if cfg!(debug_assertions) { 1038 + // Print the desc header for debugging 1039 + let desc = ret.header(pdev.as_ref())?; 1040 + dev_dbg!(pdev.as_ref(), "PmuLookupTableEntry desc: {:#?}\n", desc); 1041 + } 1042 + 1043 + Ok(ret) 1044 + } 1045 + } 1046 + 1047 + impl FwSecBiosImage { 1048 + /// Get the FwSec header ([`FalconUCodeDescV3`]). 1049 + pub(crate) fn header(&self, dev: &device::Device) -> Result<&FalconUCodeDescV3> { 1050 + // Get the falcon ucode offset that was found in setup_falcon_data. 1051 + let falcon_ucode_offset = self.falcon_ucode_offset; 1052 + 1053 + // Make sure the offset is within the data bounds. 1054 + if falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>() > self.base.data.len() { 1055 + dev_err!(dev, "fwsec-frts header not contained within BIOS bounds\n"); 1056 + return Err(ERANGE); 1057 + } 1058 + 1059 + // Read the first 4 bytes to get the version. 1060 + let hdr_bytes: [u8; 4] = self.base.data[falcon_ucode_offset..falcon_ucode_offset + 4] 1061 + .try_into() 1062 + .map_err(|_| EINVAL)?; 1063 + let hdr = u32::from_le_bytes(hdr_bytes); 1064 + let ver = (hdr & 0xff00) >> 8; 1065 + 1066 + if ver != 3 { 1067 + dev_err!(dev, "invalid fwsec firmware version: {:?}\n", ver); 1068 + return Err(EINVAL); 1069 + } 1070 + 1071 + // Return a reference to the FalconUCodeDescV3 structure. 1072 + // 1073 + // SAFETY: We have checked that `falcon_ucode_offset + size_of::<FalconUCodeDescV3>` is 1074 + // within the bounds of `data`. Also, this data vector is from ROM, and the `data` field 1075 + // in `BiosImageBase` is immutable after construction. 1076 + Ok(unsafe { 1077 + &*(self 1078 + .base 1079 + .data 1080 + .as_ptr() 1081 + .add(falcon_ucode_offset) 1082 + .cast::<FalconUCodeDescV3>()) 1083 + }) 1084 + } 1085 + 1086 + /// Get the ucode data as a byte slice 1087 + pub(crate) fn ucode(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> { 1088 + let falcon_ucode_offset = self.falcon_ucode_offset; 1089 + 1090 + // The ucode data follows the descriptor. 1091 + let ucode_data_offset = falcon_ucode_offset + desc.size(); 1092 + let size = (desc.imem_load_size + desc.dmem_load_size) as usize; 1093 + 1094 + // Get the data slice, checking bounds in a single operation. 1095 + self.base 1096 + .data 1097 + .get(ucode_data_offset..ucode_data_offset + size) 1098 + .ok_or(ERANGE) 1099 + .inspect_err(|_| dev_err!(dev, "fwsec ucode data not contained within BIOS bounds\n")) 1100 + } 1101 + 1102 + /// Get the signatures as a byte slice 1103 + pub(crate) fn sigs(&self, dev: &device::Device, desc: &FalconUCodeDescV3) -> Result<&[u8]> { 1104 + const SIG_SIZE: usize = 96 * 4; 1105 + 1106 + // The signatures data follows the descriptor. 1107 + let sigs_data_offset = self.falcon_ucode_offset + core::mem::size_of::<FalconUCodeDescV3>(); 1108 + let size = desc.signature_count as usize * SIG_SIZE; 1109 + 1110 + // Make sure the data is within bounds. 1111 + if sigs_data_offset + size > self.base.data.len() { 1112 + dev_err!( 1113 + dev, 1114 + "fwsec signatures data not contained within BIOS bounds\n" 1115 + ); 1116 + return Err(ERANGE); 1117 + } 1118 + 1119 + Ok(&self.base.data[sigs_data_offset..sigs_data_offset + size]) 891 1120 } 892 1121 }