#!/usr/bin/env python3

# hexprog.py: Python application to flash a target with an Intel hex file
# Copyright (C) 2011  Black Sphere Technologies
# Written by Gareth McMullin <gareth@blacksphere.co.nz>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import gdb
import time

# Microcode sequence to erase option bytes
stub_opterase = '\nH\x0bIA`\x0bIA`\tI\x81`\tI\x81`\x01iA\xf0 \x01\x01aA\xf0@\x01\x01a\xc4hO\xf0\x01\x064B\xfa\xd1\x00\xbe\x00 \x02@#\x01gE\xab\x89\xef\xcd'
# Microcode sequence to program option bytes
stub_optprog = '\tJ\nKS`\nKS`\x08K\x93`\x08K\x93`\x13iC\xf0\x10\x03\x13a\x01\x80\xd4hO\xf0\x01\x064B\xfa\xd1\x00\xbe\x00 \x02@#\x01gE\xab\x89\xef\xcd'

def flash_write_hex(target, hexfile, progress_cb=None):
	target.flash_probe()
	f = open(hexfile)
	addrhi = 0
	for line in f:
		if line[0] != ':':
			raise Exception("Error in hex file")
		reclen = int(line[1:3], 16)
		addrlo = int(line[3:7], 16)
		rectype = int(line[7:9], 16)
		if sum(x for x in bytes.fromhex(line[1:11+reclen*2])) & 0xff != 0:
			raise Exception("Checksum error in hex file")
		if rectype == 0: # Data record
			addr = (addrhi << 16) + addrlo
			data = bytes.fromhex(line[9:9+reclen*2])
			target.flash_write_prepare(addr, data)
			pass
		elif rectype == 4: # High address record
			addrhi = int(line[9:13], 16)
			pass
		elif rectype == 5: # Entry record
			pass
		elif rectype == 1: # End of file record
			break
		else:
			raise Exception("Invalid record in hex file")

	try:
		target.flash_commit(progress_cb)
	except:
		print("Flash write failed! Is device protected?\n")
		exit(-1)


if __name__ == "__main__":
	from serial import Serial, SerialException
	from sys import argv, platform, stdout
	from getopt import getopt

	if platform == "linux":
		print("\x1b\x5b\x48\x1b\x5b\x32\x4a") # clear terminal screen
	print("Black Magic Probe -- Target Production Programming Tool -- version 1.0")
	print("Copyright (C) 2011  Black Sphere Technologies")
	print("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>")
	print("")

	dev = "COM1" if platform == "win32" else "/dev/ttyACM0"
	baud = 115200
	scan = "jtag_scan"
	targetno = 1
	unprot = False; prot = False

	try:
		opts, args = getopt(argv[1:], "sd:b:t:rR")
		for opt in opts:
			if opt[0] == "-s":
				scan = "swdp_scan"
			elif opt[0] == "-b":
				baud = int(opt[1])
			elif opt[0] == "-d":
				dev = opt[1]
			elif opt[0] == "-t":
				targetno = int(opt[1])
			elif opt[0] == "-r":
				unprot = True
			elif opt[0] == "-R":
				prot = True
			else:
				raise Exception()

		hexfile = args[0]
	except:
		print("Usage %s [-s] [-d <dev>] [-b <baudrate>] [-t <n>] <filename>" % argv[0])
		print("\t-s : Use SW-DP instead of JTAG-DP")
		print("\t-d : Use target on interface <dev> (default: %s)" % dev)
		print("\t-b : Set device baudrate (default: %d)" % baud)
		print("\t-t : Connect to target #n (default: %d)" % targetno)
		print("\t-r : Clear flash read protection before programming")
		print("\t-R : Enable flash read protection after programming (requires power-on reset)")
		print("")
		exit(-1)

	try:
		s = Serial(dev) #, baud, timeout=0.1)
		#s.setDTR(1)
		#s.flushInput()

		#while s.read(1024):
	        #		pass

		target = gdb.Target(s)

	except SerialException as e:
		print("FATAL: Failed to open serial device!\n%s\n" % e[0])
		exit(-1)

	try:
		r = target.monitor("version")
		for s in r:
			print(s.decode(), end=' ')
	except SerialException as e:
		print("FATAL: Serial communication failure!\n%s\n" % e[0])
		exit(-1)
	#except: pass

	print("Target device scan:")
	targetlist = None
	r = target.monitor(scan)
	for s in r:
		print(s.decode(), end=' ')
	print()

	r = target.monitor("targets")
	for s in r:
		if s.startswith(b"No. Att Driver"):
			targetlist = []
		try:
			if type(targetlist) is list:
				targetlist.append(int(s[:2]))
		except:
			pass

	#if not targetlist:
	#	print("FATAL: No usable targets found!\n")
	#	exit(-1)

	if targetlist and (targetno not in targetlist):
		print("WARNING: Selected target %d not available, using %d" % (targetno, targetlist[0]))
		targetno = targetlist[0]

	print("Attaching to target %d." % targetno)
	target.attach(targetno)
	time.sleep(0.1)

	if unprot:
		print("Removing device protection.")
		# Save option bytes for later
		#optbytes = struct.unpack("8H", target.read_mem(0x1FFFF800, 16))
		# Remove protection
		target.run_stub(stub_opterase, 0x20000000)
		target.run_stub(stub_optprog, 0x20000000, 0x1FFFF800, 0x5aa5)
		target.reset()
		time.sleep(0.1)

	for m in target.flash_probe():
		print("FLASH memory -- Offset: 0x%X  BlockSize:0x%X\n" % (m.offset, m.blocksize))

	def progress(percent):
		print("Progress: %d%%\r" % percent, end=' ')
		stdout.flush()

	print("Programming target")
	flash_write_hex(target, hexfile, progress)

	print("Resetting target")
	target.reset()

	if prot:
		print("Enabling device protection.")
		target.run_stub(stub_opterase, 0x20000000)
		target.run_stub(stub_optprog, 0x20000000, 0x1FFFF800, 0x00ff)
		target.reset()

	target.detach()

	print("\nAll operations complete!\n")
