66#![ expect( dead_code) ]
77
88use crate :: driver:: Bar0 ;
9+ use crate :: firmware:: FalconUCodeDescV3 ;
910use core:: convert:: TryFrom ;
11+ use kernel:: device;
1012use kernel:: error:: Result ;
1113use kernel:: pci;
1214use 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