Enable boot-loader builds

To enable a boot-loader style application, override
"targets.bootloader_exec" or "targets.restrict_size" on a particular
target. These parameters are a bin or hex file, and an integer, in bytes,
respectively. If either override is present, then an application region
is created after the boot-loader region, when "targets.bootloader_exec"
is present, and before post-application, when "targets.restric_size" is
present. The size of the boot-loader region is read from the file
provided in the configuration.
pull/3733/head
Jimmy Brisson 2017-02-01 16:24:39 -06:00
parent ff41cc97ae
commit 9a63bfb1fb
4 changed files with 168 additions and 18 deletions

View File

@ -19,12 +19,13 @@ import re
import tempfile
from types import ListType
from shutil import rmtree
from os.path import join, exists, dirname, basename, abspath, normpath
from os import linesep, remove
from os.path import join, exists, dirname, basename, abspath, normpath, splitext
from os import linesep, remove, makedirs
from time import time
from intelhex import IntelHex
from tools.utils import mkdir, run_cmd, run_cmd_ext, NotSupportedException,\
ToolException, InvalidReleaseTargetException
ToolException, InvalidReleaseTargetException, intelhex_offset
from tools.paths import MBED_CMSIS_PATH, MBED_TARGETS_PATH, MBED_LIBRARIES,\
MBED_HEADER, MBED_DRIVERS, MBED_PLATFORM, MBED_HAL, MBED_CONFIG_FILE,\
MBED_LIBRARIES_DRIVERS, MBED_LIBRARIES_PLATFORM, MBED_LIBRARIES_HAL,\
@ -274,6 +275,29 @@ def get_mbed_official_release(version):
return mbed_official_release
def add_regions_to_profile(profile, config, toolchain_class):
"""Add regions to the build profile, if there are any.
Positional Arguments:
profile - the profile to update
config - the configuration object that owns the region
toolchain_class - the class of the toolchain being used
"""
regions = list(config.regions)
for region in regions:
for define in [(region.name.upper() + "_ADDR", region.start),
(region.name.upper() + "_SIZE", region.size)]:
profile["common"].append("-D%s=0x%x" % define)
active_region = [r for r in regions if r.active][0]
for define in [("MBED_APP_START", active_region.start),
("MBED_APP_SIZE", active_region.size)]:
profile["ld"].append(toolchain_class.make_ld_define(*define))
print("Using regions in this build:")
for region in regions:
print(" Region %s size 0x%x, offset 0x%x"
% (region.name, region.size, region.start))
def prepare_toolchain(src_paths, target, toolchain_name,
macros=None, clean=False, jobs=1,
@ -307,14 +331,16 @@ def prepare_toolchain(src_paths, target, toolchain_name,
# If the configuration object was not yet created, create it now
config = config or Config(target, src_paths, app_config=app_config)
target = config.target
# Toolchain instance
try:
toolchain = TOOLCHAIN_CLASSES[toolchain_name](
target, notify, macros, silent,
extra_verbose=extra_verbose, build_profile=build_profile)
cur_tc = TOOLCHAIN_CLASSES[toolchain_name]
except KeyError:
raise KeyError("Toolchain %s not supported" % toolchain_name)
if config.has_regions:
add_regions_to_profile(build_profile, config, cur_tc)
# Toolchain instance
toolchain = cur_tc(target, notify, macros, silent,
extra_verbose=extra_verbose, build_profile=build_profile)
toolchain.config = config
toolchain.jobs = jobs
@ -323,6 +349,41 @@ def prepare_toolchain(src_paths, target, toolchain_name,
return toolchain
def merge_region_list(region_list, destination, padding=b'\xFF'):
"""Merege the region_list into a single image
Positional Arguments:
region_list - list of regions, which should contain filenames
destination - file name to write all regions to
padding - bytes to fill gapps with
"""
merged = IntelHex()
print("Merging Regions:")
for region in region_list:
if region.active and not region.filename:
raise ToolException("Active region has no contents: No file found.")
if region.filename:
print(" Filling region %s with %s" % (region.name, region.filename))
part = intelhex_offset(region.filename, offset=region.start)
part_size = (part.maxaddr() - part.minaddr()) + 1
if part_size > region.size:
raise ToolException("Contents of region %s does not fit"
% region.name)
merged.merge(part)
pad_size = region.size - part_size
if pad_size > 0 and region != region_list[-1]:
print(" Padding region %s with 0x%x bytes" % (region.name, pad_size))
merged.puts(merged.maxaddr() + 1, padding * pad_size)
if not exists(dirname(destination)):
makedirs(dirname(destination))
print("Space used after regions merged: 0x%x" %
(merged.maxaddr() - merged.minaddr() + 1))
with open(destination, "wb+") as output:
merged.tofile(output, format='bin')
def scan_resources(src_paths, toolchain, dependencies_paths=None,
inc_dirs=None, base_path=None):
""" Scan resources using initialized toolcain
@ -453,7 +514,15 @@ def build_project(src_paths, build_path, target, toolchain_name,
resources.objects.extend(objects)
# Link Program
res, _ = toolchain.link_program(resources, build_path, name)
if toolchain.config.has_regions:
res, _ = toolchain.link_program(resources, build_path, name + "_application")
region_list = list(toolchain.config.regions)
region_list = [r._replace(filename=res) if r.active else r
for r in region_list]
res = join(build_path, name) + ".bin"
merge_region_list(region_list, res)
else:
res, _ = toolchain.link_program(resources, build_path, name)
memap_instance = getattr(toolchain, 'memap_instance', None)
memap_table = ''

View File

@ -18,8 +18,12 @@ limitations under the License.
from copy import deepcopy
import os
import sys
from collections import namedtuple
from os.path import splitext
from intelhex import IntelHex
# Implementation of mbed configuration mechanism
from tools.utils import json_file_to_dict
from tools.utils import json_file_to_dict, intelhex_offset
from tools.arm_pack_manager import Cache
from tools.targets import CUMULATIVE_ATTRIBUTES, TARGET_MAP, \
generate_py_target, get_resolution_order
@ -328,6 +332,8 @@ def _process_macros(mlist, macros, unit_name, unit_kind):
macros[macro.macro_name] = macro
Region = namedtuple("Region", "name start size active filename")
class Config(object):
"""'Config' implements the mbed configuration mechanism"""
@ -346,6 +352,8 @@ class Config(object):
"macros", "__config_path"])
}
__unused_overrides = set(["target.bootloader_img", "target.restrict_size"])
# Allowed features in configurations
__allowed_features = [
"UVISOR", "BLE", "CLIENT", "IPV4", "LWIP", "COMMON_PAL", "STORAGE", "NANOSTACK",
@ -455,6 +463,52 @@ class Config(object):
self.lib_config_data[cfg["name"]]["__config_path"]))
self.lib_config_data[cfg["name"]] = cfg
@property
def has_regions(self):
"""Does this config have regions defined?"""
if 'target_overrides' in self.app_config_data:
target_overrides = self.app_config_data['target_overrides'].get(
self.target.name, {})
return ('target.bootloader_img' in target_overrides or
'target.restrict_size' in target_overrides)
else:
return False
@property
def regions(self):
"""Generate a list of regions from the config"""
cmsis_part = Cache(False, False).index[self.target.device_name]
start = 0
target_overrides = self.app_config_data['target_overrides'].get(
self.target.name, {})
try:
rom_size = int(cmsis_part['memory']['IROM1']['size'], 0)
rom_start = int(cmsis_part['memory']['IROM1']['start'], 0)
except KeyError:
raise ConfigException("Not enough information in CMSIS packs to "
"build a bootloader project")
if 'target.bootloader_img' in target_overrides:
filename = target_overrides['target.bootloader_img']
part = intelhex_offset(filename, offset=rom_start)
if part.minaddr() != rom_start:
raise ConfigException("bootloader executable does not "
"start at 0x%x" % rom_start)
part_size = (part.maxaddr() - part.minaddr()) + 1
yield Region("bootloader", rom_start + start, part_size, False,
filename)
start += part_size
if 'target.restrict_size' in target_overrides:
new_size = int(target_overrides['target.restrict_size'], 0)
yield Region("application", rom_start + start, new_size, True, None)
start += new_size
yield Region("post_application", rom_start +start, rom_size - start,
False, None)
else:
yield Region("application", rom_start + start, rom_size - start,
True, None)
if start > rom_size:
raise ConfigException("Not enough memory on device to fit all "
"application regions")
def _process_config_and_overrides(self, data, params, unit_name, unit_kind):
"""Process "config_parameters" and "target_config_overrides" into a
@ -508,6 +562,8 @@ class Config(object):
if full_name in params:
params[full_name].set_value(val, unit_name, unit_kind,
label)
elif name in self.__unused_overrides:
pass
else:
self.config_errors.append(
ConfigException(
@ -560,6 +616,8 @@ class Config(object):
rel_names = [tgt for tgt, _ in
get_resolution_order(self.target.json_data, tname,
[])]
if full_name in self.__unused_overrides:
continue
if (full_name not in params) or \
(params[full_name].defined_by[7:] not in rel_names):
raise ConfigException(

View File

@ -68,6 +68,7 @@ class BuildApiTests(unittest.TestCase):
toolchain.config_file = "junk"
toolchain.compile_sources(res, self.build_path)
print notify.mock_calls
assert any('percent' in msg[0] and msg[0]['percent'] == 100.0
for _, msg, _ in notify.mock_calls if msg)
@ -81,10 +82,13 @@ class BuildApiTests(unittest.TestCase):
:return:
"""
app_config = "app_config"
mock_config_init.return_value = namedtuple("Config", "target")(
namedtuple("Target",
"init_hooks name features core")(lambda _, __ : None,
"Junk", [], "Cortex-M3"))
mock_target = namedtuple("Target",
"init_hooks name features core")(lambda _, __ : None,
"Junk", [], "Cortex-M3")
mock_config_init.return_value = namedtuple("Config",
"target has_regions")(
mock_target,
False)
prepare_toolchain(self.src_paths, self.target, self.toolchain_name,
app_config=app_config)
@ -100,10 +104,13 @@ class BuildApiTests(unittest.TestCase):
:param mock_config_init: mock of Config __init__
:return:
"""
mock_config_init.return_value = namedtuple("Config", "target")(
namedtuple("Target",
"init_hooks name features core")(lambda _, __ : None,
"Junk", [], "Cortex-M3"))
mock_target = namedtuple("Target",
"init_hooks name features core")(lambda _, __ : None,
"Junk", [], "Cortex-M3")
mock_config_init.return_value = namedtuple("Config",
"target has_regions")(
mock_target,
False)
prepare_toolchain(self.src_paths, self.target, self.toolchain_name)
@ -127,6 +134,7 @@ class BuildApiTests(unittest.TestCase):
app_config = "app_config"
mock_exists.return_value = False
mock_prepare_toolchain().link_program.return_value = 1, 2
mock_prepare_toolchain().config = namedtuple("Config", "has_regions")(None)
build_project(self.src_paths, self.build_path, self.target,
self.toolchain_name, app_config=app_config)
@ -154,6 +162,7 @@ class BuildApiTests(unittest.TestCase):
mock_exists.return_value = False
# Needed for the unpacking of the returned value
mock_prepare_toolchain().link_program.return_value = 1, 2
mock_prepare_toolchain().config = namedtuple("Config", "has_regions")(None)
build_project(self.src_paths, self.build_path, self.target,
self.toolchain_name)

View File

@ -28,6 +28,7 @@ from math import ceil
import json
from collections import OrderedDict
import logging
from intelhex import IntelHex
def remove_if_in(lst, thing):
if thing in lst:
@ -514,3 +515,16 @@ def print_large_string(large_string):
else:
end_index = ((string_part + 1) * string_limit) - 1
print large_string[start_index:end_index],
def intelhex_offset(filename, offset):
"""Load a hex or bin file at a particular offset"""
_, inteltype = splitext(filename)
ih = IntelHex()
if inteltype == ".bin":
ih.loadbin(filename, offset=offset)
elif inteltype == ".hex":
ih.loadhex(filename)
else:
raise ToolException("File %s does not have a known binary file type"
% filename)
return ih