1# SPDX-License-Identifier: GPL-2.0+
2# Copyright (c) 2018 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
5# Support for flashrom's FMAP format. This supports a header followed by a
6# number of 'areas', describing regions of a firmware storage device,
7# generally SPI flash.
8
9import collections
10import struct
11import sys
12
13from u_boot_pylib import tools
14
15# constants imported from lib/fmap.h
16FMAP_SIGNATURE = b'__FMAP__'
17FMAP_VER_MAJOR = 1
18FMAP_VER_MINOR = 0
19FMAP_STRLEN = 32
20
21FMAP_AREA_STATIC = 1 << 0
22FMAP_AREA_COMPRESSED = 1 << 1
23FMAP_AREA_RO = 1 << 2
24
25FMAP_HEADER_LEN = 56
26FMAP_AREA_LEN = 42
27
28FMAP_HEADER_FORMAT = '<8sBBQI%dsH'% (FMAP_STRLEN)
29FMAP_AREA_FORMAT = '<II%dsH' % (FMAP_STRLEN)
30
31FMAP_HEADER_NAMES = (
32    'signature',
33    'ver_major',
34    'ver_minor',
35    'base',
36    'image_size',
37    'name',
38    'nareas',
39)
40
41FMAP_AREA_NAMES = (
42    'offset',
43    'size',
44    'name',
45    'flags',
46)
47
48# Flags supported by areas (bits 2:0 are unused so not included here)
49FMAP_AREA_PRESERVE = 1 << 3  # Preserved by any firmware updates
50
51# These are the two data structures supported by flashrom, a header (which
52# appears once at the start) and an area (which is repeated until the end of
53# the list of areas)
54FmapHeader = collections.namedtuple('FmapHeader', FMAP_HEADER_NAMES)
55FmapArea = collections.namedtuple('FmapArea', FMAP_AREA_NAMES)
56
57
58def NameToFmap(name):
59    if type(name) == bytes:
60        name = name.decode('utf-8')
61    return name.replace('\0', '').replace('-', '_').upper()
62
63def ConvertName(field_names, fields):
64    """Convert a name to something flashrom likes
65
66    Flashrom requires upper case, underscores instead of hyphens. We remove any
67    null characters as well. This updates the 'name' value in fields.
68
69    Args:
70        field_names: List of field names for this struct
71        fields: Dict:
72            key: Field name
73            value: value of that field (string for the ones we support)
74    """
75    name_index = field_names.index('name')
76    fields[name_index] = tools.to_bytes(NameToFmap(fields[name_index]))
77
78def DecodeFmap(data):
79    """Decode a flashmap into a header and list of areas
80
81    Args:
82        data: Data block containing the FMAP
83
84    Returns:
85        Tuple:
86            header: FmapHeader object
87            List of FmapArea objects
88    """
89    fields = list(struct.unpack(FMAP_HEADER_FORMAT, data[:FMAP_HEADER_LEN]))
90    ConvertName(FMAP_HEADER_NAMES, fields)
91    header = FmapHeader(*fields)
92    areas = []
93    data = data[FMAP_HEADER_LEN:]
94    for area in range(header.nareas):
95        fields = list(struct.unpack(FMAP_AREA_FORMAT, data[:FMAP_AREA_LEN]))
96        ConvertName(FMAP_AREA_NAMES, fields)
97        areas.append(FmapArea(*fields))
98        data = data[FMAP_AREA_LEN:]
99    return header, areas
100
101def EncodeFmap(image_size, name, areas):
102    """Create a new FMAP from a list of areas
103
104    Args:
105        image_size: Size of image, to put in the header
106        name: Name of image, to put in the header
107        areas: List of FmapArea objects
108
109    Returns:
110        String containing the FMAP created
111    """
112    def _FormatBlob(fmt, names, obj):
113        params = [getattr(obj, name) for name in names]
114        ConvertName(names, params)
115        return struct.pack(fmt, *params)
116
117    values = FmapHeader(FMAP_SIGNATURE, 1, 0, 0, image_size, name, len(areas))
118    blob = _FormatBlob(FMAP_HEADER_FORMAT, FMAP_HEADER_NAMES, values)
119    for area in areas:
120        blob += _FormatBlob(FMAP_AREA_FORMAT, FMAP_AREA_NAMES, area)
121    return blob
122