binman: Add a utility module for ATF FIP
[platform/kernel/u-boot.git] / tools / binman / fip_util.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3 # Copyright 2021 Google LLC
4 # Written by Simon Glass <sjg@chromium.org>
5
6 """Support for ARM's Firmware Image Package (FIP) format
7
8 FIP is a format similar to FMAP[1] but with fewer features and an obscure UUID
9 instead of the region name.
10
11 It consists of a header and a table of entries, each pointing to a place in the
12 firmware image where something can be found.
13
14 [1] https://chromium.googlesource.com/chromiumos/third_party/flashmap/+/refs/heads/master/lib/fmap.h
15
16 If ATF updates, run this program to update the FIT_TYPE_LIST.
17
18 ARM Trusted Firmware is available at:
19
20 https://github.com/ARM-software/arm-trusted-firmware.git
21 """
22
23 from argparse import ArgumentParser
24 import collections
25 import io
26 import os
27 import re
28 import struct
29 import sys
30 from uuid import UUID
31
32 OUR_FILE = os.path.realpath(__file__)
33 OUR_PATH = os.path.dirname(OUR_FILE)
34
35 # Bring in the patman and dtoc libraries (but don't override the first path
36 # in PYTHONPATH)
37 sys.path.insert(2, os.path.join(OUR_PATH, '..'))
38
39 # pylint: disable=C0413
40 from patman import command
41 from patman import tools
42
43 # The TOC header, at the start of the FIP
44 HEADER_FORMAT = '<IIQ'
45 HEADER_LEN = 0x10
46 HEADER_MAGIC = 0xaA640001
47 HEADER_SERIAL = 0x12345678
48
49 # The entry header (a table of these comes after the TOC header)
50 UUID_LEN = 16
51 ENTRY_FORMAT = f'<{UUID_LEN}sQQQ'
52 ENTRY_SIZE = 0x28
53
54 HEADER_NAMES = (
55     'name',
56     'serial',
57     'flags',
58 )
59
60 ENTRY_NAMES = (
61     'uuid',
62     'offset',
63     'size',
64     'flags',
65 )
66
67 # Set to True to enable output from running fiptool for debugging
68 VERBOSE = False
69
70 # Use a class so we can convert the bytes, making the table more readable
71 # pylint: disable=R0903
72 class FipType:
73     """A FIP entry type that we understand"""
74     def __init__(self, name, desc, uuid_bytes):
75         """Create up a new type
76
77         Args:
78             name (str): Short name for the type
79             desc (str): Longer description for the type
80             uuid_bytes (bytes): List of 16 bytes for the UUID
81         """
82         self.name = name
83         self.desc = desc
84         self.uuid = bytes(uuid_bytes)
85
86 # This is taken from tbbr_config.c in ARM Trusted Firmware
87 FIP_TYPE_LIST = [
88     # ToC Entry UUIDs
89     FipType('scp-fwu-cfg', 'SCP Firmware Updater Configuration FWU SCP_BL2U',
90             [0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44,
91              0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]),
92     FipType('ap-fwu-cfg', 'AP Firmware Updater Configuration BL2U',
93             [0x60, 0xb3, 0xeb, 0x37, 0xc1, 0xe5, 0xea, 0x41,
94              0x9d, 0xf3, 0x19, 0xed, 0xa1, 0x1f, 0x68, 0x01]),
95     FipType('fwu', 'Firmware Updater NS_BL2U',
96             [0x4f, 0x51, 0x1d, 0x11, 0x2b, 0xe5, 0x4e, 0x49,
97              0xb4, 0xc5, 0x83, 0xc2, 0xf7, 0x15, 0x84, 0x0a]),
98     FipType('fwu-cert', 'Non-Trusted Firmware Updater certificate',
99             [0x71, 0x40, 0x8a, 0xb2, 0x18, 0xd6, 0x87, 0x4c,
100              0x8b, 0x2e, 0xc6, 0xdc, 0xcd, 0x50, 0xf0, 0x96]),
101     FipType('tb-fw', 'Trusted Boot Firmware BL2',
102             [0x5f, 0xf9, 0xec, 0x0b, 0x4d, 0x22, 0x3e, 0x4d,
103              0xa5, 0x44, 0xc3, 0x9d, 0x81, 0xc7, 0x3f, 0x0a]),
104     FipType('scp-fw', 'SCP Firmware SCP_BL2',
105             [0x97, 0x66, 0xfd, 0x3d, 0x89, 0xbe, 0xe8, 0x49,
106              0xae, 0x5d, 0x78, 0xa1, 0x40, 0x60, 0x82, 0x13]),
107     FipType('soc-fw', 'EL3 Runtime Firmware BL31',
108             [0x47, 0xd4, 0x08, 0x6d, 0x4c, 0xfe, 0x98, 0x46,
109              0x9b, 0x95, 0x29, 0x50, 0xcb, 0xbd, 0x5a, 0x00]),
110     FipType('tos-fw', 'Secure Payload BL32 (Trusted OS)',
111             [0x05, 0xd0, 0xe1, 0x89, 0x53, 0xdc, 0x13, 0x47,
112              0x8d, 0x2b, 0x50, 0x0a, 0x4b, 0x7a, 0x3e, 0x38]),
113     FipType('tos-fw-extra1', 'Secure Payload BL32 Extra1 (Trusted OS Extra1)',
114             [0x0b, 0x70, 0xc2, 0x9b, 0x2a, 0x5a, 0x78, 0x40,
115              0x9f, 0x65, 0x0a, 0x56, 0x82, 0x73, 0x82, 0x88]),
116     FipType('tos-fw-extra2', 'Secure Payload BL32 Extra2 (Trusted OS Extra2)',
117             [0x8e, 0xa8, 0x7b, 0xb1, 0xcf, 0xa2, 0x3f, 0x4d,
118              0x85, 0xfd, 0xe7, 0xbb, 0xa5, 0x02, 0x20, 0xd9]),
119     FipType('nt-fw', 'Non-Trusted Firmware BL33',
120             [0xd6, 0xd0, 0xee, 0xa7, 0xfc, 0xea, 0xd5, 0x4b,
121              0x97, 0x82, 0x99, 0x34, 0xf2, 0x34, 0xb6, 0xe4]),
122     FipType('rmm-fw', 'Realm Monitor Management Firmware',
123             [0x6c, 0x07, 0x62, 0xa6, 0x12, 0xf2, 0x4b, 0x56,
124              0x92, 0xcb, 0xba, 0x8f, 0x63, 0x36, 0x06, 0xd9]),
125     # Key certificates
126     FipType('rot-cert', 'Root Of Trust key certificate',
127             [0x86, 0x2d, 0x1d, 0x72, 0xf8, 0x60, 0xe4, 0x11,
128              0x92, 0x0b, 0x8b, 0xe7, 0x62, 0x16, 0x0f, 0x24]),
129     FipType('trusted-key-cert', 'Trusted key certificate',
130             [0x82, 0x7e, 0xe8, 0x90, 0xf8, 0x60, 0xe4, 0x11,
131              0xa1, 0xb4, 0x77, 0x7a, 0x21, 0xb4, 0xf9, 0x4c]),
132     FipType('scp-fw-key-cert', 'SCP Firmware key certificate',
133             [0x02, 0x42, 0x21, 0xa1, 0xf8, 0x60, 0xe4, 0x11,
134              0x8d, 0x9b, 0xf3, 0x3c, 0x0e, 0x15, 0xa0, 0x14]),
135     FipType('soc-fw-key-cert', 'SoC Firmware key certificate',
136             [0x8a, 0xb8, 0xbe, 0xcc, 0xf9, 0x60, 0xe4, 0x11,
137              0x9a, 0xd0, 0xeb, 0x48, 0x22, 0xd8, 0xdc, 0xf8]),
138     FipType('tos-fw-key-cert', 'Trusted OS Firmware key certificate',
139             [0x94, 0x77, 0xd6, 0x03, 0xfb, 0x60, 0xe4, 0x11,
140              0x85, 0xdd, 0xb7, 0x10, 0x5b, 0x8c, 0xee, 0x04]),
141     FipType('nt-fw-key-cert', 'Non-Trusted Firmware key certificate',
142             [0x8a, 0xd5, 0x83, 0x2a, 0xfb, 0x60, 0xe4, 0x11,
143              0x8a, 0xaf, 0xdf, 0x30, 0xbb, 0xc4, 0x98, 0x59]),
144     # Content certificates
145     FipType('tb-fw-cert', 'Trusted Boot Firmware BL2 certificate',
146             [0xd6, 0xe2, 0x69, 0xea, 0x5d, 0x63, 0xe4, 0x11,
147              0x8d, 0x8c, 0x9f, 0xba, 0xbe, 0x99, 0x56, 0xa5]),
148     FipType('scp-fw-cert', 'SCP Firmware content certificate',
149             [0x44, 0xbe, 0x6f, 0x04, 0x5e, 0x63, 0xe4, 0x11,
150              0xb2, 0x8b, 0x73, 0xd8, 0xea, 0xae, 0x96, 0x56]),
151     FipType('soc-fw-cert', 'SoC Firmware content certificate',
152             [0xe2, 0xb2, 0x0c, 0x20, 0x5e, 0x63, 0xe4, 0x11,
153              0x9c, 0xe8, 0xab, 0xcc, 0xf9, 0x2b, 0xb6, 0x66]),
154     FipType('tos-fw-cert', 'Trusted OS Firmware content certificate',
155             [0xa4, 0x9f, 0x44, 0x11, 0x5e, 0x63, 0xe4, 0x11,
156              0x87, 0x28, 0x3f, 0x05, 0x72, 0x2a, 0xf3, 0x3d]),
157     FipType('nt-fw-cert', 'Non-Trusted Firmware content certificate',
158             [0x8e, 0xc4, 0xc1, 0xf3, 0x5d, 0x63, 0xe4, 0x11,
159              0xa7, 0xa9, 0x87, 0xee, 0x40, 0xb2, 0x3f, 0xa7]),
160     FipType('sip-sp-cert', 'SiP owned Secure Partition content certificate',
161             [0x77, 0x6d, 0xfd, 0x44, 0x86, 0x97, 0x4c, 0x3b,
162              0x91, 0xeb, 0xc1, 0x3e, 0x02, 0x5a, 0x2a, 0x6f]),
163     FipType('plat-sp-cert', 'Platform owned Secure Partition content certificate',
164             [0xdd, 0xcb, 0xbf, 0x4a, 0xca, 0xd6, 0x11, 0xea,
165              0x87, 0xd0, 0x02, 0x42, 0xac, 0x13, 0x00, 0x03]),
166     # Dynamic configs
167     FipType('hw-config', 'HW_CONFIG',
168             [0x08, 0xb8, 0xf1, 0xd9, 0xc9, 0xcf, 0x93, 0x49,
169              0xa9, 0x62, 0x6f, 0xbc, 0x6b, 0x72, 0x65, 0xcc]),
170     FipType('tb-fw-config', 'TB_FW_CONFIG',
171             [0x6c, 0x04, 0x58, 0xff, 0xaf, 0x6b, 0x7d, 0x4f,
172              0x82, 0xed, 0xaa, 0x27, 0xbc, 0x69, 0xbf, 0xd2]),
173     FipType('soc-fw-config', 'SOC_FW_CONFIG',
174             [0x99, 0x79, 0x81, 0x4b, 0x03, 0x76, 0xfb, 0x46,
175              0x8c, 0x8e, 0x8d, 0x26, 0x7f, 0x78, 0x59, 0xe0]),
176     FipType('tos-fw-config', 'TOS_FW_CONFIG',
177             [0x26, 0x25, 0x7c, 0x1a, 0xdb, 0xc6, 0x7f, 0x47,
178              0x8d, 0x96, 0xc4, 0xc4, 0xb0, 0x24, 0x80, 0x21]),
179     FipType('nt-fw-config', 'NT_FW_CONFIG',
180             [0x28, 0xda, 0x98, 0x15, 0x93, 0xe8, 0x7e, 0x44,
181              0xac, 0x66, 0x1a, 0xaf, 0x80, 0x15, 0x50, 0xf9]),
182     FipType('fw-config', 'FW_CONFIG',
183             [0x58, 0x07, 0xe1, 0x6a, 0x84, 0x59, 0x47, 0xbe,
184              0x8e, 0xd5, 0x64, 0x8e, 0x8d, 0xdd, 0xab, 0x0e]),
185     ] # end
186
187 FIP_TYPES = {ftype.name: ftype for ftype in FIP_TYPE_LIST}
188
189
190 def get_type_uuid(fip_type_or_uuid):
191     """get_type_uuid() - Convert a type or uuid into both
192
193     This always returns a UUID, but may not return a type since it does not do
194     the reverse lookup.
195
196     Args:
197         fip_type_or_uuid (str or bytes): Either a string containing the name of
198             an entry (e.g. 'soc-fw') or a bytes(16) containing the UUID
199
200     Returns:
201         tuple:
202             str: fip type (None if not known)
203             bytes(16): uuid
204
205     Raises:
206         ValueError: An unknown type was requested
207     """
208     if isinstance(fip_type_or_uuid, str):
209         fip_type = fip_type_or_uuid
210         lookup = FIP_TYPES.get(fip_type)
211         if not lookup:
212             raise ValueError(f"Unknown FIP entry type '{fip_type}'")
213         uuid = lookup.uuid
214     else:
215         fip_type = None
216         uuid = fip_type_or_uuid
217     return fip_type, uuid
218
219
220 # pylint: disable=R0903
221 class FipHeader:
222     """Class to represent a FIP header"""
223     def __init__(self, name, serial, flags):
224         """Set up a new header object
225
226         Args:
227             name (str): Name, i.e. HEADER_MAGIC
228             serial (str): Serial value, i.e. HEADER_SERIAL
229             flags (int64): Flags value
230         """
231         self.name = name
232         self.serial = serial
233         self.flags = flags
234
235
236 # pylint: disable=R0903
237 class FipEntry:
238     """Class to represent a single FIP entry
239
240     This is used to hold the information about an entry, including its contents.
241     Use the get_data() method to obtain the raw output for writing to the FIP
242     file.
243     """
244     def __init__(self, uuid, offset, size, flags):
245         self.uuid = uuid
246         self.offset = offset
247         self.size = size
248         self.flags = flags
249         self.fip_type = None
250         self.data = None
251         self.valid = uuid != tools.GetBytes(0, UUID_LEN)
252         if self.valid:
253             # Look up the friendly name
254             matches = {val for (key, val) in FIP_TYPES.items()
255                        if val.uuid == uuid}
256             if len(matches) == 1:
257                 self.fip_type = matches.pop().name
258
259     @classmethod
260     def from_type(cls, fip_type_or_uuid, data, flags):
261         """Create a FipEntry from a type name
262
263         Args:
264             cls (class): This class
265             fip_type_or_uuid (str or bytes): Name of the type to create, or
266                 bytes(16) uuid
267             data (bytes): Contents of entry
268             flags (int64): Flags value
269
270         Returns:
271             FipEntry: Created 241
272         """
273         fip_type, uuid = get_type_uuid(fip_type_or_uuid)
274         fent = FipEntry(uuid, None, len(data), flags)
275         fent.fip_type = fip_type
276         fent.data = data
277         return fent
278
279
280 def decode_fip(data):
281     """Decode a FIP into a header and list of FIP entries
282
283     Args:
284         data (bytes): Data block containing the FMAP
285
286     Returns:
287         Tuple:
288             header: FipHeader object
289             List of FipArea objects
290     """
291     fields = list(struct.unpack(HEADER_FORMAT, data[:HEADER_LEN]))
292     header = FipHeader(*fields)
293     fents = []
294     pos = HEADER_LEN
295     while True:
296         fields = list(struct.unpack(ENTRY_FORMAT, data[pos:pos + ENTRY_SIZE]))
297         fent = FipEntry(*fields)
298         if not fent.valid:
299             break
300         fent.data = data[fent.offset:fent.offset + fent.size]
301         fents.append(fent)
302         pos += ENTRY_SIZE
303     return header, fents
304
305
306 class FipWriter:
307     """Class to handle writing a ARM Trusted Firmware's Firmware Image Package
308
309     Usage is something like:
310
311         fip = FipWriter(size)
312         fip.add_entry('scp-fwu-cfg', tools.ReadFile('something.bin'))
313         ...
314         data = cbw.get_data()
315
316     Attributes:
317     """
318     def __init__(self, flags, align):
319         self._fip_entries = []
320         self._flags = flags
321         self._align = align
322
323     def add_entry(self, fip_type, data, flags):
324         """Add a new entry to the FIP
325
326         Args:
327             fip_type (str): Type to add, e.g. 'tos-fw-config'
328             data (bytes): Contents of entry
329             flags (int64): Entry flags
330
331         Returns:
332             FipEntry: entry that was added
333         """
334         fent = FipEntry.from_type(fip_type, data, flags)
335         self._fip_entries.append(fent)
336         return fent
337
338     def get_data(self):
339         """Obtain the full contents of the FIP
340
341         Thhis builds the FIP with headers and all required FIP entries.
342
343         Returns:
344             bytes: data resulting from building the FIP
345         """
346         buf = io.BytesIO()
347         hdr = struct.pack(HEADER_FORMAT, HEADER_MAGIC, HEADER_SERIAL,
348                           self._flags)
349         buf.write(hdr)
350
351         # Calculate the position fo the first entry
352         offset = len(hdr)
353         offset += len(self._fip_entries) * ENTRY_SIZE
354         offset += ENTRY_SIZE   # terminating entry
355
356         for fent in self._fip_entries:
357             offset = tools.Align(offset, self._align)
358             fent.offset = offset
359             offset += fent.size
360
361         # Write out the TOC
362         for fent in self._fip_entries:
363             hdr = struct.pack(ENTRY_FORMAT, fent.uuid, fent.offset, fent.size,
364                               fent.flags)
365             buf.write(hdr)
366
367         # Write out the entries
368         for fent in self._fip_entries:
369             buf.seek(fent.offset)
370             buf.write(fent.data)
371
372         return buf.getvalue()
373
374
375 class FipReader():
376     """Class to handle reading a Firmware Image Package (FIP)
377
378     Usage is something like:
379         fip = fip_util.FipReader(data)
380         fent = fip.get_entry('fwu')
381         self.WriteFile('ufwu.bin', fent.data)
382         blob = fip.get_entry(
383             bytes([0xe3, 0xb7, 0x8d, 0x9e, 0x4a, 0x64, 0x11, 0xec,
384                    0xb4, 0x5c, 0xfb, 0xa2, 0xb9, 0xb4, 0x97, 0x88]))
385         self.WriteFile('blob.bin', blob.data)
386     """
387     def __init__(self, data, read=True):
388         """Set up a new FitReader
389
390         Args:
391             data (bytes): data to read
392             read (bool): True to read the data now
393         """
394         self.fents = collections.OrderedDict()
395         self.data = data
396         if read:
397             self.read()
398
399     def read(self):
400         """Read all the files in the FIP and add them to self.files"""
401         self.header, self.fents = decode_fip(self.data)
402
403     def get_entry(self, fip_type_or_uuid):
404         """get_entry() - Find an entry by type or UUID
405
406         Args:
407             fip_type_or_uuid (str or bytes): Name of the type to create, or
408                     bytes(16) uuid
409
410         Returns:
411             FipEntry: if found
412
413         Raises:
414             ValueError: entry type not found
415         """
416         fip_type, uuid = get_type_uuid(fip_type_or_uuid)
417         for fent in self.fents:
418             if fent.uuid == uuid:
419                 return fent
420         label = fip_type
421         if not label:
422             label = UUID(bytes=uuid)
423         raise ValueError(f"Cannot find FIP entry '{label}'")
424
425
426 def parse_macros(srcdir):
427     """parse_macros: Parse the firmware_image_package.h file
428
429     Args:
430         srcdir (str): 'arm-trusted-firmware' source directory
431
432     Returns:
433         dict:
434             key: UUID macro name, e.g. 'UUID_TRUSTED_FWU_CERT'
435             value: list:
436                 file comment, e.g. 'ToC Entry UUIDs'
437                 macro name, e.g. 'UUID_TRUSTED_FWU_CERT'
438                 uuid as bytes(16)
439
440     Raises:
441         ValueError: a line cannot be parsed
442     """
443     re_uuid = re.compile('0x[0-9a-fA-F]{2}')
444     re_comment = re.compile(r'^/\* (.*) \*/$')
445     fname = os.path.join(srcdir, 'include/tools_share/firmware_image_package.h')
446     data = tools.ReadFile(fname, binary=False)
447     macros = collections.OrderedDict()
448     comment = None
449     for linenum, line in enumerate(data.splitlines()):
450         if line.startswith('/*'):
451             mat = re_comment.match(line)
452             if mat:
453                 comment = mat.group(1)
454         else:
455             # Example: #define UUID_TOS_FW_CONFIG \
456             if 'UUID' in line:
457                 macro = line.split()[1]
458             elif '{{' in line:
459                 mat = re_uuid.findall(line)
460                 if not mat or len(mat) != 16:
461                     raise ValueError(
462                         f'{fname}: Cannot parse UUID line {linenum + 1}: Got matches: {mat}')
463
464                 uuid = bytes([int(val, 16) for val in mat])
465                 macros[macro] = comment, macro, uuid
466     if not macros:
467         raise ValueError(f'{fname}: Cannot parse file')
468     return macros
469
470
471 def parse_names(srcdir):
472     """parse_names: Parse the tbbr_config.c file
473
474     Args:
475         srcdir (str): 'arm-trusted-firmware' source directory
476
477     Returns:
478         tuple: dict of entries:
479             key: UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33'
480             tuple: entry information
481                 Description of entry, e.g. 'Non-Trusted Firmware BL33'
482                 UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33'
483                 Name of entry, e.g. 'nt-fw'
484
485     Raises:
486         ValueError: the file cannot be parsed
487     """
488     # Extract the .name, .uuid and .cmdline_name values
489     re_data = re.compile(r'\.name = "([^"]*)",\s*\.uuid = (UUID_\w*),\s*\.cmdline_name = "([^"]+)"',
490                          re.S)
491     fname = os.path.join(srcdir, 'tools/fiptool/tbbr_config.c')
492     data = tools.ReadFile(fname, binary=False)
493
494     # Example entry:
495     #   {
496     #       .name = "Secure Payload BL32 Extra2 (Trusted OS Extra2)",
497     #       .uuid = UUID_SECURE_PAYLOAD_BL32_EXTRA2,
498     #       .cmdline_name = "tos-fw-extra2"
499     #   },
500     mat = re_data.findall(data)
501     if not mat:
502         raise ValueError(f'{fname}: Cannot parse file')
503     names = {uuid: (desc, uuid, name) for desc, uuid, name in mat}
504     return names
505
506
507 def create_code_output(macros, names):
508     """create_code_output() - Create the new version of this Python file
509
510     Args:
511         macros (dict):
512             key (str): UUID macro name, e.g. 'UUID_TRUSTED_FWU_CERT'
513             value: list:
514                 file comment, e.g. 'ToC Entry UUIDs'
515                 macro name, e.g. 'UUID_TRUSTED_FWU_CERT'
516                 uuid as bytes(16)
517
518         names (dict): list of entries, each
519             tuple: entry information
520                 Description of entry, e.g. 'Non-Trusted Firmware BL33'
521                 UUID macro, e.g. 'UUID_NON_TRUSTED_FIRMWARE_BL33'
522                 Name of entry, e.g. 'nt-fw'
523
524     Returns:
525         str: Table of FipType() entries
526     """
527     def _to_hex_list(data):
528         """Convert bytes into C code
529
530         Args:
531             bytes to convert
532
533         Returns:
534             str: in the format '0x12, 0x34, 0x56...'
535         """
536         # Use 0x instead of %# since the latter ignores the 0 modifier in
537         # Python 3.8.10
538         return ', '.join(['0x%02x' % byte for byte in data])
539
540     out = ''
541     last_comment = None
542     for comment, macro, uuid in macros.values():
543         name_entry = names.get(macro)
544         if not name_entry:
545             print(f"Warning: UUID '{macro}' is not mentioned in tbbr_config.c file")
546             continue
547         desc, _, name = name_entry
548         if last_comment != comment:
549             out += f'    # {comment}\n'
550             last_comment = comment
551         out += """    FipType('%s', '%s',
552             [%s,
553              %s]),
554 """ % (name, desc, _to_hex_list(uuid[:8]), _to_hex_list(uuid[8:]))
555     return out
556
557
558 def parse_atf_source(srcdir, dstfile, oldfile):
559     """parse_atf_source(): Parse the ATF source tree and update this file
560
561     Args:
562         srcdir (str): Path to 'arm-trusted-firmware' directory. Get this from:
563             https://github.com/ARM-software/arm-trusted-firmware.git
564         dstfile (str): File to write new code to, if an update is needed
565         oldfile (str): Python source file to compare against
566
567     Raises:
568         ValueError: srcdir readme.rst is missing or the first line does not
569             match what is expected
570     """
571     # We expect a readme file
572     readme_fname = os.path.join(srcdir, 'readme.rst')
573     if not os.path.exists(readme_fname):
574         raise ValueError(
575             f"Expected file '{readme_fname}' - try using -s to specify the "
576             'arm-trusted-firmware directory')
577     readme = tools.ReadFile(readme_fname, binary=False)
578     first_line = 'Trusted Firmware-A'
579     if readme.splitlines()[0] != first_line:
580         raise ValueError(f"'{readme_fname}' does not start with '{first_line}'")
581     macros = parse_macros(srcdir)
582     names = parse_names(srcdir)
583     output = create_code_output(macros, names)
584     orig = tools.ReadFile(oldfile, binary=False)
585     re_fip_list = re.compile(r'(.*FIP_TYPE_LIST = \[).*?(    ] # end.*)', re.S)
586     mat = re_fip_list.match(orig)
587     new_code = mat.group(1) + '\n' + output + mat.group(2) if mat else output
588     if new_code == orig:
589         print(f"Existing code in '{oldfile}' is up-to-date")
590     else:
591         tools.WriteFile(dstfile, new_code, binary=False)
592         print(f'Needs update, try:\n\tmeld {dstfile} {oldfile}')
593
594
595 def main(argv, oldfile):
596     """Main program for this tool
597
598     Args:
599         argv (list): List of str command-line arguments
600         oldfile (str): Python source file to compare against
601
602     Returns:
603         int: 0 (exit code)
604     """
605     parser = ArgumentParser(epilog='''Creates an updated version of this code,
606 with a table of FIP-entry types parsed from the arm-trusted-firmware source
607 directory''')
608     parser.add_argument(
609         '-D', '--debug', action='store_true',
610         help='Enabling debugging (provides a full traceback on error)')
611     parser.add_argument(
612         '-o', '--outfile', type=str, default='fip_util.py.out',
613         help='Output file to write new fip_util.py file to')
614     parser.add_argument(
615         '-s', '--src', type=str, default='.',
616         help='Directory containing the arm-trusted-firmware source')
617     args = parser.parse_args(argv)
618
619     if not args.debug:
620         sys.tracebacklimit = 0
621
622     parse_atf_source(args.src, args.outfile, oldfile)
623     return 0
624
625
626 def fiptool(fname, *fip_args):
627     """Run fiptool with provided arguments
628
629     If the tool fails then this function raises an exception and prints out the
630     output and stderr.
631
632     Args:
633         fname (str): Filename of FIP
634         *fip_args: List of arguments to pass to fiptool
635
636     Returns:
637         CommandResult: object containing the results
638
639     Raises:
640         ValueError: the tool failed to run
641     """
642     args = ['fiptool', fname] + list(fip_args)
643     result = command.RunPipe([args], capture=not VERBOSE,
644                              capture_stderr=not VERBOSE, raise_on_error=False)
645     if result.return_code:
646         print(result.stderr, file=sys.stderr)
647         raise ValueError("Failed to run (error %d): '%s'" %
648                          (result.return_code, ' '.join(args)))
649     return result
650
651
652 if __name__ == "__main__":
653     sys.exit(main(sys.argv[1:], OUR_FILE))  # pragma: no cover