| 
					
				 | 
			
			
				@@ -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); 
			 |