#!/bin/sh
# the next line restarts using tclsh \
  exec tclsh "$0" "$@"

#  hcdl - HC11 Bootstrap Downloader Revision 0.2
#    for A1, A8 (and probably E2) family members.
#    written entirely in TCL.
#  
#  Copyright 1999, 2000, Loren Wyard-Scott (wyard@ee.ualberta.ca)
#  The author hereby grants permission to use this code in 
#  whole or in part as long as credit is given.
#
#  Usage:
#  ------
#  hcdl file [-baud baudrate] [-port serialport] [-mode dispmode]
#
#     where
#       file      is the name of the .s19 file (including the
#                 extension) to download starting at address 0x0000.
#       baudrate  is an integer value specifying the bootstrap
#                 download baud rate.  This is a XTAL-dependent
#                 value and should be set to the crystal frequency
#                 divided by 1024 for the maximum transfer rate.
#                 This is an optional argument that defaults to
#                 the value specified below.
#       serialport  is the name of the serial port that will be
#                 used.  eg)  /dev/ttyS1
#                 This is an optional argument that defaults to
#                 the value specified below.
#       dispmode  is one of the following:
#                 colour - uses ANSI escape sequences for a pretty
#                          display.
#                 quiet -  displays nothing except error messages.
#                          Useful, perhaps, when this script is
#                          called by another program.
#                 line  -  displays information, but not using
#                          a colorful display.
#                 Again, this is an optional argument that defaults to
#                 the value specified below.
#
#
#  Requires TCL version 8.1 or greater
#    available from ftp.scriptics.com 
#    (version 8.0 has faulty serial-port routines).
#
#  Installation instructions:
#  --------------------------
#  1.  Copy this file to /usr/local/bin or wherever you want
#      it to reside.
#  2.  Set permissions to allow execution by whomever you 
#      feel comfortable giving access to serial ports.
#  3.  Whoever uses this program will also need to access
#      the serial port in both read and write capacities.
#      Set permissions accordingly.
#  4.  Adjust the path to tclsh by modifying the first line
#      of this file.
#  5.  Edit the default variable values to match your
#      system (HC11 board and PC).
#
#
#  Lessons learned from writing this program:
#  ------------------------------------------
#  1.  TCL version 8.0 does not correctly support serial
#      port mode control using fconfigure.  This appears
#      to be fixed in version 8.1.1
#  2.  When sending binary data, use the binary command
#      to construct the string (or character).  This
#      seems to prevent confusion for the TCL interpreter
#      when dealing with binary codes for special characters.
#  3.  When receiving binary data, use the binary command
#      to convert the data to some other representation (perhaps
#      hex), again to avoid special character substitution.
#  4.  To transfer binary data through the serial port and to
#      avoid bizarre translation, use the -translation binary
#      option in fconfigure.
#
#  Future Directions:
#  ------------------
#   1.  Add support for other HC11 family members:  notably the E9.
#
# Revision History
# ----------------
# Revision 0.2 -- Wed May  3 10:38:32 MDT 2000
#   - Changed serial port to non-blocking operating mode.  
#   - Added polling loop which aborts after so many unsuccessfull reads of
#     the serial port.  Made somewhat CPU-independent by using the "after"
#     command.
#   - Added lockfile serial port access semaphore.  In order to avoid
#     coercing execution permissions to allow file creation in /var/lock,
#     the lock file is placed in the user's home directory:  ~/LCK..ttySx
#     (the same naming format that minicom uses).
# 
######################################################################
# The following variables may need to be changed depending upon
# your system configuration.

# If no serial port is specified on the command-line, this
# one is used. 
set serialport /dev/ttyS1

# If no baudrate is specified on the command-line, this one is used.
set baudrate   4800;		        # 4800 <-> 4.9152MHz XTAL
                                        # 7800 <-> 8.0000MHz XTAL

# If no display mode is specified, this one is used.
set dispmode   colour

######################################################################
set Revision 0.2

