|
@@ -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 {
|