diff --git a/.gitattributes b/.gitattributes
index 234192dbe9eb8afce76299392036747cbdae2adb..78c73e7851e79f41baf484dbebb76f18fb8ad3aa 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -185,6 +185,9 @@ CEP/BB/SourceDB/bootstrap -text
 Firmware/tools/bootstrap -text
 Firmware/tools/src/flash_images.sh -text
 Firmware/tools/src/restart_images.sh -text
+Firmware/tools/src/rsuctl3 -text
+Firmware/tools/src/rsuctl3.pl -text
+Firmware/tools/src/rsuctl3_reset -text
 Firmware/tools/src/view_images.sh -text
 JAVA/CEP/jParmFacade/nbproject/.cvsignore -text
 JAVA/GUI/MAC/jRSP/.cvsignore -text
diff --git a/Firmware/tools/src/Makefile.am b/Firmware/tools/src/Makefile.am
index eb1bdc9f50d7b1d9f9c75a8755c9095c33c88063..c3541d178b0fff637713c5bae56a7de33da4c3a1 100644
--- a/Firmware/tools/src/Makefile.am
+++ b/Firmware/tools/src/Makefile.am
@@ -1,7 +1,11 @@
 sbin_SCRIPTS = \
 	flash_images.sh \
 	restart_images.sh \
-	view_images.sh
+	view_images.sh \
+	rsuctl\
+	rsuctl3 \
+	rsuctl3_reset \
+	README.rsuctl
 
 dist_sbin_SCRIPTS = $(sbin_SCRIPTS)
 
