#!/usr/bin/python
# *************************************************************************
# Teensy++ 2.0 modifications by Effleurage
#  NANDway.py
#
# Teensy++ 2.0 modifications by judges@eEcho.com
# *************************************************************************
#  noralizer.py - NOR flasher for PS3
#
# Copyright (C) 2010-2011  Hector Martin "marcan" <hector@marcansoft.com>
#
# This code is licensed to you under the terms of the GNU GPL, version 2;
# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
# *************************************************************************

import serial, time, datetime, sys, struct

class TeensySerialError(Exception):
	pass

class TeensySerial(object):
	BUFSIZE = 32768

	def __init__(self, port):
		self.ser = serial.Serial(port, 9600, timeout=300, rtscts=False, dsrdtr=False, xonxoff=False, writeTimeout=120)
		if self.ser is None:
			raise TeensySerialError("could not open serial %s")%port
		self.ser.flushInput()
		self.ser.flushOutput()
		self.obuf = ""

	def write(self, s):
		if isinstance(s,int):
			s = chr(s)
		elif isinstance(s,tuple) or isinstance(s,list):
			s = ''.join([chr(c) for c in s])
		self.obuf += s
		while len(self.obuf) > self.BUFSIZE:
			self.ser.write(self.obuf[:self.BUFSIZE])
			self.obuf = self.obuf[self.BUFSIZE:]

	def flush(self):
		if len(self.obuf):
			self.ser.write(self.obuf)
			self.ser.flush()
			self.obuf = ""

	def read(self, size):
		self.flush()
		data = self.ser.read(size)
		return data

	def readbyte(self):
		return ord(self.read(1))

	def close(self):
		print
		print "Closing serial device..."
		if self.ser is None:
			print "Device already closed."
		else:
			self.ser.close()
			print "Done."

class NANDError(Exception):
	pass

