Skip to content

Commit ba21763

Browse files
authored
Merge pull request #3733 from theotherjimmy/feature-bootloader
Enable boot-loader builds
2 parents cf64f09 + d62f046 commit ba21763

File tree

5 files changed

+180
-23
lines changed

5 files changed

+180
-23
lines changed

targets/targets.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"features": [],
1111
"detect_code": [],
1212
"public": false,
13-
"default_lib": "std"
13+
"default_lib": "std",
14+
"bootloader_supported": false
1415
},
1516
"Super_Target": {
1617
"inherits": ["Target"],
@@ -455,7 +456,8 @@
455456
"detect_code": ["0220"],
456457
"device_has": ["ANALOGIN", "ANALOGOUT", "ERROR_RED", "I2C", "I2CSLAVE", "INTERRUPTIN", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SEMIHOST", "SERIAL", "SLEEP", "SPI", "SPISLAVE", "STDIO_MESSAGES", "FLASH"],
457458
"release_versions": ["2", "5"],
458-
"device_name": "MKL46Z256xxx4"
459+
"device_name": "MKL46Z256xxx4",
460+
"bootloader_supported": true
459461
},
460462
"K20D50M": {
461463
"inherits": ["Target"],
@@ -581,7 +583,8 @@
581583
"device_has": ["ANALOGIN", "ANALOGOUT", "ERROR_RED", "I2C", "I2CSLAVE", "INTERRUPTIN", "LOWPOWERTIMER", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "RTC", "SERIAL", "SERIAL_FC", "SERIAL_ASYNCH", "SLEEP", "SPI", "SPI_ASYNCH", "SPISLAVE", "STDIO_MESSAGES", "STORAGE", "TRNG", "FLASH"],
582584
"features": ["LWIP", "STORAGE"],
583585
"release_versions": ["2", "5"],
584-
"device_name": "MK64FN1M0xxx12"
586+
"device_name": "MK64FN1M0xxx12",
587+
"bootloader_supported": true
585588
},
586589
"MTS_GAMBIT": {
587590
"inherits": ["Target"],
@@ -889,7 +892,8 @@
889892
"detect_code": ["0796"],
890893
"features": ["LWIP"],
891894
"release_versions": ["2", "5"],
892-
"device_name" : "STM32F429ZI"
895+
"device_name" : "STM32F429ZI",
896+
"bootloader_supported": true
893897
},
894898
"NUCLEO_F439ZI": {
895899
"supported_form_factors": ["ARDUINO"],
@@ -1327,7 +1331,8 @@
13271331
"device_has": ["ANALOGIN", "CAN", "EMAC", "I2C", "I2CSLAVE", "INTERRUPTIN", "PORTIN", "PORTINOUT", "PORTOUT", "PWMOUT", "SERIAL", "SLEEP", "SPI", "SPISLAVE", "STDIO_MESSAGES", "TRNG", "FLASH"],
13281332
"features": ["LWIP"],
13291333
"release_versions": ["5"],
1330-
"device_name": "STM32F439ZI"
1334+
"device_name": "STM32F439ZI",
1335+
"bootloader_supported": true
13311336
},
13321337
"NZ32_SC151": {
13331338
"inherits": ["Target"],

tools/build_api.py

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
import tempfile
2020
from types import ListType
2121
from shutil import rmtree
22-
from os.path import join, exists, dirname, basename, abspath, normpath
23-
from os import linesep, remove
22+
from os.path import join, exists, dirname, basename, abspath, normpath, splitext
23+
from os import linesep, remove, makedirs
2424
from time import time
25+
from intelhex import IntelHex
2526

2627
from tools.utils import mkdir, run_cmd, run_cmd_ext, NotSupportedException,\
27-
ToolException, InvalidReleaseTargetException
28+
ToolException, InvalidReleaseTargetException, intelhex_offset
2829
from tools.paths import MBED_CMSIS_PATH, MBED_TARGETS_PATH, MBED_LIBRARIES,\
2930
MBED_HEADER, MBED_DRIVERS, MBED_PLATFORM, MBED_HAL, MBED_CONFIG_FILE,\
3031
MBED_LIBRARIES_DRIVERS, MBED_LIBRARIES_PLATFORM, MBED_LIBRARIES_HAL,\
@@ -274,6 +275,29 @@ def get_mbed_official_release(version):
274275

275276
return mbed_official_release
276277

278+
def add_regions_to_profile(profile, config, toolchain_class):
279+
"""Add regions to the build profile, if there are any.
280+
281+
Positional Arguments:
282+
profile - the profile to update
283+
config - the configuration object that owns the region
284+
toolchain_class - the class of the toolchain being used
285+
"""
286+
regions = list(config.regions)
287+
for region in regions:
288+
for define in [(region.name.upper() + "_ADDR", region.start),
289+
(region.name.upper() + "_SIZE", region.size)]:
290+
profile["common"].append("-D%s=0x%x" % define)
291+
active_region = [r for r in regions if r.active][0]
292+
for define in [("MBED_APP_START", active_region.start),
293+
("MBED_APP_SIZE", active_region.size)]:
294+
profile["ld"].append(toolchain_class.make_ld_define(*define))
295+
296+
print("Using regions in this build:")
297+
for region in regions:
298+
print(" Region %s size 0x%x, offset 0x%x"
299+
% (region.name, region.size, region.start))
300+
277301

278302
def prepare_toolchain(src_paths, target, toolchain_name,
279303
macros=None, clean=False, jobs=1,
@@ -307,14 +331,16 @@ def prepare_toolchain(src_paths, target, toolchain_name,
307331
# If the configuration object was not yet created, create it now
308332
config = config or Config(target, src_paths, app_config=app_config)
309333
target = config.target
310-
311-
# Toolchain instance
312334
try:
313-
toolchain = TOOLCHAIN_CLASSES[toolchain_name](
314-
target, notify, macros, silent,
315-
extra_verbose=extra_verbose, build_profile=build_profile)
335+
cur_tc = TOOLCHAIN_CLASSES[toolchain_name]
316336
except KeyError:
317337
raise KeyError("Toolchain %s not supported" % toolchain_name)
338+
if config.has_regions:
339+
add_regions_to_profile(build_profile, config, cur_tc)
340+
341+
# Toolchain instance
342+
toolchain = cur_tc(target, notify, macros, silent,
343+
extra_verbose=extra_verbose, build_profile=build_profile)
318344

319345
toolchain.config = config
320346
toolchain.jobs = jobs
@@ -323,6 +349,41 @@ def prepare_toolchain(src_paths, target, toolchain_name,
323349

324350
return toolchain
325351

352+
def merge_region_list(region_list, destination, padding=b'\xFF'):
353+
"""Merege the region_list into a single image
354+
355+
Positional Arguments:
356+
region_list - list of regions, which should contain filenames
357+
destination - file name to write all regions to
358+
padding - bytes to fill gapps with
359+
"""
360+
merged = IntelHex()
361+
362+
print("Merging Regions:")
363+
364+
for region in region_list:
365+
if region.active and not region.filename:
366+
raise ToolException("Active region has no contents: No file found.")
367+
if region.filename:
368+
print(" Filling region %s with %s" % (region.name, region.filename))
369+
part = intelhex_offset(region.filename, offset=region.start)
370+
part_size = (part.maxaddr() - part.minaddr()) + 1
371+
if part_size > region.size:
372+
raise ToolException("Contents of region %s does not fit"
373+
% region.name)
374+
merged.merge(part)
375+
pad_size = region.size - part_size
376+
if pad_size > 0 and region != region_list[-1]:
377+
print(" Padding region %s with 0x%x bytes" % (region.name, pad_size))
378+
merged.puts(merged.maxaddr() + 1, padding * pad_size)
379+
380+
if not exists(dirname(destination)):
381+
makedirs(dirname(destination))
382+
print("Space used after regions merged: 0x%x" %
383+
(merged.maxaddr() - merged.minaddr() + 1))
384+
with open(destination, "wb+") as output:
385+
merged.tofile(output, format='bin')
386+
326387
def scan_resources(src_paths, toolchain, dependencies_paths=None,
327388
inc_dirs=None, base_path=None):
328389
""" Scan resources using initialized toolcain
@@ -453,7 +514,15 @@ def build_project(src_paths, build_path, target, toolchain_name,
453514
resources.objects.extend(objects)
454515

455516
# Link Program
456-
res, _ = toolchain.link_program(resources, build_path, name)
517+
if toolchain.config.has_regions:
518+
res, _ = toolchain.link_program(resources, build_path, name + "_application")
519+
region_list = list(toolchain.config.regions)
520+
region_list = [r._replace(filename=res) if r.active else r
521+
for r in region_list]
522+
res = join(build_path, name) + ".bin"
523+
merge_region_list(region_list, res)
524+
else:
525+
res, _ = toolchain.link_program(resources, build_path, name)
457526

458527
memap_instance = getattr(toolchain, 'memap_instance', None)
459528
memap_table = ''

tools/config.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
from copy import deepcopy
1919
import os
2020
import sys
21+
from collections import namedtuple
22+
from os.path import splitext
23+
from intelhex import IntelHex
2124
# Implementation of mbed configuration mechanism
22-
from tools.utils import json_file_to_dict
25+
from tools.utils import json_file_to_dict, intelhex_offset
26+
from tools.arm_pack_manager import Cache
2327
from tools.targets import CUMULATIVE_ATTRIBUTES, TARGET_MAP, \
2428
generate_py_target, get_resolution_order
2529

@@ -328,6 +332,8 @@ def _process_macros(mlist, macros, unit_name, unit_kind):
328332
macros[macro.macro_name] = macro
329333

330334

335+
Region = namedtuple("Region", "name start size active filename")
336+
331337
class Config(object):
332338
"""'Config' implements the mbed configuration mechanism"""
333339

@@ -346,6 +352,8 @@ class Config(object):
346352
"macros", "__config_path"])
347353
}
348354

355+
__unused_overrides = set(["target.bootloader_img", "target.restrict_size"])
356+
349357
# Allowed features in configurations
350358
__allowed_features = [
351359
"UVISOR", "BLE", "CLIENT", "IPV4", "LWIP", "COMMON_PAL", "STORAGE", "NANOSTACK",
@@ -455,6 +463,54 @@ def add_config_files(self, flist):
455463
self.lib_config_data[cfg["name"]]["__config_path"]))
456464
self.lib_config_data[cfg["name"]] = cfg
457465

466+
@property
467+
def has_regions(self):
468+
"""Does this config have regions defined?"""
469+
if 'target_overrides' in self.app_config_data:
470+
target_overrides = self.app_config_data['target_overrides'].get(
471+
self.target.name, {})
472+
return ('target.bootloader_img' in target_overrides or
473+
'target.restrict_size' in target_overrides)
474+
else:
475+
return False
476+
477+
@property
478+
def regions(self):
479+
"""Generate a list of regions from the config"""
480+
if not self.target.bootloader_supported:
481+
raise ConfigException("Bootloader not supported on this target.")
482+
cmsis_part = Cache(False, False).index[self.target.device_name]
483+
start = 0
484+
target_overrides = self.app_config_data['target_overrides'].get(
485+
self.target.name, {})
486+
try:
487+
rom_size = int(cmsis_part['memory']['IROM1']['size'], 0)
488+
rom_start = int(cmsis_part['memory']['IROM1']['start'], 0)
489+
except KeyError:
490+
raise ConfigException("Not enough information in CMSIS packs to "
491+
"build a bootloader project")
492+
if 'target.bootloader_img' in target_overrides:
493+
filename = target_overrides['target.bootloader_img']
494+
part = intelhex_offset(filename, offset=rom_start)
495+
if part.minaddr() != rom_start:
496+
raise ConfigException("bootloader executable does not "
497+
"start at 0x%x" % rom_start)
498+
part_size = (part.maxaddr() - part.minaddr()) + 1
499+
yield Region("bootloader", rom_start + start, part_size, False,
500+
filename)
501+
start += part_size
502+
if 'target.restrict_size' in target_overrides:
503+
new_size = int(target_overrides['target.restrict_size'], 0)
504+
yield Region("application", rom_start + start, new_size, True, None)
505+
start += new_size
506+
yield Region("post_application", rom_start +start, rom_size - start,
507+
False, None)
508+
else:
509+
yield Region("application", rom_start + start, rom_size - start,
510+
True, None)
511+
if start > rom_size:
512+
raise ConfigException("Not enough memory on device to fit all "
513+
"application regions")
458514

459515
def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
460516
"""Process "config_parameters" and "target_config_overrides" into a
@@ -508,6 +564,8 @@ def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
508564
if full_name in params:
509565
params[full_name].set_value(val, unit_name, unit_kind,
510566
label)
567+
elif name in self.__unused_overrides:
568+
pass
511569
else:
512570
self.config_errors.append(
513571
ConfigException(
@@ -560,6 +618,8 @@ def get_target_config_data(self):
560618
rel_names = [tgt for tgt, _ in
561619
get_resolution_order(self.target.json_data, tname,
562620
[])]
621+
if full_name in self.__unused_overrides:
622+
continue
563623
if (full_name not in params) or \
564624
(params[full_name].defined_by[7:] not in rel_names):
565625
raise ConfigException(

tools/test/build_api/build_api_test.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def test_always_complete_build(self, *_):
6868
toolchain.config_file = "junk"
6969
toolchain.compile_sources(res, self.build_path)
7070

71+
print notify.mock_calls
7172
assert any('percent' in msg[0] and msg[0]['percent'] == 100.0
7273
for _, msg, _ in notify.mock_calls if msg)
7374

@@ -81,10 +82,13 @@ def test_prepare_toolchain_app_config(self, mock_config_init):
8182
:return:
8283
"""
8384
app_config = "app_config"
84-
mock_config_init.return_value = namedtuple("Config", "target")(
85-
namedtuple("Target",
86-
"init_hooks name features core")(lambda _, __ : None,
87-
"Junk", [], "Cortex-M3"))
85+
mock_target = namedtuple("Target",
86+
"init_hooks name features core")(lambda _, __ : None,
87+
"Junk", [], "Cortex-M3")
88+
mock_config_init.return_value = namedtuple("Config",
89+
"target has_regions")(
90+
mock_target,
91+
False)
8892

8993
prepare_toolchain(self.src_paths, self.target, self.toolchain_name,
9094
app_config=app_config)
@@ -100,10 +104,13 @@ def test_prepare_toolchain_no_app_config(self, mock_config_init):
100104
:param mock_config_init: mock of Config __init__
101105
:return:
102106
"""
103-
mock_config_init.return_value = namedtuple("Config", "target")(
104-
namedtuple("Target",
105-
"init_hooks name features core")(lambda _, __ : None,
106-
"Junk", [], "Cortex-M3"))
107+
mock_target = namedtuple("Target",
108+
"init_hooks name features core")(lambda _, __ : None,
109+
"Junk", [], "Cortex-M3")
110+
mock_config_init.return_value = namedtuple("Config",
111+
"target has_regions")(
112+
mock_target,
113+
False)
107114

108115
prepare_toolchain(self.src_paths, self.target, self.toolchain_name)
109116

@@ -127,6 +134,7 @@ def test_build_project_app_config(self, mock_prepare_toolchain, mock_exists, _,
127134
app_config = "app_config"
128135
mock_exists.return_value = False
129136
mock_prepare_toolchain().link_program.return_value = 1, 2
137+
mock_prepare_toolchain().config = namedtuple("Config", "has_regions")(None)
130138

131139
build_project(self.src_paths, self.build_path, self.target,
132140
self.toolchain_name, app_config=app_config)
@@ -154,6 +162,7 @@ def test_build_project_no_app_config(self, mock_prepare_toolchain, mock_exists,
154162
mock_exists.return_value = False
155163
# Needed for the unpacking of the returned value
156164
mock_prepare_toolchain().link_program.return_value = 1, 2
165+
mock_prepare_toolchain().config = namedtuple("Config", "has_regions")(None)
157166

158167
build_project(self.src_paths, self.build_path, self.target,
159168
self.toolchain_name)

tools/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import json
2929
from collections import OrderedDict
3030
import logging
31+
from intelhex import IntelHex
3132

3233
def remove_if_in(lst, thing):
3334
if thing in lst:
@@ -514,3 +515,16 @@ def print_large_string(large_string):
514515
else:
515516
end_index = ((string_part + 1) * string_limit) - 1
516517
print large_string[start_index:end_index],
518+
519+
def intelhex_offset(filename, offset):
520+
"""Load a hex or bin file at a particular offset"""
521+
_, inteltype = splitext(filename)
522+
ih = IntelHex()
523+
if inteltype == ".bin":
524+
ih.loadbin(filename, offset=offset)
525+
elif inteltype == ".hex":
526+
ih.loadhex(filename)
527+
else:
528+
raise ToolException("File %s does not have a known binary file type"
529+
% filename)
530+
return ih

0 commit comments

Comments
 (0)