#!/usr/bin/perl # A Perl replacement for Alteon's genfw tool for building Tigon MIPS firmware. # Doesn't use the bfd library -- this simply calls GNU tools to get the data. # # Copyright (C) 2000, 2001 Jamie Lokier # Email: # # This file is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at # your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # #-------------------------------------------------------------------------- # # Version 1.1 -- 17/May/2001. # # Use /proc/self/fd/1 in preference to /dev/stdout, as on some Linux # systems /dev/stdout doesn't exist but can be written to if you're the # superuser. This resulted in the script silently appearing to work, but # producing an empty output file. # # Added --release-number and --release-string options. # # Version 1.0 -- 15/May/2000. # # Pull a binary out of a MIPS ELF object file (actually any ELF object) # and write it in the format used by Alteon device drivers. Note, the # format can miss some things out if you used advanced linking or # compilation options. # # Another method for loading firmware is to use "objcopy -O binary" to # generate a character-by-character binary image to load directly into # the device's memory, and zero all the rest of the memory. That has # the advantage that it doesn't depend on the list of sections or # anything else. We intend to use that method in future instead of # genfw. use strict; use 5; # Requires Perl 5. use POSIX qw(strftime); use Getopt::Long; Getopt::Long::Configure ('bundling'); my $usage = "Usage: $0 [OPTIONS...] A Perl replacement for Alteon's genfw tool for building Tigon MIPS firmware. -a, --align-len= Align section lengths to a multiple of (default: 4) Newer versions of GCC and Binutils can generate smaller output, and a consequence is that sections are not always a multiple of 4 bytes long. Some device drivers that use genfw output fail if a section length is not a multiple of 4. This option only aligns the section lengths, including the array lengths if -h is not used. The section addresses are not changed. You can disable section length alignment using --align-len=1. must be a power of 2. -h [] Historical compatibility mode In this mode each array is made writable (the default is read-only) and the arrays are padded to a fixed size: MAX_TEXT_SIZE for the text section, MAX_DATA_SIZE for the data section etc. Furthermore the section addresses and sizes are declared as variables (the default is to use #define), and some fake variables indicating the firmware release are emitted (all zeros as we don't know the release number). is ignored: it was never really used even by the C version of genfw from Alteon. In general, -h is not recommended for new builds. -l, --linux Linux kernel mode In this mode the arrays are declared using as static and \"__initdata\", and \"u32\" is the default 32 bit type instead of \"U32\". This produces files suitable for the Linux kernel's acenic driver. -p, --prefix= Prefix identifier names with . (Default: \"alt\"). --release-number= Set firmware major, minor and fix release numbers. Only used to define some constants in the output file. (Default: all zeros). --release-string= Set firmware release string. If set, this is used to define a string constant in the output file. Otherwise a string is produced from the MAJOR.MINOR.FIX release number. -t, --type= Specify the name of the 32 bit type. (Default: \"U32\" when used without --linux, \"u32\" when used with). Note: you may wish to follow these additional rules to retain compatibility with the C version of genfw from Alteon: If using -p, follow it immediately by with no space between them. If using -h, make a separate word. Do not use any other options. E.g. \`-ptigon2 -h constants.h'. But note that the Alteon C version does not automatically align section lengths. This can cause problems when using newer versions of GCC and Binutils. Therefore you may wish to switch to this Perl version and forget about historical compatibility. Report bugs to . This is version 1.1, 17/May/2001\n"; my $opt_p = "alt"; my ($opt_help, $opt_u32, $opt_compat, $opt_linux, $opt_release_nr, $opt_release_string); my $opt_align = 4; my ($release_major, $release_minor, $release_fix) = (0, 0, 0); my $release_string = undef; die $usage if !GetOptions ("a|align-len=i" => \$opt_align, "h:s" => \$opt_compat, "prefix|p=s" => \$opt_p, "release-number=s" => \$opt_release_nr, "release-string=s" => \$opt_release_string, "linux|l" => \$opt_linux, "type|t=s" => \$opt_u32, "help|?" => \$opt_help); die $usage if $opt_help || @ARGV != 2; $opt_compat = defined $opt_compat; $opt_u32 = ($opt_linux ? "u32" : "U32") unless defined $opt_u32; my $align_order = 0; $align_order++ while (1<<$align_order < $opt_align && 1<<($align_order+1) >= 1<<$align_order); die "$0: Section length alignment must be a power of 2\n" if (1<<$align_order != $opt_align); if (defined $opt_release_nr) { if ($opt_release_nr !~ /([0-9]+)\.([0-9]+)\.([0-9]+)/) { die "$0: Release numbers must be of the form MAJOR.MINOR.FIX, where MAJOR, MINOR and FIX are decimal numbers\n"; } $release_major = $1+0; $release_minor = $2+0; $release_fix = $3+0; } if (defined $opt_release_string) { $release_string = $opt_release_string; $release_string =~ s/([^[:print:]])/sprintf("\%03o",ord($1))/ge; $release_string = "\"$release_string\""; } elsif (defined $release_fix) { $release_string = "\"$release_major.$release_minor.$release_fix\""; } my $infile = $ARGV[0]; my $outfile = $ARGV[1]; $infile =~ s/\'/\'\\\'\'/g; # Quote for shell. sub hex_align($$) { # Handle alignment using character-by-character processing so it's # independent of Perl's word length. Otherwise this would be the # only thing that's subtly broken with ELF64 files. my $val = $_[0]; use integer; my ($o, $i, $carry) = ($_[1]+0, length($val)-1, 0); do { my $d = $carry + ($i >= 0 ? hex(substr($val,$i,1)) : "0"); my $a = $o >= 4 ? 15 : (1<<$o)-1; $d += $a; $d -= ($d & $a); substr($val,($i>=0?$i:0),($i>=0)) = substr("0123456789abcdef", $d & 15, 1); $carry = ($d >= 16); $o = $o < 4 ? 0 : $o - 4; $i--; } while ($o || $carry); return $val; } sub findprog($) { my $prog = $_[0]; my @list = ("mips-alteon-elf-$prog", "mips-sgi-irix5.3-$prog", "mips-any-elf-$prog", "$prog"); for my $x (@list) { next unless open BIN, "$x --version|"; $_ = ; next unless close BIN; return $x if /^GNU.*$prog/; } die "$0: Couldn't find any of ",join(", ",@list),": $!\n"; } my $call; sub call($) { $call = $_[0]; open BIN, "$call |" || die "Error executing $call: $!\n"; } sub calldone() { close BIN || die "Error executing $call: $!\n"; } my $dev_stdout; if (-e "/proc/self/fd/1") { $dev_stdout = "/proc/self/fd/1"; } elsif (-e "/dev/stdout") { $dev_stdout = "/dev/stdout"; } else { die "$0: Sorry, I need /dev/stdout or /proc/self/fd/1, which do not exist on this system\n"; } my $readelf = findprog "readelf"; my $objcopy = findprog "objcopy"; my ($endian, $entry_point); my @sections = (); -r $infile || die "$0: Unable to read $infile: $!\n"; # Parse the ELF header and section list. call "$readelf -h -S '$infile'"; while (1) { die "$0: Error parsing $call: No \"ELF Header:\" line\n" if eof BIN; $_ = ; last if /^ELF Header:/i; } while (1) { die "$0: Error parsing $call: No \"Section Headers:\" line\n" if eof BIN; $_ = ; last if /^Section Headers:/i; if (/\s*Data:.*\b(big|little) endian\b/i) { $_ = $1 eq "big" ? "N*" : "V*"; die "$0: Error parsing $call: is the data big or little endian?\n" if defined $endian && $1 ne $endian; $endian = $_; } elsif (/\s*Entry point address:\s*0x0*([0-9a-f]+)\s*$/i) { die "$0: Error parsing $call: multiple entry point addresses?\n" if defined $entry_point && $1 ne $entry_point; $entry_point = $1; } } die "$0: Error parsing $call: is the data big or little endian?\n" if !defined $endian; die "$0: Error parsing $call: what is the entry point address?\n" if !defined $entry_point; $entry_point = "0" x (8 - length $entry_point).$entry_point if length $entry_point < 8; while (1) { die "$0: Error parsing $call: No \"Name Type Addr Off Size ES Flg\" header\n" if eof BIN; $_ = ; last if /^\s*\[Nr\] Name\s+Type\s+Addr\s+Off\s+Size\s+ES\s+Flg(?:$|\s)/i; } { my $_outfile = $outfile; $_outfile =~ s:^(\s):./$1:; open OUTFILE, "> $_outfile\0" || die "$0: Unable to open $outfile for writing: $!\n"; select OUTFILE; } print "/* Generated by genfw-1.1 Perl script (written by Jamie Lokier)\n on ".(strftime "%A %b %-e %H:%M:%S %Z %Y", gmtime)." */\n"; if (defined $release_string) { print "#define ${opt_p}FwReleaseString $release_string\n"; } if ($opt_compat) { print "int ${opt_p}FwReleaseMajor = $release_major;\n"; print "int ${opt_p}FwReleaseMinor = $release_minor;\n"; print "int ${opt_p}FwReleaseFix = $release_fix;\n"; print "$opt_u32 ${opt_p}FwStartAddr = 0x$entry_point;\n"; } else { print "#define ${opt_p}FwReleaseMajor $release_major\n"; print "#define ${opt_p}FwReleaseMinor $release_minor\n"; print "#define ${opt_p}FwReleaseFix $release_fix\n"; print "#define ${opt_p}FwStartAddr 0x$entry_point\n"; } # Parse the list of sections and output section sizes and addresses. while (1) { $_ = ; chomp; # $1=section nr, $2=name, $3=type, $4=VMA, $5=size, $6=optional flags. last unless /^\s*\[\s*([0-9])+\] (?:(\S+)\s+(\S+)|\s+NULL)\s+([0-9a-f]{8,})\s+[0-9a-f]{6,}\s+([0-9a-f]{6,})\s+[0-9a-f]{2,}\s+([A-Za-z]+)?(?:$|\s)/; next if !defined $2; # A "NULL" section. my ($name, $aname, $type, $addr, $len) = ($2, $2, $3, $4, $5); next if $6 !~ /A/; # A section with no "Allocate" flag isn't relevant. $aname =~ s/^\.//; $aname =~ s/[^A-Za-z0-9_]/_/g; $aname = ucfirst($aname); $len =~ s/^0+//; $len = hex_align($len, $align_order) if $align_order; printf ($opt_compat ? "$opt_u32 %sFw%sAddr = 0x%s;\nint %sFw%sLen = 0x%s;\n" : "#define %sFw%sAddr 0x%s\n#define %sFw%sLen 0x%s\n", $opt_p, $aname, $addr, $opt_p, $aname, $len); if ($type eq "NOBITS") { next; } elsif ($type eq "PROGBITS") { warn "$0: Warning: Including non-standard section: $name\n" unless $name =~ /^\.(?:text|rodata|data|sbss|bss)$/; } else { warn "$0: Warning: Including non-standard section: $name ($type)\n"; } push @sections, [$name, $aname, $len]; } calldone(); # Output the data arrays. for my $s (@sections) { my ($name, $aname, $len) = @$s; $name =~ s/\'/\'\\\'\'/g; # Quote for shell. call "$objcopy -O binary -j '$name' --change-warnings --change-section-lma '$name'=0 '$infile' $dev_stdout"; local $/ = undef; # Read the whole file. $_ = ; calldone(); $len = hex_align ($len, 2); print ($opt_compat # MAX_TEXT_LEN/4+1 should be (MAX_TEXT_LEN+3)/4, but this # definition must be compatible with old driver code that # declares the array as "extern blah[MAX_TEXT_LEN/4+1]". ? "$opt_u32 ${opt_p}Fw$aname\[(MAX_".uc($aname)."_LEN/4)+1] = {\n" : $opt_linux ? "static $opt_u32 ${opt_p}Fw$aname\[0x$len/4] __initdata = {\n" : "const $opt_u32 ${opt_p}Fw$aname\[0x$len/4] = {\n"); $_ .= "\0\0\0"; # Pad a final incomplete word for "N*". $_ = join (", ", map { sprintf ($_ < 10 ? "%d" : "0x%x", $_) } (unpack ($endian, $_))); # Word wrap. while (/\G((?:.{1,79})\s*)(?:\s|$)/g) { print $1,"\n"; } print "};\n"; } close OUTFILE || die "$0: Unable to close $outfile: $!\n";