# Establish the maximum time that the non-blocking serial port
# should be polled prior to considering the line dead (i.e. no response).
# Variable MAXPOLLCOUNT is the number of times that the line should be
# read (and nothing received), delay for 1 ms, and then re-read.
set MAXPOLLCOUNT 100;			

if {$tcl_version < 8.1} {
    puts stdout "This is TCL version $tcl_version.  You need at least 8.1."
    puts stderr "This is TCL version $tcl_version.  You need at least 8.1."
    exit 1
}


# Parse the command-line arguments.
if {$argc < 1} {
    puts stdout "HC11 Bootstrap Downloader"
    puts stdout "Usage:"
    puts stdout "hcdl file \[-baud baudrate\] \[-port portspec\] \[-mode dispmode\]"
    puts stdout "where"
    puts stdout "file      is the name of the .s19 file (including the"
    puts stdout "          extension) to download starting at address 0x0000."
    puts stdout "baudrate  is an integer value specifying the bootstrap"
    puts stdout "          download baud rate.  This is a XTAL-dependent"
    puts stdout "          value and should be set to the crystal frequency"
    puts stdout "          divided by 1024 for the maximum transfer rate."
    puts stdout "          This is an optional argument that defaults to"
    puts stdout "          the value $baudrate."
    puts stdout "serialport  is the name of the serial port that will be"
    puts stdout "            used.  This is an optional argument that"
    puts stdout "            defaults to $serialport."
    puts stdout "dispmode  is one of the following:"
    puts stdout "          colour - uses ANSI escape sequences for a pretty"
    puts stdout "                   display."
    puts stdout "           quiet -  displays nothing except error messages."
    puts stdout "                    Useful, perhaps, when this script is"
    puts stdout "                    called by another program."
    puts stdout "           line  -  displays information, but not using"
    puts stdout "                    a colorful display."

    puts stderr "hcdl called with incorrect syntax."
    exit 1
}

# The name of the file should be the first argument on the command
# line.
set filename [lindex $argv 0]
if [catch "open $filename RDONLY" fileID] {
    puts stdout $fileID
    puts stderr $fileID
    exit 1
}

# Now parse the optional arguments in pairs.
set listlength [llength $argv]
for {set argument 1} {[expr $argument+1] < $listlength} {incr argument 2} {
    set option [lindex $argv $argument]
    set value [lindex $argv [expr $argument+1]]

    if {[string match $option -baud]} {
	set baudrate $value
    } elseif {[string match $option -port]} {
	set serialport $value
    } elseif {[string match $option -mode]} {
	set dispmode $value
    } else {
	puts stdout "Unknown option/value:  $option $value"
	puts stderr "hcdl called with incorrect options."
	exit 1
    }
}

# Done parsing the command-line arguments.

# Reserve the serial port using a lockout file "LCK..ttySx" in the user's home directory.
# Create the lock file name.
set lockname "$env(HOME)/LCK..[lindex [split $serialport /] end]"
if [catch "exec lockfile  -r 1 $lockname" result] {
    puts stderr "Could not create lock file $lockname."
    puts stderr " $result"
    puts stderr "Is there another program currently using $serialport?"
    puts stderr "If not, please type:  rm -f $lockname"
    exit 1
}

# Open the serial port.
if [catch "open $serialport RDWR" serID] {
    puts stdout "Problems opening serial port:  $serialport"
    puts stderr "hcdl could not open serial port."
    ExitProgram 1
}
# Set the baudrate and the asynchronous format.
if [catch "fconfigure $serID -mode $baudrate,n,8,1 -translation binary -blocking 0"] {
    puts stdout "Problem attempting to set serial transfer rate of $baudrate."
    puts stderr "hcdl could not establish serial transfer rate."
    ExitProgram 1
}

