import json import os import subprocess import sys import pexpect import zipfile import datetime import time import waflib from waflib import Node, Logs from waflib.Build import BuildContext waf_dir = sys.path[0] sys.path.append(os.path.join(waf_dir, 'tools')) sys.path.append(os.path.join(waf_dir, 'tools/log_hashing')) sys.path.append(os.path.join(waf_dir, 'sdk/tools/')) sys.path.append(os.path.join(waf_dir, 'waftools')) import waftools.asm import waftools.gitinfo import waftools.ldscript import waftools.openocd import waftools.xcode_pebble LOGHASH_OUT_PATH = 'src/fw/loghash_dict.json' def truncate(msg): if msg is None: return msg # Don't truncate exceptions thrown by waf itself if "Traceback " in msg: return msg truncate_length = 600 if len(msg) > truncate_length: msg = msg[:truncate_length-4] + '...\n' + waflib.Logs.colors.NORMAL return msg def get_comma_separated_args(option, opt, value, parser): setattr(parser.values, option.dest, value.split(',')) old_display = waflib.Task.TaskBase.display def wrap_display(self): return truncate(old_display(self)) waflib.Task.TaskBase.display = wrap_display old_format_error = waflib.Task.TaskBase.format_error def wrap_format_error(self): return truncate(old_format_error(self)) waflib.Task.TaskBase.format_error = wrap_format_error def run_arm_gdb(ctx, elf_node, cmd_str="", target_server_port=3333): from tools.gdb_driver import find_gdb_path arm_none_eabi_path = find_gdb_path() if arm_none_eabi_path is None: ctx.fatal("pebble-gdb not found!") os.system('{} {} {} --ex="target remote :{}"'.format( arm_none_eabi_path, elf_node.path_from(ctx.path), cmd_str, target_server_port) ) def options(opt): opt.load('pebble_arm_gcc', tooldir='waftools') opt.load('show_configure', tooldir='waftools') opt.recurse('applib-targets') opt.recurse('tests') opt.recurse('src/bluetooth-fw') opt.recurse('src/fw') opt.recurse('src/idl') opt.recurse('sdk') opt.add_option('--board', action='store', choices=[ 'bb2', 'ev2_4', 'v1_5', 'v2_0', 'snowy_bb2', # alias for snowy_dvt, but with #define IS_BIGBOARD 'snowy_evt2', 'snowy_dvt', 'snowy_s3', 'spalding_bb2', # snowy_bb2 with s4 display 'spalding_evt', 'spalding', 'silk_evt', 'silk_bb', 'silk', 'silk_bb2', 'cutts_bb', 'robert_bb', 'robert_bb2', 'robert_evt', 'robert_es'], help='Which board we are targeting ' 'bb2, snowy_dvt, spalding, silk...') opt.add_option('--jtag', action='store', default=None, dest='jtag', # default is bb2 (below) choices=waftools.openocd.JTAG_OPTIONS.keys(), help='Which JTAG programmer we are using ' '(bb2 (default), olimex, ev2, etc)') opt.add_option('--internal_sdk_build', action='store_true', help='Build the internal version of the SDK') opt.add_option('--future_ux', action='store_true', help='Build future UX features and APIs. Implies --internal_sdk_build.') opt.add_option('--nosleep', action='store_true', help='Disable sleep and stop mode (to use JTAG+GDB)') opt.add_option('--nostop', action='store_true', help='Disable stop mode (to use JTAG+GDB)') opt.add_option('--lowpowerdebug', action='store_true', help='Lowpowerdebug can be toggled from the CLI but is off by default. This just turns it on by default') opt.add_option('--nowatch', action='store_true', help='Disable the watchface idle timeout') opt.add_option('--nowatchdog', action='store_true', help='Disable automatic reboots when watchdog fires') opt.add_option('--test_apps', action='store_true', help='Enables test apps (off by default)') opt.add_option('--test_apps_list', action='callback', type='string', callback=get_comma_separated_args, help='Specify AppInstallId\'s of the test apps to be compiled with the firmware') opt.add_option('--performance_tests', action='store_true', help='Enables instrumentation + apps for performance testing (off by default)') opt.add_option('--verbose_logs', action='store_true', help='Enables verbose logs (off by default)') opt.add_option('--ui_debug', action='store_true', help='Enable window dump & layer nudge CLI cmd (off by default)') opt.add_option('--qemu', action='store_true', help='Build an image for qemu instead of a real board.') opt.add_option('--nojs', action='store_true', help='Removes js support from the current build.') opt.add_option('--sdkshell', action='store_true', help='Use the sdk shell instead of the normal shell') opt.add_option('--nolog', action='store_true', help='Disable PBL_LOG macros to save space') opt.add_option('--nohash', action='store_true', help='Disable log hashing and make the logs human readable') opt.add_option('--lang', action='store', default='en_US', help='Which language to package (isocode)') opt.add_option('--compile_commands', action='store_true', help='Create a clang compile_commands.json') opt.add_option('--file', action='store', help='Specify a file to use with the flash_fw command') opt.add_option('--tty', help='Selects a tty to use for serial imaging. Must be specified for all image commands') opt.add_option('--baudrate', action='store', type=int, help='Optional: specifies the baudrate to run the targetted uart at') opt.add_option('--onlysdk', action='store_true', help="only build the sdk") opt.add_option('--qemu_host', default='localhost:12345', help='host:port for the emulator console connection') opt.add_option('--force-fit-tintin', action='store_true', help='Force fit for Tintin') opt.add_option('--no-link', action='store_true', help='Do not link the final firmware binary. This is used for static analysis') opt.add_option('--noprompt', action='store_true', help='Disable the serial console to save space') opt.add_option('--build_test_apps', action='store_true', help='Turns on building of test apps') opt.add_option('--bb_large_spi', action='store_true', help='Sets a flag to use all 8MB of BigBoard flash') opt.add_option('--profiler', action='store_true', help='Enable the profiler.') opt.add_option('--profile_interrupts', action='store_true', help='Enable profiling of all interrupts.') opt.add_option('--voice_debug', action='store_true', help='Enable all voice logging.') opt.add_option('--voice_codec_tests', action='store_true', help='Enable voice codec tests. Enables the profiler') opt.add_option('--battery_debug', action='store_true', help='Set the PMIC\'s max charging voltage to 4.3V.') opt.add_option('--no_sandbox', action='store_true', help='Disable the MPU for 3rd party apps.') opt.add_option('--malloc_instrumentation', action='store_true', help='Enables malloc instrumentation') opt.add_option('--infinite_backlight', action='store_true', help='Makes the backlight never time-out.') opt.add_option('--mfg', action='store_true', help='Enable specific MFG-only options in the PRF build') opt.add_option('--no-pulse-everywhere', action='store_true', help='Disables PULSE everywhere, uses legacy logs and prompt') opt.add_option('--bootloader-test', action='store', default='none', choices=['none', 'stage1', 'stage2'], help='Build bootloader test (stage1 or stage2). Implies --mfg.') opt.add_option('--reboot_on_bt_crash', action='store_true', help='Forces a BT ' 'chip crash to immediately force a system reboot instead of just cycling airplane mode. ' 'This makes it easier for us to actually get crash info') def handle_configure_options(conf): if conf.options.noprompt: conf.env.append_value('DEFINES', 'DISABLE_PROMPT') conf.env.DISABLE_PROMPT = True if conf.options.beta or conf.options.release: conf.env.append_value('DEFINES', 'RELEASE') if conf.options.malloc_instrumentation: conf.env.append_value('DEFINES', 'MALLOC_INSTRUMENTATION') print "Enabling malloc instrumentation" if conf.options.qemu: conf.env.append_value('DEFINES', 'TARGET_QEMU') if conf.options.test_apps_list: conf.options.test_apps = True conf.env.test_apps_list = conf.options.test_apps_list print "Enabling test apps: " + str(conf.options.test_apps_list) if conf.options.build_test_apps or conf.options.test_apps: conf.env.BUILD_TEST_APPS = True if conf.options.performance_tests: conf.env.PERFORMANCE_TESTS = True if conf.options.voice_debug: conf.env.VOICE_DEBUG = True if conf.options.voice_codec_tests: conf.env.VOICE_CODEC_TESTS = True conf.env.append_value('DEFINES', 'VOICE_CODEC_TESTS') conf.options.profiler = True if conf.env.MICRO_FAMILY == 'STM32F4': if conf.options.lowpowerdebug and not conf.options.nosleep: Logs.warn('On snowy --lowpowerdebug can only be used with --nosleep. Forcing --nosleep on!\n' 'See PBL-10174.') conf.env.append_value('DEFINES', 'PBL_NOSLEEP') if 'bb' in conf.options.board: conf.env.append_value('DEFINES', 'IS_BIGBOARD') if conf.options.nosleep: conf.env.append_value('DEFINES', 'PBL_NOSLEEP') print "Sleep/stop mode disabled" if conf.options.nostop: conf.env.append_value('DEFINES', 'PBL_NOSTOP') print "Stop mode disabled" if conf.options.lowpowerdebug: conf.env.append_value('DEFINES', 'LOW_POWER_DEBUG') print "Sleep and Stop mode debugging enabled" if conf.options.nowatch: conf.env.append_value('DEFINES', 'NO_WATCH_TIMEOUT') print "Watch watchdog disabled" if conf.options.nowatchdog: conf.env.append_value('DEFINES', 'NO_WATCHDOG') conf.env.NO_WATCHDOG = True print "Watchdog reboot disabled" if conf.options.reboot_on_bt_crash: conf.env.append_value('DEFINES', 'REBOOT_ON_BT_CRASH=1') print "BT now crash will trigger an MCU reboot" if conf.options.test_apps: conf.env.append_value('DEFINES', 'ENABLE_TEST_APPS') print "Im in ur firmware, bloatin ur binz! (Test apps enabled)" if conf.options.performance_tests: conf.env.append_value('DEFINES', 'PERFORMANCE_TESTS') conf.options.profiler = True print "Instrumentation and apps for performance measurement enabled (enables profiler)" if conf.options.verbose_logs: conf.env.append_value('DEFINES', 'VERBOSE_LOGGING') print "Verbose logging enabled" if conf.options.ui_debug: conf.env.append_value('DEFINES', 'UI_DEBUG') if conf.options.no_sandbox or conf.options.qemu: print "Sandbox disabled" else: conf.env.append_value('DEFINES', 'APP_SANDBOX') if conf.options.bb_large_spi: conf.env.append_value('DEFINES', 'LARGE_SPI_FLASH') print "Enabling 8MB BigBoard flash" if not conf.options.nolog: conf.env.append_value('DEFINES', 'PBL_LOG_ENABLED') if not conf.options.nohash: conf.env.append_value('DEFINES', 'PBL_LOGS_HASHED') if conf.options.profile_interrupts: conf.env.append_value('DEFINES', 'PROFILE_INTERRUPTS') if not conf.options.profiler: # Can't profile interrupts without the profiler enabled print "Enabling profiler" conf.options.profiler = True if conf.options.profiler: conf.env.append_value('DEFINES', 'PROFILER') if not conf.options.nostop: print "Enable --nostop for accurate profiling." conf.env.append_value('DEFINES', 'PBL_NOSTOP') if conf.options.voice_debug: conf.env.append_value('DEFINES', 'VOICE_DEBUG') if conf.options.battery_debug: conf.env.append_value('DEFINES', 'BATTERY_DEBUG') print "Enabling higher battery charge voltage." if conf.options.future_ux and not conf.is_tintin(): print "Future UX features enabled." conf.env.FUTURE_UX = True conf.env.INTERNAL_SDK_BUILD = bool(conf.options.internal_sdk_build) if conf.env.INTERNAL_SDK_BUILD: print "Internal SDK enabled" if conf.options.force_fit_tintin: conf.env.append_value('DEFINES', 'TINTIN_FORCE_FIT') print "Functionality is secondary to usability" if (conf.is_snowy_compatible() and not conf.options.no_lto) or conf.options.lto: conf.options.lto = True print "Turning on LTO." if conf.options.no_link: conf.env.NO_LINK = True print "Not linking firmware" if conf.options.infinite_backlight and 'bb' in conf.options.board: conf.env.append_value('DEFINES', 'INFINITE_BACKLIGHT') print "Enabling infinite backlight." if conf.options.bootloader_test in ['stage1', 'stage2']: print "Forcing MFG on for bootloader test build." conf.options.mfg = True if conf.options.bootloader_test == 'stage1': conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE1=1') conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE2=0') elif conf.options.bootloader_test == 'stage2': conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE1=0') conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE2=1') else: conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE1=0') conf.env.append_value('DEFINES', 'BOOTLOADER_TEST_STAGE2=0') if not conf.options.mfg and not conf.options.no_pulse_everywhere: conf.env.append_value('DEFINES', 'PULSE_EVERYWHERE=1') def _create_cm0_env(conf): prev_env = conf.env prev_variant = conf.variant # Create a new Cortex M0 environment that's used to build for the DA14681: conf.setenv('cortex-m0') # Copy the defines fron the stock env into our m0 env conf.env.append_unique('DEFINES', prev_env.DEFINES) Logs.pprint('CYAN', 'Configuring ARM cortex-m0 environment') conf.env.append_unique('DEFINES', 'ARCH_NO_NATIVE_LONG_DIVIDE') CPU_FLAGS = ['-mcpu=cortex-m0', '-mthumb'] OPT_FLAGS = [ '-fvar-tracking-assignments', # Track variable locations better '-fmessage-length=0', '-fsigned-char', '-fbuiltin', '-fno-builtin-itoa', '-ffreestanding', '-Os', ] if not conf.options.no_debug: OPT_FLAGS += [ '-g3', '-gdwarf-4', # More detailed debug info ] C_FLAGS = ['-std=c11', '-ffunction-sections', '-Wall', '-Wextra', '-Werror', '-Wpointer-arith', '-Wno-unused-parameter', '-Wno-missing-field-initializers', '-Wno-error=unused-parameter', '-Wno-packed-bitfield-compat'] conf.find_program('arm-none-eabi-gcc', var='CC', mandatory=True) conf.env.AS = conf.env.CC for tool in ['ar', 'objcopy']: conf.find_program('arm-none-eabi-' + tool, var=tool.upper(), mandatory=True) conf.env.append_unique('CFLAGS', CPU_FLAGS + OPT_FLAGS + C_FLAGS) ASFLAGS = ['-x', 'assembler-with-cpp'] conf.env.append_unique('ASFLAGS', ASFLAGS + CPU_FLAGS + OPT_FLAGS) conf.env.append_unique('LINKFLAGS', ['-Wl,--cref', '-Wl,--gc-sections', '-nostdlib', ] + CPU_FLAGS + OPT_FLAGS) conf.load('gcc gas objcopy ldscript') conf.load('file_name_c_define') conf.variant = prev_variant conf.env = prev_env def configure(conf): if not conf.options.board: conf.fatal('No board selected! ' 'You must pass a --board argument when configuring.') # Has to be 'waftools.gettext' as unadorned 'gettext' will find the gettext # module in the standard library. conf.load('waftools.gettext') conf.recurse('platform') conf.env.QEMU = conf.options.qemu conf.env.NOJS = conf.options.nojs # The BT controller is the only thing different between robert_es and robert_evt, so just # retend robert_es is robert_evt. We'll be removing robert_es fairly soon anyways. bt_board = None if conf.options.board == 'robert_es': bt_board = 'robert_es' conf.options.board = 'robert_evt' if conf.options.jtag: conf.env.JTAG = conf.options.jtag elif conf.options.board in ('snowy_bb2', 'spalding_bb2'): conf.env.JTAG = 'jtag_ftdi' elif conf.options.board in ('cutts_bb', 'robert_bb', 'robert_bb2', 'robert_evt', 'silk_evt', 'silk_bb', 'silk_bb2', 'silk'): conf.env.JTAG = 'swd_ftdi' else: # default to bb2 conf.env.JTAG = 'bb2' # Cutts and Robert access flash through the ITCM bus (except in QEMU) if (conf.is_cutts() or conf.is_robert()) and not conf.env.QEMU: conf.env.FLASH_ITCM = True else: conf.env.FLASH_ITCM = False # Set platform used for building the SDK if conf.is_tintin(): conf.env.PLATFORM_NAME = 'aplite' conf.env.MIN_SDK_VERSION = 2 elif conf.is_spalding(): conf.env.PLATFORM_NAME = 'chalk' conf.env.MIN_SDK_VERSION = 3 elif conf.is_snowy_compatible(): conf.env.PLATFORM_NAME = 'basalt' conf.env.MIN_SDK_VERSION = 2 elif conf.is_silk(): conf.env.PLATFORM_NAME = 'diorite' conf.env.MIN_SDK_VERSION = 2 elif conf.is_cutts() or conf.is_robert(): conf.env.PLATFORM_NAME = 'emery' conf.env.MIN_SDK_VERSION = 3 else: conf.fatal('No platform specified for {}!'.format(conf.options.board)) # Save this for later conf.env.BOARD = conf.options.board if conf.is_tintin(): conf.env.MICRO_FAMILY = 'STM32F2' elif conf.is_snowy_compatible() or conf.is_silk(): conf.env.MICRO_FAMILY = 'STM32F4' elif conf.is_cutts() or conf.is_robert(): conf.env.MICRO_FAMILY = 'STM32F7' else: conf.fatal('No micro family specified for {}!'.format(conf.options.board)) if conf.options.mfg: # Note that for the most part PRF and MFG firmwares are the same, so for MFG PRF builds # both MANUFACTURING_FW and RECOVERY_FW will be defined. conf.env.IS_MFG = True conf.env.append_value('DEFINES', ['MANUFACTURING_FW']) conf.find_program('node nodejs', var='NODE', errmsg="Unable to locate the Node command. " "Please check your Node installation and try again.") conf.recurse('src/idl') conf.recurse('src/fw') conf.recurse('sdk') conf.recurse('bin/boot') waftools.openocd.write_cfg(conf) # Save a baseline environment that we'll use for unit tests # Detach so operations against conf.env don't affect unit_test_env unit_test_env = conf.env.derive() unit_test_env.detach() # Save a baseline environment that we'll use for ARM environments base_env = conf.env handle_configure_options(conf) # robert_es is the exact same as robert_evt, except for the BT chip, so gets converted to # robert_evt above, but we need to handle it as robert_es here. if bt_board is None: bt_board = conf.get_board() # Select BT controller based on configuration: if conf.env.QEMU: conf.env.bt_controller = 'qemu' conf.env.append_value('DEFINES', ['BT_CONTROLLER_QEMU']) elif conf.is_tintin() or conf.is_snowy() or conf.is_spalding(): conf.env.bt_controller = 'cc2564x' conf.env.append_value('DEFINES', ['BT_CONTROLLER_CC2564X']) elif bt_board in ('silk_bb2', 'silk', 'robert_bb2', 'robert_evt'): conf.env.bt_controller = 'da14681-01' conf.env.append_value('DEFINES', ['BT_CONTROLLER_DA14681']) else: conf.env.bt_controller = 'da14681-00' conf.env.append_value('DEFINES', ['BT_CONTROLLER_DA14681']) _create_cm0_env(conf) conf.recurse('src/bluetooth-fw') Logs.pprint('CYAN', 'Configuring arm_firmware environment') conf.setenv('', base_env) conf.load('pebble_arm_gcc', tooldir='waftools') conf.setenv('arm_prf_mode', env=conf.env) conf.env.append_value('DEFINES', ['RECOVERY_FW']) Logs.pprint('CYAN', 'Configuring unit test environment') conf.setenv('local', unit_test_env) if sys.platform.startswith('linux'): libclang_path = subprocess.check_output(['llvm-config', '--libdir']).strip() conf.env.append_value('INCLUDES', [os.path.join(libclang_path, 'clang/3.2/include/'),]) # The waf clang tool likes to use llvm-ar as it's ar tool, but that doesn't work on our build # servers. Fall back to boring old ar. This will populate the 'AR' env variable so future # searches for what value to put into env['AR'] will find this one. conf.find_program('ar') conf.load('clang') conf.load('pebble_test', tooldir='waftools') conf.env.CLAR_DIR = conf.path.make_node('tools/clar/').abspath() conf.env.CFLAGS = [ '-std=c11', '-Wall', '-Werror', '-Wno-error=unused-variable', '-Wno-error=unused-function', '-Wno-error=missing-braces', '-g3', '-gdwarf-4', '-O0', '-fdata-sections', '-ffunction-sections' ] conf.env.append_value('DEFINES', 'CLAR_FIXTURE_PATH="' + conf.path.make_node('tests/fixtures/').abspath() + '"') conf.env.append_value('DEFINES', 'PBL_LOG_ENABLED') if conf.options.compile_commands: conf.load('clang_compilation_database', tooldir='waftools') if not os.path.lexists('compile_commands.json'): filename = 'compile_commands.json' source = conf.path.get_bld().make_node(filename) os.symlink(source.path_from(conf.path), filename) prev_env = conf.env Logs.pprint('CYAN', 'Configuring 32 bit host environment') # Copy 'local' to serve as the basis for '32bit': env_32bit = conf.env.derive().detach() env_32bit.append_value('CFLAGS', '-m32') env_32bit.append_value('LINKFLAGS', '-m32') env_32bit.LINK_CC = 'gcc' conf.all_envs['32bit'] = env_32bit conf.set_env(prev_env) # Note: this will modify the 'local' conf when targeting emscripten: conf.recurse('applib-targets') Logs.pprint('CYAN', 'Configuring stored apps environment') conf.setenv('stored_apps', base_env) conf.recurse('stored_apps') # Confirm that requirements-*.txt and requirements-osx-brew.txt have been satisfied. import tool_check tool_check.tool_check() # Warn user not to use Cutts BB build with a Robert screen if conf.options.board == 'cutts_bb': Logs.warn('NOTE: Do not use this build with a C2/Robert display ' '(6V6 rail will damage the display)') def _run_remote_suite(ctx, suite): # PEBBLESDK_TEST_ROOT must be defined in order to initiate integration tests try: pebblesdk_test_root = os.environ['PEBBLESDK_TEST_ROOT'] except KeyError: waflib.Logs.pprint('RED', 'Error: environment variable $PEBBLESDK_TEST_ROOT must be defined') return # Check if firmware has been built # Assume we're looking for a "normal" PBZ, as recovery PBZs aren't supported by integration tests fw_bin_path = ctx.get_tintin_fw_node().abspath() fw_bin_exists = os.path.isfile(fw_bin_path) if not fw_bin_exists: waflib.Logs.pprint('RED', ('Error: BIN not found at expected location {}, ' 'have you run `waf build` yet?'.format(fw_bin_path))) return # Check if firmware has been bundled version_string, version_ts, _ = _get_version_info(ctx) fw_type = 'qemu' if ctx.env.QEMU else 'normal' fw_pbz_path = ctx.get_pbz_node(fw_type, ctx.env.BOARD, version_string).abspath() fw_pbz_exists = os.path.isfile(fw_pbz_path) if not fw_pbz_exists: waflib.Logs.pprint('CYAN', ('Warning: PBZ not found at expected location {}, ' 'running `waf bundle`...').format(fw_pbz_path)) bundle(ctx) # Run power tests using remote_runner.py remote_runner_path = os.path.join(pebblesdk_test_root, 'remote_runner.py') if not os.path.isfile(remote_runner_path): waflib.Logs.pprint('RED', ('Error: remote_runner.py not found in {}. ' 'Are you sure that PEBBLESDK_TEST_ROOT is defined correctly?' .format(pebblesdk_test_root))) return subprocess.call([remote_runner_path, '--pbz', fw_pbz_path, '[%s]' % suite]) class power_test(BuildContext): cmd = 'power_test' def execute_build(ctx): _run_remote_suite(ctx, 'power') class integration_test(BuildContext): cmd = 'integration_test' def execute_build(ctx): _run_remote_suite(ctx, 'tintin_3x') def stop_build_timer(ctx): t = datetime.datetime.utcnow() - ctx.pbl_build_start_time node = ctx.path.get_bld().make_node('build_time') with open(node.abspath(), 'w') as fout: fout.write(str(int(round(t.total_seconds())))) def build(bld): bld.DYNAMIC_RESOURCES = [] bld.LOGHASH_DICTS = [] # Start this timer here to include the time to generate tasks. bld.pbl_build_start_time = datetime.datetime.utcnow() bld.add_post_fun(stop_build_timer) if bld.variant in ('test', 'test_rocky_emx', 'applib'): bld.set_env(bld.all_envs['local']) bld.load('file_name_c_define', tooldir='waftools') bld.recurse('platform') bld.recurse('src/idl') if bld.cmd == 'install': raise Exception("install isn't a supported command. Did you mean flash?") if bld.variant == 'pdc2png': bld.recurse('src/libutil') bld.recurse('tools') return if bld.variant == 'tools': bld.recurse('tools') return if bld.variant in ('', 'applib', 'prf'): # Dependency for SDK bld.recurse('src/fw/vendor/jerryscript') if bld.variant == '': # sdk generation bld.recurse('sdk') if bld.variant == 'applib': bld.recurse('resources') bld.recurse('src/libutil') bld.recurse('src/fw') bld.recurse('src/fw/vendor/nanopb') bld.recurse('src/include') bld.recurse('applib-targets') return if bld.options.onlysdk: # stop here, sdk generation is done return # Do not enable stationary mode in PRF or release firmware if (bld.variant != 'prf' and not bld.env.QEMU and bld.env.NORMAL_SHELL != 'sdk'): bld.env.append_value('DEFINES', 'STATIONARY_MODE') if bld.variant == 'prf': bld.set_env(bld.all_envs['arm_prf_mode']) elif bld.variant == 'test': if bld.env.APPLIB_TARGET == 'emscripten': bld.fatal('Did you mean ./waf test_rocky_emx ?') bld.recurse('src/include') bld.recurse('src/fw/vendor/jerryscript') bld.recurse('src/fw/vendor/nanopb') bld.recurse('src/libbtutil') bld.recurse('src/libos') bld.recurse('src/libutil') bld.recurse('tools') bld.recurse('tests') return elif bld.variant == 'test_rocky_emx': if bld.env.APPLIB_TARGET != 'emscripten': bld.fatal('Make sure to ./waf configure with --target=emscripten') bld.recurse('src/libutil') bld.recurse('src/libos') bld.recurse('src/fw/vendor/jerryscript') bld.recurse('src/fw/vendor/nanopb') bld.recurse('applib-targets') bld.recurse('tools') bld.recurse('tests') return if bld.variant == '': bld.recurse('stored_apps') bld.recurse('src/include') bld.recurse('src/libbtutil') bld.recurse('src/bluetooth-fw') bld.recurse('src/libc') bld.recurse('src/libos') bld.recurse('src/libutil') bld.recurse('src/fw') bld.recurse('tools/qemu_spi_cooker') # Generate resources. Leave this until the end so we collect all the env['DYNAMIC_RESOURCES'] # values that the other build steps added. bld.recurse('resources') # if we're not linking the firmware don't run these if not bld.env.NO_LINK: bld.add_post_fun(size_fw) bld.add_post_fun(size_resources) if 'PBL_LOGS_HASHED' in bld.env.DEFINES: bld.add_post_fun(merge_loghash_dicts) class build_prf(BuildContext): """executes the recovery firmware build""" cmd = 'build_prf' variant = 'prf' class build_applib(BuildContext): cmd = 'build_applib' variant = 'applib' def merge_loghash_dicts(bld): loghash_dict = bld.path.get_bld().make_node(LOGHASH_OUT_PATH) import log_hashing.newlogging log_hashing.newlogging.merge_loghash_dict_json_files(loghash_dict, bld.LOGHASH_DICTS) class SizeFirmware(BuildContext): cmd = 'size_fw' fun = 'size_fw' def size_fw(ctx): """prints size information of the firmware""" fw_elf = ctx.get_tintin_fw_node().change_ext('.elf') if fw_elf is None: ctx.fatal('No fw ELF found for size') fw_bin = ctx.get_tintin_fw_node() if fw_bin is None: ctx.fatal('No fw BIN found for size') import binutils text, data, bss = binutils.size(fw_elf.abspath()) total = text + data output = ('{:>7} {:>7} {:>7} {:>7} {:>7} filename\n' '{:7} {:7} {:7} {:7} {:7x} tintin_fw.elf'. format('text', 'data', 'bss', 'dec', 'hex', text, data, bss, total, total)) Logs.pprint('YELLOW', '\n' + output) try: space_left = _check_firmware_image_size(ctx, fw_bin.path_from(ctx.path)) except FirmwareTooLargeException as e: if ctx.env.MICRO_FAMILY == 'STM32F2' and ctx.env.QEMU: # Let us off with a warning for now Logs.warn(str(e)) else: ctx.fatal(str(e)) else: Logs.pprint('CYAN', 'FW: ' + space_left) class SizeResources(BuildContext): cmd = 'size_resources' fun = 'size_resources' def size_resources(ctx): """prints size information of resources""" if ctx.variant == 'prf': return pbpack_path = ctx.path.get_bld().find_node('system_resources.pbpack') if pbpack_path is None: ctx.fatal('No resource pbpack found') if ctx.env.MICRO_FAMILY == 'STM32F4': max_size = 512 * 1024 elif ctx.env.MICRO_FAMILY == 'STM32F7': max_size = 1024 * 1024 else: max_size = 256 * 1024 pbpack_actual_size = os.path.getsize(pbpack_path.path_from(ctx.path)) bytes_free = max_size - pbpack_actual_size from waflib import Logs Logs.pprint('CYAN', 'Resources: %d/%d (%d free)\n' % (pbpack_actual_size, max_size, bytes_free)) if pbpack_actual_size > max_size: ctx.fatal('Resources are too large for target board %d > %d' % (pbpack_actual_size, max_size)) def size(ctx): from waflib import Options Options.commands = ['size_fw', 'size_resources'] + Options.commands class size_prf(BuildContext): """checks the size of PRF""" cmd = 'size_prf' variant = 'prf' class test(BuildContext): """builds and runs the tests""" cmd = 'test' variant = 'test' class test_rocky_emx(BuildContext): """builds and runs the tests""" cmd = 'test_rocky_emx' variant = 'test_rocky_emx' def docs(ctx): """builds the documentation out to build/doxygen""" ctx.exec_command('doxygen Doxyfile', stdout=None, stderr=None) class DocsSdk(BuildContext): """builds the sdk documentation out to build/sdk//doxygen_sdk""" cmd = 'docs_sdk' fun = 'docs_sdk' def docs_sdk(ctx): pebble_sdk = ctx.path.get_bld().make_node('sdk') supported_platforms = pebble_sdk.listdir() for platform in supported_platforms: doxyfile = pebble_sdk.find_node(platform).find_node('Doxyfile-SDK.auto') if doxyfile: ctx.exec_command('doxygen {}'.format(doxyfile.path_from(ctx.path)), stdout=None, stderr=None) def docs_all(ctx): """builds the documentation with all dependency graphs out to build/doxygen""" ctx.exec_command('doxygen Doxyfile-all-graphs', stdout=None, stderr=None) # Bundle commands ################################################# def _get_version_info(ctx): # FIXME: it's probably a better idea to lift board + version info from the .bin file... this can get out of sync! git_revision = waftools.gitinfo.get_git_revision(ctx) if git_revision['TAG'] != '?': version_string = git_revision['TAG'] version_ts = int(git_revision['TIMESTAMP']) version_commit = git_revision['COMMIT'] else: version_string = 'dev' version_ts = 0 version_commit = '' return version_string, version_ts, version_commit def _make_bundle(ctx, fw_bin_path, fw_type='normal', board=None, resource_path=None, write=True): import mkbundle if board is None: board = ctx.env.BOARD b = mkbundle.PebbleBundle() version_string, version_ts, version_commit = _get_version_info(ctx) out_file = ctx.get_pbz_node(fw_type, ctx.env.BOARD, version_string).path_from(ctx.path) try: _check_firmware_image_size(ctx, fw_bin_path) b.add_firmware(fw_bin_path, fw_type, version_ts, version_commit, board, version_string) except FirmwareTooLargeException as e: ctx.fatal(str(e)) except mkbundle.MissingFileException as e: ctx.fatal('Error: Missing file ' + e.filename + ', have you run ./waf build yet?') if resource_path is not None: b.add_resources(resource_path, version_ts) if 'RELEASE' not in ctx.env.DEFINES and 'PBL_LOGS_HASHED' in ctx.env.DEFINES: loghash_dict = ctx.path.get_bld().make_node(LOGHASH_OUT_PATH).abspath() b.add_loghash(loghash_dict) # Add a LICENSE.txt file b.add_license('LICENSE.txt') # make sure ctx.capability is available ctx.recurse('platform', mandatory=False) if ctx.capability('HAS_JAVASCRIPT'): js_tooling = ctx.path.get_bld().find_node('src/fw/vendor/jerryscript/js_tooling/js_tooling.js') if js_tooling is not None: b.add_jstooling(js_tooling.path_from(ctx.path), ctx.capability('JAVASCRIPT_BYTECODE_VERSION')) if fw_type == 'normal': layouts_node = ctx.path.get_bld().find_node('resources/layouts.json.auto') if layouts_node is not None: b.add_layouts(layouts_node.path_from(ctx.path)) if write: b.write(out_file) waflib.Logs.pprint('CYAN', 'Writing bundle to: %s' % out_file) return b class BundleCommand(BuildContext): cmd = 'bundle' fun = 'bundle' def bundle(ctx): """bundles a firmware""" if ctx.env.QEMU: bundle_qemu(ctx) else: _make_bundle(ctx, ctx.get_tintin_fw_node().path_from(ctx.path), resource_path=ctx.get_pbpack_node().path_from(ctx.path)) _generate_release_notes(ctx) def _generate_release_notes(ctx): def _write_tag_to_release_notes(task): output = task.outputs[0].abspath() tag = task.generator.version_tag with open(output, 'w') as f: f.write(tag) task.dep_vars = tag git_revision = waftools.gitinfo.get_git_revision(ctx) version = "{}.{}".format(git_revision['MAJOR_VERSION'], git_revision['MINOR_VERSION']) version_hotfix = "{}.{}".format(version, git_revision['PATCH_VERSION']) summary_node = ctx.path.find_node('release-notes').find_node('summary-{}.txt'.format(version_hotfix)) if summary_node is None: summary_node = ctx.path.find_node('release-notes').find_node('summary-{}.txt'.format(version)) if summary_node is not None: ctx(rule='cp ${SRC} ${TGT}', source=summary_node, target=ctx.path.get_bld().make_node('release-notes.txt')) else: ctx(rule=_write_tag_to_release_notes, version_tag=git_revision['TAG'], target=ctx.path.get_bld().make_node('release-notes.txt')) class bundle_prf(BuildContext): """bundles a recovery firmware""" cmd = 'bundle_prf' variant = 'prf' def execute_build(ctx): _make_bundle(ctx, ctx.get_tintin_fw_node().path_from(ctx.path), fw_type='recovery') def _bundle_resourceless_fw(ctx, fw_path, fw_type): # We need to create a dummy pbpack and bundle it in. Some FW images don't use # resources, but the firmware will refuse to upgrade to a firmware if a resource # file isn't sent over, regardless of it's validity. import tempfile # We need to actually write some content in here or else the phone app won't think the # resources are valid, and put_bytes will refuse to update to any firmware image if it doesn't # come with a corresponding resource pack. No one will ever read these though so who cares # what the content is. with tempfile.NamedTemporaryFile(delete=False) as dummy_pbpack: dummy_pbpack.write('DUMMY') pbpack_path = dummy_pbpack.name try: _make_bundle(ctx, fw_path, fw_type=fw_type, resource_path=pbpack_path) finally: os.remove(pbpack_path) class bundle_recovery(BuildContext): """bundles a recovery firmware as normal firmware""" cmd = 'bundle_recovery' variant = 'prf' def execute_build(ctx): _bundle_resourceless_fw(ctx, ctx.get_tintin_fw_node().path_from(ctx.path), fw_type='recovery') class BundleQEMUCommand(BuildContext): cmd = 'bundle_qemu' fun = 'bundle_qemu' def bundle_qemu(ctx): """bundle QEMU images together into a "fake" PBZ""" qemu_image_micro(ctx) qemu_image_spi(ctx) b = _make_bundle(ctx, ctx.get_tintin_fw_node().path_from(ctx.path), resource_path=ctx.get_pbpack_node().path_from(ctx.path), write=False, board='qemu_{}'.format(ctx.env.BOARD)) version_string, _, _ = _get_version_info(ctx) qemu_pbz = ctx.get_pbz_node('qemu', ctx.env.BOARD, version_string) out_file = qemu_pbz.path_from(ctx.path) with zipfile.ZipFile(out_file, 'w', compression=zipfile.ZIP_DEFLATED) as pbz_file: pbz_file.writestr('manifest.json', json.dumps(b.bundle_manifest)) files = [ctx.get_tintin_fw_node(), ctx.get_pbpack_node(), 'qemu_micro_flash.bin', 'qemu_spi_flash.bin'] if 'PBL_LOGS_HASHED' in ctx.env.DEFINES: files.append(LOGHASH_OUT_PATH) for fitem in files: if isinstance(fitem, Node.Node): fnode = fitem else: fnode = ctx.path.get_bld().make_node(fitem) img_path = fnode.path_from(ctx.path) pbz_file.write(img_path, os.path.basename(img_path)) waflib.Logs.pprint('CYAN', 'Writing bundle to: %s' % out_file) class QemuImageMicroCommand(BuildContext): cmd = 'qemu_image_micro' fun = 'qemu_image_micro' class QemuImageMicroPrfCommand(BuildContext): cmd = 'qemu_image_prf_micro' fun = 'qemu_image_prf_micro' class QemuImageSpiCommand(BuildContext): cmd = 'qemu_image_spi' fun = 'qemu_image_spi' class MfgImageSpiCommand(BuildContext): cmd = 'mfg_image_spi' fun = 'mfg_image_spi' def qemu_image_micro(ctx): fw_hex = ctx.get_tintin_fw_node().change_ext('.hex') _create_qemu_image_micro(ctx, fw_hex.path_from(ctx.path)) def qemu_image_prf_micro(ctx): fw_hex = ctx.get_tintin_fw_node_prf().change_ext('.hex') _create_qemu_image_micro(ctx, fw_hex.path_from(ctx.path)) def _create_qemu_image_micro(ctx, path_to_firmware_hex): """creates the micro-flash image for qemu""" from intelhex import IntelHex if not ctx.env.BOOTLOADER_HEX: ctx.fatal('Board "{}" does not have a bootloader binary available' .format(ctx.env.BOARD)) micro_flash_node = ctx.path.get_bld().make_node('qemu_micro_flash.bin') micro_flash_path = micro_flash_node.path_from(ctx.path) waflib.Logs.pprint('CYAN', 'Writing micro flash image to {}'.format(micro_flash_path)) img = IntelHex(ctx.env.BOOTLOADER_HEX) img.merge(IntelHex(path_to_firmware_hex), overlap='replace') # Write firwmare image and pad up to next 512 byte multiple. This is because QEMU # assumes all block devices are multiples of 512 byte sectors img.padding = 0xff flash_end = ((img.maxaddr() + 511) // 512) * 512 img.tobinfile(micro_flash_path, start=0x08000000, end=flash_end-1) def _create_spi_flash_image(ctx, name): spi_flash_node = ctx.path.get_bld().make_node(name) spi_flash_path = spi_flash_node.path_from(ctx.path) waflib.Logs.pprint('CYAN', 'Writing SPI flash image to {}'.format(spi_flash_path)) return spi_flash_path def qemu_image_spi(ctx): """creates a SPI flash image for qemu""" if ctx.env.BOARD.startswith('silk'): resources_begin = 0x100000 image_size = 0x800000 elif ctx.env.BOARD.startswith('robert') or ctx.env.BOARD.startswith('cutts'): resources_begin = 0x200000 image_size = 0x1000000 elif ctx.env.MICRO_FAMILY == 'STM32F4': resources_begin = 0x380000 image_size = 0x1000000 else: resources_begin = 0x280000 image_size = 0x400000 spi_flash_path = _create_spi_flash_image(ctx, 'qemu_spi_flash.bin') with open(spi_flash_path, 'wb') as qemu_spi_img_file: # Pad the first section before system resources with FF's' qemu_spi_img_file.write("\xff" * resources_begin) # Write system resources: pbpack = ctx.get_pbpack_node() res_img = open(pbpack.path_from(ctx.path), 'rb').read() qemu_spi_img_file.write(res_img) # Pad with 0xFF up to image size tail_padding_size = image_size - resources_begin - len(res_img) qemu_spi_img_file.write("\xff" * tail_padding_size) with open(os.devnull, 'w') as null: qemu_spi_cooker_node = ctx.path.get_bld().make_node('qemu_spi_cooker') qemu_spi_cooker_path = qemu_spi_cooker_node.path_from(ctx.path) subprocess.check_call([qemu_spi_cooker_path, spi_flash_path], stdout=null) def mfg_image_spi(ctx): """Creates a SPI flash image of PRF for MFG pre-burn. Includes a FirmwareDescription struct""" import insert_firmware_descr if ctx.env.BOARD.startswith('silk'): prf_begin = 0x200000 image_size = 0x800000 else: ctx.fatal("MFG Image not suppored for board: {}".format(ctx.env.BOARD)) spi_flash_path = _create_spi_flash_image(ctx, 'mfg_prf_image.bin') mfg_spi_img_file = open(spi_flash_path, 'wb') # Pad the first section before PRF storage mfg_spi_img_file.write("\xff" * prf_begin) prf_path = ctx.get_tintin_fw_node_prf().path_from(ctx.path) prf_image = insert_firmware_descr.insert_firmware_description_struct(prf_path) mfg_spi_img_file.write(prf_image) # Pad with 0xff up to image size tail_padding_size = image_size - prf_begin - len(prf_image) mfg_spi_img_file.write("\xff" * tail_padding_size) def show_ttys(ctx): """Displays all available ftdi ports connected to computer""" os.system("python ./tools/log_hashing/miniterm_co.py ftdi:///?") class ConsoleCommand(BuildContext): cmd = 'console' fun = 'console' def console(ctx): """Starts miniterm with the serial console.""" # miniterm is not made to be used as a python module, so just shell out: tty = ctx.options.tty or _get_dbgserial_tty() if _is_pulse_everywhere(ctx): os.system("python ./tools/pulse_console.py -t %s" % tty) else: baudrate = ctx.options.baudrate or 230400 os.system("python ./tools/log_hashing/miniterm_co.py %s %d" % (tty, baudrate)) class ConsoleCommand(BuildContext): cmd = 'console_prf' fun = 'console_prf' def console_prf(ctx): os.putenv("PBL_CONSOLE_DICT_PATH", "build/prf/src/fw/loghash_dict.json") console(ctx) class BleConsoleCommand(BuildContext): cmd = 'ble_console' fun = 'ble_console' def ble_console(ctx): def _get_ble_tty(): import pebble_tty tty = pebble_tty.find_ble_tty() if tty is None: return None waflib.Logs.pprint('GREEN', 'No --tty argument specified, auto-selecting: %s' % tty) return tty """Starts miniterm with the serial console for the BLE chip.""" ctx.recurse('platform', mandatory=False) # FIXME: We have the ability to progam PIDs into the new round of Big Boards. TTY # path discovery should be able to use that (PBL-31111). For now, just make a best # guess at what the path should be if ctx.is_silk() or ctx.is_robert(): tty_path = _get_ble_tty() # if the bt_controller was chosen explicitly, assume we are using an eval board, which # happens to match the path for cutts elif ctx.uses_dialog_bluetooth(): tty_path = "ftdi://ftdi:2232:1/1" else: waflib.Logs.pprint('CYAN', 'Note: This platform does not have a BLE UART') tty_path = _get_dbgserial_tty() tty = ctx.options.tty or tty_path baudrate = ctx.options.baudrate or 230400 os.system("python ./tools/log_hashing/miniterm_co.py %s %d" % (tty, baudrate)) class BleConsolePrfCommand(BuildContext): cmd = 'ble_console_prf' fun = 'ble_console_prf' def ble_console_prf(ctx): os.putenv("PBL_CONSOLE_DICT_PATH", "build/prf/src/fw/loghash_dict.json") ble_console(ctx) def accessory_console(ctx): def _get_accessory_tty(): import pebble_tty tty = pebble_tty.find_accessory_tty() if tty is None: return None waflib.Logs.pprint('GREEN', 'No --tty argument specified, auto-selecting: %s' % tty) return tty """Starts miniterm with the accessory connector console.""" # miniterm is not made to be used as a python module, so just shell out: tty = ctx.options.tty or _get_accessory_tty() baudrate = ctx.options.baudrate or 115200 os.system("python ./tools/log_hashing/miniterm_co.py %s %d" % (tty, baudrate)) def qemu(ctx): # Make sure the micro-flash image is up to date. By default, we don't rebuild the # SPI flash image in case you want to continue with the stored apps, etc. you had before. from waflib import Options Options.commands = ['qemu_image_micro', 'qemu_launch'] + Options.commands def qemu_prf(ctx): # Make sure the micro-flash image is up to date. By default, we don't rebuild the # SPI flash image in case you want to continue with the stored apps, etc. you had before. from waflib import Options Options.commands = ['qemu_image_prf_micro', 'qemu_launch'] + Options.commands class QemuLaunchCommand(BuildContext): cmd = 'qemu_launch' fun = 'qemu_launch' def qemu_launch(ctx): """Starts up the emulator (qemu) """ ctx.recurse('platform', mandatory=False) qemu_machine = ctx.get_qemu_machine() if not qemu_machine or qemu_machine == 'unknown': raise Exception("Board type '{}' not supported by QEMU".format(ctx.env.BOARD)) qemu_micro_flash = ctx.path.get_bld().make_node('qemu_micro_flash.bin') qemu_spi_flash = ctx.path.get_bld().make_node('qemu_spi_flash.bin') qemu_spi_type = ctx.get_qemu_extflash_device_type() if not qemu_spi_type: raise Exception("External flash type for '{}' not specified".format(ctx.env.BOARD)) machine_dep_args = ['-machine', qemu_machine, '-cpu', ctx.get_qemu_cpu(), '-pflash', qemu_micro_flash.path_from(ctx.path), qemu_spi_type, qemu_spi_flash.path_from(ctx.path)] if ctx.has_touch(): machine_dep_args.append('-show-cursor') cmd_line = ( "qemu-system-arm -rtc base=localtime " "-monitor stdio " "-s " "-serial file:uart1.log " "-serial tcp::12344,server,nowait " # Used for bluetooth data "-serial tcp::12345,server,nowait " # Used for console ) + ' '.join(machine_dep_args) os.system(cmd_line) class QEMUConsoleCommand(BuildContext): cmd = 'qemu_console' fun = 'qemu_console' def qemu_console(ctx): """Starts miniterm configured to talk to the emulator (qemu)""" # miniterm is not made to be used as a python module, so just shell out: host_port = ctx.options.qemu_host or 'localhost:12345' # A hacky way to pass an argument if _is_pulse_everywhere(ctx): os.system("python ./tools/pulse_console.py -t %s" % ('socket://%s' % (host_port))) else: os.system("python ./tools/log_hashing/miniterm_co.py %s" % ('socket://%s' % (host_port))) class QemuGdb(BuildContext): """Starts up a gdb instance to talk to the emulator """ cmd = 'qemu_gdb' fun = 'qemu_gdb' def qemu_gdb(ctx): # First, startup the gdb proxy cmd_line = "python ./tools/qemu/qemu_gdb_proxy.py --port=1233 --target=localhost:1234" proc = pexpect.spawn(cmd_line, logfile=sys.stdout) proc.expect(["Connected to target", pexpect.TIMEOUT], timeout=10) fw_elf = ctx.get_tintin_fw_node().change_ext('.elf') run_arm_gdb(ctx, fw_elf, target_server_port=1233) class QemuGdbBoot(BuildContext): """ Starts up a gdb instance to talk to the emulator's boot ROM """ cmd = 'qemu_gdb_boot' fun = 'qemu_gdb_boot' def qemu_gdb_boot(ctx): boot_elf = ctx.get_tintin_boot_node().change_ext('.elf') run_arm_gdb(ctx, boot_elf, target_server_port=1234) class debug(BuildContext): """ Alias for gdb """ cmd = 'debug' def execute_build(ctx): gdb(ctx) class Gdb(BuildContext): """ Starts GDB and openocd (if not already running) and attaches GDB to openocd's GDB server. If openocd is already running, it will be used. """ cmd = 'gdb' fun = 'gdb' def gdb(ctx, fw_elf=None, cfg_file='openocd.cfg', is_ble=False): if fw_elf is None: fw_elf = ctx.get_tintin_fw_node().change_ext('.elf') with waftools.openocd.daemon(ctx, cfg_file, use_swd=(is_ble or 'swd' in ctx.env.JTAG)): run_arm_gdb(ctx, fw_elf, cmd_str='--init-command=".gdbinit"') class gdb_prf(BuildContext): """same as `gdb`, but loading the PRF elf instead""" cmd = 'gdb_prf' def execute_build(ctx): gdb(ctx, ctx.get_tintin_fw_node_prf().change_ext('.elf')) def openocd(ctx): """ Starts openocd and leaves it running. It will reset the board to increase the chances of attaching succesfully. """ waftools.openocd.run_command(ctx, 'init; reset', shutdown=False) # Image commands ################################################# def _get_dbgserial_tty(): import pebble_tty tty = pebble_tty.find_dbgserial_tty() if tty is None: return None waflib.Logs.pprint('GREEN', 'No --tty argument specified, auto-selecting: %s' % tty) return tty class ble_send_hci(BuildContext): """Puts MCU in HCI bypass mode. Sends specified HCI Command and returns result. i.e: ./waf send_hci 0x01 0x03 0x0C 0x00 """ cmd = 'ble_send_hci' fun = 'ble_send_hci' def ble_send_hci(ctx): import prompt import pebble_tty from serial_port_wrapper import SerialPortWrapper import struct from time import sleep from waflib import Options def _dump_hex_array(prefix, hex_array): print prefix + " [", for i in range(0, len(hex_array)): print "0x%02x " % hex_array[i], print "]" hci_bytes = [int(i, 16) for i in Options.commands] _dump_hex_array("Sent HCI CMD:", hci_bytes) try: device_tty = pebble_tty.find_dbgserial_tty() serial = SerialPortWrapper(device_tty) prompt.go_to_prompt(serial) prompt.issue_command(serial, "bt test hcipass") sleep(0.1) serial.write_fast(struct.pack('B'*len(hci_bytes), *hci_bytes)) response = serial.read() response = struct.unpack('%dB' % len(response), response) serial.write(struct.pack('B', 0x04)) # issue ctrl-d _dump_hex_array(" Got HCI EVT:", response) finally: # note: random bytes get dropped on subsequent usb ops if you forget to close! serial.close() # WAF/optparse does not have native support for adding sub-command options # or variable length options. Reset the options list to prevent innocuous # messaging about unrecognized commands Options.commands = [] return None class ImageResources(BuildContext): """flashes resources""" cmd = 'image_resources' fun = 'image_resources' def _is_pulse_everywhere(ctx): return "PULSE_EVERYWHERE=1" in ctx.env["DEFINES"] def _get_pulse_flash_tool(ctx): if _is_pulse_everywhere(ctx): return "pulse_flash_imaging" else: return "pulse_legacy_flash_imaging" def image_resources(ctx): tty = ctx.options.tty or _get_dbgserial_tty() if tty is None: waflib.Logs.pprint('RED', 'Error: --tty not specified') return tool_name = _get_pulse_flash_tool(ctx) pbpack_path = ctx.get_pbpack_node().abspath() waflib.Logs.pprint('CYAN', 'Writing pbpack "%s" to tty %s' % (pbpack_path, tty)) ret = os.system("python ./tools/%s.py -t %s -p resources %s" % (tool_name, tty, pbpack_path)) if ret != 0: ctx.fatal('Imaging failed') class ImageRecovery(BuildContext): """flashes recovery firmware""" cmd = 'image_recovery' fun = 'image_recovery' def image_recovery(ctx): tty = ctx.options.tty or _get_dbgserial_tty() if tty is None: waflib.Logs.pprint('RED', 'Error: --tty not specified') return tool_name = _get_pulse_flash_tool(ctx) recovery_bin_path = ctx.options.file or ctx.get_tintin_fw_node_prf().path_from(ctx.path) waflib.Logs.pprint('CYAN', 'Writing recovery bin "%s" to tty %s' % (recovery_bin_path, tty)) ret = os.system("python ./tools/%s.py -t %s -p firmware %s" % (tool_name, tty, recovery_bin_path)) if ret != 0: ctx.fatal('Imaging failed') # Flash commands ################################################# class FirmwareTooLargeException(Exception): pass def _check_firmware_image_size(ctx, path): BYTES_PER_K = 1024 firmware_size = os.path.getsize(path) # Determine flash and bootloader size so we can calculate the max firmware size if ctx.env.MICRO_FAMILY == 'STM32F2': # 512k of flash and 16k bootloader max_firmware_size = (512 - 16) * BYTES_PER_K elif ctx.env.MICRO_FAMILY == 'STM32F4': if ctx.env.BOARD.startswith('silk') and ctx.variant == 'prf': # silk PRF is limited to 512k to save on SPI flash space max_firmware_size = 512 * BYTES_PER_K elif ctx.env.BOARD in ('snowy_evt', 'snowy_evt2', 'spalding_evt'): # 1024k of flash and 64k bootloader max_firmware_size = (1024 - 64) * BYTES_PER_K else: # 1024k of flash and 16k bootloader max_firmware_size = (1024 - 16) * BYTES_PER_K elif ctx.env.MICRO_FAMILY == 'STM32F7': if ctx.variant == 'prf' and not ctx.env.IS_MFG: # Robert PRF is limited to 512k to save on SPI flash space max_firmware_size = 512 * BYTES_PER_K else: # 2048k of flash and 32k bootloader max_firmware_size = (2048 - 32) * BYTES_PER_K else: ctx.fatal('Cannot check firmware size against unknown micro family "{}"' .format(ctx.env.MICRO_FAMILY)) if firmware_size > max_firmware_size: raise FirmwareTooLargeException('Firmware is too large! Size is 0x%x should be less than 0x%x' \ % (firmware_size, max_firmware_size)) return ('%d / %d bytes used (%d free)' % (firmware_size, max_firmware_size, (max_firmware_size - firmware_size))) class FlashCommand(BuildContext): """alias for flash_everything""" cmd = 'flash' fun = 'flash' def flash(ctx): flash_everything(ctx, ctx.get_tintin_fw_node()) class FlashPrfCommand(BuildContext): """flashes recovery firmware as normal firmware""" cmd = 'flash_prf' fun = 'flash_prf' def flash_prf(ctx): flash_everything(ctx, ctx.get_tintin_fw_node_prf()) class FlashBootCommand(BuildContext): cmd = 'flash_boot' fun = 'flash_boot' def flash_boot(ctx): """flashes a bootloader""" if not ctx.env.BOOTLOADER_HEX: ctx.fatal("Target does not have a bootloader binary available") waftools.openocd.run_command(ctx, 'init; reset halt; ' + 'flash write_image erase ' + ctx.env.BOOTLOADER_HEX + '; ' 'reset;', expect=["wrote"], enforce_expect=True) class FlashFirmware(BuildContext): """flashes a firmware""" cmd = 'flash_fw' def execute_build(ctx): flash_fw(ctx, ctx.get_tintin_fw_node()) def flash_fw(ctx, fw_bin): _check_firmware_image_size(ctx, fw_bin.path_from(ctx.path)) hex_path = fw_bin.change_ext('.hex').path_from(ctx.path) waftools.openocd.run_command(ctx, 'init; reset halt; ' + 'flash write_image erase {}; '.format(hex_path) + 'reset;', expect=["wrote"], enforce_expect=True) def flash_everything(ctx, fw_bin): """flashes a bootloader and firmware""" if ctx.env.QEMU: ctx.fatal("I'm sorry Dave, I can't let you do that.\n" "QEMU firmwares do not work on physical hardware.\n" "Configure without --qemu and rebuild before trying again.") _check_firmware_image_size(ctx, fw_bin.path_from(ctx.path)) if not ctx.env.BOOTLOADER_HEX: ctx.fatal("Target does not have a bootloader binary available") hex_path = fw_bin.change_ext('.hex').path_from(ctx.path) waftools.openocd.run_command(ctx, 'init; reset halt; ' 'flash write_image erase ' + ctx.env.BOOTLOADER_HEX + ';\n' 'flash write_image erase ' + hex_path + '; ' 'reset;', expect=["wrote", "wrote", "shutdown"], enforce_expect=True) def force_flash(ctx): """forces a connected device into a flashing state""" (is_newer_than_0_7_0, _) = waftools.openocd.get_flavor(ctx) reset_config = waftools.openocd._get_reset_conf(ctx, is_newer_than_0_7_0, True) reset_cmd = "reset_config %s; " % reset_config waftools.openocd.run_command(ctx, reset_cmd + 'init; reset halt;', ignore_fail=True) waftools.openocd.run_command(ctx, reset_cmd + 'init; stm32x unlock 0;', ignore_fail=True) def reset(ctx): """resets a connected device""" waftools.openocd.run_command(ctx, 'init; reset;', expect=["found"]) def bork(ctx): """resets and wipes a connected a device""" waftools.openocd.run_command(ctx, 'init; reset halt;', ignore_fail=True) waftools.openocd.run_command(ctx, 'init; flash erase_sector 0 0 1;', ignore_fail=True) def make_lang(ctx): """generate translation files and update existing ones""" ctx.recurse('resources/normal/base/lang') class PackLangCommand(BuildContext): cmd = 'pack_lang' fun = 'pack_lang' def pack_lang(ctx): """generates pbpack for langs""" ctx.recurse('resources/normal/base/lang') class PackAllLangsCommand(BuildContext): cmd = 'pack_all_langs' fun = 'pack_all_langs' def pack_all_langs(ctx): """generates pbpack for all langs""" ctx.recurse('resources/normal/base/lang') # Tool build commands ################################################# class build_pdc2png(BuildContext): """executes the pdc2png build""" cmd = 'build_pdc2png' variant = 'pdc2png' class build_tools(BuildContext): """build all tools in tools/ dir""" cmd = 'build_tools' variant = 'tools' # vim:filetype=python