diff --git a/Firmware/tools/src/README.rsuctl b/Firmware/tools/src/README.rsuctl
new file mode 100644
index 0000000000000000000000000000000000000000..b53dfd06aa17c952342bf225bf70fd42b8b7551d
--- /dev/null
+++ b/Firmware/tools/src/README.rsuctl
@@ -0,0 +1,293 @@
+
+rsuctl manual
+-------------
+
+This directory contains the rsuctl utility used to update firmware on the RSP boards.
+The help information 
+
+Usage
+-----
+
+# ./rsuctl -h
+
+rsuctl (Revision: 1.15 ) # Remote System Update - program firmware in FPGA flash
+
+Usage: rsuctl [options] command
+
+  options
+    [-i ifname]      # Ethernet interface name on which to communicate (default is eth1)
+    [-m mac-address] # IP-address of the target board
+                     # (default is broadcast address ff:ff:ff:ff:ff:ff)
+
+  command (specify at least one of the following)
+    [-l] # List the images currently stored in all pages of the flash
+
+    [-b -f in.ttf -o out.bin]        # Convert a ttf file to a binary file
+    [-e -p page [-F]]                # Erase flash page, 0 <= page < 16 (-F forces erase of page 0)
+    [-x -p page                      # Start (load & reset) new firmware from the specified page
+    [-w -p page -f image[.ttf] [-F]] # Write img.bin into specified page (-F forces write to page 0)
+    [-v -p page -f image[.ttf]]      # Compare flash page with img.bin
+    [-d -p page -f dumpfile.bin]     # Dump flash page to dmp.bin
+
+    [-r] # Reset all FPGA's (load factory image)
+    [-c] # Clear all FPGA's (restart current image)
+    [-s] # Send SYNC pulse to all FPGA's
+
+    [-P] # Ping the board (find board when using broadcast address)
+    [-V] # Read RSU status and RSR versions
+    [-h] # This help text
+
+
+Introduction
+-------------
+
+The flash is divided into 16 pages. Page 0 will be write protected in
+the final system and contains the factory image for the BP FPGA. Pages
+1 to 7 may contain application images for the BP FPGA.
+
+Page 8 contains the factory image for the AP FPGA's. Pages 9 to 15
+may contain application images for the AP FPGA's.
+
+Listing the contents of the flash
+---------------------------------
+
+To list the images that are available in the flash use the -l argument:
+
+# ./rsuctl -l
+=== connected to RSP board(s) on eth1:FF:FF:FF:FF:FF:FF
+
+BP Factory  0:
+BP Image    1:    8740783 Fri Apr  7 16:02:37 2006 bp_20060407.ttf
+AP Factory  8:
+AP Image    9:   13251099 Fri Apr  7 16:01:08 2006 ap_20060407.ttf
+AP Image   15:      32366 Thu Mar 16 16:21:53 2006 lofar.jpg
+
+=== OK
+
+When an image is written to a flash page using the rsuctl utility,
+information about the file is written to the last block of the
+destination page. This acts as a directory to the flash.  This
+directory is read by the "./rsuctl -l" command. The directory block
+for the BP Factory image is empty because that image was not written
+using rsuctl and therefor the directory entry for it is missing.
+
+Writing an image to a flash page
+--------------------------------
+
+To write a new image to the flash you can use the -w argument, specify
+the destination page (-p) and the file to write.
+
+# ./rsuctl -w -p1 -f bp.ttf
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== Reading 'bp.ttf'
+=== Converting 'bp.ttf' to binary
+=== |--------------------------------|
+===  ................................
+=== erasing page 01
+=== |--------------------------------|
+===  ................................
+=== writing and verifying page 01
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Because the file extension is .ttf, this ttf text file is first
+converted to binary before being written to flash. After this
+conversion, the destination flash page is erase. As the new image is
+written each block is verified after it has been written.
+
+You can write arbitrary data to the flash, only when the file
+extension is ttf is the file converted from ttf to binary format. The
+following command writes the banner jpg from www.lofar.org to page
+15.
+
+# ./rsuctl -w -p15 -f lofar.jpg
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== Reading 'lofar.jpg'
+=== erasing page 15
+=== |--------------------------------|
+===  ................................
+=== writing and verifying page 15
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Verifying an image in the flash
+-------------------------------
+
+To verify that an image in the flash corresponds to a file on disk use
+the -v option.
+
+# ./rsuctl -v -p1 -f bp.ttf
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== Reading 'bp.ttf'
+=== Converting 'bp.ttf' to binary
+=== |--------------------------------|
+===  ................................
+=== verifying page 01
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Supplying the wrong reference file might result in the following
+output. 
+
+# ./rsuctl -v -p1 -f bp41.ttf
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== Reading 'bp41.ttf'
+=== Converting 'bp41.ttf' to binary
+=== |--------------------------------|
+===  ................................
+=== verifying page 01
+=== |--------------------------------|
+===  XXXXXXXXXXXXXXXXXX..............
+=== FAILED
+
+Each '.' represents on sector on the flash (128 blocks). When an error
+is found in any of the blocks within a sector a 'X' is shown. Note
+that the sectors at the end appear to be correct. This is because the
+page is zero padded to the end and the actual image only occupies just
+over half of the flash page.
+
+Dumping a flash page to file
+----------------------------
+The binary data stored in flash can be dumped to file with the -d
+command.
+
+# ./rsuctl -d -p15 my.jpg
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== Dumping to file 'dump.jpg'
+=== dumping page 15
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Note that the complete page (excluding the directory block) is dumped,
+irrespective of the orginal size of the image file. This results in a
+file of 4193280 bytes which is (32*128 - 1 ) * 1024). The file is
+zeropadded from the orginal image size upto the page size (minus the one
+directory block).
+
+Converting a .ttf file to a binary file
+---------------------------------------
+To prevent repeated conversion from .ttf to bin or to compare a binary
+file with the result of a dumped image you can use the -b command.
+
+# ./rsuctl -b -f bp.ttf -o bp.bin
+=== Reading 'bp.ttf'
+=== Converting 'bp.ttf' to binary
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Erasing pages
+-------------
+
+To erase a page of the flash use the -e command. The factory image in 
+page 0 is protected. To force erasing page 0 add the -F flag. Adding
+the -F flag will only disable the software protection in the rsuctl
+program, not the hardware protection on the board. The hardware
+protection of page 0 in the flash can only be disabled by setting the
+right dip switches.
+
+# ./rsuctl -e -p5
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== erasing page 05
+=== |--------------------------------|
+===  ................................
+=== OK
+
+# ./rsuctl -e -p0
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== ERR erasing factory image (page 0) prohibited, use -F to force erase of factory image at ./rsuctl line 778.
+
+# ./rsuctl -e -p0 -F
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== erasing page 00
+=== |--------------------------------|
+===  ................................
+=== OK
+
+Resetting All FPGA's
+--------------------
+
+To return to the factory default images on all FPGA's use the -r
+command.
+
+# ./rsuctl -r
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== waiting 3 seconds after reset
+=== reconfiguring AP's with factory image (page 8)
+=== waiting 1 second after AP reconfig
+=== waiting 3 seconds after clear
+
+RSU Status:
+  rdy  : Configuration done
+  err  : Configuration successfull
+  fpga : BP was reconfigured
+  image: Factory image
+  trig : User reconfig
+
+RSP   version: 2
+BP    version: 4.1
+AP[0] version: 4.1
+AP[1] version: 4.1
+AP[2] version: 4.1
+AP[3] version: 4.1
+=== OK
+
+Clearing FPGA's
+---------------
+
+Clearing FPGA's will help resyncing the internal datapaths if needed.
+
+# ./rsuctl -c
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== waiting 3 seconds after clear
+
+RSU Status:
+  rdy  : Configuration done
+  err  : Configuration successfull
+  fpga : BP was reconfigured
+  image: Factory image
+  trig : User reconfig
+
+RSP   version: 2
+BP    version: 4.1
+AP[0] version: 4.1
+AP[1] version: 4.1
+AP[2] version: 4.1
+AP[3] version: 4.1
+=== OK
+
+Send SYNC to all FPGA's
+-----------------------
+
+# ./rsuctl -s
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+=== OK
+
+Read RSU status and versions
+----------------------------
+
+# ./rsuctl -V
+=== connected to RSP board(s) on eth1:ff:ff:ff:ff:ff:ff
+
+RSU Status:
+  rdy  : Configuration done
+  err  : Configuration successfull
+  fpga : BP was reconfigured
+  image: Factory image
+  trig : User reconfig
+
+RSP   version: 2
+BP    version: 4.1
+AP[0] version: 4.1
+AP[1] version: 4.1
+AP[2] version: 4.1
+AP[3] version: 4.1
+=== OK
+
+
+END OF DOCUMENT
+
diff --git a/Firmware/tools/src/rsuctl b/Firmware/tools/src/rsuctl
new file mode 100755
index 0000000000000000000000000000000000000000..09a3849589aff36954466de15d570f0d780fed13
--- /dev/null
+++ b/Firmware/tools/src/rsuctl
@@ -0,0 +1,1002 @@
+#!/usr/bin/perl
+$VERSION = q$Revision$;
+#
+# rsuctl # Remote System Update - program firmware in FPGA flash
+#
+# Usage: rsuctl [options] command
+#
+#   options
+#     [-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+#     -m mac-address # MAC-address of the target board (Mandatory)
+#
+#   command (specify at least one of the following)
+#     [-l] # List the images currently stored in all pages of the flash
+#
+#     [-b -f in.ttf -o out.bin]        # Convert a ttf file to a binary file
+#     [-e -p page [-F]]                # Erase flash page,  0 <= page < 16 (-F forces erase of page 0)
+#     [-x -p page                      # Start (load & reset) new firmware from the specified page
+#     [-w -p page -f image[.ttf] [-F]] # Write img.bin into specified page (-F forces write to page 0)
+#     [-v -p page -f image[.ttf]]      # Compare flash page with img.bin
+#     [-d -p page -f dumpfile.bin]     # Dump flash page to dmp.bin
+#
+#     [-r] # Reset all FPGA's (load factory image)
+#     [-c] # Clear all FPGA's (restart current image)
+#     [-s] # Send SYNC pulse to all FPGA's
+#
+#     [-q] # Prevent checking for running RSPDriver (prevents pause at start)
+#     [-P] # Ping the board (find board when using broadcast address)
+#     [-V] # Read RSU status and RSR versions
+#     [-h] # This help text
+#
+#
+
+use Net::RawIP;
+use Getopt::Std;
+use File::stat;
+use Switch;
+use POSIX; # for geteuid()
+
+$g_debug  = 0;  # set to 1 to enable debug output
+$g_packet = ''; # global packet string
+$g_error  = 0;  # global error status 
+
+#
+# Definitions
+#
+$PID_RSR      = 0x01;
+$PID_RSU      = 0x02;
+
+$REG_STATUS       = 0x00;
+$REG_STATUS_SIZE  = 200;
+$REG_VERSION      = 0x01;
+$REG_VERSION_SIZE = 2;
+
+$REG_RW       = 0x01;
+$REG_ERASE    = 0x02;
+$REG_RECONFIG = 0x03;
+$REG_CLEAR    = 0x04;
+
+$CMD_ERASE = 0x01;
+
+$CMD_SYNC  = 0x01;
+$CMD_CLEAR = 0x02;
+$CMD_RESET = 0x04;
+
+$MEPHDRSIZE = 16;
+
+$TYPE_READ     = 0x01;
+$TYPE_WRITE    = 0x02;
+$TYPE_READACK  = 0x03;
+$TYPE_WRITEACK = 0x04;
+
+$ADDR_BLP_NONE = 0x00;
+$ADDR_RSP      = 0x01;
+
+$FLASHSIZE         = 64 * 1024 * 1024; # 64MB
+$BLOCKSIZE         = 1024; # block  = 1024 bytes
+$BLOCKS_PER_SECTOR = 128;  # sector = 128 blocks
+$SECTORS_PER_PAGE  = 32;   # page   = 32 sectors
+$BLOCKS_PER_PAGE   = $BLOCKS_PER_SECTOR * $SECTORS_PER_PAGE;
+$PAGES             = 16;
+$PROGRESS_SIZE     = 32;
+
+$RESPONSETIMEOUT = 2;
+$TO = 5;
+
+#
+# pack format for the MEP message
+# 
+# C 0x10 Ethernet Type byte 0
+# C 0xFA Ethernet Type byte 1
+# C TYPE
+# C STATUS
+# S FRAMELENGTH
+# C BLPID
+# C RSP
+# C PID
+# C REGID
+# S OFFSET
+# S PAYLOAD_LENGTH
+# S SEQ_NR
+# S RESERVED
+#
+$MEPSENDFORMAT="CCCCSCCCCSSSS";
+$MEPRECVFORMAT="H28CCSCCCCSSSS";
+
+# ETHLEN = max length of Ethernet payload
+$ETHLEN = 1500;
+
+#
+# Error code
+#
+%STATUS = ( 0 => "=== OK",
+	    1 => "=== ERR unknown MEP message type",
+	    2 => "=== ERR illegal BLP address",
+	    3 => "=== ERR invalid PID",
+	    4 => "=== ERR register does not exist",
+	    5 => "=== ERR offset too large",
+	    6 => "=== ERR message is too large",
+	    7 => "=== ERR message corruption during RSP processing",
+	    8 => "=== ERR message lost during RSP processing"
+	    );
+
+#
+# usage
+#
+sub usage
+{
+    print STDERR <<END;
+
+# Remote System Update - program firmware in FPGA flash
+
+Usage: rsuctl [options] command
+
+  standalone command (doesn't need an RSP MAC address to run)
+    [-b -f in.ttf -o out.bin] # Convert a ttf file to a binary file
+
+  options
+    [-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+    -m mac-address # MAC-address of the target board (Mandatory)
+
+  command (specify at least one of the following, need -m option at least)
+    [-l] # List the images currently stored in all pages of the flash
+
+    [-e -p page [-F]]                # Erase flash page, 0 <= page < 16 (-F forces erase of page 0)
+    [-x -p page                      # Start (load & reset) new firmware from the specified page
+    [-w -p page -f image[.ttf] [-F]] # Write img.bin into specified page (-F forces write to page 0)
+    [-v -p page -f image[.ttf]]      # Compare flash page with img.bin
+    [-d -p page -f dumpfile.bin]     # Dump flash page to dmp.bin
+
+    [-r] # Reset all FPGA's (load factory image)
+    [-c] # Clear all FPGA's (restart current image)
+    [-s] # Send SYNC pulse to all FPGA's
+    
+    [-q] # Prevent checking for running RSPDriver (prevents pause at start)
+    [-P] # Ping the board (find board when using broadcast address)
+    [-V] # Read RSU status and RSR versions
+    [-h] # This help text
+
+END
+
+    exit;
+}
+
+#
+# pack_rsu($type, $regid, $size, $offset)
+#
+# Pack an RSU message
+#
+sub pack_rsu
+{
+    my ($type, $regid, $size, $offset) = @_;
+
+    return pack($MEPSENDFORMAT,
+		0x10, 0xFA, $type, 0x00, $MEPHDRSIZE + $size,
+		$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSU, $regid, $offset, $size, 0, 0);
+}
+
+#
+# pcapcallback, save packet in $g_packet
+#
+# savepacket($handle, $hdr, $packet)
+#
+sub savepacket
+{
+    my ($handle, $hdr, $packet) = @_;
+    $g_packet = $packet;
+}
+
+#
+# ($payload,$error) = readresponse($pcap)
+#
+sub readresponse
+{
+    my ($pcap) = @_;
+    my ($retval) = 0;
+
+    do { # keep going when we receive invalid messages
+	$retval = 0; # assume success
+
+	# wait for message to arrive
+	eval {
+	    use POSIX qw(SIGALRM);
+	    POSIX::sigaction(SIGALRM,
+			     POSIX::SigAction->new(sub { die }))
+		|| die "=== ERR error setting SIGALRM handler: $!\n";
+	    
+	alarm $RESPONSETIMEOUT;
+	    (0 == loop ($pcap, 1, \&savepacket, ''))
+		|| die "=== ERR Failed to receive response";
+	    alarm 0;
+	};
+	if ($@) {
+	    $retval = 1; # fatal (timeout)
+	    return ('', $retval, '');
+	}
+	
+	($ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved) =
+	    unpack($MEPRECVFORMAT, $g_packet);
+	
+	if ($TYPE_WRITEACK != $type && $TYPE_READACK != $type) {
+	    $retval = 2; # ignore invalid message
+	    print STDERR "ignoring message with invalid TYPE\n" if ($g_debug);
+	}
+	if ($PID_RSU != $pid && $PID_RSR != $pid) {
+	    $retval = 2; # ignore invalid message
+	    print STDERR "ignoring message with invalid PID\n" if ($g_debug);
+	}
+	
+	printf STDERR "0x%s 0x%02x 0x%02x %5d 0x%02x 0x%02x 0x%02x 0x%02x %5d %5d %5d %5d\n",
+	$ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved if ($g_debug);
+	
+	if (0 == $retval && 0 != $status) {
+	    $retval = 1; # fatal
+	    print STDERR "=== FATAL protocol error: status=", $STATUS{$status}, "\n";
+	    print STDERR "=== Are you using the correct Ethernet interface? Use -i argument to change, e.g. -i eth1\n";
+	    exit;
+	}
+    } until ($retval != 2);
+ 
+    return ('', $retval, '') if ($retval);
+
+    return (substr($g_packet, 14 + $MEPHDRSIZE), $retval, $ethdr);
+}
+
+#
+# flash($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image)
+#
+sub flash
+{
+    my ($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image) = @_;
+    
+    printf STDERR "=== %s page %02d\n", $msg, $page;
+    print  STDERR "=== |" . ("-" x $SECTORS_PER_PAGE) . "|\n===  ";
+    
+    my ($success) = 1;
+    # leave last block for directory entry
+    my ($block);
+    for ($block = 0; $block < $BLOCKS_PER_PAGE - 1; $block += $blockinc) {
+	
+	if ($writecb) {
+	    $packet  = pack_rsu($TYPE_WRITE, $cmd, $size,
+				($page * $BLOCKS_PER_PAGE) + $block);
+	    $packet .= &$writecb($page, $block, $image);
+	    
+	    $sock->send_eth_frame($packet);
+	    ($payload, $error) = readresponse($pcap);
+
+	    return $error if ($error);
+	}
+	
+	if ($readcb) {
+	    $packet  = pack_rsu($TYPE_READ, $cmd, $size,
+				($page * $BLOCKS_PER_PAGE) + $block);
+	    
+	    $sock->send_eth_frame($packet);
+	    ($payload, $error) = readresponse($pcap);
+	    return $error if ($error);
+
+	    $success = &$readcb($page, $block, $payload, $image) && $success;
+	}
+	
+	if (0 == $block % $BLOCKS_PER_SECTOR) {
+	    if ($success) { print STDERR "."; }
+	    else          { print STDERR "X"; }
+	    $success = 1;
+	}
+    }
+    print STDERR "\n";
+
+    return 0;
+}
+
+#
+# writedir($sock, $pcap, $page, $entry)
+# Write the entry to the last block of page $page
+#
+sub writedir
+{
+    my ($sock, $pcap, $page, $entry) = @_;
+
+    die "=== ERR internal error: size of direntry > $BLOCKSIZE" if (length($entry) > $BLOCKSIZE);
+
+    print STDERR "writing dir @", (($page + 1) * $BLOCKS_PER_PAGE) - 1, "\n" if ($g_debug);
+
+    # add zero padding if needed
+    $entry .= pack("C", 0) x ($BLOCKSIZE - length($entry)) if (length($entry) < $BLOCKSIZE);
+
+    # write last block with dir entry
+    $packet  = pack_rsu($TYPE_WRITE, $REG_RW, $BLOCKSIZE,
+			(($page + 1) * $BLOCKS_PER_PAGE) - 1);
+    $packet .= $entry;
+
+    my $error = 2;
+    $sock->send_eth_frame($packet);
+
+    ($result, $error) = readresponse($pcap);
+
+    return $error;
+}
+
+#
+# readdir($sock, $pcap, $page)
+# Read the directory entry from the specified page
+#
+sub readdirentry
+{
+    my ($sock, $pcap, $page) = @_;
+
+    my ($packet)  = pack_rsu($TYPE_READ, $REG_RW, $BLOCKSIZE,
+			     (($page + 1) * $BLOCKS_PER_PAGE) -1);
+
+    $sock->send_eth_frame($packet);
+
+    my $error = 0;
+    ($entry, $error) = readresponse($pcap);
+    return ('', $error) if $error;
+
+    $entry = '' if (substr($entry,0,1) eq pack("C", 0xff));
+
+    return ($entry, $error);
+}
+
+#
+# erasecb($page, $block, $image)
+#
+sub erasecb
+{
+    my ($page, $block, $image) = @_;
+
+    print STDERR "erasecb: $page, $block\n" if ($g_debug);
+
+    return pack("C", $CMD_ERASE);
+}
+
+#
+# erase($sock, $pcap, $page)
+# page = -1 means all pages
+#
+sub erase
+{
+    my ($sock, $pcap, $page) = @_;
+
+    return flash($sock, $pcap, $page, $BLOCKS_PER_SECTOR,
+		 "erasing", $REG_ERASE, 1,
+		 \&erasecb, 0, '');
+}
+
+#
+# Verify the data
+#
+sub verifycb
+{
+    my ($page, $block, $payload, $image) = @_;
+
+    print STDERR "verifycb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+    my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+    # append zeroes to fill buffer to $BLOCKSIZE
+    $refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+    if ($payload ne $refblock) {
+	print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+	$g_error = 1;
+	return 0;
+    }
+
+    return 1;
+}
+
+#
+# verify($sock, $pcap, $page, $image)
+#
+sub verify
+{
+    my ($sock, $pcap, $page, $image) = @_;
+
+    return flash($sock, $pcap, $page, 1,
+		 "verifying", $REG_RW, $BLOCKSIZE,
+		 0, \&verifycb, $image);
+}
+
+#
+# Dump the data to file
+#
+sub dumpcb
+{
+    my ($page, $block, $payload, $image) = @_;
+
+    print STDERR "dumpcb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+    print DUMPFILE $payload;
+
+    return 1;
+}
+
+#
+# dump($sock, $pcap, $page)
+#
+sub dump($$$)
+{
+    my ($sock, $pcap, $page) = @_;
+
+    return flash($sock, $pcap, $page, 1,
+		 "dumping", $REG_RW, $BLOCKSIZE,
+		 0, \&dumpcb, '');
+}
+
+#
+# Write the data
+#
+sub writepagecb
+{
+    my ($page, $block, $image) = @_;
+
+    print STDERR "writepagecb: page=$page, block=$block\n" if ($g_debug);
+
+    my ($data) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+    # append zeroes to fill buffer to $BLOCKSIZE
+    $data .= pack("C", 0) x ($BLOCKSIZE - length($data));
+
+    return $data;
+}
+
+#
+# Read and verify the data
+#
+sub readpagecb
+{
+    my ($page, $block, $payload, $image) = @_;
+
+    print STDERR "readpagecb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+    my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+    # append zeroes to fill buffer to $BLOCKSIZE
+    $refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+    if ($payload ne $refblock) {
+	print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+	$g_error = 1;
+	return 0;
+    }
+
+    return 1;
+}
+
+#
+# Create a direntry string based on filename
+# direntry($filename)
+#
+sub formatentry
+{
+    my ($filename) = @_;
+
+    $stat = stat($filename) || die "=== ERR failed to stat file '$filename'";
+    $datetime = ctime($stat->mtime);
+    chop($datetime);
+
+    # return 'size YYYY mm dd hh:mm filename'
+    return sprintf("%10d %s %s", $stat->size, $datetime, $filename);
+}
+
+#
+# writepage($sock, $pcap, $page, $filename, $image)
+#
+sub writepage
+{
+    my ($sock, $pcap, $page, $filename, $image) = @_;
+
+    $error = flash($sock, $pcap, $page, 1,
+		   "writing and verifying", $REG_RW, $BLOCKSIZE,
+		   \&writepagecb, \&readpagecb, $image);
+
+    if (!$error) {
+	$error = writedir($sock, $pcap, $page, formatentry($filename));
+    } else {
+	# clear entry, an error has occured
+	writedir($sock, $pcap, $page, '');
+    }
+
+    return $error;
+}
+
+#
+# Read entire ttffile into a string
+# convert it to a binary string and
+# return than string
+#
+# ttfread($filename)
+#
+sub fread
+{
+    my ($filename) = @_;
+    local $/;
+    
+    # open input and output files
+    open(TTFFILE, $filename) || die "=== ERR failed to open file '$filename'";
+    $ttf = <TTFFILE>;
+    close(TTFFILE);
+
+    # remove \r's
+    # replace \n, by spaces
+    $ttf =~ tr/\r//;
+    $ttf =~ tr/\n/ /;
+
+    @bytes = split(/,/, $ttf);
+    $result = '';
+    foreach $byte (@bytes) {
+	$result .= pack("C", $byte);
+    }
+
+    # return the binary string
+    return $result;
+}
+
+#
+# Read image from ttf or bin file. If filename ends in .ttf
+# then the file is converted from ttf to bin format first.
+#
+# readimage($filename)
+#
+sub readimage
+{
+    my ($filename) = @_;
+    local $/ = undef; # read entire file
+    
+    # open input and output files
+    die "=== ERR no image file specified" if (!defined($filename));
+    open(IMAGEFILE, $filename) || die "=== ERR failed to open file '$filename'";
+    print STDERR "=== Reading '$filename'\n";
+    $data = <IMAGEFILE>; # read entire file
+    close(IMAGEFILE);
+
+    @parts = split(/\./, $filename);
+    if (@parts[-1] eq "ttf") {
+	# replace \r\n, by spaces
+	$data =~ tr/\r\n/ /;
+
+	@bytes = split(/,/, $data);
+
+	print STDERR "=== Converting '$filename' to binary\n";
+	print STDERR "=== |" . ("-" x $PROGRESS_SIZE) . "|\n===  ";
+
+	$i = 0;
+	$interval = $#bytes / $PROGRESS_SIZE;
+	$interval = 1 if ($interval < .5);
+
+	$bindata = '';
+	foreach $byte (@bytes) {
+	    $bindata .= pack("C", $byte);
+
+	    $i++;
+	    print STDERR "." if (0 == $i % $interval);
+	}
+
+	print STDERR "\n";
+	return $bindata;
+    }
+
+    return $data;
+}
+
+#
+# Read RSU status from RSR status register
+# readrsustatus($sock, $pcap)
+#
+sub readrsustatus
+{
+    my ($sock, $pcap) = @_;
+
+    $size = $REG_STATUS_SIZE;
+    $packet = pack($MEPSENDFORMAT,
+		   0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		   $ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_STATUS, 0, $size, 0, 0);
+    $sock->send_eth_frame($packet);
+    my $error = 0;
+    ($result, $error) = readresponse($pcap);
+    return $error if $error;
+
+    $rsustatus = unpack("C", substr($result, 164, 1));
+
+    print STDERR sprintf("\nRSU Status:\n  rdy  : %s\n  err  : %s\n  fpga : %s\n  image: %s\n  trig : %s\n\n",
+			 ('Configuration ongoing', 'Configuration done')[$rsustatus & 1],
+			 ('Configuration successfull', 'Configuration error')[($rsustatus & 2) >> 1],
+			 ('BP was reconfigured', 'AP was reconfigured')[($rsustatus & 4) >> 2],
+			 ('Factory image', 'Application image')[($rsustatus & 8) >> 3],
+			 ('Board reset', 'User reconfig', 'User reset', 'Watchdog')[($rsustatus >> 4) & 7]);
+
+    return $error;
+}
+
+#
+# Read versions of all FPGA's (handy after reconfig, reset, clear, etc)
+# readversions($sock, $pcap)
+#
+sub readversions
+{
+    my ($sock, $pcap) = @_;
+
+    # read BP version
+    $size = $REG_VERSION_SIZE;
+    $packet = pack($MEPSENDFORMAT,
+		   0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		   $ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+    $sock->send_eth_frame($packet);
+    my $error = 0;
+    ($result, $error) = readresponse($pcap);
+    return $error if $error;
+
+    ($id, $version) = unpack("CC", $result);
+    $maj = $version >> 4;
+    $min = $version & 15;
+    print STDERR "RSP   version: $id\nBP    version: $maj.$min\n";
+
+    # read AP versions
+    for ($ap = 0; $ap < 4; $ap++) {
+	$packet = pack($MEPSENDFORMAT,
+		       0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		       1 << $ap, 0, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	($id, $version) = unpack("CC", $result);
+	$maj = $version >> 4;
+	$min = $version & 15;
+	print STDERR "AP[$id] version: $maj.$min\n";
+    }
+
+    return 0;
+}
+
+#
+# Find MAC addresses of active boards
+# ping($sock, $pcap)
+#
+sub ping
+{
+    my ($sock, $pcap) = @_;
+
+    # read BP version
+    $size = $REG_VERSION_SIZE;
+    $packet = pack($MEPSENDFORMAT,
+		   0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		   $ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+    $sock->send_eth_frame($packet);
+    my $error = 0;
+
+    while (!$error) {
+	($result, $error, $ethdr) = readresponse($pcap);
+	$mac = substr($ethdr, 12, 12);
+	$mac =~ s/(..)(..)(..)(..)(..)(..)/$1:$2:$3:$4:$5:$6/;
+	print STDERR "Found RSP board on MAC: ", $mac, "\n" if (!$error);
+    }
+
+    return 0;
+}
+
+#
+# Reconfigure. Load new firmware from flash into FPGA's. After loading
+# has finished, the FPGA will be reset and restart with new firmware.
+#
+# reconfig($sock, $pcap, $page)
+#
+sub reconfig
+{
+    my ($sock, $pcap, $page) = @_;
+
+    $subpage = $page & 7;
+    $fpga    = 1 - ($page >> 3);
+
+    $cmd = ($fpga << 7) | $subpage;
+
+    $packet  = pack_rsu($TYPE_WRITE, $REG_RECONFIG, 1, 0);
+    $packet .= pack("C", $cmd);
+	
+    $sock->send_eth_frame($packet);
+    
+    # there will be no response
+    # ($response, $error) = readresponse($pcap);
+
+    return 0;
+}
+
+#
+# Reset control for all FPGA's
+#
+# clearctl($sock, $pcap, $cmd)
+# valid values for $cmd:  $CMD_SYNC, $CMD_CLEAR, $CMD_RESET
+#
+sub clearctl
+{
+    my ($sock, $pcap, $cmd) = @_;
+
+    $packet  = pack_rsu($TYPE_WRITE, $REG_CLEAR, 1, 0);
+    $packet .= pack("C", $cmd);
+    
+    $sock->send_eth_frame($packet);
+    ($response, $error) = readresponse($pcap);
+
+    return $error;
+}
+
+#
+# Report result
+# report($error, $report_ok = false)
+#
+sub report
+{
+    my ($error, $report_ok) = @_;
+
+    if ($error || $g_error) {
+	print STDERR "=== FAILED\n";
+	exit 1;
+    }
+    else {
+	print STDERR "=== OK\n" if ($report_ok);
+    }
+}
+
+#
+# Change range of the page parameter
+# chkpage($page)
+#
+sub chkpage
+{
+    my ($page) = @_;
+
+    die "=== ERR no flash page specified, use -p" if (!defined($page));
+    if ($page < 0 || $page >= $PAGES) {
+	die "=== ERR invalid page index $page, should be >= 0 and < $PAGES";
+    }
+}
+
+#
+# Get rsu status and versions
+# version_cmd($sock, $pcap)
+#
+sub version_cmd
+{
+    my ($sock, $pcap) = @_;
+
+    $error = readrsustatus($sock, $pcap);
+    $error = readversions($sock, $pcap) && $error;
+    return $error;
+}
+
+sub main
+{
+    my $sock = 0;
+
+    print STDERR "=== rsuctl ($VERSION)\n";
+
+    Getopt::Std::getopts("i:m:lbf:o:exwvdp:FrcsPVhq");
+
+    # check that at least one argument is present
+    usage() if ($opt_h);
+
+    if (defined($opt_b)) {
+	
+	# CONVERT TO BINARY
+	#
+	# readimage
+
+	die "=== ERR no input file specified"  if (!defined($opt_f) || ($opt_f eq ""));
+	die "=== ERR no output file specified" if (!defined($opt_o) || ($opt_o eq ""));
+	$image = readimage($opt_f);
+
+	open(DUMPFILE, ">$opt_o") || die "=== ERR failed to open file '$opt_o' for writing\n";
+	print DUMPFILE $image;
+	close(DUMPFILE);
+	report($error, true);
+
+	return;
+    }
+
+    if (!defined($opt_m)) {
+	print STDERR "=== ERR Must specify MAC address using -m option";
+	usage();
+    }
+    usage() unless ($opt_l || $opt_b || $opt_e || $opt_x || $opt_w || $opt_v ||
+		    $opt_d || $opt_r || $opt_c || $opt_s || $opt_P || $opt_V || $opt_h);
+    
+    # assign default values
+    $opt_i = 'eth1'              if !defined($opt_i);
+    
+    # Create socket to the boards
+    if (!defined($opt_b)) {
+
+	die "=== ERR this program needs to run as root to send/recv raw Ethernet frames" if (0 != geteuid());
+
+	$sock = new Net::RawIP;
+	$sock->ethnew($opt_i);
+	$sock->ethset(dest => $opt_m);
+
+	$TIMEOUT=5;
+	$pcap = $sock->pcapinit($opt_i, "not ether dst $opt_m", $ETHLEN, $TIMEOUT);
+
+	die "=== ERR could not open raw socket to RSP board(s) on $opt_i:$opt_m: $!\n" unless $sock;
+	  
+	print STDERR "=== connected to RSP board(s) on $opt_i:$opt_m\n";
+
+	if (!defined($opt_q)) {
+	    print STDERR "=== checking for running RSPDriver\n";
+	    ($payload, $error) = readresponse($pcap);
+	    die "=== RSPDriver appears to be running, stop it first\n" if ($payload ne '');
+	}
+    }
+
+    if (defined($opt_l)) {
+
+	# LIST IMAGES IN FLASH
+	#
+	# readdirentry for all pages
+
+	print STDERR "=== listing flash contents\n\n";
+	for ($page = 0; $page < $PAGES; $page++) {
+	    ($entry, $error) = readdirentry($sock, $pcap, $page);
+	    break if $error;
+	    print STDERR sprintf("%s %s %2d: %s\n", ($page < 8 ?'BP':'AP'),
+				 (0 == $page % 8 ? 'Factory':'Image  '),
+				 $page, $entry) if ((0 == $page % 8) || $entry ne '');
+	}
+	print STDERR "\n";
+	report($error, true);
+	
+	return;
+    }
+
+    if (defined($opt_e)) {
+
+	# ERASE
+	#
+	# erase flash page
+
+	die "=== ERR erasing factory image (page 0) prohibited, use -F to force erase of factory image" if (!$opt_F && (0 == $opt_p));
+	chkpage($opt_p);
+
+	report(erase($sock, $pcap, $opt_p), true);
+
+	return;
+    }
+    if (defined($opt_x)) {
+
+	# RECONFIG
+	#
+	# reconfig BP or AP
+	# if BP wait $TO seconds
+	# clear
+	# wait $TO seconds
+	# read rsustatus & version
+
+	chkpage($opt_p);
+	report(reconfig($sock, $pcap, $opt_p));
+	print STDERR "=== waiting $TO seconds after reconfig\n"; sleep $TO;
+	report(clearctl($sock, $pcap, $CMD_CLEAR));
+	report($error) if $error;
+	print STDERR "=== waiting $TO seconds after clear\n"; sleep $TO;
+	$error = readrsustatus($sock, $pcap);
+	$error = readversions($sock, $pcap) && $error;
+	report($error, true);
+
+	return;
+    }
+    if (defined($opt_w)) {
+
+	# WRITE
+	#
+	# readimage
+	# erase page
+	# write & verify page
+
+	die "=== ERR writing to factory image (page 0) prohibited, use -F to force write to factory image" if (!$opt_F && (0 == $opt_p));
+	chkpage($opt_p);
+
+	$image = readimage($opt_f);
+	report(erase($sock, $pcap, $opt_p));
+	report(writepage($sock, $pcap, $opt_p, $opt_f, $image), true);
+
+	return;
+    }
+    if (defined($opt_v)) {
+
+	# VERIFY
+	#
+	# readimage reference
+	# verify flash page against reference
+
+	chkpage($opt_p);
+	$image = readimage($opt_f);
+	report(verify($sock, $pcap, $opt_p, $image), true);
+
+	return;
+    }
+    if (defined($opt_d)) {
+
+	# DUMP
+	#
+	# dump flash page to file
+
+	chkpage($opt_p);
+	die "=== ERR no image file specified" if (!defined($opt_f));
+
+	open(DUMPFILE, ">$opt_f") || die "=== ERR failed to open file '$opt_f' for writing\n";
+	print STDERR "=== Dumping to file '$opt_f'\n";
+	$error = dump($sock, $pcap, $opt_p);
+	close(DUMPFILE);
+	report($error, true);
+
+	return;
+    }
+    if (defined($opt_r)) {
+	
+	# RESET
+	#
+	# send reset (factory image (page 0) will start on BP)
+	# wait $DTO seconds
+	# reconfigure AP's with page 8 (factory default for AP's)
+	# wait $TO second
+	# read rsustatus and versions
+	
+	$DTO = $TO * 2;
+
+	report(clearctl($sock, $pcap, $CMD_RESET));
+	print STDERR "=== waiting $DTO seconds after reset\n"; sleep $DTO;
+	print STDERR "=== reconfiguring AP's with factory image (page 8)\n";
+	$error = reconfig($sock, $pcap, 8);
+
+	# don't check the error return code because we know
+	# it indicates failure while the reconfig is actually in progress
+
+	print STDERR "=== waiting $TO second after AP reconfig\n"; sleep $TO;
+	report(version_cmd($sock, $pcap) && $error, true);
+
+	return;
+    }
+    if (defined($opt_c)) {
+
+	# CLEAR
+	#
+	# send clear
+	# wait $TO seconds
+	# read rsustatus and versions
+	
+	report(clearctl($sock, $pcap, $CMD_CLEAR));
+	print STDERR "=== waiting $TO seconds after clear\n"; sleep $TO;
+	report(version_cmd($sock, $pcap), true);
+
+	return;
+    }
+    if (defined($opt_s)) {
+
+	# SYNC
+	#
+	# send sync
+
+	report(clearctl($sock, $pcap, $CMD_SYNC), true);
+
+	return;
+    }
+    if (defined($opt_P)) {
+
+	report(ping($sock, $pcap), true);
+	return;
+
+    }
+    if (defined($opt_V)) {
+
+	# VERSIONS
+	#
+	# read rsustatus and versions
+
+	report(version_cmd($sock, $pcap), true);
+
+	return;
+    }
+
+}
+
+main();
diff --git a/Firmware/tools/src/rsuctl3 b/Firmware/tools/src/rsuctl3
new file mode 100755
index 0000000000000000000000000000000000000000..fad33dfeef95ed185dfe034975dacad9eb52523e
--- /dev/null
+++ b/Firmware/tools/src/rsuctl3
@@ -0,0 +1,969 @@
+#!/usr/bin/perl
+$VERSION = q$Revision: 3.00$;
+
+#
+# rsuctl # Remote System Update - program firmware in FPGA flash
+#
+# Usage: rsuctl [options] command
+#
+#   options
+#     [-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+#     -m mac-address # MAC-address of the target board (Mandatory)
+#
+#   command (specify at least one of the following)
+#     [-l] # List the images currently stored in all pages of the flash
+#
+#     [-e -p page [-F]]                                 # Erase flash page,  0 <= page < 16 (-F forces erase of page 0)
+#     [-x -p page                                       # Start (load & reset) new firmware from the specified page
+#     [-w -p page -b bp_img[.hex] -a ap_img[.hex] [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+#     [-v -p page -b bp_img[.hex] -a ap_img[.hex]]      # Compare flash page with bp and ap image
+#     [-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+#
+#     [-r] # Reset all FPGA's (load factory image)
+#     [-c] # Clear all FPGA's (restart current image)
+#     [-s] # Send SYNC pulse to all FPGA's
+#
+#     [-q] # Prevent checking for running RSPDriver (prevents pause at start)
+#     [-P] # Ping the board (find board when using broadcast address)
+#     [-V] # Read RSU status and RSR versions
+#     [-h] # This help text
+#
+#
+
+#use Socket;
+use Net::RawIP;
+use Getopt::Std;
+use File::stat;
+use Switch;
+use POSIX; # for geteuid()
+
+$g_debug  = 0;  # set to 1 to enable debug output
+$g_packet = ''; # global packet string
+$g_error  = 0;  # global error status
+
+#
+# Definitions
+#
+$PID_RSR      = 0x01;
+$PID_RSU      = 0x02;
+
+$REG_STATUS       = 0x00;
+$REG_STATUS_SIZE  = 200;
+$REG_VERSION      = 0x01;
+$REG_VERSION_SIZE = 2;
+
+$REG_RW       = 0x01;
+$REG_ERASE    = 0x02;
+$REG_RECONFIG = 0x03;
+$REG_CLEAR    = 0x04;
+
+$CMD_ERASE = 0x01;
+
+$CMD_SYNC  = 0x01;
+$CMD_CLEAR = 0x02;
+$CMD_RESET = 0x04;
+
+$MEPHDRSIZE = 16;
+
+$TYPE_READ     = 0x01;
+$TYPE_WRITE    = 0x02;
+$TYPE_READACK  = 0x03;
+$TYPE_WRITEACK = 0x04;
+
+$ADDR_BLP_NONE = 0x00;
+$ADDR_RSP      = 0x01;
+
+$FLASHSIZE         = 64 * 1024 * 1024; # 64MB
+$BLOCKSIZE         = 1024; # block  = 1024 bytes
+$BLOCKS_PER_SECTOR = 128;  # sector = 128 blocks
+$SECTORS_PER_PAGE  = 32;   # page   = 32 sectors
+$BLOCKS_PER_PAGE   = $BLOCKS_PER_SECTOR * $SECTORS_PER_PAGE; # 4096 blocks
+$PAGESIZE          = $SECTORS_PER_PAGE * $BLOCKS_PER_SECTOR * $BLOCKSIZE; # 4MB  
+$PAGES             = 16;
+
+$PROGRESS_SIZE     = 32;
+
+$RESPONSETIMEOUT = 2;
+$TO = 5;
+
+#
+# pack format for the MEP message
+#
+# C 0x10 Ethernet Type byte 0
+# C 0xFA Ethernet Type byte 1
+# C TYPE
+# C STATUS
+# S FRAMELENGTH
+# C BLPID
+# C RSP
+# C PID
+# C REGID
+# S OFFSET
+# S PAYLOAD_LENGTH
+# S SEQ_NR
+# S RESERVED
+#
+$MEPSENDFORMAT="CCCCSCCCCSSSS";
+$MEPRECVFORMAT="H28CCSCCCCSSSS";
+
+# ETHLEN = max length of Ethernet payload
+$ETHLEN = 1500;
+
+#
+# Error code
+#
+%STATUS = ( 0 => "=== OK",
+	1 => "=== ERR unknown MEP message type",
+	2 => "=== ERR illegal BLP address",
+	3 => "=== ERR invalid PID",
+	4 => "=== ERR register does not exist",
+	5 => "=== ERR offset too large",
+	6 => "=== ERR message is too large",
+	7 => "=== ERR message corruption during RSP processing",
+	8 => "=== ERR message lost during RSP processing"
+);
+
+#
+# usage
+#
+sub usage
+{
+	print STDERR <<END;
+
+	# Remote System Update - program firmware in FPGA flash
+		
+	Usage: rsuctl [options] command
+
+	options
+	[-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+	-m mac-address # MAC-address of the target board (Mandatory)
+
+	command (specify at least one of the following, need -m option at least)
+	[-l] # List the images currently stored in all pages of the flash
+
+	[-e -p page [-F]]                                 # Erase flash page, 0 <= page < 16 (-F forces erase of page 0)
+	[-x -p page                                       # Start (load & reset) new firmware from the specified page
+	[-w -p page -b bp_image.hex -a ap_image.hex [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+	[-v -p page -b bp_image.hex -a ap_image.hex]      # Compare flash page with bp and ap image
+	[-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+
+	[-r] # Reset all FPGA's (load factory image)
+	[-c] # Clear all FPGA's (restart current image)
+	[-s] # Send SYNC pulse to all FPGA's
+
+	[-q] # Prevent checking for running RSPDriver (prevents pause at start)
+	[-P] # Ping the board (find board when using broadcast address)
+	[-V] # Read RSU status and RSR versions
+	[-h] # This help text
+
+END
+
+	exit;
+}
+
+#
+# pack_rsu($type, $regid, $size, $offset)
+#
+# Pack an RSU message
+#
+sub pack_rsu
+{
+	my ($type, $regid, $size, $offset) = @_;
+	
+	return pack($MEPSENDFORMAT,
+		0x10, 0xFA, $type, 0x00, $MEPHDRSIZE + $size,
+		$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSU, $regid, $offset, $size, 0, 0);;
+}
+
+#
+# pcapcallback, save packet in $g_packet
+#
+# savepacket($handle, $hdr, $packet)
+#
+sub savepacket
+{
+	my ($handle, $hdr, $packet) = @_;
+	$g_packet = $packet;
+}
+
+#
+# ($payload,$error) = readresponse($pcap)
+#
+sub readresponse
+{
+	my ($pcap) = @_;
+	my ($retval) = 0;
+
+	do { # keep going when we receive invalid messages
+		$retval = 0; # assume success
+
+		# wait for message to arrive
+		eval {
+			use POSIX qw(SIGALRM);
+			POSIX::sigaction(SIGALRM,
+			POSIX::SigAction->new(sub { die }))
+			|| die "=== ERR error setting SIGALRM handler: $!\n";
+
+			alarm $RESPONSETIMEOUT;
+			(0 == loop ($pcap, 1, \&savepacket, ''))
+			|| die "=== ERR Failed to receive response";
+			alarm 0;
+		};
+		if ($@) {
+			$retval = 1; # fatal (timeout)
+			return ('', $retval, '');
+		}
+
+		($ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved) =
+		unpack($MEPRECVFORMAT, $g_packet);
+
+		if ($TYPE_WRITEACK != $type && $TYPE_READACK != $type) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid TYPE\n" if ($g_debug);
+		}
+		if ($PID_RSU != $pid && $PID_RSR != $pid) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid PID\n" if ($g_debug);
+		}
+
+		printf STDERR "0x%s 0x%02x 0x%02x %5d 0x%02x 0x%02x 0x%02x 0x%02x %5d %5d %5d %5d\n",
+		$ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved if ($g_debug);
+
+		if (0 == $retval && 0 != $status) {
+			$retval = 1; # fatal
+			print STDERR "=== FATAL protocol error: status=", $STATUS{$status}, "\n";
+			print STDERR "=== Are you using the correct Ethernet interface? Use -i argument to change, e.g. -i eth1\n";
+			exit;
+		}
+	} until ($retval != 2);
+
+	return ('', $retval, '') if ($retval);
+
+	return (substr($g_packet, 14 + $MEPHDRSIZE), $retval, $ethdr);
+}
+
+#
+# flash($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image)
+#
+sub flash
+{
+	my ($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image) = @_;
+
+	printf STDERR "=== %s page %02d\n", $msg, $page;
+	print  STDERR "=== |" . ("-" x $SECTORS_PER_PAGE) . "|\n===  ";
+	#print STDERR "Image: $image \n"; 
+	
+	my ($success) = 1;
+	# leave last block for directory entry
+	my ($block);
+	for ($block = 0; $block < $BLOCKS_PER_PAGE - 1; $block += $blockinc) {
+		if ($writecb) {
+			$packet  = pack_rsu($TYPE_WRITE, $cmd, $size, ($page * $BLOCKS_PER_PAGE) + $block);
+
+			$packet .= &$writecb($page, $block, $image);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+
+			return $error if ($error);
+		}
+
+		if ($readcb) {
+			$packet  = pack_rsu($TYPE_READ, $cmd, $size,
+			($page * $BLOCKS_PER_PAGE) + $block);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+			return $error if ($error);
+
+			$success = &$readcb($page, $block, $payload, $image) && $success;
+		}
+
+		if (0 == $block % $BLOCKS_PER_SECTOR) {
+			if ($success) { print STDERR "."; }
+			else          { print STDERR "X"; }
+			$success = 1;
+		}
+	}
+	print STDERR "\n";
+
+	return 0;
+}
+
+#
+# writedir($sock, $pcap, $page, $entry)
+# Write the entry to the last block of page $page
+#
+sub writedir
+{
+	my ($sock, $pcap, $page, $entry) = @_;
+
+	die "=== ERR internal error: size of direntry > $BLOCKSIZE" if (length($entry) > $BLOCKSIZE);
+
+	print STDERR "writing dir @", (($page + 1) * $BLOCKS_PER_PAGE) - 1, "\n" if ($g_debug);
+
+	# add zero padding if needed
+	$entry .= pack("C", 0) x ($BLOCKSIZE - length($entry)) if (length($entry) < $BLOCKSIZE);
+
+	# write last block with dir entry
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) - 1);
+	$packet .= $entry;
+
+	my $error = 2;
+	$sock->send_eth_frame($packet);
+
+	($result, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# readdir($sock, $pcap, $page)
+# Read the directory entry from the specified page
+#
+sub readdirentry
+{
+	my ($sock, $pcap, $page) = @_;
+
+	my ($packet)  = pack_rsu($TYPE_READ, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) -1);
+
+	$sock->send_eth_frame($packet);
+
+	my $error = 0;
+	($entry, $error) = readresponse($pcap);
+	return ('', $error) if $error;
+
+	$entry = '' if (substr($entry,0,1) eq pack("C", 0xff));
+
+	return ($entry, $error);
+}
+
+#
+# erasecb($page, $block, $image)
+#
+sub erasecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "erasecb: $page, $block\n" if ($g_debug);
+
+	return pack("C", $CMD_ERASE);
+}
+
+#
+# erase($sock, $pcap, $page)
+# page = -1 means all pages
+#
+sub erase
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, $BLOCKS_PER_SECTOR,
+	"erasing", $REG_ERASE, 1,
+	\&erasecb, 0, '');
+}
+
+#
+# Verify the data
+#
+sub verifycb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "verifycb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# verify($sock, $pcap, $page, $image)
+#
+sub verify
+{
+	my ($sock, $pcap, $page, $image) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"verifying", $REG_RW, $BLOCKSIZE,
+	0, \&verifycb, $image);
+}
+
+#
+# Dump the data to file
+#
+sub dumpcb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "dumpcb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	print DUMPFILE $payload;
+
+	return 1;
+}
+
+#
+# dump($sock, $pcap, $page)
+#
+sub dump($$$)
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"dumping", $REG_RW, $BLOCKSIZE,
+	0, \&dumpcb, '');
+}
+
+#
+# Write the data
+#
+sub writepagecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "writepagecb: page=$page, block=$block\n" if ($g_debug);
+
+	my ($data) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$data .= pack("C", 0) x ($BLOCKSIZE - length($data));
+
+	return $data;
+}
+
+#
+# Read and verify the data
+#
+sub readpagecb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "readpagecb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# Create a direntry string based on filename
+# direntry($filename)
+#
+sub formatentry
+{
+	my ($bp_filename, $ap_filename) = @_;
+
+	$stat = stat($bp_filename) || die "=== ERR failed to stat file '$bp_filename'";
+	$bp_datetime = ctime($stat->mtime);
+	chop($bp_datetime);
+
+	$stat = stat($ap_filename) || die "=== ERR failed to stat file '$ap_filename'";
+	$ap_datetime = ctime($stat->mtime);
+	chop($ap_datetime);
+
+	# return 'size YYYY mm dd hh:mm bp_filename ; size YYYY mm dd hh:mm ap_filename'
+	return sprintf("%s( %s ) ; %s( %s )", $bp_filename, $bp_datetime, $ap_filename, $ap_datetime);
+}
+
+#
+# writepage($sock, $pcap, $page, $bp_filename, $ap_filename, $image)
+#
+sub writepage
+{
+	my ($sock, $pcap, $page, $bp_filename, $ap_filename, $image) = @_;
+
+	$error = flash($sock, $pcap, $page, 1,
+	"writing and verifying", $REG_RW, $BLOCKSIZE,
+	\&writepagecb, \&readpagecb, $image);
+
+	if (!$error) {
+		$error = writedir($sock, $pcap, $page, formatentry($bp_filename, $ap_filename));
+	} else {
+		# clear entry, an error has occured
+		writedir($sock, $pcap, $page, '');
+	}
+	return $error;
+}
+
+
+#
+# Read images from the hex files. 
+# then the files are converted from hex to bin format.
+#
+# readhexfiles($filename_bp, &filename_ap )
+#
+sub readhexfiles
+{
+	my ($filename_bp, $filename_ap) = @_;
+	local $/ = undef; # read entire file
+
+	# open input and output files
+	die "=== ERR no image file specified" if ((!defined($filename_bp)) or (!defined($filename_ap)));
+	
+	open(IMAGEFILE, $filename_bp) || die "=== ERR failed to open file '$filename_bp'";
+	print STDERR "=== Reading '$filename_bp'\n";
+	$data = <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	open(IMAGEFILE, $filename_ap) || die "=== ERR failed to open file '$filename_ap'";
+	print STDERR "=== Reading '$filename_ap'\n";
+	$data .= <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	print STDERR "=== Converting '$filename_bp' and '$filename_ap' to binary\n";
+	print STDERR "=== |" . ("-" x $PROGRESS_SIZE) . "|\n===  ";
+
+	$i = 0;
+	$interval = length($data) / ($PROGRESS_SIZE * 2);
+	$interval = 1 if ($interval < .5);
+
+	$bindata = '';
+	
+	for ($byte = 0; $byte < length($data); $byte += 2) {
+		$bytestr = substr($data, $byte, 2);
+		
+		$bindata .= pack("C", hex($bytestr));
+		
+		# print progress bar
+		$i++;
+		print STDERR "." if (0 == $i % $interval);
+	}
+	
+	# append FF's to fill buffer to $IMAGESIZE - 1
+	$bindata .= pack("C", 255) x (($PAGESIZE - $BLOCKSIZE) - length($bindata));
+	
+	print STDERR "\n";
+	
+	return $bindata;
+}
+
+#
+# Read RSU status from RSR status register
+# readrsustatus($sock, $pcap)
+#
+sub readrsustatus
+{
+	my ($sock, $pcap) = @_;
+
+	$size = $REG_STATUS_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_STATUS, 0, $size, 0, 0);
+	
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	$rsustatus = unpack("C", substr($result, 164, 1));
+
+	print STDERR sprintf("\nRSU Status:\n	Rdy      : %s\n	FpgaType : %s\n	ImageType: %s\n	Trig     : %s\n\n",
+	('Configuration ongoing', 'Configuration done')[$rsustatus & 1],
+	('loaded from flash', 'loaded via JTAG')[($rsustatus >> 2) & 1],
+	('Factory image is running', 'User image is running')[($rsustatus >> 3) & 1],
+	('Reconfiguration due to button reset'
+	, 'Unknown'
+	, 'Reconfiguration due to over temperature'
+	, 'Unknown'
+	, 'Reconfiguration due to user reset'
+	, 'Unknown'
+	, 'Reconfiguration due to watchdog reset' )[($rsustatus >> 4) & 7]);
+
+	return $error;
+}
+
+#
+# Read versions of all FPGA's (handy after reconfig, reset, clear, etc)
+# readversions($sock, $pcap)
+#
+sub readversions
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	($id, $version) = unpack("CC", $result);
+	$maj = $version >> 4;
+	$min = $version & 15;
+	print STDERR "RSP   version: $id\nBP    version: $maj.$min\n";
+
+	# read AP versions
+	for ($ap = 0; $ap < 4; $ap++) {
+		$packet = pack($MEPSENDFORMAT,
+		0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		1 << $ap, 0, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+		$sock->send_eth_frame($packet);
+		($result, $error) = readresponse($pcap);
+		return $error if $error;
+
+		($id, $version) = unpack("CC", $result);
+		$maj = $version >> 4;
+		$min = $version & 15;
+		print STDERR "AP[$id] version: $maj.$min\n";
+	}
+
+	return 0;
+}
+
+#
+# Find MAC addresses of active boards
+# ping($sock, $pcap)
+#
+sub ping
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+
+	while (!$error) {
+		($result, $error, $ethdr) = readresponse($pcap);
+		$mac = substr($ethdr, 12, 12);
+		$mac =~ s/(..)(..)(..)(..)(..)(..)/$1:$2:$3:$4:$5:$6/;
+		print STDERR "Found RSP board on MAC: ", $mac, "\n" if (!$error);
+	}
+
+	return 0;
+}
+
+#
+# Reconfigure. Load new firmware from flash into FPGA's. After loading
+# has finished, the FPGA will be reset and restart with new firmware.
+#
+# reconfig($sock, $pcap, $page)
+#
+sub reconfig
+{
+	my ($sock, $pcap, $page) = @_;
+
+	$cmd = $page & 15; # use bit 0..3
+	$cmd = $cmd | 128;
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RECONFIG, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+
+	# there will be no response
+	# ($response, $error) = readresponse($pcap);
+
+	return 0;
+}
+
+#
+# Reset control for all FPGA's
+#
+# clearctl($sock, $pcap, $cmd)
+# valid values for $cmd:  $CMD_SYNC, $CMD_CLEAR, $CMD_RESET
+#
+sub clearctl
+{
+	my ($sock, $pcap, $cmd) = @_;
+
+	$packet  = pack_rsu($TYPE_WRITE, $REG_CLEAR, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+	($response, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# Report result
+# report($error, $report_ok = false)
+#
+sub report
+{
+	my ($error, $report_ok) = @_;
+
+	if ($error || $g_error) {
+		print STDERR "=== FAILED\n";
+		exit 1;
+	}
+	else {
+		print STDERR "=== OK\n" if ($report_ok);
+	}
+}
+
+#
+# Change range of the page parameter
+# chkpage($page)
+#
+sub chkpage
+{
+	my ($page) = @_;
+
+	die "=== ERR no flash page specified, use -p" if (!defined($page));
+	if ($page < 0 || $page >= $PAGES) {
+		die "=== ERR invalid page index $page, should be >= 0 and < $PAGES";
+	}
+}
+
+#
+# Get rsu status and versions
+# version_cmd($sock, $pcap)
+#
+sub version_cmd
+{
+	my ($sock, $pcap) = @_;
+
+	$error = readrsustatus($sock, $pcap);
+	$error = readversions($sock, $pcap) && $error;
+	return $error;
+}
+
+sub main
+{
+	my $sock = 0;
+
+	print STDERR "=== rsuctl ($VERSION), for RSP3 boards only\n";
+	
+	Getopt::Std::getopts("i:m:lexwvb:a:dp:f:FrcsPVhq");
+
+	# check that at least one argument is present
+	usage() if ($opt_h);
+
+	if (!defined($opt_m)) {
+		print STDERR "=== ERR Must specify MAC address using -m option";
+		usage();
+	}
+
+	usage() unless ($opt_l || $opt_e || $opt_x || $opt_w || $opt_v ||
+	$opt_d || $opt_r || $opt_c || $opt_s || $opt_P || $opt_V || $opt_h);
+
+	# assign default values
+	$opt_i = 'eth1'              if !defined($opt_i);
+
+	# Create socket to the boards
+	die "=== ERR this program needs to run as root to send/recv raw Ethernet frames" if (0 != geteuid());
+
+	$sock = new Net::RawIP;
+	$sock->ethnew($opt_i);
+	$sock->ethset(dest => $opt_m);
+
+	$TIMEOUT=5;
+	$pcap = $sock->pcapinit($opt_i, "not ether dst $opt_m", $ETHLEN, $TIMEOUT);
+
+	die "=== ERR could not open raw socket to RSP board(s) on $opt_i:$opt_m: $!\n" unless $sock;
+
+	print STDERR "=== connected to RSP board(s) on $opt_i:$opt_m\n";
+
+	if (!defined($opt_q)) {
+		print STDERR "=== checking for running RSPDriver\n";
+		($payload, $error) = readresponse($pcap);
+		die "=== RSPDriver appears to be running, stop it first\n" if ($payload ne '');
+	}
+
+	if (defined($opt_l)) {
+
+		# LIST IMAGES IN FLASH
+		#
+		# readdirentry for all pages
+
+		print STDERR "=== listing flash contents\n\n";
+		for ($page = 0; $page < $PAGES; $page++) {
+			($entry, $error) = readdirentry($sock, $pcap, $page);
+			break if $error;
+			print STDERR sprintf("%s %2d: %s\n", 
+				(0 == $page ? 'Factory':'Image  '),	$page, $entry) if ((0 == $page) || $entry ne '');
+		}
+		print STDERR "\n";
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_e)) {
+
+		# ERASE
+		#
+		# erase flash page
+
+		die "=== ERR erasing factory image (page 0) prohibited, use -F to force erase of factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		report(erase($sock, $pcap, $opt_p), true);
+
+		return;
+	}
+
+	if (defined($opt_x)) {
+
+		# RECONFIG
+		#
+		# reconfig BP and AP
+		# wait $TO seconds
+		# clear
+		# wait $TO seconds
+		# read rsustatus & version
+		
+		chkpage($opt_p);
+		report(reconfig($sock, $pcap, $opt_p));
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		report($error) if $error;
+		print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		$error = readrsustatus($sock, $pcap);
+		$error = readversions($sock, $pcap) && $error;
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_w)) {
+
+		# WRITE
+		#
+		# readimage
+		# erase page
+		# write & verify page
+
+		die "=== ERR writing to factory image (page 0) prohibited, use -F to force write to factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		$image = readhexfiles($opt_b, $opt_a);
+		report(erase($sock, $pcap, $opt_p));
+		report(writepage($sock, $pcap, $opt_p, $opt_b, $opt_a, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_v)) {
+
+		# VERIFY
+		#
+		# readimage reference
+		# verify flash page against reference
+
+		chkpage($opt_p);
+		$image = readhexfiles($opt_b, $opt_a);
+		report(verify($sock, $pcap, $opt_p, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_d)) {
+
+		# DUMP
+		#
+		# dump flash page to file
+
+		chkpage($opt_p);
+		die "=== ERR no image file specified" if (!defined($opt_f));
+
+		open(DUMPFILE, ">$opt_f") || die "=== ERR failed to open file '$opt_f' for writing\n";
+		print STDERR "=== Dumping to file '$opt_f'\n";
+		$error = dump($sock, $pcap, $opt_p);
+		close(DUMPFILE);
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_r)) {
+
+		# RESET
+		#
+		# send reset (factory image (page 0) will start on BP)
+		# wait $DTO seconds
+		# reconfigure AP's with page 8 (factory default for AP's)
+		# wait $TO second
+		# read rsustatus and versions
+
+		report(reconfig($sock, $pcap, 0)); # set image 0 (factory image)
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		print STDERR "=== reconfiguring BP and AP's with factory image\n";
+
+		# don't check the error return code because we know
+		# it indicates failure while the reconfig is actually in progress
+		
+		report(version_cmd($sock, $pcap) && $error, true);
+
+		return;
+	}
+
+	if (defined($opt_c)) {
+
+		# CLEAR
+		#
+		# send clear
+		# wait $TO seconds
+		# read rsustatus and versions
+
+		report(clearctl($sock, $pcap, $CMD_CLEAR));
+		print STDERR "=== waiting $TO seconds after clear\n"; sleep $TO;
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+
+	if (defined($opt_s)) {
+
+		# SYNC
+		#
+		# send sync
+
+		report(clearctl($sock, $pcap, $CMD_SYNC), true);
+
+		return;
+	}
+
+	if (defined($opt_P)) {
+		report(ping($sock, $pcap), true);
+		return;
+	}
+
+	if (defined($opt_V)) {
+		# versions
+		#
+		# read rsustatus and versions
+
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+}
+
+main();
\ No newline at end of file
diff --git a/Firmware/tools/src/rsuctl3.pl b/Firmware/tools/src/rsuctl3.pl
new file mode 100644
index 0000000000000000000000000000000000000000..fad33dfeef95ed185dfe034975dacad9eb52523e
--- /dev/null
+++ b/Firmware/tools/src/rsuctl3.pl
@@ -0,0 +1,969 @@
+#!/usr/bin/perl
+$VERSION = q$Revision: 3.00$;
+
+#
+# rsuctl # Remote System Update - program firmware in FPGA flash
+#
+# Usage: rsuctl [options] command
+#
+#   options
+#     [-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+#     -m mac-address # MAC-address of the target board (Mandatory)
+#
+#   command (specify at least one of the following)
+#     [-l] # List the images currently stored in all pages of the flash
+#
+#     [-e -p page [-F]]                                 # Erase flash page,  0 <= page < 16 (-F forces erase of page 0)
+#     [-x -p page                                       # Start (load & reset) new firmware from the specified page
+#     [-w -p page -b bp_img[.hex] -a ap_img[.hex] [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+#     [-v -p page -b bp_img[.hex] -a ap_img[.hex]]      # Compare flash page with bp and ap image
+#     [-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+#
+#     [-r] # Reset all FPGA's (load factory image)
+#     [-c] # Clear all FPGA's (restart current image)
+#     [-s] # Send SYNC pulse to all FPGA's
+#
+#     [-q] # Prevent checking for running RSPDriver (prevents pause at start)
+#     [-P] # Ping the board (find board when using broadcast address)
+#     [-V] # Read RSU status and RSR versions
+#     [-h] # This help text
+#
+#
+
+#use Socket;
+use Net::RawIP;
+use Getopt::Std;
+use File::stat;
+use Switch;
+use POSIX; # for geteuid()
+
+$g_debug  = 0;  # set to 1 to enable debug output
+$g_packet = ''; # global packet string
+$g_error  = 0;  # global error status
+
+#
+# Definitions
+#
+$PID_RSR      = 0x01;
+$PID_RSU      = 0x02;
+
+$REG_STATUS       = 0x00;
+$REG_STATUS_SIZE  = 200;
+$REG_VERSION      = 0x01;
+$REG_VERSION_SIZE = 2;
+
+$REG_RW       = 0x01;
+$REG_ERASE    = 0x02;
+$REG_RECONFIG = 0x03;
+$REG_CLEAR    = 0x04;
+
+$CMD_ERASE = 0x01;
+
+$CMD_SYNC  = 0x01;
+$CMD_CLEAR = 0x02;
+$CMD_RESET = 0x04;
+
+$MEPHDRSIZE = 16;
+
+$TYPE_READ     = 0x01;
+$TYPE_WRITE    = 0x02;
+$TYPE_READACK  = 0x03;
+$TYPE_WRITEACK = 0x04;
+
+$ADDR_BLP_NONE = 0x00;
+$ADDR_RSP      = 0x01;
+
+$FLASHSIZE         = 64 * 1024 * 1024; # 64MB
+$BLOCKSIZE         = 1024; # block  = 1024 bytes
+$BLOCKS_PER_SECTOR = 128;  # sector = 128 blocks
+$SECTORS_PER_PAGE  = 32;   # page   = 32 sectors
+$BLOCKS_PER_PAGE   = $BLOCKS_PER_SECTOR * $SECTORS_PER_PAGE; # 4096 blocks
+$PAGESIZE          = $SECTORS_PER_PAGE * $BLOCKS_PER_SECTOR * $BLOCKSIZE; # 4MB  
+$PAGES             = 16;
+
+$PROGRESS_SIZE     = 32;
+
+$RESPONSETIMEOUT = 2;
+$TO = 5;
+
+#
+# pack format for the MEP message
+#
+# C 0x10 Ethernet Type byte 0
+# C 0xFA Ethernet Type byte 1
+# C TYPE
+# C STATUS
+# S FRAMELENGTH
+# C BLPID
+# C RSP
+# C PID
+# C REGID
+# S OFFSET
+# S PAYLOAD_LENGTH
+# S SEQ_NR
+# S RESERVED
+#
+$MEPSENDFORMAT="CCCCSCCCCSSSS";
+$MEPRECVFORMAT="H28CCSCCCCSSSS";
+
+# ETHLEN = max length of Ethernet payload
+$ETHLEN = 1500;
+
+#
+# Error code
+#
+%STATUS = ( 0 => "=== OK",
+	1 => "=== ERR unknown MEP message type",
+	2 => "=== ERR illegal BLP address",
+	3 => "=== ERR invalid PID",
+	4 => "=== ERR register does not exist",
+	5 => "=== ERR offset too large",
+	6 => "=== ERR message is too large",
+	7 => "=== ERR message corruption during RSP processing",
+	8 => "=== ERR message lost during RSP processing"
+);
+
+#
+# usage
+#
+sub usage
+{
+	print STDERR <<END;
+
+	# Remote System Update - program firmware in FPGA flash
+		
+	Usage: rsuctl [options] command
+
+	options
+	[-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+	-m mac-address # MAC-address of the target board (Mandatory)
+
+	command (specify at least one of the following, need -m option at least)
+	[-l] # List the images currently stored in all pages of the flash
+
+	[-e -p page [-F]]                                 # Erase flash page, 0 <= page < 16 (-F forces erase of page 0)
+	[-x -p page                                       # Start (load & reset) new firmware from the specified page
+	[-w -p page -b bp_image.hex -a ap_image.hex [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+	[-v -p page -b bp_image.hex -a ap_image.hex]      # Compare flash page with bp and ap image
+	[-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+
+	[-r] # Reset all FPGA's (load factory image)
+	[-c] # Clear all FPGA's (restart current image)
+	[-s] # Send SYNC pulse to all FPGA's
+
+	[-q] # Prevent checking for running RSPDriver (prevents pause at start)
+	[-P] # Ping the board (find board when using broadcast address)
+	[-V] # Read RSU status and RSR versions
+	[-h] # This help text
+
+END
+
+	exit;
+}
+
+#
+# pack_rsu($type, $regid, $size, $offset)
+#
+# Pack an RSU message
+#
+sub pack_rsu
+{
+	my ($type, $regid, $size, $offset) = @_;
+	
+	return pack($MEPSENDFORMAT,
+		0x10, 0xFA, $type, 0x00, $MEPHDRSIZE + $size,
+		$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSU, $regid, $offset, $size, 0, 0);;
+}
+
+#
+# pcapcallback, save packet in $g_packet
+#
+# savepacket($handle, $hdr, $packet)
+#
+sub savepacket
+{
+	my ($handle, $hdr, $packet) = @_;
+	$g_packet = $packet;
+}
+
+#
+# ($payload,$error) = readresponse($pcap)
+#
+sub readresponse
+{
+	my ($pcap) = @_;
+	my ($retval) = 0;
+
+	do { # keep going when we receive invalid messages
+		$retval = 0; # assume success
+
+		# wait for message to arrive
+		eval {
+			use POSIX qw(SIGALRM);
+			POSIX::sigaction(SIGALRM,
+			POSIX::SigAction->new(sub { die }))
+			|| die "=== ERR error setting SIGALRM handler: $!\n";
+
+			alarm $RESPONSETIMEOUT;
+			(0 == loop ($pcap, 1, \&savepacket, ''))
+			|| die "=== ERR Failed to receive response";
+			alarm 0;
+		};
+		if ($@) {
+			$retval = 1; # fatal (timeout)
+			return ('', $retval, '');
+		}
+
+		($ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved) =
+		unpack($MEPRECVFORMAT, $g_packet);
+
+		if ($TYPE_WRITEACK != $type && $TYPE_READACK != $type) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid TYPE\n" if ($g_debug);
+		}
+		if ($PID_RSU != $pid && $PID_RSR != $pid) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid PID\n" if ($g_debug);
+		}
+
+		printf STDERR "0x%s 0x%02x 0x%02x %5d 0x%02x 0x%02x 0x%02x 0x%02x %5d %5d %5d %5d\n",
+		$ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved if ($g_debug);
+
+		if (0 == $retval && 0 != $status) {
+			$retval = 1; # fatal
+			print STDERR "=== FATAL protocol error: status=", $STATUS{$status}, "\n";
+			print STDERR "=== Are you using the correct Ethernet interface? Use -i argument to change, e.g. -i eth1\n";
+			exit;
+		}
+	} until ($retval != 2);
+
+	return ('', $retval, '') if ($retval);
+
+	return (substr($g_packet, 14 + $MEPHDRSIZE), $retval, $ethdr);
+}
+
+#
+# flash($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image)
+#
+sub flash
+{
+	my ($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image) = @_;
+
+	printf STDERR "=== %s page %02d\n", $msg, $page;
+	print  STDERR "=== |" . ("-" x $SECTORS_PER_PAGE) . "|\n===  ";
+	#print STDERR "Image: $image \n"; 
+	
+	my ($success) = 1;
+	# leave last block for directory entry
+	my ($block);
+	for ($block = 0; $block < $BLOCKS_PER_PAGE - 1; $block += $blockinc) {
+		if ($writecb) {
+			$packet  = pack_rsu($TYPE_WRITE, $cmd, $size, ($page * $BLOCKS_PER_PAGE) + $block);
+
+			$packet .= &$writecb($page, $block, $image);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+
+			return $error if ($error);
+		}
+
+		if ($readcb) {
+			$packet  = pack_rsu($TYPE_READ, $cmd, $size,
+			($page * $BLOCKS_PER_PAGE) + $block);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+			return $error if ($error);
+
+			$success = &$readcb($page, $block, $payload, $image) && $success;
+		}
+
+		if (0 == $block % $BLOCKS_PER_SECTOR) {
+			if ($success) { print STDERR "."; }
+			else          { print STDERR "X"; }
+			$success = 1;
+		}
+	}
+	print STDERR "\n";
+
+	return 0;
+}
+
+#
+# writedir($sock, $pcap, $page, $entry)
+# Write the entry to the last block of page $page
+#
+sub writedir
+{
+	my ($sock, $pcap, $page, $entry) = @_;
+
+	die "=== ERR internal error: size of direntry > $BLOCKSIZE" if (length($entry) > $BLOCKSIZE);
+
+	print STDERR "writing dir @", (($page + 1) * $BLOCKS_PER_PAGE) - 1, "\n" if ($g_debug);
+
+	# add zero padding if needed
+	$entry .= pack("C", 0) x ($BLOCKSIZE - length($entry)) if (length($entry) < $BLOCKSIZE);
+
+	# write last block with dir entry
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) - 1);
+	$packet .= $entry;
+
+	my $error = 2;
+	$sock->send_eth_frame($packet);
+
+	($result, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# readdir($sock, $pcap, $page)
+# Read the directory entry from the specified page
+#
+sub readdirentry
+{
+	my ($sock, $pcap, $page) = @_;
+
+	my ($packet)  = pack_rsu($TYPE_READ, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) -1);
+
+	$sock->send_eth_frame($packet);
+
+	my $error = 0;
+	($entry, $error) = readresponse($pcap);
+	return ('', $error) if $error;
+
+	$entry = '' if (substr($entry,0,1) eq pack("C", 0xff));
+
+	return ($entry, $error);
+}
+
+#
+# erasecb($page, $block, $image)
+#
+sub erasecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "erasecb: $page, $block\n" if ($g_debug);
+
+	return pack("C", $CMD_ERASE);
+}
+
+#
+# erase($sock, $pcap, $page)
+# page = -1 means all pages
+#
+sub erase
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, $BLOCKS_PER_SECTOR,
+	"erasing", $REG_ERASE, 1,
+	\&erasecb, 0, '');
+}
+
+#
+# Verify the data
+#
+sub verifycb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "verifycb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# verify($sock, $pcap, $page, $image)
+#
+sub verify
+{
+	my ($sock, $pcap, $page, $image) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"verifying", $REG_RW, $BLOCKSIZE,
+	0, \&verifycb, $image);
+}
+
+#
+# Dump the data to file
+#
+sub dumpcb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "dumpcb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	print DUMPFILE $payload;
+
+	return 1;
+}
+
+#
+# dump($sock, $pcap, $page)
+#
+sub dump($$$)
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"dumping", $REG_RW, $BLOCKSIZE,
+	0, \&dumpcb, '');
+}
+
+#
+# Write the data
+#
+sub writepagecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "writepagecb: page=$page, block=$block\n" if ($g_debug);
+
+	my ($data) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$data .= pack("C", 0) x ($BLOCKSIZE - length($data));
+
+	return $data;
+}
+
+#
+# Read and verify the data
+#
+sub readpagecb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "readpagecb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# Create a direntry string based on filename
+# direntry($filename)
+#
+sub formatentry
+{
+	my ($bp_filename, $ap_filename) = @_;
+
+	$stat = stat($bp_filename) || die "=== ERR failed to stat file '$bp_filename'";
+	$bp_datetime = ctime($stat->mtime);
+	chop($bp_datetime);
+
+	$stat = stat($ap_filename) || die "=== ERR failed to stat file '$ap_filename'";
+	$ap_datetime = ctime($stat->mtime);
+	chop($ap_datetime);
+
+	# return 'size YYYY mm dd hh:mm bp_filename ; size YYYY mm dd hh:mm ap_filename'
+	return sprintf("%s( %s ) ; %s( %s )", $bp_filename, $bp_datetime, $ap_filename, $ap_datetime);
+}
+
+#
+# writepage($sock, $pcap, $page, $bp_filename, $ap_filename, $image)
+#
+sub writepage
+{
+	my ($sock, $pcap, $page, $bp_filename, $ap_filename, $image) = @_;
+
+	$error = flash($sock, $pcap, $page, 1,
+	"writing and verifying", $REG_RW, $BLOCKSIZE,
+	\&writepagecb, \&readpagecb, $image);
+
+	if (!$error) {
+		$error = writedir($sock, $pcap, $page, formatentry($bp_filename, $ap_filename));
+	} else {
+		# clear entry, an error has occured
+		writedir($sock, $pcap, $page, '');
+	}
+	return $error;
+}
+
+
+#
+# Read images from the hex files. 
+# then the files are converted from hex to bin format.
+#
+# readhexfiles($filename_bp, &filename_ap )
+#
+sub readhexfiles
+{
+	my ($filename_bp, $filename_ap) = @_;
+	local $/ = undef; # read entire file
+
+	# open input and output files
+	die "=== ERR no image file specified" if ((!defined($filename_bp)) or (!defined($filename_ap)));
+	
+	open(IMAGEFILE, $filename_bp) || die "=== ERR failed to open file '$filename_bp'";
+	print STDERR "=== Reading '$filename_bp'\n";
+	$data = <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	open(IMAGEFILE, $filename_ap) || die "=== ERR failed to open file '$filename_ap'";
+	print STDERR "=== Reading '$filename_ap'\n";
+	$data .= <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	print STDERR "=== Converting '$filename_bp' and '$filename_ap' to binary\n";
+	print STDERR "=== |" . ("-" x $PROGRESS_SIZE) . "|\n===  ";
+
+	$i = 0;
+	$interval = length($data) / ($PROGRESS_SIZE * 2);
+	$interval = 1 if ($interval < .5);
+
+	$bindata = '';
+	
+	for ($byte = 0; $byte < length($data); $byte += 2) {
+		$bytestr = substr($data, $byte, 2);
+		
+		$bindata .= pack("C", hex($bytestr));
+		
+		# print progress bar
+		$i++;
+		print STDERR "." if (0 == $i % $interval);
+	}
+	
+	# append FF's to fill buffer to $IMAGESIZE - 1
+	$bindata .= pack("C", 255) x (($PAGESIZE - $BLOCKSIZE) - length($bindata));
+	
+	print STDERR "\n";
+	
+	return $bindata;
+}
+
+#
+# Read RSU status from RSR status register
+# readrsustatus($sock, $pcap)
+#
+sub readrsustatus
+{
+	my ($sock, $pcap) = @_;
+
+	$size = $REG_STATUS_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_STATUS, 0, $size, 0, 0);
+	
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	$rsustatus = unpack("C", substr($result, 164, 1));
+
+	print STDERR sprintf("\nRSU Status:\n	Rdy      : %s\n	FpgaType : %s\n	ImageType: %s\n	Trig     : %s\n\n",
+	('Configuration ongoing', 'Configuration done')[$rsustatus & 1],
+	('loaded from flash', 'loaded via JTAG')[($rsustatus >> 2) & 1],
+	('Factory image is running', 'User image is running')[($rsustatus >> 3) & 1],
+	('Reconfiguration due to button reset'
+	, 'Unknown'
+	, 'Reconfiguration due to over temperature'
+	, 'Unknown'
+	, 'Reconfiguration due to user reset'
+	, 'Unknown'
+	, 'Reconfiguration due to watchdog reset' )[($rsustatus >> 4) & 7]);
+
+	return $error;
+}
+
+#
+# Read versions of all FPGA's (handy after reconfig, reset, clear, etc)
+# readversions($sock, $pcap)
+#
+sub readversions
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	($id, $version) = unpack("CC", $result);
+	$maj = $version >> 4;
+	$min = $version & 15;
+	print STDERR "RSP   version: $id\nBP    version: $maj.$min\n";
+
+	# read AP versions
+	for ($ap = 0; $ap < 4; $ap++) {
+		$packet = pack($MEPSENDFORMAT,
+		0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		1 << $ap, 0, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+		$sock->send_eth_frame($packet);
+		($result, $error) = readresponse($pcap);
+		return $error if $error;
+
+		($id, $version) = unpack("CC", $result);
+		$maj = $version >> 4;
+		$min = $version & 15;
+		print STDERR "AP[$id] version: $maj.$min\n";
+	}
+
+	return 0;
+}
+
+#
+# Find MAC addresses of active boards
+# ping($sock, $pcap)
+#
+sub ping
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+
+	while (!$error) {
+		($result, $error, $ethdr) = readresponse($pcap);
+		$mac = substr($ethdr, 12, 12);
+		$mac =~ s/(..)(..)(..)(..)(..)(..)/$1:$2:$3:$4:$5:$6/;
+		print STDERR "Found RSP board on MAC: ", $mac, "\n" if (!$error);
+	}
+
+	return 0;
+}
+
+#
+# Reconfigure. Load new firmware from flash into FPGA's. After loading
+# has finished, the FPGA will be reset and restart with new firmware.
+#
+# reconfig($sock, $pcap, $page)
+#
+sub reconfig
+{
+	my ($sock, $pcap, $page) = @_;
+
+	$cmd = $page & 15; # use bit 0..3
+	$cmd = $cmd | 128;
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RECONFIG, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+
+	# there will be no response
+	# ($response, $error) = readresponse($pcap);
+
+	return 0;
+}
+
+#
+# Reset control for all FPGA's
+#
+# clearctl($sock, $pcap, $cmd)
+# valid values for $cmd:  $CMD_SYNC, $CMD_CLEAR, $CMD_RESET
+#
+sub clearctl
+{
+	my ($sock, $pcap, $cmd) = @_;
+
+	$packet  = pack_rsu($TYPE_WRITE, $REG_CLEAR, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+	($response, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# Report result
+# report($error, $report_ok = false)
+#
+sub report
+{
+	my ($error, $report_ok) = @_;
+
+	if ($error || $g_error) {
+		print STDERR "=== FAILED\n";
+		exit 1;
+	}
+	else {
+		print STDERR "=== OK\n" if ($report_ok);
+	}
+}
+
+#
+# Change range of the page parameter
+# chkpage($page)
+#
+sub chkpage
+{
+	my ($page) = @_;
+
+	die "=== ERR no flash page specified, use -p" if (!defined($page));
+	if ($page < 0 || $page >= $PAGES) {
+		die "=== ERR invalid page index $page, should be >= 0 and < $PAGES";
+	}
+}
+
+#
+# Get rsu status and versions
+# version_cmd($sock, $pcap)
+#
+sub version_cmd
+{
+	my ($sock, $pcap) = @_;
+
+	$error = readrsustatus($sock, $pcap);
+	$error = readversions($sock, $pcap) && $error;
+	return $error;
+}
+
+sub main
+{
+	my $sock = 0;
+
+	print STDERR "=== rsuctl ($VERSION), for RSP3 boards only\n";
+	
+	Getopt::Std::getopts("i:m:lexwvb:a:dp:f:FrcsPVhq");
+
+	# check that at least one argument is present
+	usage() if ($opt_h);
+
+	if (!defined($opt_m)) {
+		print STDERR "=== ERR Must specify MAC address using -m option";
+		usage();
+	}
+
+	usage() unless ($opt_l || $opt_e || $opt_x || $opt_w || $opt_v ||
+	$opt_d || $opt_r || $opt_c || $opt_s || $opt_P || $opt_V || $opt_h);
+
+	# assign default values
+	$opt_i = 'eth1'              if !defined($opt_i);
+
+	# Create socket to the boards
+	die "=== ERR this program needs to run as root to send/recv raw Ethernet frames" if (0 != geteuid());
+
+	$sock = new Net::RawIP;
+	$sock->ethnew($opt_i);
+	$sock->ethset(dest => $opt_m);
+
+	$TIMEOUT=5;
+	$pcap = $sock->pcapinit($opt_i, "not ether dst $opt_m", $ETHLEN, $TIMEOUT);
+
+	die "=== ERR could not open raw socket to RSP board(s) on $opt_i:$opt_m: $!\n" unless $sock;
+
+	print STDERR "=== connected to RSP board(s) on $opt_i:$opt_m\n";
+
+	if (!defined($opt_q)) {
+		print STDERR "=== checking for running RSPDriver\n";
+		($payload, $error) = readresponse($pcap);
+		die "=== RSPDriver appears to be running, stop it first\n" if ($payload ne '');
+	}
+
+	if (defined($opt_l)) {
+
+		# LIST IMAGES IN FLASH
+		#
+		# readdirentry for all pages
+
+		print STDERR "=== listing flash contents\n\n";
+		for ($page = 0; $page < $PAGES; $page++) {
+			($entry, $error) = readdirentry($sock, $pcap, $page);
+			break if $error;
+			print STDERR sprintf("%s %2d: %s\n", 
+				(0 == $page ? 'Factory':'Image  '),	$page, $entry) if ((0 == $page) || $entry ne '');
+		}
+		print STDERR "\n";
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_e)) {
+
+		# ERASE
+		#
+		# erase flash page
+
+		die "=== ERR erasing factory image (page 0) prohibited, use -F to force erase of factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		report(erase($sock, $pcap, $opt_p), true);
+
+		return;
+	}
+
+	if (defined($opt_x)) {
+
+		# RECONFIG
+		#
+		# reconfig BP and AP
+		# wait $TO seconds
+		# clear
+		# wait $TO seconds
+		# read rsustatus & version
+		
+		chkpage($opt_p);
+		report(reconfig($sock, $pcap, $opt_p));
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		report($error) if $error;
+		print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		$error = readrsustatus($sock, $pcap);
+		$error = readversions($sock, $pcap) && $error;
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_w)) {
+
+		# WRITE
+		#
+		# readimage
+		# erase page
+		# write & verify page
+
+		die "=== ERR writing to factory image (page 0) prohibited, use -F to force write to factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		$image = readhexfiles($opt_b, $opt_a);
+		report(erase($sock, $pcap, $opt_p));
+		report(writepage($sock, $pcap, $opt_p, $opt_b, $opt_a, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_v)) {
+
+		# VERIFY
+		#
+		# readimage reference
+		# verify flash page against reference
+
+		chkpage($opt_p);
+		$image = readhexfiles($opt_b, $opt_a);
+		report(verify($sock, $pcap, $opt_p, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_d)) {
+
+		# DUMP
+		#
+		# dump flash page to file
+
+		chkpage($opt_p);
+		die "=== ERR no image file specified" if (!defined($opt_f));
+
+		open(DUMPFILE, ">$opt_f") || die "=== ERR failed to open file '$opt_f' for writing\n";
+		print STDERR "=== Dumping to file '$opt_f'\n";
+		$error = dump($sock, $pcap, $opt_p);
+		close(DUMPFILE);
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_r)) {
+
+		# RESET
+		#
+		# send reset (factory image (page 0) will start on BP)
+		# wait $DTO seconds
+		# reconfigure AP's with page 8 (factory default for AP's)
+		# wait $TO second
+		# read rsustatus and versions
+
+		report(reconfig($sock, $pcap, 0)); # set image 0 (factory image)
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		print STDERR "=== reconfiguring BP and AP's with factory image\n";
+
+		# don't check the error return code because we know
+		# it indicates failure while the reconfig is actually in progress
+		
+		report(version_cmd($sock, $pcap) && $error, true);
+
+		return;
+	}
+
+	if (defined($opt_c)) {
+
+		# CLEAR
+		#
+		# send clear
+		# wait $TO seconds
+		# read rsustatus and versions
+
+		report(clearctl($sock, $pcap, $CMD_CLEAR));
+		print STDERR "=== waiting $TO seconds after clear\n"; sleep $TO;
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+
+	if (defined($opt_s)) {
+
+		# SYNC
+		#
+		# send sync
+
+		report(clearctl($sock, $pcap, $CMD_SYNC), true);
+
+		return;
+	}
+
+	if (defined($opt_P)) {
+		report(ping($sock, $pcap), true);
+		return;
+	}
+
+	if (defined($opt_V)) {
+		# versions
+		#
+		# read rsustatus and versions
+
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+}
+
+main();
\ No newline at end of file
diff --git a/Firmware/tools/src/rsuctl3_reset b/Firmware/tools/src/rsuctl3_reset
new file mode 100755
index 0000000000000000000000000000000000000000..cd65665acd130682a9cd069863ac35764fd336f1
--- /dev/null
+++ b/Firmware/tools/src/rsuctl3_reset
@@ -0,0 +1,970 @@
+#!/usr/bin/perl
+$VERSION = q$Revision: 3.00$;
+
+#
+# rsuctl # Remote System Update - program firmware in FPGA flash
+#
+# Usage: rsuctl [options] command
+#
+#   options
+#     [-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+#     -m mac-address # MAC-address of the target board (Mandatory)
+#
+#   command (specify at least one of the following)
+#     [-l] # List the images currently stored in all pages of the flash
+#
+#     [-e -p page [-F]]                                 # Erase flash page,  0 <= page < 16 (-F forces erase of page 0)
+#     [-x -p page                                       # Start (load & reset) new firmware from the specified page
+#     [-w -p page -b bp_img[.hex] -a ap_img[.hex] [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+#     [-v -p page -b bp_img[.hex] -a ap_img[.hex]]      # Compare flash page with bp and ap image
+#     [-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+#
+#     [-r] # Reset all FPGA's (load factory image)
+#     [-c] # Clear all FPGA's (restart current image)
+#     [-s] # Send SYNC pulse to all FPGA's
+#
+#     [-q] # Prevent checking for running RSPDriver (prevents pause at start)
+#     [-P] # Ping the board (find board when using broadcast address)
+#     [-V] # Read RSU status and RSR versions
+#     [-h] # This help text
+#
+#
+
+#use Socket;
+use Net::RawIP;
+use Getopt::Std;
+use File::stat;
+use Switch;
+use POSIX; # for geteuid()
+
+$g_debug  = 0;  # set to 1 to enable debug output
+$g_packet = ''; # global packet string
+$g_error  = 0;  # global error status
+
+#
+# Definitions
+#
+$PID_RSR      = 0x01;
+$PID_RSU      = 0x02;
+
+$REG_STATUS       = 0x00;
+$REG_STATUS_SIZE  = 200;
+$REG_VERSION      = 0x01;
+$REG_VERSION_SIZE = 2;
+
+$REG_RW       = 0x01;
+$REG_ERASE    = 0x02;
+$REG_RECONFIG = 0x03;
+$REG_CLEAR    = 0x04;
+
+$CMD_ERASE = 0x01;
+
+$CMD_SYNC  = 0x01;
+$CMD_CLEAR = 0x02;
+$CMD_RESET = 0x04;
+
+$MEPHDRSIZE = 16;
+
+$TYPE_READ     = 0x01;
+$TYPE_WRITE    = 0x02;
+$TYPE_READACK  = 0x03;
+$TYPE_WRITEACK = 0x04;
+
+$ADDR_BLP_NONE = 0x00;
+$ADDR_RSP      = 0x01;
+
+$FLASHSIZE         = 64 * 1024 * 1024; # 64MB
+$BLOCKSIZE         = 1024; # block  = 1024 bytes
+$BLOCKS_PER_SECTOR = 128;  # sector = 128 blocks
+$SECTORS_PER_PAGE  = 32;   # page   = 32 sectors
+$BLOCKS_PER_PAGE   = $BLOCKS_PER_SECTOR * $SECTORS_PER_PAGE; # 4096 blocks
+$PAGESIZE          = $SECTORS_PER_PAGE * $BLOCKS_PER_SECTOR * $BLOCKSIZE; # 4MB  
+$PAGES             = 16;
+
+$PROGRESS_SIZE     = 32;
+
+$RESPONSETIMEOUT = 2;
+$TO = 5;
+
+#
+# pack format for the MEP message
+#
+# C 0x10 Ethernet Type byte 0
+# C 0xFA Ethernet Type byte 1
+# C TYPE
+# C STATUS
+# S FRAMELENGTH
+# C BLPID
+# C RSP
+# C PID
+# C REGID
+# S OFFSET
+# S PAYLOAD_LENGTH
+# S SEQ_NR
+# S RESERVED
+#
+$MEPSENDFORMAT="CCCCSCCCCSSSS";
+$MEPRECVFORMAT="H28CCSCCCCSSSS";
+
+# ETHLEN = max length of Ethernet payload
+$ETHLEN = 1500;
+
+#
+# Error code
+#
+%STATUS = ( 0 => "=== OK",
+	1 => "=== ERR unknown MEP message type",
+	2 => "=== ERR illegal BLP address",
+	3 => "=== ERR invalid PID",
+	4 => "=== ERR register does not exist",
+	5 => "=== ERR offset too large",
+	6 => "=== ERR message is too large",
+	7 => "=== ERR message corruption during RSP processing",
+	8 => "=== ERR message lost during RSP processing"
+);
+
+#
+# usage
+#
+sub usage
+{
+	print STDERR <<END;
+
+	# Remote System Update - program firmware in FPGA flash
+		
+	Usage: rsuctl [options] command
+
+	options
+	[-i ifname]    # Ethernet interface name on which to communicate (default is eth1)
+	-m mac-address # MAC-address of the target board (Mandatory)
+
+	command (specify at least one of the following, need -m option at least)
+	[-l] # List the images currently stored in all pages of the flash
+
+	[-e -p page [-F]]                                 # Erase flash page, 0 <= page < 16 (-F forces erase of page 0)
+	[-x -p page                                       # Start (load & reset) new firmware from the specified page
+	[-w -p page -b bp_image.hex -a ap_image.hex [-F]] # Write bp and ap image into specified page (-F forces write to page 0)
+	[-v -p page -b bp_image.hex -a ap_image.hex]      # Compare flash page with bp and ap image
+	[-d -p page -f dumpfile.bin]                      # Dump flash page to dmp.bin
+
+	[-r] # Reset all FPGA's (load factory image)
+	[-c] # Clear all FPGA's (restart current image)
+	[-s] # Send SYNC pulse to all FPGA's
+
+	[-q] # Prevent checking for running RSPDriver (prevents pause at start)
+	[-P] # Ping the board (find board when using broadcast address)
+	[-V] # Read RSU status and RSR versions
+	[-h] # This help text
+
+END
+
+	exit;
+}
+
+#
+# pack_rsu($type, $regid, $size, $offset)
+#
+# Pack an RSU message
+#
+sub pack_rsu
+{
+	my ($type, $regid, $size, $offset) = @_;
+	
+	return pack($MEPSENDFORMAT,
+		0x10, 0xFA, $type, 0x00, $MEPHDRSIZE + $size,
+		$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSU, $regid, $offset, $size, 0, 0);;
+}
+
+#
+# pcapcallback, save packet in $g_packet
+#
+# savepacket($handle, $hdr, $packet)
+#
+sub savepacket
+{
+	my ($handle, $hdr, $packet) = @_;
+	$g_packet = $packet;
+}
+
+#
+# ($payload,$error) = readresponse($pcap)
+#
+sub readresponse
+{
+	my ($pcap) = @_;
+	my ($retval) = 0;
+
+	do { # keep going when we receive invalid messages
+		$retval = 0; # assume success
+
+		# wait for message to arrive
+		eval {
+			use POSIX qw(SIGALRM);
+			POSIX::sigaction(SIGALRM,
+			POSIX::SigAction->new(sub { die }))
+			|| die "=== ERR error setting SIGALRM handler: $!\n";
+
+			alarm $RESPONSETIMEOUT;
+			(0 == loop ($pcap, 1, \&savepacket, ''))
+			|| die "=== ERR Failed to receive response";
+			alarm 0;
+		};
+		if ($@) {
+			$retval = 1; # fatal (timeout)
+			return ('', $retval, '');
+		}
+
+		($ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved) =
+		unpack($MEPRECVFORMAT, $g_packet);
+
+		if ($TYPE_WRITEACK != $type && $TYPE_READACK != $type) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid TYPE\n" if ($g_debug);
+		}
+		if ($PID_RSU != $pid && $PID_RSR != $pid) {
+			$retval = 2; # ignore invalid message
+			print STDERR "ignoring message with invalid PID\n" if ($g_debug);
+		}
+
+		printf STDERR "0x%s 0x%02x 0x%02x %5d 0x%02x 0x%02x 0x%02x 0x%02x %5d %5d %5d %5d\n",
+		$ethdr, $type, $status, $framesize, $blp, $rsp, $pid, $regid, $offset, $size, $seqnr, $reserved if ($g_debug);
+
+		if (0 == $retval && 0 != $status) {
+			$retval = 1; # fatal
+			print STDERR "=== FATAL protocol error: status=", $STATUS{$status}, "\n";
+			print STDERR "=== Are you using the correct Ethernet interface? Use -i argument to change, e.g. -i eth1\n";
+			exit;
+		}
+	} until ($retval != 2);
+
+	return ('', $retval, '') if ($retval);
+
+	return (substr($g_packet, 14 + $MEPHDRSIZE), $retval, $ethdr);
+}
+
+#
+# flash($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image)
+#
+sub flash
+{
+	my ($sock, $pcap, $page, $blockinc, $msg, $cmd, $size, $writecb, $readcb, $image) = @_;
+
+	printf STDERR "=== %s page %02d\n", $msg, $page;
+	print  STDERR "=== |" . ("-" x $SECTORS_PER_PAGE) . "|\n===  ";
+	#print STDERR "Image: $image \n"; 
+	
+	my ($success) = 1;
+	# leave last block for directory entry
+	my ($block);
+	for ($block = 0; $block < $BLOCKS_PER_PAGE - 1; $block += $blockinc) {
+		if ($writecb) {
+			$packet  = pack_rsu($TYPE_WRITE, $cmd, $size, ($page * $BLOCKS_PER_PAGE) + $block);
+
+			$packet .= &$writecb($page, $block, $image);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+
+			return $error if ($error);
+		}
+
+		if ($readcb) {
+			$packet  = pack_rsu($TYPE_READ, $cmd, $size,
+			($page * $BLOCKS_PER_PAGE) + $block);
+
+			$sock->send_eth_frame($packet);
+			($payload, $error) = readresponse($pcap);
+			return $error if ($error);
+
+			$success = &$readcb($page, $block, $payload, $image) && $success;
+		}
+
+		if (0 == $block % $BLOCKS_PER_SECTOR) {
+			if ($success) { print STDERR "."; }
+			else          { print STDERR "X"; }
+			$success = 1;
+		}
+	}
+	print STDERR "\n";
+
+	return 0;
+}
+
+#
+# writedir($sock, $pcap, $page, $entry)
+# Write the entry to the last block of page $page
+#
+sub writedir
+{
+	my ($sock, $pcap, $page, $entry) = @_;
+
+	die "=== ERR internal error: size of direntry > $BLOCKSIZE" if (length($entry) > $BLOCKSIZE);
+
+	print STDERR "writing dir @", (($page + 1) * $BLOCKS_PER_PAGE) - 1, "\n" if ($g_debug);
+
+	# add zero padding if needed
+	$entry .= pack("C", 0) x ($BLOCKSIZE - length($entry)) if (length($entry) < $BLOCKSIZE);
+
+	# write last block with dir entry
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) - 1);
+	$packet .= $entry;
+
+	my $error = 2;
+	$sock->send_eth_frame($packet);
+
+	($result, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# readdir($sock, $pcap, $page)
+# Read the directory entry from the specified page
+#
+sub readdirentry
+{
+	my ($sock, $pcap, $page) = @_;
+
+	my ($packet)  = pack_rsu($TYPE_READ, $REG_RW, $BLOCKSIZE,
+	(($page + 1) * $BLOCKS_PER_PAGE) -1);
+
+	$sock->send_eth_frame($packet);
+
+	my $error = 0;
+	($entry, $error) = readresponse($pcap);
+	return ('', $error) if $error;
+
+	$entry = '' if (substr($entry,0,1) eq pack("C", 0xff));
+
+	return ($entry, $error);
+}
+
+#
+# erasecb($page, $block, $image)
+#
+sub erasecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "erasecb: $page, $block\n" if ($g_debug);
+
+	return pack("C", $CMD_ERASE);
+}
+
+#
+# erase($sock, $pcap, $page)
+# page = -1 means all pages
+#
+sub erase
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, $BLOCKS_PER_SECTOR,
+	"erasing", $REG_ERASE, 1,
+	\&erasecb, 0, '');
+}
+
+#
+# Verify the data
+#
+sub verifycb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "verifycb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# verify($sock, $pcap, $page, $image)
+#
+sub verify
+{
+	my ($sock, $pcap, $page, $image) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"verifying", $REG_RW, $BLOCKSIZE,
+	0, \&verifycb, $image);
+}
+
+#
+# Dump the data to file
+#
+sub dumpcb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "dumpcb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	print DUMPFILE $payload;
+
+	return 1;
+}
+
+#
+# dump($sock, $pcap, $page)
+#
+sub dump($$$)
+{
+	my ($sock, $pcap, $page) = @_;
+
+	return flash($sock, $pcap, $page, 1,
+	"dumping", $REG_RW, $BLOCKSIZE,
+	0, \&dumpcb, '');
+}
+
+#
+# Write the data
+#
+sub writepagecb
+{
+	my ($page, $block, $image) = @_;
+
+	print STDERR "writepagecb: page=$page, block=$block\n" if ($g_debug);
+
+	my ($data) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$data .= pack("C", 0) x ($BLOCKSIZE - length($data));
+
+	return $data;
+}
+
+#
+# Read and verify the data
+#
+sub readpagecb
+{
+	my ($page, $block, $payload, $image) = @_;
+
+	print STDERR "readpagecb: page=$page, block=$block, length(payload)=", length($payload), "\n" if ($g_debug);
+
+	my ($refblock) = substr($image, $block * $BLOCKSIZE, $BLOCKSIZE);
+
+	# append zeroes to fill buffer to $BLOCKSIZE
+	$refblock .= pack("C", 0) x ($BLOCKSIZE - length($refblock));
+
+	if ($payload ne $refblock) {
+		print STDERR "=== ERR failed to verify block $block in page $page\n" if ($g_debug);
+		$g_error = 1;
+		return 0;
+	}
+
+	return 1;
+}
+
+#
+# Create a direntry string based on filename
+# direntry($filename)
+#
+sub formatentry
+{
+	my ($bp_filename, $ap_filename) = @_;
+
+	$stat = stat($bp_filename) || die "=== ERR failed to stat file '$bp_filename'";
+	$bp_datetime = ctime($stat->mtime);
+	chop($bp_datetime);
+
+	$stat = stat($ap_filename) || die "=== ERR failed to stat file '$ap_filename'";
+	$ap_datetime = ctime($stat->mtime);
+	chop($ap_datetime);
+
+	# return 'size YYYY mm dd hh:mm bp_filename ; size YYYY mm dd hh:mm ap_filename'
+	return sprintf("%s( %s ) ; %s( %s )", $bp_filename, $bp_datetime, $ap_filename, $ap_datetime);
+}
+
+#
+# writepage($sock, $pcap, $page, $bp_filename, $ap_filename, $image)
+#
+sub writepage
+{
+	my ($sock, $pcap, $page, $bp_filename, $ap_filename, $image) = @_;
+
+	$error = flash($sock, $pcap, $page, 1,
+	"writing and verifying", $REG_RW, $BLOCKSIZE,
+	\&writepagecb, \&readpagecb, $image);
+
+	if (!$error) {
+		$error = writedir($sock, $pcap, $page, formatentry($bp_filename, $ap_filename));
+	} else {
+		# clear entry, an error has occured
+		writedir($sock, $pcap, $page, '');
+	}
+	return $error;
+}
+
+
+#
+# Read images from the hex files. 
+# then the files are converted from hex to bin format.
+#
+# readhexfiles($filename_bp, &filename_ap )
+#
+sub readhexfiles
+{
+	my ($filename_bp, $filename_ap) = @_;
+	local $/ = undef; # read entire file
+
+	# open input and output files
+	die "=== ERR no image file specified" if ((!defined($filename_bp)) or (!defined($filename_ap)));
+	
+	open(IMAGEFILE, $filename_bp) || die "=== ERR failed to open file '$filename_bp'";
+	print STDERR "=== Reading '$filename_bp'\n";
+	$data = <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	open(IMAGEFILE, $filename_ap) || die "=== ERR failed to open file '$filename_ap'";
+	print STDERR "=== Reading '$filename_ap'\n";
+	$data .= <IMAGEFILE>; # read entire file
+	close(IMAGEFILE);
+	
+	# remove \r\n from data file
+	$data =~ s/\r//;
+	$data =~ s/\n//;
+	
+	print STDERR "=== Converting '$filename_bp' and '$filename_ap' to binary\n";
+	print STDERR "=== |" . ("-" x $PROGRESS_SIZE) . "|\n===  ";
+
+	$i = 0;
+	$interval = length($data) / ($PROGRESS_SIZE * 2);
+	$interval = 1 if ($interval < .5);
+
+	$bindata = '';
+	
+	for ($byte = 0; $byte < length($data); $byte += 2) {
+		$bytestr = substr($data, $byte, 2);
+		
+		$bindata .= pack("C", hex($bytestr));
+		
+		# print progress bar
+		$i++;
+		print STDERR "." if (0 == $i % $interval);
+	}
+	
+	# append FF's to fill buffer to $IMAGESIZE - 1
+	$bindata .= pack("C", 255) x (($PAGESIZE - $BLOCKSIZE) - length($bindata));
+	
+	print STDERR "\n";
+	
+	return $bindata;
+}
+
+#
+# Read RSU status from RSR status register
+# readrsustatus($sock, $pcap)
+#
+sub readrsustatus
+{
+	my ($sock, $pcap) = @_;
+
+	$size = $REG_STATUS_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_STATUS, 0, $size, 0, 0);
+	
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	$rsustatus = unpack("C", substr($result, 164, 1));
+
+	print STDERR sprintf("\nRSU Status:\nRdy       : %s\nFpgaType  : %s\nImageType : %s\nTrig      : %s\n\n",
+	('Configuration ongoing', 'Configuration done')[$rsustatus & 1],
+	('loaded from flash', 'loaded via JTAG')[($rsustatus >> 2) & 1],
+	('Factory image is running', 'User image is running')[($rsustatus >> 3) & 1],
+	('Reconfiguration due to button reset'
+	, 'Reconfiguration due to over temperature'
+	, 'Reconfiguration due to user reset'
+	, 'Unknown'
+	, 'Reconfiguration due to watchdog reset'
+	, 'Unknown'
+	, 'Unknown'
+	, 'Unknown' )[($rsustatus >> 4) & 7]);
+
+	return $error;
+}
+
+#
+# Read versions of all FPGA's (handy after reconfig, reset, clear, etc)
+# readversions($sock, $pcap)
+#
+sub readversions
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+	($result, $error) = readresponse($pcap);
+	return $error if $error;
+
+	($id, $version) = unpack("CC", $result);
+	$maj = $version >> 4;
+	$min = $version & 15;
+	print STDERR "RSP   version: $id\nBP    version: $maj.$min\n";
+
+	# read AP versions
+	for ($ap = 0; $ap < 4; $ap++) {
+		$packet = pack($MEPSENDFORMAT,
+		0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+		1 << $ap, 0, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+		$sock->send_eth_frame($packet);
+		($result, $error) = readresponse($pcap);
+		return $error if $error;
+
+		($id, $version) = unpack("CC", $result);
+		$maj = $version >> 4;
+		$min = $version & 15;
+		print STDERR "AP[$id] version: $maj.$min\n";
+	}
+
+	return 0;
+}
+
+#
+# Find MAC addresses of active boards
+# ping($sock, $pcap)
+#
+sub ping
+{
+	my ($sock, $pcap) = @_;
+
+	# read BP version
+	$size = $REG_VERSION_SIZE;
+	$packet = pack($MEPSENDFORMAT,
+	0x10, 0xFA, $TYPE_READ, 0x00, $MEPHDRSIZE + $size,
+	$ADDR_BLP_NONE, $ADDR_RSP, $PID_RSR, $REG_VERSION, 0, $size, 0, 0);
+	$sock->send_eth_frame($packet);
+	my $error = 0;
+
+	while (!$error) {
+		($result, $error, $ethdr) = readresponse($pcap);
+		$mac = substr($ethdr, 12, 12);
+		$mac =~ s/(..)(..)(..)(..)(..)(..)/$1:$2:$3:$4:$5:$6/;
+		print STDERR "Found RSP board on MAC: ", $mac, "\n" if (!$error);
+	}
+
+	return 0;
+}
+
+#
+# Reconfigure. Load new firmware from flash into FPGA's. After loading
+# has finished, the FPGA will be reset and restart with new firmware.
+#
+# reconfig($sock, $pcap, $page)
+#
+sub reconfig
+{
+	my ($sock, $pcap, $page) = @_;
+
+	$cmd = $page & 15; # use bit 0..3
+	$cmd = $cmd | 128;
+	$packet  = pack_rsu($TYPE_WRITE, $REG_RECONFIG, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+
+	# there will be no response
+	# ($response, $error) = readresponse($pcap);
+
+	return 0;
+}
+
+#
+# Reset control for all FPGA's
+#
+# clearctl($sock, $pcap, $cmd)
+# valid values for $cmd:  $CMD_SYNC, $CMD_CLEAR, $CMD_RESET
+#
+sub clearctl
+{
+	my ($sock, $pcap, $cmd) = @_;
+
+	$packet  = pack_rsu($TYPE_WRITE, $REG_CLEAR, 1, 0);
+	$packet .= pack("C", $cmd);
+
+	$sock->send_eth_frame($packet);
+	($response, $error) = readresponse($pcap);
+
+	return $error;
+}
+
+#
+# Report result
+# report($error, $report_ok = false)
+#
+sub report
+{
+	my ($error, $report_ok) = @_;
+
+	if ($error || $g_error) {
+		print STDERR "=== FAILED\n";
+		exit 1;
+	}
+	else {
+		print STDERR "=== OK\n" if ($report_ok);
+	}
+}
+
+#
+# Change range of the page parameter
+# chkpage($page)
+#
+sub chkpage
+{
+	my ($page) = @_;
+
+	die "=== ERR no flash page specified, use -p" if (!defined($page));
+	if ($page < 0 || $page >= $PAGES) {
+		die "=== ERR invalid page index $page, should be >= 0 and < $PAGES";
+	}
+}
+
+#
+# Get rsu status and versions
+# version_cmd($sock, $pcap)
+#
+sub version_cmd
+{
+	my ($sock, $pcap) = @_;
+
+	$error = readrsustatus($sock, $pcap);
+	$error = readversions($sock, $pcap) && $error;
+	return $error;
+}
+
+sub main
+{
+	my $sock = 0;
+
+	print STDERR "=== rsuctl ($VERSION), for RSP3 boards only\n";
+	
+	Getopt::Std::getopts("i:m:lexwvb:a:dp:f:FrcsPVhq");
+
+	# check that at least one argument is present
+	usage() if ($opt_h);
+
+	if (!defined($opt_m)) {
+		print STDERR "=== ERR Must specify MAC address using -m option";
+		usage();
+	}
+
+	usage() unless ($opt_l || $opt_e || $opt_x || $opt_w || $opt_v ||
+	$opt_d || $opt_r || $opt_c || $opt_s || $opt_P || $opt_V || $opt_h);
+
+	# assign default values
+	$opt_i = 'eth1'              if !defined($opt_i);
+
+	# Create socket to the boards
+	die "=== ERR this program needs to run as root to send/recv raw Ethernet frames" if (0 != geteuid());
+
+	$sock = new Net::RawIP;
+	$sock->ethnew($opt_i);
+	$sock->ethset(dest => $opt_m);
+
+	$TIMEOUT=5;
+	$pcap = $sock->pcapinit($opt_i, "not ether dst $opt_m", $ETHLEN, $TIMEOUT);
+
+	die "=== ERR could not open raw socket to RSP board(s) on $opt_i:$opt_m: $!\n" unless $sock;
+
+	print STDERR "=== connected to RSP board(s) on $opt_i:$opt_m\n";
+
+	if (!defined($opt_q)) {
+		print STDERR "=== checking for running RSPDriver\n";
+		($payload, $error) = readresponse($pcap);
+		die "=== RSPDriver appears to be running, stop it first\n" if ($payload ne '');
+	}
+
+	if (defined($opt_l)) {
+
+		# LIST IMAGES IN FLASH
+		#
+		# readdirentry for all pages
+
+		print STDERR "=== listing flash contents\n\n";
+		for ($page = 0; $page < $PAGES; $page++) {
+			($entry, $error) = readdirentry($sock, $pcap, $page);
+			break if $error;
+			print STDERR sprintf("%s %2d: %s\n", 
+				(0 == $page ? 'Factory':'Image  '),	$page, $entry) if ((0 == $page) || $entry ne '');
+		}
+		print STDERR "\n";
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_e)) {
+
+		# ERASE
+		#
+		# erase flash page
+
+		die "=== ERR erasing factory image (page 0) prohibited, use -F to force erase of factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		report(erase($sock, $pcap, $opt_p), true);
+
+		return;
+	}
+
+	if (defined($opt_x)) {
+
+		# RECONFIG
+		#
+		# reconfig BP and AP
+		# wait $TO seconds
+		# clear
+		# wait $TO seconds
+		# read rsustatus & version
+		
+		chkpage($opt_p);
+		report(reconfig($sock, $pcap, $opt_p));
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		report($error) if $error;
+		#print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		#$error = readrsustatus($sock, $pcap);
+		#$error = readversions($sock, $pcap) && $error;
+		#report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_w)) {
+
+		# WRITE
+		#
+		# readimage
+		# erase page
+		# write & verify page
+
+		die "=== ERR writing to factory image (page 0) prohibited, use -F to force write to factory image" if (!$opt_F && (0 == $opt_p));
+		chkpage($opt_p);
+
+		$image = readhexfiles($opt_b, $opt_a);
+		report(erase($sock, $pcap, $opt_p));
+		report(writepage($sock, $pcap, $opt_p, $opt_b, $opt_a, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_v)) {
+
+		# VERIFY
+		#
+		# readimage reference
+		# verify flash page against reference
+
+		chkpage($opt_p);
+		$image = readhexfiles($opt_b, $opt_a);
+		report(verify($sock, $pcap, $opt_p, $image), true);
+
+		return;
+	}
+
+	if (defined($opt_d)) {
+
+		# DUMP
+		#
+		# dump flash page to file
+
+		chkpage($opt_p);
+		die "=== ERR no image file specified" if (!defined($opt_f));
+
+		open(DUMPFILE, ">$opt_f") || die "=== ERR failed to open file '$opt_f' for writing\n";
+		print STDERR "=== Dumping to file '$opt_f'\n";
+		$error = dump($sock, $pcap, $opt_p);
+		close(DUMPFILE);
+		report($error, true);
+
+		return;
+	}
+
+	if (defined($opt_r)) {
+
+		# RESET
+		#
+		# send reset (factory image (page 0) will start on BP)
+		# wait $DTO seconds
+		# reconfigure AP's with page 8 (factory default for AP's)
+		# wait $TO second
+		# read rsustatus and versions
+
+		report(reconfig($sock, $pcap, 0)); # set image 0 (factory image)
+		report(clearctl($sock, $pcap, $CMD_RESET));
+		print STDERR "=== waiting $TO seconds after reset\n"; sleep $TO;
+		print STDERR "=== reconfiguring BP and AP's with factory image\n";
+
+		# don't check the error return code because we know
+		# it indicates failure while the reconfig is actually in progress
+		
+		report(version_cmd($sock, $pcap) && $error, true);
+
+		return;
+	}
+
+	if (defined($opt_c)) {
+
+		# CLEAR
+		#
+		# send clear
+		# wait $TO seconds
+		# read rsustatus and versions
+
+		report(clearctl($sock, $pcap, $CMD_CLEAR));
+		print STDERR "=== waiting $TO seconds after clear\n"; sleep $TO;
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+
+	if (defined($opt_s)) {
+
+		# SYNC
+		#
+		# send sync
+
+		report(clearctl($sock, $pcap, $CMD_SYNC), true);
+
+		return;
+	}
+
+	if (defined($opt_P)) {
+		report(ping($sock, $pcap), true);
+		return;
+	}
+
+	if (defined($opt_V)) {
+		# versions
+		#
+		# read rsustatus and versions
+
+		report(version_cmd($sock, $pcap), true);
+
+		return;
+	}
+}
+
+main();