|
@@ -3,18 +3,19 @@
|
|
|
use strict;
|
|
|
use integer;
|
|
|
|
|
|
-my $FW_MAGIC = 0x7a07fbd6;
|
|
|
+my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
|
|
|
|
|
|
my %datatypes = (
|
|
|
'end' => 0, # End of data
|
|
|
'data' => 1, # FPGA flash data
|
|
|
- 'target' => 2, # Firmware target string
|
|
|
+ 'target' => 2, # Firmware target mask and string
|
|
|
'note' => 3, # Informative string
|
|
|
'espota' => 4, # ESP32 OTA image
|
|
|
'fpgainit' => 5, # FPGA bypass (transient) image during update
|
|
|
'esppart' => 6, # ESP32 partition table
|
|
|
'espsys' => 7, # ESP32 boot loader, OTA control partition...
|
|
|
- 'esptool' => 8 # esptool.py options for flashing
|
|
|
+ 'esptool' => 8, # esptool.py options for flashing
|
|
|
+ 'boardinfo' => 9 # board_info block address (FPGA)
|
|
|
);
|
|
|
my @type;
|
|
|
foreach my $t (keys(%datatypes)) {
|
|
@@ -25,29 +26,15 @@ my $FDF_OPTIONAL = 0x0001;
|
|
|
|
|
|
my $STRING_MAX_LEN = 4095;
|
|
|
|
|
|
+my %int_shifts = ('' => 0, 'k' => 10, 'm' => 20, 'g' => 30,
|
|
|
+ 't' => 40, 'p' => 50, 'e' => 60);
|
|
|
sub getint($) {
|
|
|
my($s) = @_;
|
|
|
|
|
|
return undef
|
|
|
- unless ($s =~ /^(([1-9][0-9]+)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
|
|
|
-
|
|
|
- my $o = oct($3) + $2;
|
|
|
- my $p = lc($5);
|
|
|
-
|
|
|
- if ($p eq 'k') {
|
|
|
- $o <<= 10;
|
|
|
- } elsif ($p eq 'm') {
|
|
|
- $o <<= 20;
|
|
|
- } elsif ($p eq 'g') {
|
|
|
- $o <<= 30;
|
|
|
- } elsif ($p eq 't') {
|
|
|
- $o <<= 40;
|
|
|
- } elsif ($p eq 'p') {
|
|
|
- $o <<= 50;
|
|
|
- } elsif ($p eq 'e') {
|
|
|
- $o <<= 60;
|
|
|
- }
|
|
|
- return $o;
|
|
|
+ unless ($s =~ /^(([1-9][0-9]*)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
|
|
|
+
|
|
|
+ return (oct($3) + $2) << $int_shifts{lc($5)};
|
|
|
}
|
|
|
sub filelen($) {
|
|
|
my($f) = @_;
|
|
@@ -55,31 +42,78 @@ sub filelen($) {
|
|
|
|
|
|
return $s[7];
|
|
|
}
|
|
|
-sub output_chunk($$$) {
|
|
|
- my($out,$data,$options) = @_;
|
|
|
-
|
|
|
- print $out pack("VvvVV", $FW_MAGIC,
|
|
|
- $options->{'type'}, $options->{'flags'},
|
|
|
- length($data), $options->{'addr'});
|
|
|
- printf STDERR "chunk: type %s (%u) flags 0x%x length %u addr 0x%x\n",
|
|
|
- $type[$options->{'type'}], $options->{'type'}, $options->{'flags'},
|
|
|
- length($data), $options->{'addr'};
|
|
|
+
|
|
|
+our $outfile;
|
|
|
+
|
|
|
+my %default_options = (
|
|
|
+ 'fw' => 1, # Default and minimum firmware version
|
|
|
+ 'type' => $datatypes{'data'},
|
|
|
+ 'addr' => 0,
|
|
|
+ 'flags' => 0,
|
|
|
+ 'vmatch' => 0,
|
|
|
+ 'vmask' => 0,
|
|
|
+ 'vmin' => 0,
|
|
|
+ 'vmax' => 0xffff
|
|
|
+);
|
|
|
+my %need_versions = (
|
|
|
+ 'vmatch' => 2, 'vmask' => 2, 'vmin' => 2, 'vmax' => 2
|
|
|
+);
|
|
|
+
|
|
|
+sub output_chunk($$%) {
|
|
|
+ my($out,$data,%opts) = @_;
|
|
|
+
|
|
|
+ foreach my $o (keys(%default_options)) {
|
|
|
+ $opts{$o} = $default_options{$o} unless (defined($opts{$o}));
|
|
|
+ }
|
|
|
+
|
|
|
+ $opts{'vmatch'} &= 0xffffffff;
|
|
|
+ $opts{'vmask'} &= 0xffffffff;
|
|
|
+ $opts{'vmin'} &= 0xffff;
|
|
|
+ $opts{'vmax'} &= 0xffff;
|
|
|
+
|
|
|
+ my $version = $opts{'fw'};
|
|
|
+ foreach my $o (keys(%need_versions)) {
|
|
|
+ if ($opts{$o} ne $default_options{$o} &&
|
|
|
+ $need_versions{$o} > $version) {
|
|
|
+ $version = $need_versions{$o};
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!defined($FW_MAGIC[$version])) {
|
|
|
+ die "$0: $outfile: invalid firmware format version: $version\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ print $out pack('VvvVV',
|
|
|
+ $FW_MAGIC[$version],
|
|
|
+ $opts{'type'}, $opts{'flags'},
|
|
|
+ length($data), $opts{'addr'});
|
|
|
+ if ($version >= 2) {
|
|
|
+ print $out pack('VVvvV',
|
|
|
+ $opts{'vmatch'}, $opts{'vmask'},
|
|
|
+ $opts{'vmin'}, $opts{'vmax'},
|
|
|
+ 0);
|
|
|
+ }
|
|
|
+ printf STDERR "chunk: fw %u type %s (%u) flags 0x%x length %u addr 0x%x ver 0x%x/0x%x,%u:%u\n",
|
|
|
+ $version, $type[$opts{'type'}], $opts{'type'},
|
|
|
+ $opts{'flags'},
|
|
|
+ length($data), $opts{'addr'},
|
|
|
+ $opts{'vmatch'}, $opts{'vmask'}, $opts{'vmin'}, $opts{'vmax'};
|
|
|
print $out $data;
|
|
|
}
|
|
|
|
|
|
if (!scalar(@ARGV)) {
|
|
|
- die "Usage: $0 [-o outfile] [options command]...\n".
|
|
|
+ die "Usage: $0 [-o outfile] [-fwmin ver] [options command]...\n".
|
|
|
"Options:\n".
|
|
|
"\t-type datatype\n".
|
|
|
"\t-addr address (or equivalent)\n".
|
|
|
"\t-optional\n".
|
|
|
"\t-required\n".
|
|
|
+ "\t-ver match,mask,min,max\n".
|
|
|
"Commands:\n".
|
|
|
"\t-file data_file\n".
|
|
|
"\t-str data_string\n";
|
|
|
}
|
|
|
|
|
|
-our $outfile;
|
|
|
our $out;
|
|
|
|
|
|
sub delete_out {
|
|
@@ -106,13 +140,8 @@ if ($outfile ne '' && $outfile ne '-') {
|
|
|
|
|
|
binmode $out;
|
|
|
|
|
|
-my %default_options = {
|
|
|
- 'type' => $datatypes{'data'},
|
|
|
- 'addr' => 0,
|
|
|
- 'flags' => 0
|
|
|
-};
|
|
|
my $err;
|
|
|
-my %options = %default_options;
|
|
|
+my %options = ();
|
|
|
while (1) {
|
|
|
my $what = shift @ARGV;
|
|
|
|
|
@@ -120,13 +149,24 @@ while (1) {
|
|
|
|
|
|
if ($what eq '-type') {
|
|
|
my $arg = lc(shift @ARGV);
|
|
|
- $options{'type'} = $datatypes{$arg} || getint($arg);
|
|
|
- if (!$arg) {
|
|
|
+ my $type = defined($datatypes{$arg}) ? $datatypes{$arg} : getint($arg);
|
|
|
+ if (!defined($type)) {
|
|
|
die "$0: invalid data type: $arg";
|
|
|
}
|
|
|
- } elsif ($what eq '-addr') {
|
|
|
- my $arg = shift @ARGV;
|
|
|
- $options{'addr'} = getint($arg);
|
|
|
+ $options{'type'} = $type;
|
|
|
+ } elsif ($what =~ /^\-(addr|flags|vmatch|vmask|vmin|vmax)$/) {
|
|
|
+ my $opt = $1;
|
|
|
+ my $arg = getint(shift @ARGV);
|
|
|
+ $options{$opt} = $arg if (defined($arg));
|
|
|
+ } elsif ($what eq '-fwmin') {
|
|
|
+ my $arg = getint(shift @ARGV);
|
|
|
+ $default_options{'fw'} = $arg if (defined($arg));
|
|
|
+ } elsif ($what eq '-ver') {
|
|
|
+ my @vp = split(/(?:[,:\/]|\.+)/, shift @ARGV);
|
|
|
+ foreach my $opt (qw(vmatch vmask vmin vmax)) {
|
|
|
+ my $arg = getint(shift @vp);
|
|
|
+ $options{$opt} = $arg if (defined($arg));
|
|
|
+ }
|
|
|
} elsif ($what eq '-optional') {
|
|
|
$options{'flags'} |= $FDF_OPTIONAL;
|
|
|
} elsif ($what eq '-required') {
|
|
@@ -144,23 +184,24 @@ while (1) {
|
|
|
my $dlen = read($in, $data, $is[7]);
|
|
|
close($in);
|
|
|
|
|
|
- output_chunk($out, $data, \%options);
|
|
|
- undef $data;
|
|
|
-
|
|
|
- %options = %default_options;
|
|
|
+ output_chunk($out, $data, %options);
|
|
|
+ %options = ();
|
|
|
} elsif ($what eq '-str') {
|
|
|
my $str = shift @ARGV;
|
|
|
|
|
|
if (length($str) > $STRING_MAX_LEN) {
|
|
|
- die "$0: string too long\n";
|
|
|
+ die "$0: $outfile: string too long\n";
|
|
|
}
|
|
|
|
|
|
- output_chunk($out, $str, \%options);
|
|
|
- %options = %default_options;
|
|
|
+ output_chunk($out, $str, %options);
|
|
|
+ %options = ();
|
|
|
+ } elsif ($what eq '-empty') {
|
|
|
+ output_chunk($out, '', %options);
|
|
|
+ %options = ();
|
|
|
} else {
|
|
|
die "$0: unknown argument: $what\n";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-output_chunk($out, '', {'type' => $datatypes{'end'}});
|
|
|
+output_chunk($out, '', ('type' => $datatypes{'end'}));
|
|
|
close($out);
|