@@ -30,6 +30,9 @@ class DasharoCorebootImage:
3030 region_regexp = re .compile ('' .join (region_patterns ), re .MULTILINE )
3131 """Regular expression variable used to extract the flashmap regions"""
3232
33+ ifdtool_pattern = r'^FLREG(?P<id>\d+):\s+(?P<reg_val>0x[0-9a-fA-F]+)\s*?\n\s+Flash Region \d+ \((?P<name>.+?)\): (?P<start>[0-9a-fA-F]+) - (?P<end>[0-9a-fA-F]+)(?: \((?P<status>unused)\))?'
34+ ifdtool_regexp = re .compile (ifdtool_pattern , re .MULTILINE )
35+
3336 # Regions to consider as data, they should not contain any code ever.
3437 # Some of the regions are used only by certain platforms and may not be met
3538 # on Dasharo builds.
@@ -38,9 +41,13 @@ class DasharoCorebootImage:
3841 'CONSOLE' , 'RW_FWID_A' , 'RW_FWID_B' , 'VBLOCK_A' , 'RO_VPD' ,
3942 'VBLOCK_B' , 'HSPHY_FW' , 'RW_ELOG' , 'FMAP' , 'RO_FRID' ,
4043 'RO_FRID_PAD' , 'SPD_CACHE' , 'FPF_STATUS' , 'RO_LIMITS_CFG' ,
41- 'RW_DDR_TRAINING' , 'GBB' , 'BOOTORDER' ]
44+ 'RW_DDR_TRAINING' , 'GBB' , 'BOOTORDER' , 'RESERVED' , 'BPA' ,
45+ 'ROMHOLE' ]
4246 """A list of region names known to contain data"""
4347
48+ IFD_DATA_REGIONS = ['Flash Descriptor' , 'Platform Data' , 'GbE' ]
49+ """A list of IFD regions known to contain data"""
50+
4451 # Regions that are not CBFSes and may contain open-source code
4552 # Their whole size is counted as code.
4653 CODE_REGIONS = ['BOOTBLOCK' ]
@@ -53,6 +60,9 @@ class DasharoCorebootImage:
5360 'SIGN_CSE' ]
5461 """A list of region names known to contain closed-source code"""
5562
63+ IFD_BLOB_REGIONS = ['Intel ME' , 'IE' , 'PTT' , '10GbE_0' , '10GbE_1' , 'EC' ]
64+ """A list of closed-source code IFD regions"""
65+
5666 # Regions to not account for in calculations.
5767 # These are containers aggregating smaller regions.
5868 SKIP_REGIONS = ['RW_MISC' , 'UNIFIED_MRC_CACHE' , 'RW_SHARED' , 'SI_ALL' ,
@@ -61,12 +71,20 @@ class DasharoCorebootImage:
6171 """A list of region names known to be containers or aliases of other
6272 regions. These regions are skipped from classification."""
6373
74+ # Regions to not account for in calculations when ifdtool is used.
75+ # These regions will be classified based on their presence in IFD.
76+ IFD_SKIP_REGIONS = ['SI_DESC' , 'SI_ME' , 'SI_GBE' , 'SI_PDR' , 'SI_EC' ,
77+ 'SI_DEVICEEXT' , 'SI_BIOS2' , 'SI_DEVICEEXT2' ,
78+ 'SI_IE' , 'SI_10GBE0' , 'SI_10GBE1' , 'SI_PTT' ]
79+ """A list of region names to be skipped when ifdtool is used.
80+ These regions willbe classified by IFD region purpose."""
81+
6482 # Regions to count as empty/unused
6583 EMPTY_REGIONS = ['UNUSED' , 'RW_UNUSED' , 'SI_DEVICEEXT2' ]
6684 """A list of region names known to be empty spaces, e.g. between IFD
6785 regions."""
6886
69- def __init__ (self , image_path , verbose = False ):
87+ def __init__ (self , image_path , verbose = False , microarch = "" ):
7088 """DasharoCorebootImage class init method
7189
7290 Initializes the class fields for storing the firmware image components
@@ -83,14 +101,22 @@ def __init__(self, image_path, verbose=False):
83101 """
84102 self .image_path = image_path
85103 """Path to the image represented by DasharoCorebootImage class"""
104+ self .microarch = microarch
105+ """CPU michroarchitecture supported by the firmware binary to be passed to ifdtool.
106+ For a complete list of supported microarchitectures, use 'ifdtool -h'.
107+ """
86108 self .image_size = os .path .getsize (image_path )
87109 """Image size in bytes"""
88110 self .fmap_regions = {}
89111 """A dictionary holding the coreboot image flashmap regions"""
112+ self .ifdtool_regions = {}
113+ """A dictionary holding regions found by ifdtool"""
90114 self .cbfs_images = []
91115 """A list holding the regions with CBFS"""
92116 self .num_regions = 0
93117 """Total number of flashmap regions"""
118+ self .num_ifdtool_regions = 0
119+ """Total number of regions found by ifdtool"""
94120 self .num_cbfses = 0
95121 """Total number of flashmap regions containing CBFSes"""
96122 self .open_code_size = 0
@@ -109,18 +135,31 @@ def __init__(self, image_path, verbose=False):
109135 """A list holding flashmap regions filled with data"""
110136 self .empty_regions = []
111137 """A list holding empty flashmap regions"""
138+ self .closed_code_regions_ifdtool = []
139+ """A list holding ifdtool regions filled with closed-source code"""
140+ self .data_regions_ifdtool = []
141+ """A list holding ifdtool regions filled with data"""
142+ self .empty_regions_ifdtool = []
143+ """A list holding empty ifdtool regions"""
112144 # This type of regions will be counted as closed-source at the end of
113145 # metrics calculation. Keep them in separate array to export them into
114146 # CSV later for review.
115147 self .uncategorized_regions = []
116148 """A list holding flashmap regions that could not be classified.
117149 Counted as closed-source code at the end of calculation process.
118150 """
119-
151+ self .uncategorized_regions_ifdtool = []
152+ """A list holding ifdtool regions that could not be classified.
153+ Counted as closed-source code at the end of calculation process.
154+ """
120155 self .debug = verbose
121156 """Used to enable verbose debug output from the parsing process"""
157+ self .use_ifdtool = bool (microarch )
158+ """If `microarch` argument is set, use ifdtool"""
122159
123160 self ._parse_cb_fmap_layout ()
161+ if self .use_ifdtool :
162+ self ._parse_ifdtool_regions (microarch )
124163 self ._calculate_metrics ()
125164
126165 def __len__ (self ):
@@ -256,6 +295,55 @@ def _validate_fmap_layout(self):
256295
257296 return hole_size
258297
298+ def _parse_ifdtool_regions (self , microarch ):
299+ cmd = ['ifdtool' , '-p' , microarch , '-d' , self .image_path ]
300+ output = subprocess .run (cmd , text = True , capture_output = True )
301+ for match in re .finditer (self .ifdtool_regexp , output .stdout ):
302+ # Do not add regions marked as unused
303+ if not bool (match .group ('status' )):
304+ self .ifdtool_regions [self .num_ifdtool_regions ] = {
305+ 'id' : int (match .group ('id' )),
306+ 'reg_val' : match .group ('reg_val' ),
307+ 'name' : match .group ('name' ),
308+ 'start' : f"0x{ match .group ('start' )} " ,
309+ 'end' : f"0x{ match .group ('end' )} " ,
310+ }
311+ start_int = int (self .ifdtool_regions [self .num_ifdtool_regions ]['start' ], 16 )
312+ end_int = int (self .ifdtool_regions [self .num_ifdtool_regions ]['end' ], 16 )
313+ self .ifdtool_regions [self .num_ifdtool_regions ]['size' ] = end_int - start_int + 1
314+ self .num_ifdtool_regions += 1
315+ if self .debug :
316+ print ('IFD regions:' )
317+ [print (self .ifdtool_regions [i ]) for i in range (self .num_ifdtool_regions )]
318+
319+ def _classify_ifdtool_region (self , region ):
320+ if self ._is_empty (int (region ["start" ], 16 ), int (region ["end" ],16 )):
321+ self .empty_regions_ifdtool .append (region )
322+ return
323+ if region ["name" ] in self .IFD_BLOB_REGIONS :
324+ self .closed_code_regions_ifdtool .append (region )
325+ elif region ["name" ] in self .IFD_DATA_REGIONS :
326+ self .data_regions_ifdtool .append (region )
327+ elif region ["name" ] == "BIOS" :
328+ return
329+ else :
330+ self .uncategorized_regions_ifdtool .append (region )
331+
332+ def _is_empty (self , start , end ):
333+ """Checks if a flash region is empty, where empty is defined as filled with 0x00 or 0xFF bytes.
334+
335+ :param: start: Start address of the region
336+ :type start: int
337+ :param end: End address of the region
338+ :type end: int
339+
340+ :rtype: bool
341+ """
342+ with open (self .image_path , 'rb' ) as f :
343+ f .seek (start )
344+ region_data = f .read (end - start + 1 )
345+ return all (b in (0x00 , 0xFF ) for b in region_data )
346+
259347 def _classify_region (self , region ):
260348 """Classifies the flashmap regions into basic categories
261349
@@ -296,6 +384,8 @@ def _classify_region(self, region):
296384 # Skip CBFSes because they have separate class and methods to
297385 # calculate metrics
298386 return
387+ elif self .use_ifdtool and region ['name' ] in self .IFD_SKIP_REGIONS :
388+ return
299389 elif region ['name' ] in self .SKIP_REGIONS :
300390 return
301391 elif region ['name' ] in self .CODE_REGIONS :
@@ -363,11 +453,15 @@ def _calculate_metrics(self):
363453 if fmap_hole > 0 :
364454 self .closed_code_size += fmap_hole
365455
456+ if self .use_ifdtool :
457+ for i in range (self .num_ifdtool_regions ):
458+ self ._classify_ifdtool_region (self .ifdtool_regions [i ])
459+
366460 self .open_code_size += self ._sum_sizes (self .open_code_regions )
367- self .closed_code_size += self ._sum_sizes (self .closed_code_regions )
368- self .data_size += self ._sum_sizes (self .data_regions )
369- self .empty_size += self ._sum_sizes (self .empty_regions )
370- self .closed_code_size += self ._sum_sizes (self .uncategorized_regions )
461+ self .closed_code_size += self ._sum_sizes (self .closed_code_regions ) + self . _sum_sizes ( self . closed_code_regions_ifdtool )
462+ self .data_size += self ._sum_sizes (self .data_regions ) + self . _sum_sizes ( self . data_regions_ifdtool )
463+ self .empty_size += self ._sum_sizes (self .empty_regions ) + self . _sum_sizes ( self . empty_regions_ifdtool )
464+ self .closed_code_size += self ._sum_sizes (self .uncategorized_regions ) + self . _sum_sizes ( self . uncategorized_regions_ifdtool )
371465 if len (self .uncategorized_regions ) != 0 :
372466 print ('INFO: Found %d uncategorized regions of total size %d bytes'
373467 % (len (self .uncategorized_regions ),
@@ -407,8 +501,9 @@ def _normalize_sizes(self):
407501 # It may happen that the FMAP does not cover whole flash size and the
408502 # first region will start with non-zero offset. Check if first region
409503 # offset is zero, if not count all bytes from the start of flash to the
410- # start of first region as closed source.
411- if self .fmap_regions [0 ]['offset' ] != 0 :
504+ # start of first region as closed source. This is only done if ifdtool
505+ # is not used, because ifdtool will always parse those regions correctly.
506+ if self .fmap_regions [0 ]['offset' ] != 0 and not self .use_ifdtool :
412507 self .closed_code_size += self .fmap_regions [0 ]['offset' ]
413508
414509 # Final check if all sizes are summing up to whole image size
@@ -448,6 +543,12 @@ def _export_regions_md(self, file, regions, category):
448543 region ['name' ], hex (region ['offset' ]),
449544 hex (region ['size' ]), category ))
450545
546+ def _export_ifdtool_regions_md (self , file , regions , category ):
547+ for region in regions :
548+ file .write ('| {} | {} | {} | {} | {} |\n ' .format (
549+ region ['name' ], region ['start' ], region ['end' ],
550+ hex (region ['size' ]), category ))
551+
451552 def export_markdown (self , file , mkdocs ):
452553 """Opens a file and saves the openness report in markdown format
453554
@@ -509,6 +610,19 @@ def export_markdown(self, file, mkdocs):
509610 self ._export_regions_md (md , self .data_regions , 'data' )
510611 self ._export_regions_md (md , self .empty_regions , 'empty' )
511612
613+ if self .use_ifdtool :
614+ if not mkdocs :
615+ md .write ('\n ## IFD regions\n \n ' )
616+ else :
617+ md .write ('\n ### IFD regions\n \n ' )
618+
619+ md .write ('| IFD region | Start | End | Size | Category |\n ' )
620+ md .write ('| -------------- | ----- | --- | ---- | -------- |\n ' )
621+ self ._export_ifdtool_regions_md (md , self .closed_code_regions_ifdtool ,
622+ 'closed-source' )
623+ self ._export_ifdtool_regions_md (md , self .data_regions_ifdtool , 'data' )
624+ self ._export_ifdtool_regions_md (md , self .empty_regions_ifdtool , 'empty' )
625+
512626 for cbfs in self .cbfs_images :
513627 md .write ('\n ' )
514628 cbfs .export_markdown (md , mkdocs )
0 commit comments