class NANDFlasher(TeensySerial):
	VERSION_MAJOR = 0
	VERSION_MINOR = 0
	NAND_ID = 0
	NAND_DISABLE_PULLUPS = 0
	MF_ID = 0
	DEVICE_ID = 0
	NAND_PAGE_SZ = 0
	NAND_RAS = 0 # Redundent Area Size
	NAND_PAGE_SZ_PLUS_RAS = 0
	NAND_NPAGES = 0
	NAND_NBLOCKS = 0
	NAND_PAGES_PER_BLOCK = 0
	NAND_BLOCK_SZ = 0
	NAND_BLOCK_SZ_PLUS_RAS = 0
	NAND_BUS_WIDTH = 0
	NAND_NPLANES = 0
	NAND_PLANE_SZ = 0

	# Teensy commands
	CMD_PING1 = 0
	CMD_PING2 = 1
	CMD_BOOTLOADER = 2
	CMD_IO_LOCK = 3
	CMD_IO_RELEASE = 4
	CMD_PULLUPS_DISABLE = 5
	CMD_PULLUPS_ENABLE = 6
	CMD_NAND0_ID = 7
	CMD_NAND0_READPAGE = 8
	CMD_NAND0_WRITEPAGE = 9
	CMD_NAND0_ERASEBLOCK = 10
	CMD_NAND1_ID = 11
	CMD_NAND1_READPAGE = 12
	CMD_NAND1_WRITEPAGE = 13
	CMD_NAND1_ERASEBLOCK = 14

	def __init__(self, port, nand_id, ver_major, ver_minor):
		if port:
			TeensySerial.__init__(self, port)
		self.NAND_ID = nand_id & 1
		self.NAND_DISABLE_PULLUPS = nand_id & 10
		self.VERSION_MAJOR = ver_major
		self.VERSION_MINOR = ver_minor

	def ping(self):
		self.write(self.CMD_PING1)
		self.write(self.CMD_PING2)
		ver_major = self.readbyte()
		ver_minor = self.readbyte()
		freeram = (self.readbyte() << 8) | self.readbyte()
		if (ver_major != self.VERSION_MAJOR) or (ver_minor != self.VERSION_MINOR):
			print "Ping failed (expected v%d.%02d, got v%d.%02d)"%(self.VERSION_MAJOR, self.VERSION_MINOR, ver_major, ver_minor)
			self.close()
			sys.exit(1)

		return freeram

	def readid(self):
		if (self.NAND_DISABLE_PULLUPS == 0):
			self.write(self.CMD_PULLUPS_ENABLE)
		else:
			self.write(self.CMD_PULLUPS_DISABLE)

		if (self.NAND_ID == 1):
			self.write(self.CMD_NAND1_ID)
		else:
			self.write(self.CMD_NAND0_ID)

		isCommandSupported = self.readbyte()
		if (isCommandSupported != 89): #'Y'
			print
			print "NAND_ID 1 not supported for Signal Booster Edition! Exiting..."
			self.close()
			sys.exit(1)

		nand_info = self.read(25)
		
		#print "%x, %x, %x, %x, %x"%(self.MF_ID, self.DEVICE_ID, info1, info, info3)
		print "Raw ID data: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x"%(ord(nand_info[0]), ord(nand_info[1]), ord(nand_info[2]), ord(nand_info[3]), ord(nand_info[4]))

		self.MF_ID = ord(nand_info[0])
		self.DEVICE_ID = ord(nand_info[1])
		self.NAND_PAGE_SZ = (ord(nand_info[5]) << 24) | (ord(nand_info[6]) << 16) | (ord(nand_info[7]) << 8) | ord(nand_info[8])
		self.NAND_RAS = (ord(nand_info[9]) << 8) | ord(nand_info[10])
		self.NAND_BUS_WIDTH = ord(nand_info[11])
		self.NAND_BLOCK_SZ = (ord(nand_info[12]) << 24) | (ord(nand_info[13]) << 16) | (ord(nand_info[14]) << 8) | ord(nand_info[15])
		self.NAND_NBLOCKS = (ord(nand_info[16]) << 24) | (ord(nand_info[17]) << 16) | (ord(nand_info[18]) << 8) | ord(nand_info[19])
		self.NAND_NPLANES = ord(nand_info[20])
		self.NAND_PLANE_SZ = (ord(nand_info[21]) << 24) | (ord(nand_info[22]) << 16) | (ord(nand_info[23]) << 8) | ord(nand_info[24])

		if (self.NAND_PAGE_SZ <= 0):
			print
			print "Error reading size of NAND! Exiting..."
			self.close()
			sys.exit(1)
		if (self.NAND_BUS_WIDTH != 8):
			print
			print "Only 8-bit NANDs are supported! Exiting..."
			self.close()
			sys.exit(1)
		if (self.MF_ID == 0):
			print
			print "Unknown chip manufacturer! Exiting..."
			self.close()
			sys.exit(1)
		if (self.DEVICE_ID == 0):
			print
			print "Unknown device id! Exiting..."
			self.close()
			sys.exit(1)

		if self.MF_ID == 0x98 and self.DEVICE_ID == 0xdc:
			# TC58NVG2S3E
			self.NAND_NBLOCKS = self.NAND_NBLOCKS // 4
			self.NAND_PLANE_SZ = self.NAND_PLANE_SZ // 4

		self.NAND_PAGES_PER_BLOCK = self.NAND_BLOCK_SZ / self.NAND_PAGE_SZ
		self.NAND_PAGE_SZ_PLUS_RAS = self.NAND_PAGE_SZ + self.NAND_RAS
		self.NAND_NPAGES = self.NAND_PAGES_PER_BLOCK * self.NAND_NBLOCKS
		self.NAND_BLOCK_SZ_PLUS_RAS = self.NAND_PAGES_PER_BLOCK * self.NAND_PAGE_SZ_PLUS_RAS
			
	def printstate(self):
		print "NAND%d information:"%self.NAND_ID
		self.readid()

		print
		if self.MF_ID == 0xEC:
			print "NAND chip manufacturer: Samsung (0x%02x)"%self.MF_ID
			if self.DEVICE_ID == 0xA1:
				print "NAND chip type:         K9F1G08R0A (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xD5:
				print "NAND chip type:         K9GAG08U0M (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xF1:
				print "NAND chip type:         K9F1G08U0A (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0x79:
				print "NAND chip type:         K9T1G08U0M (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xDA:
				print "NAND chip type:         K9F2G08U0M (0x%02x)"%self.DEVICE_ID
			else:
				print "NAND chip type:         unknown (0x%02x)"%self.DEVICE_ID
		elif self.MF_ID == 0xAD:
			print "NAND chip manufacturer: Hynix (0x%02x)"%self.MF_ID
			if self.DEVICE_ID == 0x73:
				print "NAND chip type:         HY27US08281A (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xD7:
				print "NAND chip type:         H27UBG8T2A (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xDA:
				print "NAND chip type:         HY27UF082G2B (0x%02x)"%self.DEVICE_ID
			elif self.DEVICE_ID == 0xDC:
				print "NAND chip type:         H27U4G8F2D (0x%02x)"%self.DEVICE_ID
			else:
				print "NAND chip type:         unknown (0x%02x)"%self.DEVICE_ID
		elif self.MF_ID == 0x98:
			print "NAND chip manufacturer: Toshiba (0x%02x)"%self.MF_ID
			if self.DEVICE_ID == 0xdc:
				print "NAND chip type:         TC58NVG2S3E (0x%02x)"%self.DEVICE_ID
			else:
				print "NAND chip type:         unknown (0x%02x)"%self.DEVICE_ID
		else:
			print "NAND chip manufacturer: unknown (0x%02x)"%self.MF_ID
			print "NAND chip type:         unknown (0x%02x)"%self.DEVICE_ID
			#self.MF_ID = 0
			#self.DEVICE_ID = 0
			#return

		print
		print "NAND size:              %d MB"%(self.NAND_BLOCK_SZ * self.NAND_NBLOCKS / 1024 / 1024)
		print "NAND plus RAS size:     %d MB"%(self.NAND_BLOCK_SZ_PLUS_RAS * self.NAND_NBLOCKS / 1024 / 1024)
		print "Page size:              %d bytes"%(self.NAND_PAGE_SZ)
		print "Page plus RAS size:     %d bytes"%(self.NAND_PAGE_SZ_PLUS_RAS)
		print "Block size:             %d bytes"%(self.NAND_BLOCK_SZ)
		print "Block plus RAS size:    %d bytes"%(self.NAND_BLOCK_SZ_PLUS_RAS)
		print "RAS size:               %d bytes"%(self.NAND_RAS)
		print "Plane size:             %d bytes"%(self.NAND_PLANE_SZ)
		print "Pages per block:        %d"%(self.NAND_PAGES_PER_BLOCK)
		print "Number of blocks:       %d"%(self.NAND_NBLOCKS)
		print "Number of pages:        %d"%(self.NAND_NPAGES)
		print "Number of planes:       %d"%(self.NAND_NPLANES)
		print "Bus width:              %d-bit"%(self.NAND_BUS_WIDTH)
		#print

	def bootloader(self):
		self.write(self.CMD_BOOTLOADER)
		self.flush()

	def read_result(self):
		# read status byte
		res = self.readbyte()
		
		# 'K' = okay, 'T' = timeout error when writing, 'R' = Teensy receive buffer timeout, 'V' = Verification error
		error_msg = ""
		
		if (res != 75): #'K'
			if (res == 84): #'T'
				error_msg = "RY/BY timeout error while writing!"
			elif (res == 82): #'R'
				self.close()
				raise NANDError("Teensy receive buffer timeout! Disconnect and reconnect Teensy!")
			elif (res == 86): #'V'
				error_msg = "Verification error!"
			elif (res == 80): #'P'
				error_msg = "Device is write-protected!"
			else:
				self.close()
				raise NANDError("Received unknown error! (Got 0x%02x)"%res)

			print error_msg
			return 0

		return 1

	def erase_block(self, pagenr):
		if (self.NAND_ID == 1):
			self.write(self.CMD_NAND1_ERASEBLOCK)
		else:
			self.write(self.CMD_NAND0_ERASEBLOCK)

		pgblock = pagenr / self.NAND_PAGES_PER_BLOCK
		
		# row (page number) address
		self.write(pagenr & 0xFF)
		self.write((pagenr >> 8) & 0xFF)
		self.write((pagenr >> 16) & 0xFF)

		if self.read_result() == 0:
			print "Block %x - error erasing block"%(pgblock)
			return 0

		return 1

	def readpage(self, page):
		if (self.NAND_ID == 1):
			self.write(self.CMD_NAND1_READPAGE)
		else:
			self.write(self.CMD_NAND0_READPAGE)

		# address
		#self.write(0x0)
		#self.write(0x0)
		self.write(page & 0xFF)
		self.write((page >> 8) & 0xFF)
		self.write((page >> 16) & 0xFF)
		
		if self.read_result() == 0:
			return "error"
		
		data = self.read(self.NAND_PAGE_SZ_PLUS_RAS)
		return data
		
	def writepage(self, data, pagenr):
		if len(data) != self.NAND_PAGE_SZ_PLUS_RAS:
			print "Incorrent data size %d"%(len(data))
			
		pgblock = pagenr / self.NAND_PAGES_PER_BLOCK
		pgoff = pagenr % self.NAND_PAGES_PER_BLOCK
		
		if (self.NAND_ID == 1):
			self.write(self.CMD_NAND1_WRITEPAGE)
		else:
			self.write(self.CMD_NAND0_WRITEPAGE)

		# address
		#self.write(0x0)
		#self.write(0x0)
		self.write(pagenr & 0xFF)
		self.write((pagenr >> 8) & 0xFF)
		self.write((pagenr >> 16) & 0xFF)

		self.write(data)
		
		if self.read_result() == 0:
			return 0
		
		return 1

	def dump(self, filename, block_offset, nblocks):
		fo = open(filename,"wb")

		if nblocks == 0:
			nblocks = self.NAND_NBLOCKS

		if nblocks > self.NAND_NBLOCKS:
			nblocks = self.NAND_NBLOCKS
		
		for page in range(block_offset*self.NAND_PAGES_PER_BLOCK, (block_offset+nblocks)*self.NAND_PAGES_PER_BLOCK, 1):
			data = self.readpage(page)
			fo.write(data)
			print "\r%d KB / %d KB"%((page-(block_offset*self.NAND_PAGES_PER_BLOCK)+1)*self.NAND_PAGE_SZ_PLUS_RAS/1024, nblocks*self.NAND_BLOCK_SZ_PLUS_RAS/1024),
			sys.stdout.flush()

		return

	def program_block(self, data, pgblock, verify):
		pagenr = 0
		
		datasize = len(data)
		if datasize != self.NAND_BLOCK_SZ_PLUS_RAS:
			print "Incorrect length %d != %d"%(datasize, self.NAND_BLOCK_SZ_PLUS_RAS)
			return -1
		
		while pagenr < self.NAND_PAGES_PER_BLOCK:
			real_pagenr = (pgblock * self.NAND_PAGES_PER_BLOCK) + pagenr
			if pagenr == 0:
				self.erase_block(real_pagenr)

			self.writepage(data[pagenr*self.NAND_PAGE_SZ_PLUS_RAS:(pagenr+1)*self.NAND_PAGE_SZ_PLUS_RAS], real_pagenr)
				
			pagenr += 1

		# verification
		if verify == 1:
			pagenr = 0;
			while pagenr < self.NAND_PAGES_PER_BLOCK:
				real_pagenr = (pgblock * self.NAND_PAGES_PER_BLOCK) + pagenr
				if data[pagenr*self.NAND_PAGE_SZ_PLUS_RAS:(pagenr+1)*self.NAND_PAGE_SZ_PLUS_RAS] != self.readpage(real_pagenr):
					print
					print "Error! Block verification failed. block=0x%x page=%d"%(pgblock, real_pagenr)
					return  -1
					
				pagenr += 1
				
		return 0

	def program(self, data, verify, block_offset, nblocks):
		datasize = len(data)

		if nblocks == 0:
			nblocks = self.NAND_NBLOCKS - block_offset
			
		# validate that the data is a multiplication of self.NAND_BLOCK_SZ_PLUS_RAS
		if datasize % self.NAND_BLOCK_SZ_PLUS_RAS:
			print "Error: expecting file size to be a multiplication of block+ras size: %d"%(self.NAND_BLOCK_SZ_PLUS_RAS)
			return -1

		# validate that the the user didn't want to read from incorrect place in the file
		if block_offset + nblocks > datasize/self.NAND_BLOCK_SZ_PLUS_RAS:
			print "Error: file is %x bytes long and last block is at %x"%(datasize, (block_offset + nblocks + 1) * self.NAND_BLOCK_SZ_PLUS_RAS)
			return -1
		
		# validate that the the user didn't want to write to incorrect place in the NAND
		if block_offset + nblocks > self.NAND_NBLOCKS:
			print "Error: nand has %x blocks. writing outside the nand's capacity"%(self.NAND_NBLOCKS, block_offset + nblocks + 1)
			return -1
		
		block = 0

		print "Writing %x blocks to device (starting at offset %x)..."%(nblocks, block_offset)
		
		while block < nblocks:
			pgblock = block+block_offset
			self.program_block(data[pgblock*self.NAND_BLOCK_SZ_PLUS_RAS:(pgblock+1)*self.NAND_BLOCK_SZ_PLUS_RAS], pgblock, verify)
			print "\r%d KB / %d KB"%(((block+1)*self.NAND_BLOCK_SZ_PLUS_RAS)/1024, (nblocks*self.NAND_BLOCK_SZ_PLUS_RAS)/1024),
			sys.stdout.flush()

			block += 1

		print
		
