Skip to content

Commit b260810

Browse files
committed
src: Add a flash layout table as part of the build.
1 parent 6a4b97f commit b260810

File tree

2 files changed

+248
-1
lines changed

2 files changed

+248
-1
lines changed

src/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
CODAL_DIR = ../lib/codal
22
RM = /bin/rm
3+
PYTHON3 ?= python3
4+
5+
SRC_HEX = $(CODAL_DIR)/MICROBIT.hex
6+
SRC_MAP = $(CODAL_DIR)/build/MICROBIT.map
7+
DEST_HEX = ./MICROBIT.hex
38

49
.PHONY: all clean
510

@@ -9,7 +14,7 @@ all:
914
cat codal.patch | git -C $(CODAL_DIR) apply -
1015
(cd $(CODAL_DIR) && ./build.py)
1116
arm-none-eabi-size $(CODAL_DIR)/build/MICROBIT
12-
cp $(CODAL_DIR)/MICROBIT.hex .
17+
$(PYTHON3) addlayouttable.py $(SRC_HEX) $(SRC_MAP) -o $(DEST_HEX)
1318

1419
clean:
1520
$(MAKE) -C codal_port clean

src/addlayouttable.py

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
#!/usr/bin/env python3
2+
3+
"""
4+
Add a flash layout table to a hex firmware for MicroPython on the micro:bit.
5+
6+
Usage: ./addlayouttable.py <firmware.hex> <firmware.map> [-o <combined.hex>]
7+
8+
Output goes to stdout if no filename is given.
9+
10+
The layout table is a sequence of 16-byte entries. The last entry contains the
11+
header (including magic numbers) and is aligned to the end of a page such that
12+
the final byte of the layout table is the final byte of the page it resides in.
13+
This is so it can be quickly and easily searched for.
14+
15+
The layout table has the following format. All integer values are unsigned and
16+
store little endian.
17+
18+
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
19+
20+
ID HT REG_PAGE REG_LEN HASH_DATA
21+
(additional regions)
22+
...
23+
MAGIC1 VERSION TABLE_LEN NUM_REG PSIZE_LOG2 MAGIC2
24+
25+
The values are:
26+
27+
ID - 1 byte - region id for this entry, defined by the region
28+
HT - 1 byte - hash type of the region hash data
29+
REG_PAGE - 2 bytes - starting page number of the region
30+
REG_LEN - 4 bytes - length in bytes of the region
31+
HASH_DATA - 8 bytes - data for the hash of this region
32+
HT=0: hash data is empty
33+
HT=1: hash data contains 8 bytes of verbatim data
34+
HT=2: hash data contains a 4-byte pointer to a string
35+
36+
MAGIC1 - 4 bytes - 0x597F30FE
37+
VERSION - 2 bytes - table version (currently 1)
38+
TABLE_LEN - 2 bytes - length in bytes of the table excluding this header row
39+
NUM_REG - 2 bytes - number of regions
40+
PSIZE_LOG2 - 2 bytes - native page size of the flash, log-2
41+
MAGIC2 - 4 bytes - 0xC1B1D79D
42+
43+
"""
44+
45+
import argparse
46+
import binascii
47+
import struct
48+
import sys
49+
50+
IHEX_TYPE_DATA = 0
51+
IHEX_TYPE_EXT_LIN_ADDR = 4
52+
53+
NRF_PAGE_SIZE_LOG2 = 12
54+
NRF_PAGE_SIZE = 1 << NRF_PAGE_SIZE_LOG2
55+
56+
57+
class FlashLayout:
58+
MAGIC1 = 0x597F30FE
59+
MAGIC2 = 0xC1B1D79D
60+
VERSION = 1
61+
62+
REGION_HASH_NONE = 0
63+
REGION_HASH_DATA = 1
64+
REGION_HASH_PTR = 2
65+
66+
def __init__(self):
67+
self.data = b""
68+
self.num_regions = 0
69+
70+
def add_region(
71+
self, region_id, region_addr, region_len, region_hash_type, region_hash=None
72+
):
73+
# Compute/validate the hash data.
74+
if region_addr % NRF_PAGE_SIZE != 0:
75+
assert 0, region_addr
76+
if region_hash_type == FlashLayout.REGION_HASH_NONE:
77+
assert region_hash is None
78+
region_hash = b"\x00" * 8
79+
elif region_hash_type == FlashLayout.REGION_HASH_DATA:
80+
assert len(region_hash) == 8
81+
elif region_hash_type == FlashLayout.REGION_HASH_PTR:
82+
region_hash = struct.pack("<II", region_hash, 0)
83+
84+
# Increase number of regions.
85+
self.num_regions += 1
86+
87+
# Add the region data.
88+
self.data += struct.pack(
89+
"<BBHI8s",
90+
region_id,
91+
region_hash_type,
92+
region_addr // NRF_PAGE_SIZE,
93+
region_len,
94+
region_hash,
95+
)
96+
97+
def finalise(self):
98+
# Add padding to data to align it to 16 bytes.
99+
if len(self.data) % 16 != 0:
100+
self.data += b"\xff" * 16 - len(self.data) % 16
101+
102+
# Add 16-byte "header" at the end with magic numbers and meta data.
103+
self.data += struct.pack(
104+
"<IHHHHI",
105+
FlashLayout.MAGIC1,
106+
FlashLayout.VERSION,
107+
len(self.data),
108+
self.num_regions,
109+
NRF_PAGE_SIZE_LOG2,
110+
FlashLayout.MAGIC2,
111+
)
112+
113+
114+
def make_ihex_record(addr, type, data):
115+
record = struct.pack(">BHB", len(data), addr & 0xFFFF, type) + data
116+
checksum = (-(sum(record))) & 0xFF
117+
return ":%s%02X" % (str(binascii.hexlify(record), "utf8").upper(), checksum)
118+
119+
120+
def parse_map_file(filename, symbols):
121+
parse_symbols = False
122+
with open(filename) as f:
123+
for line in f:
124+
line = line.strip()
125+
if line == "Linker script and memory map":
126+
parse_symbols = True
127+
elif parse_symbols and line.startswith("0x00"):
128+
line = line.split()
129+
if len(line) >= 2 and line[1] in symbols:
130+
symbols[line[1]] = int(line[0], 16)
131+
132+
133+
def output_firmware(dest, firmware, layout_addr, layout_data):
134+
# Output head of firmware.
135+
for line in firmware[:-2]:
136+
print(line, end="", file=dest)
137+
138+
# Output layout data.
139+
print(
140+
make_ihex_record(
141+
0,
142+
IHEX_TYPE_EXT_LIN_ADDR,
143+
struct.pack(">H", layout_addr >> 16),
144+
),
145+
file=dest,
146+
)
147+
for i in range(0, len(layout_data), 16):
148+
chunk = layout_data[i : min(i + 16, len(layout_data))]
149+
print(
150+
make_ihex_record(layout_addr + i, IHEX_TYPE_DATA, chunk),
151+
file=dest,
152+
)
153+
154+
# Output tail of firmware.
155+
print(firmware[-2], end="", file=dest)
156+
print(firmware[-1], end="", file=dest)
157+
158+
159+
def main():
160+
arg_parser = argparse.ArgumentParser(
161+
description="Add UICR region to hex firmware for the micro:bit."
162+
)
163+
arg_parser.add_argument(
164+
"-o",
165+
"--output",
166+
default=sys.stdout,
167+
type=argparse.FileType("wt"),
168+
help="output file (default is stdout)",
169+
)
170+
arg_parser.add_argument("firmware", nargs=1, help="input MicroPython firmware")
171+
arg_parser.add_argument(
172+
"mapfile",
173+
nargs=1,
174+
help="input map file",
175+
)
176+
args = arg_parser.parse_args()
177+
178+
# Read in the firmware from the given hex file.
179+
with open(args.firmware[0], "rt") as f:
180+
firmware = f.readlines()
181+
182+
# Parse the linker map file, looking for the following symbols.
183+
symbols = {
184+
key: None
185+
for key in [
186+
"_binary_softdevice_bin_start",
187+
"__isr_vector",
188+
"__etext",
189+
"__data_start__",
190+
"__data_end__",
191+
"_fs_start",
192+
"_fs_end",
193+
"microbit_version_string",
194+
]
195+
}
196+
parse_map_file(args.mapfile[0], symbols)
197+
198+
# Get the required symbol addresses.
199+
sd_start = symbols["_binary_softdevice_bin_start"]
200+
sd_end = symbols["__isr_vector"]
201+
mp_start = symbols["__isr_vector"]
202+
data_len = symbols["__data_end__"] - symbols["__data_start__"]
203+
mp_end = symbols["__etext"] + data_len
204+
mp_version = symbols["microbit_version_string"]
205+
fs_start = symbols["_fs_start"]
206+
fs_end = symbols["_fs_end"]
207+
208+
# Make the flash layout information table.
209+
layout = FlashLayout()
210+
layout.add_region(1, sd_start, sd_end - sd_start, FlashLayout.REGION_HASH_NONE)
211+
layout.add_region(
212+
2, mp_start, mp_end - mp_start, FlashLayout.REGION_HASH_PTR, mp_version
213+
)
214+
layout.add_region(3, fs_start, fs_end - fs_start, FlashLayout.REGION_HASH_NONE)
215+
layout.finalise()
216+
217+
# Compute layout address.
218+
layout_addr = (
219+
((mp_end >> NRF_PAGE_SIZE_LOG2) << NRF_PAGE_SIZE_LOG2)
220+
+ NRF_PAGE_SIZE
221+
- len(layout.data)
222+
)
223+
if layout_addr < mp_end:
224+
layout_addr += NRF_PAGE_SIZE
225+
if layout_addr >= fs_start:
226+
print("ERROR: Flash layout information overlaps with filesystem")
227+
sys.exit(1)
228+
229+
# Print information.
230+
if args.output is not sys.stdout:
231+
fmt = "{:13} 0x{:05x}..0x{:05x}"
232+
print(fmt.format("SoftDevice", sd_start, sd_end))
233+
print(fmt.format("MicroPython", mp_start, mp_end))
234+
print(fmt.format("Layout table", layout_addr, layout_addr + len(layout.data)))
235+
print(fmt.format("Filesystem", fs_start, fs_end))
236+
237+
# Output the new firmware as a hex file.
238+
output_firmware(args.output, firmware, layout_addr, layout.data)
239+
240+
241+
if __name__ == "__main__":
242+
main()

0 commit comments

Comments
 (0)