#! /usr/bin/perl #------------------------------------------------------------------------------- # %M% %W% %G% # # Interface routines for AYUCR bootload.asm #------------------------------------------------------------------------------- use Device::SerialPort 0.12; #------------------------------------------------------------------------------- # Turn off I/O buffering so comm routines flush each char as it is queued up $| = 1; #------------------------------------------------------------------------------- # Processor memory layout my $PAGESIZE = 64; my $NULLVAL = "FF"; my $MINUSER = 0x01; my $MAXUSER = 0x37; my $MINBOOT = 0x38; my $MAXBOOT = 0x3F; my $MINADDR = 0x00; my $MAXADDR = 0x3F; # Communications settings my $PORT = "/dev/ttyS1"; my $BAUD = 2400; my $DATA = 8; my $STOP = 1; my $PARI = "none"; my $TOMS = 1000; #------------------------------------------------------------------------------- my ($filename) = @ARGV; if (!defined $filename) { print "usage: upfirm.pl \n"; exit; } # Read, parse and display image to load read_hex ($filename); dump_pages (\@page_list); print "\n"; # Init print "Initializing Communications...\n"; init_comm (); # Read existing firmware print "Reading Firmware"; read_firmware (); dump_pages (\@chip_list); write_hex ("previous.hex", \@chip_list); # Compare protected regions to ensure they are compatible $errors = compare_pages (\@page_list, \@chip_list, 0, 0) + compare_pages (\@page_list, \@chip_list, $MINBOOT, $MAXBOOT); if ($errors) { print "\nWARNING!\n", "The reserved bootloader portions of the new and existing program images\n", "are different. Attempting to load this image using the bootloader may\n", "result in damage to the firmware and may require reflashing the chip\n"; ; } print "Writing Firmware."; write_firmware (\@page_list); print "Verifying Firmware"; read_firmware (); $errors = compare_pages (\@page_list, \@chip_list, $MINADDR, $MAXADDR); if ($errors) { print "\nWARNING!\n", "Error verifying firmware. Do not power down controller. Attempt to\n", "reflash now.\n" } else { print "Update successful.\n"; } # close serial port undef $port; #------------------------------------------------------------------------------- # Read and parse hex file sub read_hex { my $filename = shift; open (HEX, $filename) || die "Could not open $filename\n"; while () { my ($count, $address, $type, $data, $checksum) = m/^:(\S\S)(\S\S\S\S)(\S\S)(\S*)(\S\S)/; # Convert data fields from hex $count = hex ($count); my $page = int ((hex ($address) + 1) / $PAGESIZE); my $offset = hex ($address) % $PAGESIZE; # Confirm checksum of data $sum = $count + hex (substr ($address, 0, 2)) + hex (substr ($address, 2, 2)) + hex ($type) + hex_to_sum ($data); $sum = sprintf ("%02X", (~$sum + 1) & 0xff); die "Record at address ($address) has bad checksum ($checksum) I get $sum\n" if ($sum ne $checksum); # Add data records to page list if ($type eq "00") { if (!defined $page_list[$page]) { $page_list[$page] = " " x $PAGESIZE; } substr ($page_list[$page], $offset * 2, $count * 2) = $data; } # printf ("$type %2d $address(%04X %2d) $data $checksum $sum\n", $count, $page, $offset); } close (HEX); } #------------------------------------------------------------------------------- # Write hex file sub write_hex { my $filename = shift; my $pages = shift; open (HEX, ">$filename") || die "Could not open $filename\n"; # Loop over all pages for ($MINADDR .. $MAXADDR) { if (defined $$pages[$_]) { # break each page into blocks of 16 bytes for $block (0..3) { my $offset = $block * $PAGESIZE/2; my $addr = $_ * $PAGESIZE + $offset/2; my $page = substr ($$pages[$_], $offset, $PAGESIZE/2); # Loop over block until everything has been dumped for (;;) { ($pre, $data, $rest) = ($page =~ m/(\s*)(\S+)(.*)/); last if $data eq ""; # Update address to account for skipped bytes $addr += length ($pre)/2; $count = length ($data)/2; $sum = $count + int ($addr / 0xFF) + ($addr & 0xFF) + hex_to_sum ($data); $sum = (~$sum + 1) & 0xFF; printf HEX ":%02X%04X%02X%s%02X\r\n", $count, $addr, 0x00, $data, $sum; $addr += length ($data)/2; $page = $rest; } } } } print HEX ":00000001FF\r\n"; close (HEX); } #------------------------------------------------------------------------------- # sub dump_pages { my $pages = shift; for $num (0 .. $#$pages) { if (defined $$pages[$num]) { my $page = $$pages[$num]; printf ("%03X-%02X : |%s|\n", $num, 0, substr ($page, 0, $PAGESIZE)); printf ("%03X-%02X : |%s|\n", $num, $PAGESIZE, substr ($page, $PAGESIZE, $PAGESIZE)); } } } #------------------------------------------------------------------------------- # Check that the data is appropriate to download sub compare_pages { my $new = shift; my $old = shift; my $low = shift; my $high = shift; my $errors = 0; # Check interrupt vector and boot loader for (0x38 .. 0x3F) { $errors++ if ($$new[$_] ne $$old[$_]) } $errors; } #------------------------------------------------------------------------------- # Converts hex string to data bytes. Empty values are set to the NULLVAL sub hex_to_bytes { my ($data) = @_; my $length = length ($data) / 2; my $bytes = ""; for $offset (0 .. $length - 1) { my $byte = substr ($data, $offset * 2, 2); $byte = $byte eq " " ? $NULLVAL : $byte; $bytes .= chr (hex ($byte)); } return $bytes; } #------------------------------------------------------------------------------- # Converts data bytes to a hex string. Empty values are set to the NULLVAL sub bytes_to_hex { my ($data) = @_; my $length = length ($data); my $string = ""; for $offset (0 .. $length - 1) { $string .= sprintf ("%02X", ord (substr ($data, $offset, 1))); } return $string; } #------------------------------------------------------------------------------- # sum the hex bytes sub hex_to_sum { my ($data) = @_; my $sum = 0; for (0 .. length ($data) / 2 - 1) { $byte = substr ($data, $_ * 2, 2); $sum += hex ($byte); } return $sum; } #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- sub init_comm { $port = Device::SerialPort->new ($PORT) || die "Could not create port object\n"; $port->baudrate ($BAUD); $port->databits ($DATA); $port->stopbits ($STOP); $port->read_const_time ($TOMS); # Look for controller $port->write ("/M"); get_line (); $line = get_line (); die "Could not find controller [$line]\n" if ($line ne "APC"); die "NO OK\n" if !get_ok (); print "Hardware : $line\n"; # Get Version $port->write ("/V"); get_line (); print "Version : ", get_line (), "\n"; die "NO OK\n" if !get_ok (); # Start bootloader and look for prompt for (1 .. 10) { $port->write ("/B"); return if $port->read (1) eq "K"; } undef $port; die "Could not find boot loader on $PORT\n"; } #------------------------------------------------------------------------------- # get line sub get_line { my $line = ""; while (1) { $ch = $port->read (1); last if ($ch eq ""); # Check for end of line if ($ch eq chr (13)) { $port->read (1); last; } $line .= $ch; } return $line; } #------------------------------------------------------------------------------- # get OK sub get_ok { my $ok = $port->read (2); return $ok eq "OK" } #------------------------------------------------------------------------------- # Retun address as a LSB + MSB 16 bit word sub get_address { my $page_num = shift; my $addr = $page_num * $PAGESIZE / 2; my $low_addr = $addr & 0xFF; my $high_addr = int ($addr / 0xFF); chr ($low_addr) . chr ($high_addr); } #------------------------------------------------------------------------------- # Get the checksum sub calc_checksum { my $checksum = 0; foreach (@_) { foreach $c (split (//)) { $checksum += ord ($c); } } chr ($checksum & 0xFF); } #------------------------------------------------------------------------------- # Generate a command string including a command char, address and checksum sub get_command { my $command = shift; my $page = shift; my $address = get_address ($page); my $checksum = calc_checksum ($address); $command . $address . $checksum; } #------------------------------------------------------------------------------- sub read_page { my ($page) = @_; $port->write (get_command ("R", $page)); $port->read (64); } #------------------------------------------------------------------------------- sub erase_page { my ($page) = @_; $port->write (get_command ("E", $page)); } #------------------------------------------------------------------------------- sub wait_k { my $data = ""; my $timeout = 5; while (1) { $data .= $prompt = $port->read (1); last if $prompt eq "K"; # Check timeout last if ($prompt eq "" && $timeout-- == 0) } $data; } #------------------------------------------------------------------------------- # Write a single page to the chip sub write_page { my ($page_num, $page) = @_; my $len = length ($page); # Data checks if (! defined $page) { erase_page ($page_num); return; } if ($len != $PAGESIZE * 2) { print "\nInvalid data page size ($len) for page $page_num\n"; return; } $address = get_address ($page_num); $data = hex_to_bytes ($page); $checksum = calc_checksum ($address, $data); $command = "W" . $address . $data . $checksum; $port->write ($command); } #------------------------------------------------------------------------------- sub write_firmware { my $pages = shift; # Write all non protected pages for ($MINUSER .. $MAXUSER) { $ret = write_page ($_, $$pages[$_]); $prompt = wait_k (); if ($prompt ne "K") { printf ("Error [$prompt] writing page %2X:%3X\n", $_, $_ * $PAGESIZE / 2); return; } print "."; } print "\n"; } #------------------------------------------------------------------------------- sub read_firmware { # Read all pages and create a list for ($MINADDR .. $MAXADDR) { my $data = read_page ($_); # Check for an error if ($data eq "CK") { print "\nChecksum error issuing command $_\n"; redo; } $checksum = $port->read (1); $page = bytes_to_hex ($data); wait_k (); # Check checksum if ($checksum != calc_checksum ($data)) { print "\nChecksum error reading page $_\n"; redo; } print "."; # Remove NULL commands for (my $offset = 0; $offset < length ($page); $offset += 4) { if (substr ($page, $offset, 4) eq "FF3F") { substr ($page, $offset, 4) = " "; } } $chip_list[$_] = $page if ($page !~ /^\s*$/); } print "\n"; }