#!@PERL@
#
# Copyright (c) 1994-1996, 1998-2000 University of Utah and the Flux Group.
# All rights reserved.
# @OSKIT-FLUX-GPLUS@
#

# This little script uses GNU ld to build a Linux 16-bit compressed boot image
# from a set of boot modules.

#
# Usage: mklinux2 [options] [files ...]
#
# Options:
#   -x filename
#      Use <filename> as the MultiBoot adaptor file instead of the default.
#   -o filename
#      Specify the output <filename> of the resulting Linux image. The
#      default is "zImage" in the current directory.
#   -c 'string'
#      Specify a command line <string> to pass to the kernel when it is
#      invoked.
#   -stdin
#      Take file specifications from stdin, in addition to those on the
#      command line. This is useful when using another script to build
#      a list of file specifications which can then be piped into this
#      script.
#   -save-temps
#      Debugging option to save temporary files instead of deleting them
#      after the image is built.
#   [files ...]
#      A list of file specifications in the format pathname1:pathname2,
#      where pathname1 is the name of a local module which is placed in the
#      boot image. If :pathname2 is provided, it specifies the name to give
#      the module in the image. If :pathname2 is omitted, it defaults to
#      pathname1.
#
#   The first file specification is typically the name of the Oskit kernel.
#

$bootdir = $ENV{"BOOTDIR"};
$cc = $ENV{"CC"};
$ld = $ENV{"LD"};
if (!$cc) { $cc = "@CC@"; }
if (!$ld) { $ld = "@LD@"; }
if (!$bootdir) { $bootdir = "@prefix@/lib/boot"; }
$bb="$bootdir/linuxboot.bin";

@modules = ();
$outfile="zImage";
$savetemps=0;
$fromstdin = 0;
$ldopts="-Ttext 0 -defsym _start=0 -oformat binary";
$cmdlinefile="bootadapter_cmdline";

$def_syssize = 0x7f00;
$def_setupssecs = 4;

if ($#ARGV == -1) {
        print "Usage: mklinux2 [option | filename]\n";
	exit;
}


# Parse the command-line options
while ($#ARGV >= 0) {
        if ($ARGV[0] eq "-x") { $bb = $ARGV[1]; shift @ARGV; }
	elsif ($ARGV[0] eq "-o") { $outfile = $ARGV[1]; shift @ARGV; }
	elsif ($ARGV[0] eq "-c") { $cmdline = $ARGV[1]; shift @ARGV; }
	elsif ($ARGV[0] eq "-stdin") { $fromstdin = 1; }
	elsif ($ARGV[0] eq "-save-temps") { $savetemps = 1; }
	else { push(@modules, $ARGV[0]); }
	shift @ARGV;
}

# Add in anything from stdin if they've asked for it.
if ($fromstdin) {
        while (<STDIN>) {
		@words = split;
		push(@modules, @words);
	}
}

# Link in the command line if they specified one
if ($cmdline) {
        open(CMD, ">$outfile.cmdline") || 
	  die "could not open cmdline file: $!\n";
	print CMD "$cmdline\n";
	push(@modules, "${outfile}.cmdline:${cmdlinefile}");
	close(CMD);
}

open(OUT, ">${outfile}.mods.S") || die "Could not open outfile: $!\n";

@files = ();
@linksmade = ();

# Wrap each of the input files in a .o file.
# At the same time, build an assembly language module
# containing a table describing the boot modules.
print OUT ".text; .long 0xf00baabb\n";
foreach $module (@modules) {

        $file = $module;
	$file =~ s/:.*$//;
	
	$string = $module;
	$string =~ s/^[^:]*://;
	
	if (!length($string)) { $string = $file; }
	
	if (!$FOUND{$file}) {	# Must be unique or we get linker probs
	        push(@files, $file); #This is where the shell script bogged down
	      }
	$FOUND{$file} = 1;

	# Convert all non alphanumeric characters to underscores.
	# The BFD binary input format will do the same thing
	# to produce the symbol names that it wraps around the input files
	$sym_name = $file;
	$sym_name =~ s/[^a-zA-Z0-9]/_/g;
	
	print OUT<<EOL;
.long _binary_${sym_name}_start
.long _binary_${sym_name}_end
.long cmdline_${sym_name}
.data 
cmdline_${sym_name}: .ascii "${string}\\0"
.text
EOL
}

print OUT ".long 0; .data; .align 4\n";
close(OUT);

# Turn the assembly discription module into a .o
$res = system("${cc} -c -o ${outfile}.mods.o ${outfile}.mods.S");
if ($res) {
  &cleanup();
  die "FATAL: C compiler ($cc) failed\n";
}

# Parse the file list, doing any necessary magic to the files.
# I'm leaving this in for now for experimentation with different
# linker args to try to get library files to load properly.
$filelist = "";
foreach $file (@files) {
  ## Escape any '$' that occur in file names (happens a lot with Java classes)
  $file =~ s/\$/\\\$/g;
  $filelist .= "$file ";
}

# Link the module vector file with the boot module files.
# Use the binary bfd backend for both the input bmod files and the output file.
# There is no meaning to "entry point address" in the binary backend,
# but the linker doesn't know that, so it looks for _start and warns
# when it doesn't find it (and falls back to zero).  To stifle the warning
# we can define the `_start' symbol on the command line; I think this
# works with all GNU ld versions.
$res = system("$ld $ldopts -o $outfile.tmp $outfile.mods.o -format binary $filelist -format default");

if ($res) {
  &cleanup();
  die "FATAL: linker ($ld) failed: $res\n";
}

# Compress the whole output file as one big glob.
$res = system("gzip -9 <$outfile.tmp >$outfile.tmp.gz");

if ($res) {
  &cleanup();
  die "FATAL: gzip failed: $res\n";
}

# Create the final boot image by tacking that onto the end of 'linuxboot.bin'.
$res = system("cat $bootdir/linuxboot.bin $outfile.tmp.gz >$outfile");

if ($res) {
  &cleanup();
  die "FATAL: final cat failed: $res\n";
}

# Update the system size in the boot image, so we only load as much 
# as we need, not DEF_SYSSIZE
open(OUT, "+< ${outfile}") || die "Couldn't open $outfile.\n";
$size = (stat OUT)[7];
$sys_size = ($size + 15) / 16;
if ($sys_size > $def_syssize) {
	print "System size is too big!\n";
}

seek(OUT, 500, 0) || die "Couldn't seek to SYS_SIZE offset.\n";
$buf = pack("v", $sys_size);
(syswrite(OUT, $buf, 2) == 2) || die "Couldn't write system size!\n";

close OUT;


# Nuke our temp files, unless asked not to.
if (!$savetemps) {
  system("rm -f $outfile.mods.S $outfile.mods.o ${outfile}.cmdline $outfile.tmp*");
}

exit(0);

# A stub if we need to do any cleanup on failure.
sub cleanup {
  ;
}
