Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to argparse #155

Merged
merged 1 commit into from
Mar 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 80 additions & 176 deletions cc2538-bsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import struct
import binascii
import traceback
import argparse

try:
import magic
Expand Down Expand Up @@ -1032,174 +1033,77 @@ def parse_page_address_range(device, pg_range):
return [page_addr[0], (page_addr[1] - page_addr[0])]


def print_version():
def version():
# Get the version using "git describe".
try:
p = Popen(['git', 'describe', '--tags', '--match', '[0-9]*'],
stdout=PIPE, stderr=PIPE)
p.stderr.close()
line = p.stdout.readlines()[0]
version = line.decode('utf-8').strip()
return line.decode('utf-8').strip()
except:
# We're not in a git repo, or git failed, use fixed version string.
version = __version__
print('%s %s' % (sys.argv[0], version))


def usage():
print("""Usage: %s [-DhqVfewvr] [-l length] [-p port] [-b baud] [-a addr] \
[-i addr] [--bootloader-active-high] [--bootloader-invert-lines] [--bootloader-sonoff-usb] [file.bin]
-h, --help This help
-q Quiet
-V Verbose
-f Force operation(s) without asking any questions
-e Mass erase
-E, --erase-page p/a,range Receives an address(a) range or page(p) range,
default is address(a)
eg: -E a,0x00000000,0x00001000,
-E p,1,4
-w Write
-W, --erase-write Write after erasing section to write to (avoids
a mass erase). Rounds up section to erase if
not page aligned.
-v Verify (CRC32 check)
-r Read
-l length Length of read
-p port Serial port (default: first USB-like port in /dev)
-b baud Baud speed (default: 500000)
-a addr Target address
-i, --ieee-address addr Set the secondary 64 bit IEEE address
--bootloader-active-high Use active high signals to enter bootloader
--bootloader-invert-lines Inverts the use of RTS and DTR to enter bootloader
--bootloader-sonoff-usb Toggles RTS and DTR in the correct pattern for Sonoff USB dongle
-D, --disable-bootloader After finishing, disable the bootloader
--version Print script version

Examples:
./%s -e -w -v example/main.bin
./%s -e -w -v --ieee-address 00:12:4b:aa:bb:cc:dd:ee example/main.bin

""" % (sys.argv[0], sys.argv[0], sys.argv[0]))
return __version__


def cli_setup():
parser = argparse.ArgumentParser()

parser.add_argument('-q', action='store_true', help='Quiet')
parser.add_argument('-V', action='store_true', help='Verbose')
parser.add_argument('-f', '--force', action='store_true', help='Force operation(s) without asking any questions')
parser.add_argument('-e', '--erase', action='store_true', help='Mass erase')
parser.add_argument('-E', '--erase-page', help='Receives an address(a) range or page(p) range, default is address(a) eg: -E a,0x00000000,0x00001000, -E p,1,4')
parser.add_argument('-w', '--write', action='store_true', help='Write')
parser.add_argument('-W', '--erase-write', action='store_true', help='Write after erasing section to write to (avoids a mass erase). Rounds up section to erase if not page aligned.')
parser.add_argument('-v', '--verify', action='store_true', help='Verify (CRC32 check)')
parser.add_argument('-r', '--read', action='store_true', help='Read')
parser.add_argument('-l', '--len', type=int, default=0x80000, help='Length of read')
parser.add_argument('-p', '--port', type=int, help='Serial port (default: first USB-like port in /dev)')
parser.add_argument('-b', '--baud', type=int, default=500000, help='Baud speed')
parser.add_argument('-a', '--address', type=int, help='Target address')
parser.add_argument('-i', '--ieee-address', help='Set the secondary 64 bit IEEE address')
parser.add_argument('--bootloader-active-high', action='store_true', help='Use active high signals to enter bootloader')
parser.add_argument('--bootloader-invert-lines', action='store_true', help='Inverts the use of RTS and DTR to enter bootloader')
parser.add_argument('--bootloader-sonoff-usb', action='store_true', help='Toggles RTS and DTR in the correct pattern for Sonoff USB dongle')
parser.add_argument('-D', '--disable-bootloader', action='store_true', help='After finishing, disable the bootloader')
parser.add_argument('--version', action='version', version='%(prog)s ' + version())
parser.add_argument('file')

return parser.parse_args()

if __name__ == "__main__":
args = cli_setup()

conf = {
'port': 'auto',
'baud': 500000,
'force_speed': 0,
'address': None,
'force': 0,
'erase': 0,
'write': 0,
'erase_write': 0,
'erase_page': 0,
'verify': 0,
'read': 0,
'len': 0x80000,
'fname': '',
'ieee_address': 0,
'bootloader_active_high': False,
'bootloader_invert_lines': False,
'bootloader_sonoff_usb':False,
'disable-bootloader': 0
}

