#!/usr/bin/perl use strict; use integer; use Digest::MD5; my $FW_MAGIC = 0x7a07fbd6; my %datatypes = ( 'end' => 0, # End of data 'data' => 1, # FPGA flash data 'target' => 2, # Firmware target string 'note' => 3, # Informative string 'espota' => 4, # ESP32 OTA image 'fpgainit' => 5 # FPGA bypass (transient) image during update ); my $FDF_OPTIONAL = 0x0001; my $STRING_MAX_LEN = 4095; 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; } sub filelen($) { my($f) = @_; my @s = stat($f); 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 %u flags 0x%x length %u addr 0x%x\n", $options->{'type'}, $options->{'flags'}, length($data), $options->{'addr'}; print $out $data; } if (!scalar(@ARGV)) { die "Usage: $0 [-o outfile] [options command]...\n". "Options:\n". "\t-type datatype\n". "\t-addr address (or equivalent)\n". "\t-optional\n". "\t-required\n". "Commands:\n". "\t-file data_file\n". "\t-str data_string\n"; } our $outfile; 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 %default_options = { 'type' => $datatypes{'data'}, 'addr' => 0, 'flags' => 0 }; my $err; my %options = %default_options; while (1) { my $what = shift @ARGV; last if (!defined($what)); if ($what eq '-type') { my $arg = lc(shift @ARGV); $options{'type'} = $datatypes{$arg} || getint($arg); if (!$arg) { die "$0: invalid data type: $arg"; } } elsif ($what eq '-addr') { my $arg = shift @ARGV; $options{'addr'} = getint($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); undef $data; %options = %default_options; } elsif ($what eq '-str') { my $str = shift @ARGV; if (length($str) > $STRING_MAX_LEN) { die "$0: string too long\n"; } output_chunk($out, $str, \%options); %options = %default_options; } else { die "$0: unknown argument: $what\n"; } } output_chunk($out, '', {'type' => $datatypes{'end'}}); close($out);