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:: * ;
@@ -195,8 +197,8 @@ impl Vbios {
195
197
pub ( crate ) fn new ( pdev : & pci:: Device , bar0 : & Bar0 ) -> Result < Vbios > {
196
198
// Images to extract from iteration
197
199
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 ;
200
202
201
203
// Parse all VBIOS images in the ROM
202
204
for image_result in VbiosIterator :: new ( pdev, bar0) ? {
@@ -230,12 +232,14 @@ impl Vbios {
230
232
}
231
233
232
234
// 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) ) =
235
236
( second_fwsec_image, first_fwsec_image, pci_at_image)
236
237
{
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) ) ?;
237
241
Ok ( Vbios {
238
- fwsec_image : second,
242
+ fwsec_image : second. build ( pdev ) ? ,
239
243
} )
240
244
} else {
241
245
dev_err ! (
@@ -245,6 +249,10 @@ impl Vbios {
245
249
Err ( EINVAL )
246
250
}
247
251
}
252
+
253
+ pub ( crate ) fn fwsec_image ( & self ) -> & FwSecBiosImage {
254
+ & self . fwsec_image
255
+ }
248
256
}
249
257
250
258
/// PCI Data Structure as defined in PCI Firmware Specification
@@ -677,7 +685,7 @@ bios_image! {
677
685
PciAt : PciAtBiosImage , // PCI-AT compatible BIOS image
678
686
Efi : EfiBiosImage , // EFI (Extensible Firmware Interface)
679
687
Nbsi : NbsiBiosImage , // NBSI (Nvidia Bios System Interface)
680
- FwSec : FwSecBiosImage , // FWSEC (Firmware Security)
688
+ FwSec : FwSecBiosBuilder , // FWSEC (Firmware Security)
681
689
}
682
690
683
691
/// The PciAt BIOS image is typically the first BIOS image type found in the BIOS image chain.
@@ -699,9 +707,29 @@ struct NbsiBiosImage {
699
707
// NBSI-specific fields can be added here in the future.
700
708
}
701
709
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 {
703
730
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 ,
705
733
}
706
734
707
735
// Convert from BiosImageBase to BiosImage
@@ -713,7 +741,12 @@ impl TryFrom<BiosImageBase> for BiosImage {
713
741
0x00 => Ok ( BiosImage :: PciAt ( base. try_into ( ) ?) ) ,
714
742
0x03 => Ok ( BiosImage :: Efi ( EfiBiosImage { base } ) ) ,
715
743
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
+ } ) ) ,
717
750
_ => Err ( EINVAL ) ,
718
751
}
719
752
}
@@ -857,3 +890,265 @@ impl TryFrom<BiosImageBase> for PciAtBiosImage {
857
890
} )
858
891
}
859
892
}
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