#!/usr/bin/perl -w

# parse_prism_ap_fw - convert binary tertiary firmware to S-Record format

# Copyright (C) 2004 Pavel Roskin <proski@gnu.org>

# This script is Free Software, and it can be copied, distributed and
# modified as defined in the GNU General Public License.  A copy of
# its license can be downloaded from http://www.gnu.org/copyleft/gpl.html

# Usage:
#      parse_prism_ap_fw infile

use strict;

sub swap16 ($)
{
	my $word = shift(@_);
	return (($word & 0xff) << 8) + (($word & 0xff00) >> 8);
	my $radix = 0x1000000;
	my $res = 0;
	while ($word != 0) {
		$res += $radix * ($word & 0xff);
		$word >>= 8;
		$radix >>= 8;
	}
	return $res;
}

sub swap32 ($)
{
	my $word = shift(@_);
	my $radix = 0x1000000;
	my $res = 0;
	while ($word != 0) {
		$res += $radix * ($word & 0xff);
		$word >>= 8;
		$radix >>= 8;
	}
	return $res;
}

sub readnum_ab ()
{
	my $byte_a;
	read INFILE,$byte_a,1;
	my $byte_b;
	read INFILE,$byte_b,1;
	return (ord($byte_a) << 8) + ord($byte_b);
}

sub readnum_cdab ()
{
	my $byte_a;
	read INFILE,$byte_a,1;
	my $byte_b;
	read INFILE,$byte_b,1;
	my $byte_c;
	read INFILE,$byte_c,1;
	my $byte_d;
	read INFILE,$byte_d,1;
	return (ord($byte_c) << 24) + (ord($byte_d) << 16) + (ord($byte_a) << 8) + ord($byte_b);
}

