bossac.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # Copyright (c) 2017 Linaro Limited.
  2. # Copyright (c) 2020 Gerson Fernando Budke <nandojve@gmail.com>
  3. #
  4. # SPDX-License-Identifier: Apache-2.0
  5. '''bossac-specific runner (flash only) for Atmel SAM microcontrollers.'''
  6. import pathlib
  7. import pickle
  8. import platform
  9. import subprocess
  10. import sys
  11. from runners.core import ZephyrBinaryRunner, RunnerCaps
  12. # This is needed to load edt.pickle files.
  13. try:
  14. from devicetree import edtlib # pylint: disable=unused-import
  15. MISSING_EDTLIB = False
  16. except ImportError:
  17. # This can happen when building the documentation for the
  18. # runners package if edtlib is not on sys.path. This is fine
  19. # to ignore in that case.
  20. MISSING_EDTLIB = True
  21. if platform.system() == 'Darwin':
  22. DEFAULT_BOSSAC_PORT = None
  23. else:
  24. DEFAULT_BOSSAC_PORT = '/dev/ttyACM0'
  25. DEFAULT_BOSSAC_SPEED = '115200'
  26. class BossacBinaryRunner(ZephyrBinaryRunner):
  27. '''Runner front-end for bossac.'''
  28. def __init__(self, cfg, bossac='bossac', port=DEFAULT_BOSSAC_PORT,
  29. speed=DEFAULT_BOSSAC_SPEED):
  30. super().__init__(cfg)
  31. self.bossac = bossac
  32. self.port = port
  33. self.speed = speed
  34. @classmethod
  35. def name(cls):
  36. return 'bossac'
  37. @classmethod
  38. def capabilities(cls):
  39. return RunnerCaps(commands={'flash'})
  40. @classmethod
  41. def do_add_parser(cls, parser):
  42. parser.add_argument('--bossac', default='bossac',
  43. help='path to bossac, default is bossac')
  44. parser.add_argument('--bossac-port', default=DEFAULT_BOSSAC_PORT,
  45. help='serial port to use, default is ' +
  46. str(DEFAULT_BOSSAC_PORT))
  47. parser.add_argument('--speed', default=DEFAULT_BOSSAC_SPEED,
  48. help='serial port speed to use, default is ' +
  49. DEFAULT_BOSSAC_SPEED)
  50. @classmethod
  51. def do_create(cls, cfg, args):
  52. return BossacBinaryRunner(cfg, bossac=args.bossac,
  53. port=args.bossac_port, speed=args.speed)
  54. def read_help(self):
  55. """Run bossac --help and return the output as a list of lines"""
  56. self.require(self.bossac)
  57. try:
  58. # BOSSA > 1.9.1 returns OK
  59. out = self.check_output([self.bossac, '--help']).decode()
  60. except subprocess.CalledProcessError as ex:
  61. # BOSSA <= 1.9.1 returns an error
  62. out = ex.output.decode()
  63. return out.split('\n')
  64. def supports(self, flag):
  65. """Check if bossac supports a flag by searching the help"""
  66. for line in self.read_help():
  67. if flag in line:
  68. return True
  69. return False
  70. def is_extended_samba_protocol(self):
  71. ext_samba_versions = ['CONFIG_BOOTLOADER_BOSSA_ARDUINO',
  72. 'CONFIG_BOOTLOADER_BOSSA_ADAFRUIT_UF2']
  73. for x in ext_samba_versions:
  74. if self.build_conf.getboolean(x):
  75. return True
  76. return False
  77. def is_partition_enabled(self):
  78. return self.build_conf.getboolean('CONFIG_USE_DT_CODE_PARTITION')
  79. def get_chosen_code_partition_node(self):
  80. # Get the EDT Node corresponding to the zephyr,code-partition
  81. # chosen DT node
  82. # Ensure the build directory has a compiled DTS file
  83. # where we expect it to be.
  84. b = pathlib.Path(self.cfg.build_dir)
  85. edt_pickle = b / 'zephyr' / 'edt.pickle'
  86. if not edt_pickle.is_file():
  87. error_msg = "can't load devicetree; expected to find:" \
  88. + str(edt_pickle)
  89. raise RuntimeError(error_msg)
  90. # Load the devicetree.
  91. with open(edt_pickle, 'rb') as f:
  92. edt = pickle.load(f)
  93. return edt.chosen_node('zephyr,code-partition')
  94. def get_board_name(self):
  95. if 'CONFIG_BOARD' not in self.build_conf:
  96. return '<board>'
  97. return self.build_conf['CONFIG_BOARD']
  98. def get_dts_img_offset(self):
  99. if self.build_conf.getboolean('CONFIG_BOOTLOADER_BOSSA_LEGACY'):
  100. return 0
  101. if self.build_conf.getboolean('CONFIG_HAS_FLASH_LOAD_OFFSET'):
  102. return self.build_conf['CONFIG_FLASH_LOAD_OFFSET']
  103. return 0
  104. def get_image_offset(self, supports_offset):
  105. """Validates and returns the flash offset"""
  106. dts_img_offset = self.get_dts_img_offset()
  107. if int(str(dts_img_offset), 16) > 0:
  108. if not supports_offset:
  109. old_sdk = 'This version of BOSSA does not support the' \
  110. ' --offset flag. Please upgrade to a newer Zephyr' \
  111. ' SDK version >= 0.12.0.'
  112. raise RuntimeError(old_sdk)
  113. return dts_img_offset
  114. return None
  115. def set_serial_config(self):
  116. if platform.system() == 'Linux' or platform.system() == 'Darwin':
  117. self.require('stty')
  118. # GNU coreutils uses a capital F flag for 'file'
  119. flag = '-F' if platform.system() == 'Linux' else '-f'
  120. if self.is_extended_samba_protocol():
  121. self.speed = '1200'
  122. cmd_stty = ['stty', flag, self.port, 'raw', 'ispeed', self.speed,
  123. 'ospeed', self.speed, 'cs8', '-cstopb', 'ignpar',
  124. 'eol', '255', 'eof', '255']
  125. self.check_call(cmd_stty)
  126. def make_bossac_cmd(self):
  127. self.ensure_output('bin')
  128. cmd_flash = [self.bossac, '-p', self.port, '-R', '-e', '-w', '-v',
  129. '-b', self.cfg.bin_file]
  130. dt_chosen_code_partition_nd = self.get_chosen_code_partition_node()
  131. if self.is_partition_enabled():
  132. if dt_chosen_code_partition_nd is None:
  133. error_msg = 'The device tree zephyr,code-partition chosen' \
  134. ' node must be defined.'
  135. raise RuntimeError(error_msg)
  136. offset = self.get_image_offset(self.supports('--offset'))
  137. if offset is not None and int(str(offset), 16) > 0:
  138. cmd_flash += ['-o', '%s' % offset]
  139. elif dt_chosen_code_partition_nd is not None:
  140. error_msg = 'There is no CONFIG_USE_DT_CODE_PARTITION Kconfig' \
  141. ' defined at ' + self.get_board_name() + \
  142. '_defconfig file.\n This means that' \
  143. ' zephyr,code-partition device tree node should not' \
  144. ' be defined. Check Zephyr SAM-BA documentation.'
  145. raise RuntimeError(error_msg)
  146. return cmd_flash
  147. def get_darwin_serial_device_list(self):
  148. """
  149. Get a list of candidate serial ports on Darwin by querying the IOKit
  150. registry.
  151. """
  152. import plistlib
  153. ioreg_out = self.check_output(['ioreg', '-r', '-c', 'IOSerialBSDClient',
  154. '-k', 'IOCalloutDevice', '-a'])
  155. serial_ports = plistlib.loads(ioreg_out, fmt=plistlib.FMT_XML)
  156. return [port["IOCalloutDevice"] for port in serial_ports]
  157. def get_darwin_user_port_choice(self):
  158. """
  159. Ask the user to select the serial port from a set of candidate ports
  160. retrieved from IOKit on Darwin.
  161. Modelled on get_board_snr() in the nrfjprog runner.
  162. """
  163. devices = self.get_darwin_serial_device_list()
  164. if len(devices) == 0:
  165. raise RuntimeError('No candidate serial ports were found!')
  166. elif len(devices) == 1:
  167. print('Using only serial device on the system: ' + devices[0])
  168. return devices[0]
  169. elif not sys.stdin.isatty():
  170. raise RuntimeError('Refusing to guess which serial port to use: '
  171. f'there are {len(devices)} available. '
  172. '(Interactive prompts disabled since standard '
  173. 'input is not a terminal - please specify a '
  174. 'port using --bossac-port instead)')
  175. print('There are multiple serial ports available on this system:')
  176. for i, device in enumerate(devices, 1):
  177. print(f' {i}. {device}')
  178. p = f'Please select one (1-{len(devices)}, or EOF to exit): '
  179. while True:
  180. try:
  181. value = input(p)
  182. except EOFError:
  183. sys.exit(0)
  184. try:
  185. value = int(value)
  186. except ValueError:
  187. continue
  188. if 1 <= value <= len(devices):
  189. break
  190. return devices[value - 1]
  191. def do_run(self, command, **kwargs):
  192. if MISSING_EDTLIB:
  193. self.logger.warning(
  194. 'could not import edtlib; something may be wrong with the '
  195. 'python environment')
  196. if platform.system() == 'Windows':
  197. msg = 'CAUTION: BOSSAC runner not support on Windows!'
  198. raise RuntimeError(msg)
  199. elif platform.system() == 'Darwin' and self.port is None:
  200. self.port = self.get_darwin_user_port_choice()
  201. self.require(self.bossac)
  202. self.set_serial_config()
  203. self.check_call(self.make_bossac_cmd())