123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- # Copyright (c) 2017 Linaro Limited.
- # Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
- #
- # SPDX-License-Identifier: Apache-2.0
- '''bossac-specific runner (flash only) for Atmel SAM microcontrollers.'''
- import pathlib
- import pickle
- import platform
- import subprocess
- import sys
- from runners.core import ZephyrBinaryRunner, RunnerCaps
- # This is needed to load edt.pickle files.
- try:
- from devicetree import edtlib # pylint: disable=unused-import
- MISSING_EDTLIB = False
- except ImportError:
- # This can happen when building the documentation for the
- # runners package if edtlib is not on sys.path. This is fine
- # to ignore in that case.
- MISSING_EDTLIB = True
- if platform.system() == 'Darwin':
- DEFAULT_BOSSAC_PORT = None
- else:
- DEFAULT_BOSSAC_PORT = '/dev/ttyACM0'
- DEFAULT_BOSSAC_SPEED = '115200'
- class BossacBinaryRunner(ZephyrBinaryRunner):
- '''Runner front-end for bossac.'''
- def __init__(self, cfg, bossac='bossac', port=DEFAULT_BOSSAC_PORT,
- speed=DEFAULT_BOSSAC_SPEED):
- super().__init__(cfg)
- self.bossac = bossac
- self.port = port
- self.speed = speed
- @classmethod
- def name(cls):
- return 'bossac'
- @classmethod
- def capabilities(cls):
- return RunnerCaps(commands={'flash'})
- @classmethod
- def do_add_parser(cls, parser):
- parser.add_argument('--bossac', default='bossac',
- help='path to bossac, default is bossac')
- parser.add_argument('--bossac-port', default=DEFAULT_BOSSAC_PORT,
- help='serial port to use, default is ' +
- str(DEFAULT_BOSSAC_PORT))
- parser.add_argument('--speed', default=DEFAULT_BOSSAC_SPEED,
- help='serial port speed to use, default is ' +
- DEFAULT_BOSSAC_SPEED)
- @classmethod
- def do_create(cls, cfg, args):
- return BossacBinaryRunner(cfg, bossac=args.bossac,
- port=args.bossac_port, speed=args.speed)
- def read_help(self):
- """Run bossac --help and return the output as a list of lines"""
- self.require(self.bossac)
- try:
- # BOSSA > 1.9.1 returns OK
- out = self.check_output([self.bossac, '--help']).decode()
- except subprocess.CalledProcessError as ex:
- # BOSSA <= 1.9.1 returns an error
- out = ex.output.decode()
- return out.split('\n')
- def supports(self, flag):
- """Check if bossac supports a flag by searching the help"""
- for line in self.read_help():
- if flag in line:
- return True
- return False
- def is_extended_samba_protocol(self):
- ext_samba_versions = ['CONFIG_BOOTLOADER_BOSSA_ARDUINO',
- 'CONFIG_BOOTLOADER_BOSSA_ADAFRUIT_UF2']
- for x in ext_samba_versions:
- if self.build_conf.getboolean(x):
- return True
- return False
- def is_partition_enabled(self):
- return self.build_conf.getboolean('CONFIG_USE_DT_CODE_PARTITION')
- def get_chosen_code_partition_node(self):
- # Get the EDT Node corresponding to the zephyr,code-partition
- # chosen DT node
- # Ensure the build directory has a compiled DTS file
- # where we expect it to be.
- b = pathlib.Path(self.cfg.build_dir)
- edt_pickle = b / 'zephyr' / 'edt.pickle'
- if not edt_pickle.is_file():
- error_msg = "can't load devicetree; expected to find:" \
- + str(edt_pickle)
- raise RuntimeError(error_msg)
- # Load the devicetree.
- with open(edt_pickle, 'rb') as f:
- edt = pickle.load(f)
- return edt.chosen_node('zephyr,code-partition')
- def get_board_name(self):
- if 'CONFIG_BOARD' not in self.build_conf:
- return '<board>'
- return self.build_conf['CONFIG_BOARD']
- def get_dts_img_offset(self):
- if self.build_conf.getboolean('CONFIG_BOOTLOADER_BOSSA_LEGACY'):
- return 0
- if self.build_conf.getboolean('CONFIG_HAS_FLASH_LOAD_OFFSET'):
- return self.build_conf['CONFIG_FLASH_LOAD_OFFSET']
- return 0
- def get_image_offset(self, supports_offset):
- """Validates and returns the flash offset"""
- dts_img_offset = self.get_dts_img_offset()
- if int(str(dts_img_offset), 16) > 0:
- if not supports_offset:
- old_sdk = 'This version of BOSSA does not support the' \
- ' --offset flag. Please upgrade to a newer Zephyr' \
- ' SDK version >= 0.12.0.'
- raise RuntimeError(old_sdk)
- return dts_img_offset
- return None
- def set_serial_config(self):
- if platform.system() == 'Linux' or platform.system() == 'Darwin':
- self.require('stty')
- # GNU coreutils uses a capital F flag for 'file'
- flag = '-F' if platform.system() == 'Linux' else '-f'
- if self.is_extended_samba_protocol():
- self.speed = '1200'
- cmd_stty = ['stty', flag, self.port, 'raw', 'ispeed', self.speed,
- 'ospeed', self.speed, 'cs8', '-cstopb', 'ignpar',
- 'eol', '255', 'eof', '255']
- self.check_call(cmd_stty)
- def make_bossac_cmd(self):
- self.ensure_output('bin')
- cmd_flash = [self.bossac, '-p', self.port, '-R', '-e', '-w', '-v',
- '-b', self.cfg.bin_file]
- dt_chosen_code_partition_nd = self.get_chosen_code_partition_node()
- if self.is_partition_enabled():
- if dt_chosen_code_partition_nd is None:
- error_msg = 'The device tree zephyr,code-partition chosen' \
- ' node must be defined.'
- raise RuntimeError(error_msg)
- offset = self.get_image_offset(self.supports('--offset'))
- if offset is not None and int(str(offset), 16) > 0:
- cmd_flash += ['-o', '%s' % offset]
- elif dt_chosen_code_partition_nd is not None:
- error_msg = 'There is no CONFIG_USE_DT_CODE_PARTITION Kconfig' \
- ' defined at ' + self.get_board_name() + \
- '_defconfig file.\n This means that' \
- ' zephyr,code-partition device tree node should not' \
- ' be defined. Check Zephyr SAM-BA documentation.'
- raise RuntimeError(error_msg)
- return cmd_flash
- def get_darwin_serial_device_list(self):
- """
- Get a list of candidate serial ports on Darwin by querying the IOKit
- registry.
- """
- import plistlib
- ioreg_out = self.check_output(['ioreg', '-r', '-c', 'IOSerialBSDClient',
- '-k', 'IOCalloutDevice', '-a'])
- serial_ports = plistlib.loads(ioreg_out, fmt=plistlib.FMT_XML)
- return [port["IOCalloutDevice"] for port in serial_ports]
- def get_darwin_user_port_choice(self):
- """
- Ask the user to select the serial port from a set of candidate ports
- retrieved from IOKit on Darwin.
- Modelled on get_board_snr() in the nrfjprog runner.
- """
- devices = self.get_darwin_serial_device_list()
- if len(devices) == 0:
- raise RuntimeError('No candidate serial ports were found!')
- elif len(devices) == 1:
- print('Using only serial device on the system: ' + devices[0])
- return devices[0]
- elif not sys.stdin.isatty():
- raise RuntimeError('Refusing to guess which serial port to use: '
- f'there are {len(devices)} available. '
- '(Interactive prompts disabled since standard '
- 'input is not a terminal - please specify a '
- 'port using --bossac-port instead)')
- print('There are multiple serial ports available on this system:')
- for i, device in enumerate(devices, 1):
- print(f' {i}. {device}')
- p = f'Please select one (1-{len(devices)}, or EOF to exit): '
- while True:
- try:
- value = input(p)
- except EOFError:
- sys.exit(0)
- try:
- value = int(value)
- except ValueError:
- continue
- if 1 <= value <= len(devices):
- break
- return devices[value - 1]
- def do_run(self, command, **kwargs):
- if MISSING_EDTLIB:
- self.logger.warning(
- 'could not import edtlib; something may be wrong with the '
- 'python environment')
- if platform.system() == 'Windows':
- msg = 'CAUTION: BOSSAC runner not support on Windows!'
- raise RuntimeError(msg)
- elif platform.system() == 'Darwin' and self.port is None:
- self.port = self.get_darwin_user_port_choice()
- self.require(self.bossac)
- self.set_serial_config()
- self.check_call(self.make_bossac_cmd())
|