if [string match $dispmode colour] {
    puts stdout "\x1b\[?25l\x1b\[2J\x1b\[2;25H\x1b\[41m-----Bootstrap Downloader------"
    puts stdout "\x1b\[3;25H HC11 A1/A8/E2    Revision $Revision "
    puts stdout "\x1b\[4;25H Copyright 2000 L. Wyard-Scott "
    puts stdout "\x1b\[5;25H-------------------------------\x1b\[m"
    puts stdout "\x1b\[6;25HFilename:  $filename"

    puts stdout  "\x1b\[17;25H\x1b\[1mStatus:  \x1b\[m"    
    puts stdout "\x1b\[19;25H\x1b\[1mByte     of 255:\x1b\[m"
    puts stdout "\x1b\[20;25H\x1b\[1mReturned byte:  \x1b\[m"

} elseif [string match $dispmode line] {
    puts stdout "-----Bootstrap Downloader------"
    puts stdout " HC11 A1/A8/E2    Revision $Revision "
    puts stdout " Copyright 2000 L. Wyard-Scott "
    puts stdout "-------------------------------"
    puts stdout "Filename:  $filename"
}

# Show the connection speed.
if [string match $dispmode colour] {   
    puts stdout "\x1b\[7;25H\x1b\[mSerial Mode: [fconfigure $serID -mode]"
} elseif [string match $dispmode line] {   
    puts stdout "Serial Mode: [fconfigure $serID -mode]"
}

# Create a global array that will contain the
# data read in from the file.
set sdata(0) ""

######################################################################
proc readfile {fileID} {
    # Read in an .s19 file into global array called sdata.
    
    global sdata

    # Grab the S record type.
    set rectype [read $fileID 2]
    if ![string match S9 $rectype] {
	# puts stdout "Processing record:  $rectype"
	
	set numdata [expr 0x[read $fileID 2]]
	set numdata [expr $numdata -3]
	# puts stdout "Number of pieces of data:  $numdata"
	
	set addr [expr 0x[read $fileID 4]]
	# puts stdout "Starting address:  $addr"

	for {set i 0} {$i<$numdata} {incr i} {
	    # puts stdout "$i-" nonewline
	    set sdata([expr $addr + $i]) [read $fileID 2]
	}
	gets $fileID
	readfile $fileID
    }
}

######################################################################
proc filldata {length} {
    # Ensures that all array elements from 0 to length of global
    # array sdata are initialized to FF.
    
    global sdata

    for {set i 0} {$i<$length} {incr i} {
	set sdata($i) FF
    }
}

######################################################################
proc printdata {length} {
    
    global sdata
    
    set numblock [expr $length/32]
    for {set block 0} {$block < $numblock} {incr block} {
	puts stdout "$block: " nonewline
	for {set i 0} {$i < 32} {incr i} {
	    puts stdout $sdata([expr $i + 32 * $block]) nonewline
	}
	puts stdout "";			# Append new line.
    }
}

######################################################################
proc senddata {length serID} {
    # Actual routine to send info to HC11 bootstrap program.
    # sends length bytes through serial port identified by serID.
    # Data is contained in global sdata.

    global sdata
    global dispmode
    global MAXPOLLCOUNT

    # First send FF to synchronize with the HC11.
    if [string match $dispmode colour] {
	puts stdout "\x1b\[17;34H Sending Synchronization Byte"
    } elseif [string match $dispmode line] {
	puts stdout "Sending Synchronization Byte"
    }


    sendbyte FF $serID
    # Nothing is transmitted by the HC11.  This is used to set
    # the baudrate.  

    if [string match $dispmode colour] {    
	puts stdout "\x1b\[17;34H Transmitting File Data      "
    } elseif [string match $dispmode line] {
	puts stdout "Transmitting File Data      "
    }

    for {set i 0} {$i<$length} {incr i} {
	set byte $sdata($i)

	if [string match $dispmode colour] {    
	    puts stdout  "\x1b\[19;30H$i"
	    puts stdout "\x1b\[19;43H$byte"
	} elseif [string match $dispmode line] {
	    puts stdout "." nonewline
	    flush stdout
	}

	# Actually send the byte out.
	sendbyte $byte $serID

	# Set a timer to abort if nothing is received (rather than block).

	# Get the handshaking byte.
	set handshake [receivebyte $serID $MAXPOLLCOUNT]

	if [string match NoChar $handshake] {
	    if [string match $dispmode colour] {    	
		puts stdout "\x1b\[17;34H Handshaking failure.     "
		puts stdout "\x1b\[24;1H\x1b\[m"
	    } 
	    puts stderr "Error:  Handshaking byte not received."
	    puts stderr "        Is the HC11 in bootstrap download mode?"
	    ExitProgram 1
	}

	# Both the byte and handshake variables are capital hex 
	# and can now be compared.
	if ![string match $byte $handshake] {
	    if [string match $dispmode colour] {    
		puts stdout "\x1b\[17;34H Handshaking error!            "
		puts stdout "\x1b\[18;34H Sent:  $byte  Received:  $handshake"
		puts stdout "\x1b\[24;1H\x1b\[m"
	    } else {
		puts stdout "Handshaking error!            "
		puts stdout "Sent:  $byte  Received:  $handshake"
	    }
	    puts stdout "hcdl encountered handshaking error."
	    ExitProgram 1
	}

	if [string match $dispmode colour] {    	
	    puts stdout "\x1b\[20;43H$handshake"
	}
    }

    if [string match $dispmode colour] {    	
	puts stdout "\x1b\[17;34H Successfully Completed.     "
    } elseif [string match $dispmode line] {
	puts stdout " Successfully Completed.       "
    }
}