sub crc_print
{
	my $line = sprintf(shift (@_), @_);

	# Split, clean empty elements.
	my @twochars = split (/(..)/, $line);
	foreach my $index (0 .. $#twochars) {
		unless ($twochars[$index] =~ m/../) {
			splice (@twochars, $index, 1);
		}
		last if ($index >= $#twochars);
	}

	# Print Sn, find record length
	printf OUTFILE "%s", shift(@twochars);
	my $bytelen = hex $twochars[0];

	# Calculate CRC, print the line
	my $crc = 0;
	foreach my $index (0 .. $bytelen - 1) {
		$crc += hex($twochars[$index]);
		printf OUTFILE "$twochars[$index]";
	}
	printf OUTFILE "%02X\n", (0xFF - $crc) & 0xFF;
}



my @bl_offset;
my @bl_addr;
my @bl_len;

sub find_offset ($)
{
	my $a = shift(@_);
	my $block = 0;
	while (defined $bl_addr[$block]) {
		if ( ($bl_addr[$block] <= $a) &&
		     ($bl_addr[$block] + $bl_len[$block] > $a) ) {
			return ($bl_offset[$block] + $a - $bl_addr[$block]);
		}
		$block++;
	}
	return 0;
}

# Print message and exit (like "die", but without raising an exception).
# Newline is added at the end.
sub error
{
	printf STDERR "ERROR: ";
	printf STDERR @_;
	printf STDERR "\n";
	exit 1;
}


my $addr;
my $len;
my $line;
my $start_addr = 0;
my $pdr6addr = 0;
my $fwidaddr = 0;

my $nic3841 = [
	[0x8002, 1, 0, 0],
	[0x8002, 1, 0, 1],
	[0x8003, 1, 0, 0],
	[0x8003, 1, 0, 1],
	[0x8004, 1, 0, 0],
	[0x8008, 1, 0, 0],
];

my $nic3842 = [
	[0x800A, 1, 0, 0],
	[0x800B, 1, 0, 0],
	[0x800C, 1, 0, 0],
	[0x800D, 1, 0, 0],
	[0x800E, 1, 0, 0],
	[0x8012, 1, 0, 0],
	[0x8013, 1, 0, 0],
	[0x8014, 1, 0, 0],
	[0x8015, 1, 0, 0],
	[0x8016, 1, 0, 0],
	[0x8017, 1, 0, 0],
	[0x8018, 1, 0, 0],
	[0x8019, 1, 0, 0],
	[0x801A, 1, 0, 0],
	[0x801B, 1, 0, 0],
	[0x801C, 1, 0, 0],
	[0x801D, 1, 0, 0],
	[0x8021, 1, 0, 0],
	[0x8022, 1, 0, 0],
	[0x8023, 1, 0, 0],
	[0x8024, 1, 0, 0],
];


if ($#ARGV != 0) {
	error ("Usage: parse_prism_ap_fw infile");
}

unless (open (INFILE, "< $ARGV[0]")) {
	error ("couldn't open $ARGV[0] for reading: $!");
}

# Skip to the beginning of firmware
$/ = "\x00";
while (<INFILE>) {
	last if (/reserved\./);
}


#
# First pass
#

# Data blocks
my $min_addr = 0x7fffffff;
my $max_addr = 0;
while (!eof(INFILE)) {
	$addr = readnum_cdab();
	$len = 2 * readnum_ab();
	if ($len == 0) {
		$start_addr = $addr;
		last;
	}

	push @bl_offset, tell(INFILE);
	push @bl_addr, $addr;
	push @bl_len, $len;

	$min_addr = $addr if ($addr < $min_addr);
	$max_addr = ($addr + $len) if ($addr + $len > $max_addr);

	seek (INFILE, $len, 1);
}

# Find first non-zero word
while (readnum_ab() == 0) {}
my $pdroffset = tell(INFILE) - 2;
seek(INFILE, $pdroffset, 0);

# PDRs
while (!eof(INFILE)) {
	my $pdtype = readnum_ab();
	if ($pdtype == 6) {
		$pdr6addr = readnum_cdab();
		seek (INFILE, 2, 1);
		next;
	} elsif ($pdtype == 0x107) {
		$fwidaddr = readnum_cdab() + 0x2a;
		seek (INFILE, 2, 1);
		next;
	} elsif ($pdtype == 0) {
		last;
	}
	seek (INFILE, 6, 1);
}

if ($pdr6addr == 0) {
	error "Cannot identify firmware - no PDR 6";
}

my $pdr6offset = find_offset($pdr6addr);
if ($pdr6offset == 0) {
	error "Cannot identify firmware - PDR 6 doesn't point to the image";
}

my $fwidoffset = find_offset($fwidaddr);
if ($fwidoffset == 0) {
	error "Cannot identify firmware - Firmware ID string is not in the image";
}

seek(INFILE, $fwidoffset, 0);
my $tertmagic = readnum_ab();
if ($tertmagic != 0x6554) {
	error "Firmware ID doesn't start with \"Te\"";
}

seek(INFILE, $pdr6offset + 14, 0);
my $hwver = readnum_ab();
seek(INFILE, $pdr6offset + 20, 0);
my $compid = readnum_ab();
my $variant = readnum_ab();
my $major = readnum_ab();
my $minor = readnum_ab();

if ($compid != 0x014b) {
	error "Component ID %04X - not tertiary firmware?\n", $compid;
}

my $nictable;
my $hwchar;
my $hwtext;
if ($hwver == 1) {
	$hwtext = "hfa3841 (Prism 2)";
	$nictable = $nic3841;
	$hwchar = "1";

} elsif ($hwver == 2) {
	$hwtext = "hfa3842 (Prism 2.5 and 3)";
	$nictable = $nic3842;
	$hwchar = "f";
} else {
	error "Unknown hardware version %d\n", $hwver;
}

printf "Found tertiary firmware %d.%d.%d for %s\n",
       $major, $minor, $variant, $hwtext;

my $intname;
my $varstr;
if ($major == 0) {
	$varstr = sprintf("c%01x", $variant);
} else {
	$varstr = sprintf("%02x", $variant);
}

$intname = sprintf("t%s%02x%02x%s.hex", $hwchar, $major, $minor, $varstr);
printf "Firmware internal name: %s\n", $intname;

unless (open(OUTFILE, "> $intname")) {
	error "Cannot open %s for writing: $!", $intname;
}


#
# Second pass
#

# Data blocks
my $block = 0;
while (defined $bl_addr[$block]) {
	seek(INFILE, $bl_offset[$block], 0);
	my $len = $bl_len[$block];
	my $count = 0;
	while (1) {
		my $byte1;
		my $byte2;
		if ($count % 16 == 0) {
			my $rest = 16;
			$rest = $len if ($len < 16);
			$line = sprintf("S3%02X%08X", $rest + 5,
			       $bl_addr[$block] + $count);
		}
		read INFILE, $byte1, 1;
		read INFILE, $byte2, 1;
		$line .= sprintf("%02X%02X", ord($byte2), ord($byte1));
		$count += 2;
		$len -= 2;
		last if ($len <= 0);
		if ($count % 16 == 0 && $len) {
			crc_print ($line);
		}
	}
	crc_print ($line);
	$block++;
}

# FWID PDR - firmware file name
crc_print("S311FF000000FFFFFFFF%08X0E000000", swap32($fwidaddr));

# Other PDRs
seek(INFILE, $pdroffset, 0);
while (!eof(INFILE)) {
	my $pdtype = readnum_ab();
	last if ($pdtype == 0);
	my $pdaddr = readnum_cdab();
	my $pdlen = readnum_ab();
	crc_print("S311FF000000%08X%08X%08X",
		  swap32($pdtype), swap32($pdaddr), swap32($pdlen));
}

# CRC record - cover whole image, don't program CRC
crc_print("S311FF100000%08X%08X00000000", swap32($min_addr),
	  swap32($max_addr - $min_addr));

# Firmware version
crc_print "S311FF20000005000100%04X%04X%04X%04X", swap16($compid),
       swap16($variant), swap16($major), swap16($minor);

# Compatibility ranges
if ($hwver == 1) {
	crc_print "S313FF2000000600020001000100010001000100";
	crc_print "S313FF2000000600020001000200010001000100";
	crc_print "S313FF2000000600020001000300010002000200";

} elsif ($hwver == 2) {
	crc_print "S313FF2000000600020001000100010001000100";
	crc_print "S313FF2000000600020001000200020001000100";
	if ((($major << 8) + $minor) >= 0x0102) {
		# tertiary f/w 1.2.x and newer - primary f/w 1.1.x
		crc_print "S313FF2000000600020001000300010004000400";
	} else {
		# tertiary f/w 1.1.x and older - primary f/w 1.0.x
		crc_print "S313FF2000000600020001000300010002000200";
	}
}

# Included name
$line = "S317FF20000007000180";
my @namechars = split(//, $intname);
foreach (@namechars) {
	$line .= sprintf("%02X", ord($_));
}
$line .= "0000";
crc_print $line;

# NIC ID table
my $nic = 0;
while (defined $nictable->[$nic][0]) {
	crc_print("S311FF20000005000400%04X%04X%04X%04X",
	       swap16($nictable->[$nic][0]), swap16($nictable->[$nic][3]),
	       swap16($nictable->[$nic][1]), swap16($nictable->[$nic][2]));
	$nic++;
}

# At last, write starting address
crc_print "S705%08X", $start_addr;

close(INFILE);
close(OUTFILE);
