Browse Source

flashmax.pl: support multiversion (v2) firmware files

Support multiversion firmware files for "make upload PORT=" or calling
flashmax.pl directly.

flashmax.pl can now also write the board revision to flash if needed.

This should conclude the multi-version firmware support.
H. Peter Anvin 2 years ago
parent
commit
5e6c806502
2 changed files with 181 additions and 18 deletions
  1. 2 6
      Makefile
  2. 179 12
      tools/flashmax.pl

+ 2 - 6
Makefile

@@ -84,9 +84,7 @@ ifeq ($(PORT),)
 
 # Generic upload for newer firmware
 upload:
-	@echo 'NOT IMPLEMENTED YET, should be:'
-	@echo '$(CURL) -v --data-binary @fpga/output/$(PROJECT).fw http://$(ip)/sys/fwupdate'
-	@exit 1
+	$(CURL) -v --data-binary @fpga/output/$(PROJECT).fw 'http://$(ip)/sys/fwupdate'
 
 # Version-specific uploads for older firmware
 upload-v%:
@@ -102,9 +100,7 @@ else
 
 # Generic upload for newer firmware
 upload:
-	@echo 'NOT IMPLEMENTED YET, should be:'
-	@echo '$(PERL) ./tools/flashmax.pl fpga/output/$(PROJECT).fw $(PORT) $(FLASHOPT)'
-	@exit 1
+	$(PERL) ./tools/flashmax.pl fpga/output/$(PROJECT).fw '$(PORT)' $(FLASHOPT)
 
 # Version-specific uploads for older firmware
 upload-v%:

+ 179 - 12
tools/flashmax.pl

@@ -9,12 +9,12 @@ use Time::HiRes qw(usleep tv_interval);
 use Digest::CRC qw(crc32);
 use v5.10;			# For "state"
 
-my $esptool = ($^O eq 'MSWin32') ? 'esptool.exe' : 'esptool.py';
+my $esptool = 'esptool.py';
 $esptool = $ENV{'ESPTOOL'} || $esptool;
 
-my $esp_retries = 5;
+my $esp_retries = 10;
 
