#!/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);