mkfwimage.pl 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #!/usr/bin/perl
  2. use strict;
  3. use integer;
  4. my @FW_MAGIC = (undef, 0x7a07fbd6, 0xa924ed0b);
  5. my %datatypes = (
  6. 'end' => 0, # End of data
  7. 'data' => 1, # FPGA flash data
  8. 'target' => 2, # Firmware target mask and string
  9. 'note' => 3, # Informative string
  10. 'espota' => 4, # ESP32 OTA image
  11. 'fpgainit' => 5, # FPGA bypass (transient) image during update
  12. 'esppart' => 6, # ESP32 partition table
  13. 'espsys' => 7, # ESP32 boot loader, OTA control partition...
  14. 'esptool' => 8, # esptool.py options for flashing
  15. 'boardinfo' => 9 # board_info block address (FPGA)
  16. );
  17. my @type;
  18. foreach my $t (keys(%datatypes)) {
  19. $type[$datatypes{$t}] = $t;
  20. }
  21. my $FDF_OPTIONAL = 0x0001;
  22. my $STRING_MAX_LEN = 4095;
  23. my %int_shifts = ('' => 0, 'k' => 10, 'm' => 20, 'g' => 30,
  24. 't' => 40, 'p' => 50, 'e' => 60);
  25. sub getint($) {
  26. my($s) = @_;
  27. return undef
  28. unless ($s =~ /^(([1-9][0-9]*)|(0(x[0-9a-f]+|[0-7]*)))([kmgtpe]?)$/i);
  29. return (oct($3) + $2) << $int_shifts{lc($5)};
  30. }
  31. sub filelen($) {
  32. my($f) = @_;
  33. my @s = stat($f);
  34. return $s[7];
  35. }
  36. our $outfile;
  37. my %default_options = (
  38. 'fw' => 1, # Default and minimum firmware version
  39. 'type' => $datatypes{'data'},
  40. 'addr' => 0,
  41. 'flags' => 0,
  42. 'vmatch' => 0,
  43. 'vmask' => 0,
  44. 'vmin' => 0,
  45. 'vmax' => 0xffff
  46. );
  47. my %need_versions = (
  48. 'vmatch' => 2, 'vmask' => 2, 'vmin' => 2, 'vmax' => 2
  49. );
  50. sub output_chunk($$%) {
  51. my($out,$data,%opts) = @_;
  52. foreach my $o (keys(%default_options)) {
  53. $opts{$o} = $default_options{$o} unless (defined($opts{$o}));
  54. }
  55. $opts{'vmatch'} &= 0xffffffff;
  56. $opts{'vmask'} &= 0xffffffff;
  57. $opts{'vmin'} &= 0xffff;
  58. $opts{'vmax'} &= 0xffff;
  59. my $version = $opts{'fw'};
  60. foreach my $o (keys(%need_versions)) {
  61. if ($opts{$o} ne $default_options{$o} &&
  62. $need_versions{$o} > $version) {
  63. $version = $need_versions{$o};
  64. }
  65. }
  66. if (!defined($FW_MAGIC[$version])) {
  67. die "$0: $outfile: invalid firmware format version: $version\n";
  68. }
  69. print $out pack('VvvVV',
  70. $FW_MAGIC[$version],
  71. $opts{'type'}, $opts{'flags'},
  72. length($data), $opts{'addr'});
  73. if ($version >= 2) {
  74. print $out pack('VVvvV',
  75. $opts{'vmatch'}, $opts{'vmask'},
  76. $opts{'vmin'}, $opts{'vmax'},
  77. 0);
  78. }
  79. printf STDERR "chunk: fw %u type %s (%u) flags 0x%x length %u addr 0x%x ver 0x%x/0x%x,%u:%u\n",
  80. $version, $type[$opts{'type'}], $opts{'type'},
  81. $opts{'flags'},
  82. length($data), $opts{'addr'},
  83. $opts{'vmatch'}, $opts{'vmask'}, $opts{'vmin'}, $opts{'vmax'};
  84. print $out $data;
  85. }
  86. if (!scalar(@ARGV)) {
  87. die "Usage: $0 [-o outfile] [-fwmin ver] [options command]...\n".
  88. "Options:\n".
  89. "\t-type datatype\n".
  90. "\t-addr address (or equivalent)\n".
  91. "\t-optional\n".
  92. "\t-required\n".
  93. "\t-ver match,mask,min,max\n".
  94. "Commands:\n".
  95. "\t-file data_file\n".
  96. "\t-str data_string\n";
  97. }
  98. our $out;
  99. sub delete_out {
  100. close($out);
  101. unlink($outfile);
  102. }
  103. if ($ARGV[0] eq '-o') {
  104. shift @ARGV;
  105. $outfile = shift @ARGV;
  106. }
  107. if ($outfile ne '' && $outfile ne '-') {
  108. open($out, '>', $outfile) or
  109. die "$0: $outfile: $!\n";
  110. $SIG{'INT'} = \&delete_out;
  111. $SIG{'QUIT'} = \&delete_out;
  112. $SIG{'TERM'} = \&delete_out;
  113. $SIG{'__DIE__'} = \&delete_out;
  114. } else {
  115. $outfile = '-';
  116. $out = \*STDOUT;
  117. }
  118. binmode $out;
  119. my $err;
  120. my %options = ();
  121. while (1) {
  122. my $what = shift @ARGV;
  123. last if (!defined($what));
  124. if ($what eq '-type') {
  125. my $arg = lc(shift @ARGV);
  126. my $type = defined($datatypes{$arg}) ? $datatypes{$arg} : getint($arg);
  127. if (!defined($type)) {
  128. die "$0: invalid data type: $arg";
  129. }
  130. $options{'type'} = $type;
  131. } elsif ($what =~ /^\-(addr|flags|vmatch|vmask|vmin|vmax)$/) {
  132. my $opt = $1;
  133. my $arg = getint(shift @ARGV);
  134. $options{$opt} = $arg if (defined($arg));
  135. } elsif ($what eq '-fwmin') {
  136. my $arg = getint(shift @ARGV);
  137. $default_options{'fw'} = $arg if (defined($arg));
  138. } elsif ($what eq '-ver') {
  139. my @vp = split(/(?:[,:\/]|\.+)/, shift @ARGV);
  140. foreach my $opt (qw(vmatch vmask vmin vmax)) {
  141. my $arg = getint(shift @vp);
  142. $options{$opt} = $arg if (defined($arg));
  143. }
  144. } elsif ($what eq '-optional') {
  145. $options{'flags'} |= $FDF_OPTIONAL;
  146. } elsif ($what eq '-required') {
  147. $options{'flags'} &= ~$FDF_OPTIONAL;
  148. } elsif ($what eq '-file') {
  149. my $infile = shift @ARGV;
  150. my $in;
  151. if (!open($in, '<', $infile)) {
  152. die "$0: $infile: $!\n";
  153. }
  154. binmode($in);
  155. my @is = stat($in);
  156. my $data;
  157. my $dlen = read($in, $data, $is[7]);
  158. close($in);
  159. output_chunk($out, $data, %options);
  160. %options = ();
  161. } elsif ($what eq '-str') {
  162. my $str = shift @ARGV;
  163. if (length($str) > $STRING_MAX_LEN) {
  164. die "$0: $outfile: string too long\n";
  165. }
  166. output_chunk($out, $str, %options);
  167. %options = ();
  168. } elsif ($what eq '-empty') {
  169. output_chunk($out, '', %options);
  170. %options = ();
  171. } else {
  172. die "$0: unknown argument: $what\n";
  173. }
  174. }
  175. output_chunk($out, '', ('type' => $datatypes{'end'}));
  176. close($out);