def ps3_validate_block(block_data, page_plus_ras_sz, page_sz, blocknr):
	spare1 = block_data[page_sz:page_plus_ras_sz]
	spare2 = block_data[page_plus_ras_sz+page_sz:page_plus_ras_sz*2]
	
	if blocknr == 0x1FF:
		return 1

	if ord(spare1[0]) != 0xFF or ord(spare2[0]) != 0xFF:
		return 0
		
	return 1


if __name__ == "__main__":
	VERSION_MAJOR = 0
	VERSION_MINOR = 65

	print "NANDway v%d.%02d - Teensy++ 2.0 NAND Flasher for PS3/Xbox/Wii"%(VERSION_MAJOR, VERSION_MINOR)
	print "(Original NORway.py by judges <judges@eEcho.com>)"
	print "(Original noralizer.py by Hector Martin \"marcan\" <hector@marcansoft.com>)"
	print

	if len(sys.argv) == 1:
		print "Usage:"
		print "NANDway.py Serial-Port 0/1 Command"
		print
		print "  Serial-Port  Name of serial port to open (eg. COM1, COM2, /dev/ttyACM0, etc)"
		print "  0/1  NAND id number: 0-NAND0, 1-NAND1"
		print "  Commands:"
		print "  *  info"
		print "     Displays information about NAND"
		print "  *  dump Filename [Offset] [Length]"
		print "     Dumps to Filename at [Offset] and [Length] "
		print "  *  vwrite/write Filename [Offset] [Length]"
		print "     Flashes (v=verify) Filename at [Offset] and [Length]"
		print "  *  vdiffwrite/diffwrite Filename Diff-file"
		print "     Flashes (v=verify) Filename using a Diff-file"
		print "  *  ps3badblocks Filename"
		print "     Identifies bad blocks in Filename (raw dump)"
		print "  *  bootloader"
		print "     Enters Teensy's bootloader mode (for Teensy reprogramming)"
		print
		print "     Notes: 1) All offsets and lengths are in hex (number of blocks)"
		print "            2) The Diff-file is a file which lists all the changed"
		print "               offsets of a dump file. This will increase flashing"
		print "               time dramatically."
		print
		print "Examples:"
		print "  NANDway.py COM1 0 info"
		print "  NANDway.py COM1 0 dump d:\myflash.bin"
		print "  NANDway.py COM1 1 dump d:\myflash.bin 3d a0"
		print "  NANDway.py COM1 0 write d:\myflash.bin"
		print "  NANDway.py COM3 1 write d:\myflash.bin 20 1c"
		print "  NANDway.py COM3 0 vwrite d:\myflash.bin"
		print "  NANDway.py COM3 1 vwrite d:\myflash.bin 8d 20"
		print "  NANDway.py COM4 0 diffwrite d:\myflash.bin d:\myflash_diff.txt"
		print "  NANDway.py COM3 1 vdiffwrite d:\myflash.bin d:\myflash_diff.txt"
		print "  NANDway.py COM1 0 bootloader"
		print "  NANDway.py ps3badblocks d:\myflash.bin"
		sys.exit(0)

	if (len(sys.argv) == 3) and (sys.argv[1] == "ps3badblocks"):
		tStart = time.time()

		data = open(sys.argv[2],"rb").read()

		datasize = len(data)
		page_sz = 2048
		page_plus_ras_sz = 2112
		nblocks = 1024
		pages_per_block = 64
		block = 0
		block_plus_ras_sz=page_plus_ras_sz*pages_per_block
		block_offset=0
		
		tStart = time.time()
		
		while block < nblocks:
			pgblock = block+block_offset

			block_data=data[pgblock*(block_plus_ras_sz):(pgblock+1)*(block_plus_ras_sz)]
			block_valid = ps3_validate_block(block_data, page_plus_ras_sz, page_sz, block)
			if block_valid == 0:
				print
				print "Invalid block: %d (0x%X)"%(pgblock, pgblock)
				print
				
			print "\r%d KB / %d KB"%(((block+1)*(block_plus_ras_sz))/1024, (nblocks*(block_plus_ras_sz))/1024),
			sys.stdout.flush()

			block += 1

		print
		print "Done. [%s]"%(datetime.timedelta(seconds=time.time() - tStart))
		sys.exit(0)
		
	n = NANDFlasher(sys.argv[1], int(sys.argv[2], 10), VERSION_MAJOR, VERSION_MINOR)
	print "Pinging Teensy..."
	freeram = n.ping()
	print "Available memory: %d bytes"%(freeram)
	print
	
	tStart = time.time()
	if len(sys.argv) in (5,6,7) and sys.argv[3] == "dump":
		n.printstate()
		print
		print "Dumping...",
		sys.stdout.flush()
		print
		
		block_offset=0
		nblocks=0

		if len(sys.argv) == 6:
			block_offset=int(sys.argv[5],16)
		elif len(sys.argv) == 7:
			block_offset=int(sys.argv[5],16)
			nblocks=int(sys.argv[6],16)

		n.dump(sys.argv[4], block_offset, nblocks)
		
		print
		print "Done. [%s]"%(datetime.timedelta(seconds=time.time() - tStart))
			
	if len(sys.argv) == 4 and sys.argv[3] == "info":
		n.printstate()
		print
			
	elif len(sys.argv) in (5,6,7) and (sys.argv[3] == "write" or sys.argv[3] == "vwrite"):
		n.printstate()
		print
		
		print "Writing...",
		sys.stdout.flush()

		print
		
		data = open(sys.argv[4],"rb").read()

		block_offset=0
		nblocks=0
		verify=0

		if (sys.argv[3] == "vwrite"):
			verify=1
		
		if len(sys.argv) == 6:
			block_offset=int(sys.argv[5],16)
		elif len(sys.argv) == 7:
			block_offset=int(sys.argv[5],16)
			nblocks=int(sys.argv[6],16)

		n.program(data, verify, block_offset, nblocks)
			
		print
		print "Done. [%s]"%(datetime.timedelta(seconds=time.time() - tStart))
		
	elif len(sys.argv) == 6 and (sys.argv[3] == "diffwrite" or sys.argv[3] == "vdiffwrite"):
		n.printstate()
		print
		print "Writing using diff file ..."
		sys.stdout.flush()
		print
		
		data = open(sys.argv[4],"rb").read()
		diff_data = open(sys.argv[5],"rb").readlines()

		block_offset=0
		nblocks=0
		verify=0
		nlines=len(diff_data)
		cur_line=0

		if (sys.argv[3] == "vdiffwrite"):
			verify=1
		
		for line in diff_data:
			addr=int(line[2:], 16)
			if addr % n.NAND_BLOCK_SZ_PLUS_RAS:
				print "Error: incorrect address for block addr=%x. addresses must be on a per-block boundary"%(addr)
				sys.exit(0)

			block_offset=addr/n.NAND_BLOCK_SZ_PLUS_RAS
			print "Programming offset %x block %x (%d/%d)"%(addr, block_offset, cur_line+1, nlines)
			n.program(data, verify, block_offset, 1)
			cur_line += 1
			
		print
		print "Done. [%s]"%(datetime.timedelta(seconds=time.time() - tStart))
		
	elif len(sys.argv) == 4 and sys.argv[3] == "bootloader":
		print
		print "Entering Teensy's bootloader mode... Goodbye!"
		n.bootloader()
		sys.exit(0)

	n.ping()