blob: f21fdf27bc3914289adc5bbaa8c4a7b016256dae [file] [log] [blame]
Marti Bolivar113ee652017-10-09 22:59:50 -04001#! /usr/bin/env python3
2
3# Copyright (c) 2017 Linaro Limited.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""Zephyr board flashing script
8
9This script is a transparent replacement for an existing Zephyr flash
10script. If it can invoke the flashing tools natively, it will do so; otherwise,
11it delegates to the shell script passed as second argument."""
12
13import abc
14from os import path
15import os
Marti Bolivar257fa4a2017-10-09 23:03:29 -040016import pprint
Marti Bolivar289c0f02017-10-10 00:11:53 -040017import platform
Marti Bolivar113ee652017-10-09 22:59:50 -040018import sys
19import subprocess
Marti Bolivar257fa4a2017-10-09 23:03:29 -040020import time
21
22
23def get_env_or_bail(env_var):
24 try:
25 return os.environ[env_var]
26 except KeyError:
27 print('Variable {} not in environment:'.format(
28 env_var), file=sys.stderr)
29 pprint.pprint(dict(os.environ), stream=sys.stderr)
30 raise
Marti Bolivar113ee652017-10-09 22:59:50 -040031
32
33def get_env_bool_or(env_var, default_value):
34 try:
35 return bool(int(os.environ[env_var]))
36 except KeyError:
37 return default_value
38
39
40def check_call(cmd, debug):
41 if debug:
42 print(' '.join(cmd))
43 subprocess.check_call(cmd)
44
45
Marti Bolivar257fa4a2017-10-09 23:03:29 -040046def check_output(cmd, debug):
47 if debug:
48 print(' '.join(cmd))
49 return subprocess.check_output(cmd)
50
51
Marti Bolivar113ee652017-10-09 22:59:50 -040052class ZephyrBinaryFlasher(abc.ABC):
53 '''Abstract superclass for flasher objects.'''
54
55 def __init__(self, debug=False):
56 self.debug = debug
57
58 @staticmethod
59 def create_for_shell_script(shell_script, debug):
60 '''Factory for using as a drop-in replacement to a shell script.
61
62 Get flasher instance to use in place of shell_script, deriving
63 flasher configuration from the environment.'''
64 for sub_cls in ZephyrBinaryFlasher.__subclasses__():
65 if sub_cls.replaces_shell_script(shell_script):
66 return sub_cls.create_from_env(debug)
67 raise ValueError('no flasher replaces script {}'.format(shell_script))
68
69 @staticmethod
70 @abc.abstractmethod
71 def replaces_shell_script(shell_script):
72 '''Check if this flasher class replaces FLASH_SCRIPT=shell_script.'''
73
74 @staticmethod
75 @abc.abstractmethod
76 def create_from_env(debug):
77 '''Create new flasher instance from environment variables.
78
79 This class must be able to replace the current FLASH_SCRIPT. The
80 environment variables expected by that script are used to build
81 the flasher in a backwards-compatible manner.'''
82
83 @abc.abstractmethod
84 def flash(self, **kwargs):
85 '''Flash the board.'''
86
87
Marti Bolivar289c0f02017-10-10 00:11:53 -040088class BossacBinaryFlasher(ZephyrBinaryFlasher):
89 '''Flasher front-end for bossac.'''
90
91 def __init__(self, bin_name, bossac='bossac', debug=False):
92 super(BossacBinaryFlasher, self).__init__(debug=debug)
93 self.bin_name = bin_name
94 self.bossac = bossac
95
96 def replaces_shell_script(shell_script):
97 return shell_script == 'bossa-flash.sh'
98
99 def create_from_env(debug):
100 '''Create flasher from environment.
101
102 Required:
103
104 - O: build output directory
105 - KERNEL_BIN_NAME: name of kernel binary
106
107 Optional:
108
109 - BOSSAC: path to bossac, default is bossac
110 '''
111 bin_name = path.join(get_env_or_bail('O'),
112 get_env_or_bail('KERNEL_BIN_NAME'))
113 bossac = os.environ.get('BOSSAC', 'bossac')
114 return BossacBinaryFlasher(bin_name, bossac=bossac, debug=debug)
115
116 def flash(self, **kwargs):
117 if platform.system() != 'Linux':
118 msg = 'CAUTION: No flash tool for your host system found!'
119 raise NotImplementedError(msg)
120
121 cmd_stty = ['stty', '-F', '/dev/ttyACM0', 'raw', 'ispeed', '1200',
122 'ospeed', '1200', 'cs8', '-cstopb', 'ignpar', 'eol', '255',
123 'eof', '255']
124 cmd_flash = [self.bossac, '-R', '-e', '-w', '-v', '-b', self.bin_name]
125
126 check_call(cmd_stty, self.debug)
127 check_call(cmd_flash, self.debug)
128
129
Marti Bolivar257fa4a2017-10-09 23:03:29 -0400130class DfuUtilBinaryFlasher(ZephyrBinaryFlasher):
131 '''Flasher front-end for dfu-util.'''
132
133 def __init__(self, pid, alt, img, dfuse=None, exe='dfu-util', debug=False):
134 super(DfuUtilBinaryFlasher, self).__init__(debug=debug)
135 self.pid = pid
136 self.alt = alt
137 self.img = img
138 self.dfuse = dfuse
139 self.exe = exe
140 try:
141 self.list_pattern = ', alt={}'.format(int(self.alt))
142 except ValueError:
143 self.list_pattern = ', name={}'.format(self.alt)
144
145 def replaces_shell_script(shell_script):
146 return shell_script == 'dfuutil.sh'
147
148 def create_from_env(debug):
149 '''Create flasher from environment.
150
151 Required:
152
153 - DFUUTIL_PID: USB VID:PID of the board
154 - DFUUTIL_ALT: interface alternate setting number or name
155 - DFUUTIL_IMG: binary to flash
156
157 Optional:
158
159 - DFUUTIL_DFUSE_ADDR: target address if the board is a
160 DfuSe device. Ignored if not present.
161 - DFUUTIL: dfu-util executable, defaults to dfu-util.
162 '''
163 pid = get_env_or_bail('DFUUTIL_PID')
164 alt = get_env_or_bail('DFUUTIL_ALT')
165 img = get_env_or_bail('DFUUTIL_IMG')
166 dfuse = os.environ.get('DFUUTIL_DFUSE_ADDR', None)
167 exe = os.environ.get('DFUUTIL', 'dfu-util')
168
169 return DfuUtilBinaryFlasher(pid, alt, img, dfuse=dfuse, exe=exe,
170 debug=debug)
171
172 def find_device(self):
173 output = check_output([self.exe, '-l'], self.debug)
174 output = output.decode(sys.getdefaultencoding())
175 return self.list_pattern in output
176
177 def flash(self, **kwargs):
178 reset = 0
179 if not self.find_device():
180 reset = 1
181 print('Please reset your board to switch to DFU mode...')
182 while not self.find_device():
183 time.sleep(0.1)
184
185 cmd = [self.exe, '-d,{}'.format(self.pid)]
186 if self.dfuse is not None:
187 cmd.extend(['-s', '{}:leave'.format(self.dfuse)])
188 cmd.extend(['-a', self.alt, '-D', self.img])
189 check_call(cmd, self.debug)
190 if reset:
191 print('Now reset your board again to switch back to runtime mode.')
192
193
Marti Bolivar4bfbe252017-10-09 23:46:50 -0400194class PyOcdBinaryFlasher(ZephyrBinaryFlasher):
195 '''Flasher front-end for pyocd-flashtool.'''
196
197 def __init__(self, bin_name, target, flashtool='pyocd-flashtool',
198 board_id=None, daparg=None, debug=False):
199 super(PyOcdBinaryFlasher, self).__init__(debug=debug)
200 self.bin_name = bin_name
201 self.target = target
202 self.flashtool = flashtool
203 self.board_id = board_id
204 self.daparg = daparg
205
206 def replaces_shell_script(shell_script):
207 return shell_script == 'pyocd.sh'
208
209 def create_from_env(debug):
210 '''Create flasher from environment.
211
212 Required:
213
214 - O: build output directory
215 - KERNEL_BIN_NAME: name of kernel binary
216 - PYOCD_TARGET: target override
217
218 Optional:
219
220 - PYOCD_FLASHTOOL: flash tool path, defaults to pyocd-flashtool
221 - PYOCD_BOARD_ID: ID of board to flash, default is to guess
222 - PYOCD_DAPARG_ARG: arguments to pass to flashtool, default is none
223 '''
224 bin_name = path.join(get_env_or_bail('O'),
225 get_env_or_bail('KERNEL_BIN_NAME'))
226 target = get_env_or_bail('PYOCD_TARGET')
227
228 flashtool = os.environ.get('PYOCD_FLASHTOOL', 'pyocd-flashtool')
229 board_id = os.environ.get('PYOCD_BOARD_ID', None)
230 daparg = os.environ.get('PYOCD_DAPARG_ARG', None)
231
232 return PyOcdBinaryFlasher(bin_name, target,
233 flashtool=flashtool, board_id=board_id,
234 daparg=daparg, debug=debug)
235
236 def flash(self, **kwargs):
237 daparg_args = []
238 if self.daparg is not None:
239 daparg_args = ['-da', self.daparg]
240
241 board_args = []
242 if self.board_id is not None:
243 board_args = ['-b', self.board_id]
244
245 cmd = ([self.flashtool] +
246 daparg_args +
247 ['-t', self.target] +
248 board_args +
249 [self.bin_name])
250
251 print('Flashing Target Device')
252 check_call(cmd, self.debug)
253
254
Marti Bolivar113ee652017-10-09 22:59:50 -0400255# TODO: Stop using environment variables.
256#
257# Migrate the build system so we can use an argparse.ArgumentParser and
258# per-flasher subparsers, so invoking the script becomes something like:
259#
260# python zephyr_flash_debug.py openocd --openocd-bin=/openocd/path ...
261#
262# For now, maintain compatibility.
263def flash(shell_script_full, debug):
264 shell_script = path.basename(shell_script_full)
265 try:
266 flasher = ZephyrBinaryFlasher.create_for_shell_script(shell_script,
267 debug)
268 except ValueError:
269 # Can't create a flasher; fall back on shell script.
270 check_call([shell_script_full, 'flash'], debug)
271 return
272
273 flasher.flash()
274
275
276if __name__ == '__main__':
277 debug = True
278 try:
279 debug = get_env_bool_or('KBUILD_VERBOSE', False)
280 if len(sys.argv) != 3 or sys.argv[1] != 'flash':
281 raise ValueError('usage: {} flash path-to-script'.format(
282 sys.argv[0]))
283 flash(sys.argv[2], debug)
284 except Exception as e:
285 if debug:
286 raise
287 else:
288 print('Error: {}'.format(e), file=sys.stderr)
289 print('Re-run with KBUILD_VERBOSE=1 for a stack trace.',
290 file=sys.stderr)
291 sys.exit(1)