# http://www.python.org/doc/2.5.2/lib/module-getopt.html
if args.V:
QUIET = 10
elif args.q:
QUIET = 0

try:
opts, args = getopt.getopt(sys.argv[1:],
"DhqVfeE:wWvrp:b:a:l:i:",
['help', 'ieee-address=','erase-write=',
'erase-page=',
'disable-bootloader',
'bootloader-active-high',
'bootloader-invert-lines',
'bootloader-sonoff-usb', 'version'])
except getopt.GetoptError as err:
# print help information and exit:
print(str(err)) # will print something like "option -a not recognized"
usage()
sys.exit(2)

for o, a in opts:
if o == '-V':
QUIET = 10
elif o == '-q':
QUIET = 0
elif o == '-h' or o == '--help':
usage()
sys.exit(0)
elif o == '-f':
conf['force'] = 1
elif o == '-e':
conf['erase'] = 1
elif o == '-w':
conf['write'] = 1
elif o == '-W' or o == '--erase-write':
conf['erase_write'] = 1
elif o == '-E' or o == '--erase-page':
conf['erase_page'] = str(a)
elif o == '-v':
conf['verify'] = 1
elif o == '-r':
conf['read'] = 1
elif o == '-p':
conf['port'] = a
elif o == '-b':
conf['baud'] = eval(a)
conf['force_speed'] = 1
elif o == '-a':
conf['address'] = eval(a)
elif o == '-l':
conf['len'] = eval(a)
elif o == '-i' or o == '--ieee-address':
conf['ieee_address'] = str(a)
elif o == '--bootloader-active-high':
conf['bootloader_active_high'] = True
elif o == '--bootloader-invert-lines':
conf['bootloader_invert_lines'] = True
elif o == '--bootloader-sonoff-usb':
conf['bootloader_sonoff_usb'] = True
elif o == '-D' or o == '--disable-bootloader':
conf['disable-bootloader'] = 1
elif o == '--version':
print_version()
sys.exit(0)
else:
assert False, "Unhandled option"

try:
# Sanity checks
# check for input/output file
if conf['write'] or conf['erase_write'] or conf['read'] or conf['verify']:
try:
args[0]
except:
raise Exception('No file path given.')

if (conf['write'] and conf['read']) or (conf['erase_write'] and conf['read']):
if not (conf['force'] or
if (args.write and args.read) or (args.erase_write and args.read):
if not (args.force or
query_yes_no("You are reading and writing to the same "
"file. This will overwrite your input file. "
"Do you want to continue?", "no")):
raise Exception('Aborted by user.')
if ((conf['erase'] and conf['read']) or (conf['erase_page'] and conf['read'])
and not (conf['write'] or conf['erase_write'])):
if not (conf['force'] or
if ((args.erase and args.read) or (args.erase_page and args.read)
and not (args.write or args.erase_write)):
if not (args.force or
query_yes_no("You are about to erase your target before "
"reading. Do you want to continue?", "no")):
raise Exception('Aborted by user.')

if (conf['read'] and not (conf['write'] or conf['erase_write'])
and conf['verify']):
if (args.read and not (args.write or args.erase_write)
and args.verify):
raise Exception('Verify after read not implemented.')

if conf['len'] < 0:
if args.len < 0:
raise Exception('Length must be positive but %d was provided'
% (conf['len'],))
% (args.len,))

# Try and find the port automatically
if conf['port'] == 'auto':
if not args.port:
ports = []

# Get a list of all USB-like names in /dev
Expand All @@ -1214,20 +1118,20 @@ def usage():

if ports:
# Found something - take it
conf['port'] = ports[0]
args.port = ports[0]
else:
raise Exception('No serial port found.')

cmd = CommandInterface()
cmd.open(conf['port'], conf['baud'])
cmd.invoke_bootloader(conf['bootloader_active_high'],
conf['bootloader_invert_lines'],
conf['bootloader_sonoff_usb'])
cmd.open(args.port, args.baud)
cmd.invoke_bootloader(args.bootloader_active_high,
args.bootloader_invert_lines,
args.bootloader_sonoff_usb)
mdebug(5, "Opening port %(port)s, baud %(baud)d"
% {'port': conf['port'], 'baud': conf['baud']})
if conf['write'] or conf['erase_write'] or conf['verify']:
mdebug(5, "Reading data from %s" % args[0])
firmware = FirmwareFile(args[0])
% {'port': args.port, 'baud': args.baud})
if args.write or args.erase_write or args.verify:
mdebug(5, "Reading data from %s" % args.file)
firmware = FirmwareFile(args.file)

mdebug(5, "Connecting to target...")