######################################################################
proc sendbyte {hexvalue serID} {
    # Sends a byte specfied in hex out to the serial port.
    
    set data [binary format H2 $hexvalue]

    # The NULL needs to be handled just a little differently.
    if [string match 00 $hexvalue] {
	puts $serID \x00 nonewline
    } else {
	puts $serID $data nonewline
    }
    flush $serID
    
#    puts stdout "Sent: $hexvalue  " nonewline
}


######################################################################
proc receivebyte {serID PollCount} {
    # Receives a byte from the specified serial port and returns 
    # it in 2-digit hex form.  The incoming information is polled.
    # If the serial port is read PollCount times, then the 
    # routine returns the string "NoChar" rather than the byte received.


    set PollNumber 0
    
    while {$PollNumber < $PollCount} {
	binary scan [read $serID 1] H2 value
	# Make the Hex letters capital for comparison.
	if [info exists value] {
	    set value [string toupper $value]
	    return $value
	} else {
	    after 1;			# Delay 1 millisecond.
	    incr PollNumber
	}
    }
    return "NoChar"
}

######################################################################
proc comparebyte {arg1 arg2 errormsg} {
    # Procedure to compare the 2-digit (hex) arguments.  If
    # they are not equal, the error message is displayed and
    # the application aborted.
    
    global dispmode
    
    if ![string match  $arg1 $arg2] {
	if [string match $dispmode colour] {    
	    puts stdout "\x1b\[17;34H $errormsg"
	    puts stdout "\x1b\[18;34H Comparison:  $arg1, $arg2"
	    puts stdout "\x1b\[24;1H\x1b\[m"
	} else {
	    puts stdout $errormsg
	    puts stdout "Comparison:  $arg1, $arg2"
	}
	ExitProgram 1
    }
}

######################################################################
proc ExitProgram {exitvalue} {
    # Procedure to intercept termination of the program.  Removes the
    # lock file and closes the files.

    global serID
    global fileID
    global lockname

    # Close the files.
    close $serID
    close $fileID
    
    # Remove the lockfile.
    exec rm -f $lockname
    
    exit $exitvalue
}

######################################################################
# Fill the entire array with value 'FF'.
filldata 256

# Read the information from the .s19 file.
readfile $fileID

# Display the contents of the array if in an appropriate mode.
if { [string match $dispmode colour] | [string match $dispmode line] } {
    # Position the cursor if in colour mode.
    if [string match $dispmode colour] {   
	puts stdout "\x1b\[8;1H"
    }

    # Display the contents.
    printdata 256
}

senddata 256 $serID

if [string match $dispmode colour] {   
    puts stdout "\x1b\[24;1H\x1b\[m"
}

ExitProgram 0