-my $FW_MAGIC = 0x7a07fbd6;
+my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
 
 my %datatypes = (
     'end'       => 0,		# End of data
@@ -33,10 +33,14 @@ foreach my $t (keys(%datatypes)) {
     $type[$datatypes{$t}] = $t;
 }
 
-my $FDF_OPTIONAL = 0x0001;
+my $FDF_OPTIONAL  = 0x0001;
+my $FDF_PRETARGET = 0x0002;
 
 my $STRING_MAX_LEN = 4095;
 
+my $boardinfo_addr = 0;
+my $boardinfo_len  = 4096;
+
 # For debug; DC1-4 replaced with functional names
 my @ascii = qw(NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI
 	       DLE XON WRST XOFF WGO NAK SYN ETB CAN EM SUB ESC FS GS RS US);
@@ -98,6 +102,12 @@ sub getint($) {
     return $o;
 }
 
+sub match_version($$) {
+    my($ver,$pattern) = @_;
+
+    return 1;			# FIX THIS
+}
+
 sub filelen($) {
     my($f) = @_;
     my @s = stat($f);
@@ -178,6 +188,7 @@ sub run_esptool($$$$@)
 	    print STDERR "ok\n";
 	    last;
 	} elsif ($retries) {
+	    @output = ();
 	    print STDERR "failed, retrying\n";
 	    usleep(1000000);
 	} else {
@@ -191,15 +202,106 @@ sub run_esptool($$$$@)
     return %outinfo;
 }
 
+sub target_string_valid($)
+{
+    my($v) = @_;
+
+    return $v =~ /^(\S+) v((?:0|[1-9][0-9]*)(?:\.0|\.[1-9][0-9]*)*)(?: ([a-zA-Z0-9]*))?$/;
+}
+
+sub match_version($$)
+{
+    my($version,$pattern) = @_;
+
+    my @vv = target_string_valid($version);
+
+    return 0 unless (defined($vv[0]));
+    
+    my $v_board = $vv[0];
+    my @v_ver   = split(/\./, $vv[1]);
+    my $v_flags = $vv[2];
+
+    return 1 if ($pattern eq $v_board); # Board only matchall pattern
+    
+    if ($pattern !~ /^(\S+) v((?:0|[1-9][0-9]*)(?:\.\.0|\.[1-9][0-9]*)*)(?:(\-)((?:0|[1-9][0-9]*)(?:\.\.0|\.[1-9][0-9]*)*))?(?: ([\+\-a-zA-Z0-9]*))?$/) {
+	return 0;
+    }
+
+    return 0 if ($1 ne $v_board);
+
+    my @p_min = split(/\./, $2);
+    my @p_max = split(/\./, $3 eq '' ? $2 : $4);
+    my $p_flags = $5;
+
+    while (scalar(@p_min) || scalar(@p_max)) {
+	my $mi = shift(@p_min);
+	my $ma = shift(@p_max);
+	my $ve = shift(@v_ver) || 0;
+
+	return 0 if (defined($mi) && $ve < $mi);
+	return 0 if (defined($ma) && $ve > $ma);
+    }
+
+    my $flag_pol = 1;
+
+    foreach my $c (split(//, $p_flags)) {
+	if ($c eq '-') {
+	    $flag_pol = 0;
+	} elsif ($c eq '+') {
+	    $flag_pol = 1;
+	} else {
+	    return 0 if ((index($v_flags, $c) != -1) != $flag_pol);
+	}
+    }
+
+    return 1;
+}
+    
+sub get_target_board($$)
+{
+    my($port,$boardinfo) = @_;
+
+    run_esptool($port, { 'before' => 'default_reset', 'after' => 'hard_reset' },
+		'read_flash', { },
+		''.$boardinfo_addr, ''.$boardinfo_len, $boardinfo);
+
+    open(my $bi, '<:raw', $boardinfo) or return undef;
+    my $bid;
+    read($bi, $bid, $boardinfo_len);
+    close($bi);
+    return undef if (length($bid) != $boardinfo_len);
+
+    my @bh = unpack('VVVVZ[256]', $bid);
+    if ($bh[0] == 0x6682df97 && $bh[1] == 0xe2a0d506) {
+	# Standard format board_info structure
+	substr($bid, 12, 4) = "\0\0\0\0"; # Clear the CRC field
+	if ($bh[2] >= 16 && $bh[2] <= $boardinfo_len &&
+	    crc32(substr($bid, 0, $bh[2])) == $bh[3]) {
+	    return $bh[4];
+	}
+    } elsif ($bid =~ /^([[:print:]]+)\0/) {
+	# Preliminary board_info (only version string)
+	return $1;
+    }
+
+    return undef;
+}
+    
 my @args = @ARGV;
 my $esponly = 0;
 my $file;
+my $target_board = undef;
+my $setver = 0;
+
 while (1) {
     $file = shift(@args);
     last if ($file !~ /^\-/);
 
     if ($file eq '--esponly') {
 	$esponly = 1;
+    } elsif ($file eq '--setver') {
+	$target_board = shift(@args);
+	$setver = defined($target_board);
     } elsif ($file eq '--') {
 	$file = shift(@args);
 	last;
@@ -211,7 +313,7 @@ while (1) {
 
 my $port = shift(@args);
 if (!defined($port)) {
-    die "Usage: $0 file.fw port [esptool options...]\n";
+    die "Usage: $0 [--esponly][--setver versionoptions] file.fw port [esptool_options...]\n";
 }
 
 if (!File::Spec->file_name_is_absolute($port)) {
@@ -227,10 +329,38 @@ if (!File::Spec->file_name_is_absolute($port)) {
 }
 print STDERR "Using serial port device $port\n";
 
+my $td = File::Temp->newdir(CLEANUP => 1);
+my $boardinfo = File::Spec->catfile($td, 'boardinfo.bin');
+
+my @espfiles = ();
+
+if (defined($target_board)) {
+    # Forcibly write target board version
+    if (!target_string_valid($target_board)) {
+	die "$0: $port: invalid firmware target string: $target_board\n";
+    }
+
+    open(my $bi, '>:raw', $boardinfo)
+	or die "$0: $port: $boardinfo: $!\n";
+    my $bid = $target_board . "\0";
+    $bid .= "\xff" x ($boardinfo_len - length($bid));
+    print $bi $bid;
+    close($bi);
+    push(@espfiles, ''.$boardinfo_addr, $boardinfo);
+} else {
+    # Get the board version from target flash
+    
+    $target_board = get_target_board($port, $boardinfo);
+    if (!defined($target_board) || !target_string_valid($target_board)) {
+	die "$0: $port: board version not programmed, specify with --setver\n";
+    }
+}
+
 open(my $fw, '<:gzip', $file)
     or die "$0: $file: $!\n";
 
 my @chunks = ();
+my $version_match = undef;
 
 my $err = 0;
 
@@ -239,8 +369,21 @@ while (read($fw, $hdr, 16) == 16) {
     # magic type flags data_len addr
     my @h = unpack('VvvVV', $hdr);
     my $c = { 'hdr' => $hdr, 'magic' => $h[0], 'type' => $h[1],
-		  'flags' => $h[2], 'len' => $h[3], 'addr' => $h[4] };
-    if ($c->{'magic'} != $FW_MAGIC) {
+	      'flags' => $h[2], 'len' => $h[3], 'addr' => $h[4],
+	      'vmatch' => 0, 'vmask' => 0, 'vmin' => 0, 'vmax' => 0xffff };
+    if ($c->{'magic'} == $FW_MAGIC[2]) {
+	if (read($fw, $hdr, 16) != 16) {
+	    print STDERR "$0: $file: short header read\n";
+	    $err = 1;
+	    last;
+	}
+	my @h = unpack('VVvvV', $hdr);
+	$c->{'hdr'}   .= $hdr;
+	$c->{'vmatch'} = $h[0];
+	$c->{'vmask'}  = $h[1];
+	$c->{'vmin'}   = $h[2];
+	$c->{'vmax'}   = $h[3];
+    } elsif ($c->{'magic'} != $FW_MAGIC[1]) {
 	print STDERR "$0: $file: bad chunk magic\n";
 	$err = 1;
 	last;
@@ -255,6 +398,31 @@ while (read($fw, $hdr, 16) == 16) {
 	last;
     }
     $c->{'data'} = $d;
+
+    if ($t eq 'target') {
+	my $is_match = match_version($target_board, $d);
+	if ($c->{'magic'} == $FW_MAGIC[1] || $is_match) {
+	    $version_match = $c;
+	}
+	print STDERR "$0: $file: supports hardware: $d",
+	    ($is_match ? ' (match)' : ''), "\n";
+    } elsif ($t eq 'end' || $t eq 'note' || ($c->{'flags'} & $FDF_PRETARGET)) {
+	# Not filtered
+    } else {
+	if (!defined($version_match)) {
+	    print STDERR "$0: $file: hardware version $target_board not supported\n";
+	    $err = 1;
+	    last;
+	}
+	
+	if ($c->{'vmin'} > $version_match->{'vmax'} ||
+	    $c->{'vmax'} < $version_match->{'vmin'} ||
+	    (($c->{'vmatch'} ^ $version_match->{'vmatch'}) & $c->{'vmask'})) {
+	    # Not applicable to this board
+	    next;
+	}
+    }
+    
     push(@chunks, $c);
 
     last if ($t eq 'end'); # End of stream
@@ -263,9 +431,6 @@ while (read($fw, $hdr, 16) == 16) {
 close($fw);
 exit $err if ($err);
 
-my $td = File::Temp->newdir(CLEANUP => 1);
-
-my @espfiles = ();
 my %espopt = ('before' => 'default_reset', 'after' => 'hard_reset',
 	      'baud' => 115200, 'port' => undef, 'chip' => undef,
 	      'flash_mode' => undef, 'flash_freq' => undef,
@@ -292,6 +457,8 @@ foreach my $c ( @chunks ) {
 	print $tf $c->{'data'};
 	close($tf);
 	push(@espfiles, '0x'.$addr, $tff);
+    } elsif ($t eq 'note' || ($t eq 'target' && $c != $version_match)) {
+	# Skip
     } else {
 	print $fpgafh $c->{'hdr'};
 	print $fpgafh $c->{'data'};
@@ -308,8 +475,8 @@ foreach my $e (keys %espopt) {
     }
 }
 
-run_esptool($port, { hgrep sub {!/^flash_/}, %espopt },
-	    'write_flash', { hgrep sub {/^flash_/}, %espopt },
+run_esptool($port, { hgrep {!/^flash_/} %espopt },
+	    'write_flash', { hgrep {/^flash_/} %espopt },
 	    '-z', @espfiles);
 
 my $SerialPort = eval {