| # SPDX-License-Identifier: GPL-2.0+ | |
| # Copyright (c) 2018 Google, Inc | |
| # Written by Simon Glass <sjg@chromium.org> | |
| # | |
| # Support for flashrom's FMAP format. This supports a header followed by a | |
| # number of 'areas', describing regions of a firmware storage device, | |
| # generally SPI flash. | |
| import collections | |
| import struct | |
| import sys | |
| from u_boot_pylib import tools | |
| # constants imported from lib/fmap.h | |
| FMAP_SIGNATURE = b'__FMAP__' | |
| FMAP_VER_MAJOR = 1 | |
| FMAP_VER_MINOR = 0 | |
| FMAP_STRLEN = 32 | |
| FMAP_AREA_STATIC = 1 << 0 | |
| FMAP_AREA_COMPRESSED = 1 << 1 | |
| FMAP_AREA_RO = 1 << 2 | |
| FMAP_HEADER_LEN = 56 | |
| FMAP_AREA_LEN = 42 | |
| FMAP_HEADER_FORMAT = '<8sBBQI%dsH'% (FMAP_STRLEN) | |
| FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN) | |
| FMAP_HEADER_NAMES = ( | |
| 'signature', | |
| 'ver_major', | |
| 'ver_minor', | |
| 'base', | |
| 'image_size', | |
| 'name', | |
| 'nareas', | |
| ) | |
| FMAP_AREA_NAMES = ( | |
| 'offset', | |
| 'size', | |
| 'name', | |
| 'flags', | |
| ) | |
| # Flags supported by areas (bits 2:0 are unused so not included here) | |
| FMAP_AREA_PRESERVE = 1 << 3 # Preserved by any firmware updates | |
| # These are the two data structures supported by flashrom, a header (which | |
| # appears once at the start) and an area (which is repeated until the end of | |
| # the list of areas) | |
| FmapHeader = collections.namedtuple('FmapHeader', FMAP_HEADER_NAMES) | |
| FmapArea = collections.namedtuple('FmapArea', FMAP_AREA_NAMES) | |
| def NameToFmap(name): | |
| if type(name) == bytes: | |
| name = name.decode('utf-8') | |
| return name.replace('\0', '').replace('-', '_').upper() | |
| def ConvertName(field_names, fields): | |
| """Convert a name to something flashrom likes | |
| Flashrom requires upper case, underscores instead of hyphens. We remove any | |
| null characters as well. This updates the 'name' value in fields. | |
| Args: | |
| field_names: List of field names for this struct | |
| fields: Dict: | |
| key: Field name | |
| value: value of that field (string for the ones we support) | |
| """ | |
| name_index = field_names.index('name') | |
| fields[name_index] = tools.to_bytes(NameToFmap(fields[name_index])) | |
| def DecodeFmap(data): | |
| """Decode a flashmap into a header and list of areas | |
| Args: | |
| data: Data block containing the FMAP | |
| Returns: | |
| Tuple: | |
| header: FmapHeader object | |
| List of FmapArea objects | |
| """ | |
| fields = list(struct.unpack(FMAP_HEADER_FORMAT, data[:FMAP_HEADER_LEN])) | |
| ConvertName(FMAP_HEADER_NAMES, fields) | |
| header = FmapHeader(*fields) | |
| areas = [] | |
| data = data[FMAP_HEADER_LEN:] | |
| for area in range(header.nareas): | |
| fields = list(struct.unpack(FMAP_AREA_FORMAT, data[:FMAP_AREA_LEN])) | |
| ConvertName(FMAP_AREA_NAMES, fields) | |
| areas.append(FmapArea(*fields)) | |
| data = data[FMAP_AREA_LEN:] | |
| return header, areas | |
| def EncodeFmap(image_size, name, areas): | |
| """Create a new FMAP from a list of areas | |
| Args: | |
| image_size: Size of image, to put in the header | |
| name: Name of image, to put in the header | |
| areas: List of FmapArea objects | |
| Returns: | |
| String containing the FMAP created | |
| """ | |
| def _FormatBlob(fmt, names, obj): | |
| params = [getattr(obj, name) for name in names] | |
| ConvertName(names, params) | |
| return struct.pack(fmt, *params) | |
| values = FmapHeader(FMAP_SIGNATURE, 1, 0, 0, image_size, name, len(areas)) | |
| blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, values) | |
| for area in areas: | |
| blob += _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area) | |
| return blob |