Expand All @@ -1250,16 +1154,16 @@ def usage():
device = CC2538(cmd)

# Choose a good default address unless the user specified -a
if conf['address'] == None:
conf['address'] = device.flash_start_addr
if not args.address:
args.address = device.flash_start_addr

if conf['force_speed'] != 1 and device.has_cmd_set_xosc:
if args.force_speed != 1 and device.has_cmd_set_xosc:
if cmd.cmdSetXOsc(): # switch to external clock source
cmd.close()
conf['baud'] = 1000000
cmd.open(conf['port'], conf['baud'])
args.baud = 1000000
cmd.open(args.port, args.baud)
mdebug(6, "Opening port %(port)s, baud %(baud)d"
% {'port': conf['port'], 'baud': conf['baud']})
% {'port': args.port, 'baud': args.baud})
mdebug(6, "Reconnecting to target at higher speed...")
if (cmd.sendSynch() != 1):
raise CmdException("Can't connect to target after clock "
Expand All @@ -1269,47 +1173,47 @@ def usage():
raise CmdException("Can't switch target to external clock "
"source. (Try forcing speed)")

if conf['erase']:
if args.erase:
mdebug(5, " Performing mass erase")
if device.erase():
mdebug(5, " Erase done")
else:
raise CmdException("Erase failed")

if conf['erase_page']:
erase_range = parse_page_address_range(device, conf['erase_page'])
if args.erase_page:
erase_range = parse_page_address_range(device, args.erase_page)
mdebug(5, "Erasing %d bytes at addres 0x%x"
% (erase_range[1], erase_range[0]))
cmd.cmdEraseMemory(erase_range[0], erase_range[1])
mdebug(5, " Partial erase done ")

if conf['write']:
if args.write:
# TODO: check if boot loader back-door is open, need to read
# flash size first to get address
if cmd.writeMemory(conf['address'], firmware.bytes):
if cmd.writeMemory(args.address, firmware.bytes):
mdebug(5, " Write done ")
else:
raise CmdException("Write failed ")

if conf['erase_write']:
if args.erase_write:
# TODO: check if boot loader back-door is open, need to read
# flash size first to get address
# Round up to ensure page alignment
erase_len = device.page_align_up(len(firmware.bytes))
erase_len = min(erase_len, device.size)
if cmd.cmdEraseMemory(conf['address'], erase_len):
if cmd.cmdEraseMemory(args.address, erase_len):
mdebug(5, " Erase before write done ")
if cmd.writeMemory(conf['address'], firmware.bytes):
if cmd.writeMemory(args.address, firmware.bytes):
mdebug(5, " Write done ")
else:
raise CmdException("Write failed ")

if conf['verify']:
if args.verify:
mdebug(5, "Verifying by comparing CRC32 calculations.")

crc_local = firmware.crc32()
# CRC of target will change according to length input file
crc_target = device.crc(conf['address'], len(firmware.bytes))
crc_target = device.crc(args.address, len(firmware.bytes))

if crc_local == crc_target:
mdebug(5, " Verified (match: 0x%08x)" % crc_local)
Expand All @@ -1318,8 +1222,8 @@ def usage():
raise Exception("NO CRC32 match: Local = 0x%x, "
"Target = 0x%x" % (crc_local, crc_target))

if conf['ieee_address'] != 0:
ieee_addr = parse_ieee_address(conf['ieee_address'])
if args.ieee_address != 0:
ieee_addr = parse_ieee_address(args.ieee_address)
mdebug(5, "Setting IEEE address to %s"
% (':'.join(['%02x' % b
for b in struct.pack('>Q', ieee_addr)])))
Expand All @@ -1332,26 +1236,26 @@ def usage():
else:
raise CmdException("Set address failed ")

if conf['read']:
length = conf['len']
if args.read:
length = args.len

# Round up to a 4-byte boundary
length = (length + 3) & ~0x03

mdebug(5, "Reading %s bytes starting at address 0x%x"
% (length, conf['address']))
with open(args[0], 'wb') as f:
% (length, args.address))
with open(args.file, 'wb') as f:
for i in range(0, length >> 2):
# reading 4 bytes at a time
rdata = device.read_memory(conf['address'] + (i * 4))
rdata = device.read_memory(args.address + (i * 4))
mdebug(5, " 0x%x: 0x%02x%02x%02x%02x"
% (conf['address'] + (i * 4), rdata[0], rdata[1],
% (args.address + (i * 4), rdata[0], rdata[1],
rdata[2], rdata[3]), '\r')
f.write(rdata)
f.close()
mdebug(5, " Read done ")

if conf['disable-bootloader']:
if args.disable_bootloader:
device.disable_bootloader()

cmd.cmdReset()
Expand Down