123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- #!/usr/bin/perl
- use strict;
- use integer;
- my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
- my %datatypes = (
- 'end' => 0, # End of data
- 'data' => 1, # FPGA flash data
- '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
- 'boardinfo' => 9 # board_info block address (FPGA)
- );
- my @type;
- foreach my $t (keys(%datatypes)) {
- $type[$datatypes{$t}] = $t;
- }
- 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);
- return (oct($3) + $2) << $int_shifts{lc($5)};
- }
- sub filelen($) {
- my($f) = @_;
- my @s = stat($f);
- return $s[7];
- }
- 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] [-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 $out;
- sub delete_out {
- close($out);
- unlink($outfile);
- }
- if ($ARGV[0] eq '-o') {
- shift @ARGV;
- $outfile = shift @ARGV;
- }
- if ($outfile ne '' && $outfile ne '-') {
- open($out, '>', $outfile) or
- die "$0: $outfile: $!\n";
- $SIG{'INT'} = \&delete_out;
- $SIG{'QUIT'} = \&delete_out;
- $SIG{'TERM'} = \&delete_out;
- $SIG{'__DIE__'} = \&delete_out;
- } else {
- $outfile = '-';
- $out = \*STDOUT;
- }
- binmode $out;
- my $err;
- my %options = ();
- while (1) {
- my $what = shift @ARGV;
- last if (!defined($what));
- if ($what eq '-type') {
- my $arg = lc(shift @ARGV);
- my $type = defined($datatypes{$arg}) ? $datatypes{$arg} : getint($arg);
- if (!defined($type)) {
- die "$0: invalid data type: $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') {
- $options{'flags'} &= ~$FDF_OPTIONAL;
- } elsif ($what eq '-file') {
- my $infile = shift @ARGV;
- my $in;
- if (!open($in, '<', $infile)) {
- die "$0: $infile: $!\n";
- }
- binmode($in);
- my @is = stat($in);
- my $data;
- my $dlen = read($in, $data, $is[7]);
- close($in);
- output_chunk($out, $data, %options);
- %options = ();
- } elsif ($what eq '-str') {
- my $str = shift @ARGV;
- if (length($str) > $STRING_MAX_LEN) {
- die "$0: $outfile: string too long\n";
- }
-
- 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'}));
- close($out);
|