From 93867f30c7e1fece2e19ea5840b8f96382230ee2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Radek=20Mat=C4=9Bjka?= Date: Wed, 19 Dec 2012 15:06:55 -0600 Subject: [PATCH] updated kernel module benchmark, added endianness conversion --- bench/bench | 15 +- bench/ncrcv | 15 +- bench/novaboot | 1077 ++++++++++++++++++++++ bench/plotres.m | 2 +- bench/result/2012-12-19-145516_bench.raw | 4 + bench/result/2012-12-19-145516_bench.res | 12 + kernel/canethgw.c | 4 +- utils/cegwbench/Makefile | 2 + utils/cegwbench/cegwbench.c | 10 +- 9 files changed, 1116 insertions(+), 25 deletions(-) create mode 100755 bench/novaboot create mode 100644 bench/result/2012-12-19-145516_bench.raw create mode 100644 bench/result/2012-12-19-145516_bench.res diff --git a/bench/bench b/bench/bench index 15a003f..294b813 100755 --- a/bench/bench +++ b/bench/bench @@ -15,20 +15,21 @@ fi # bench kernel #----------------- modprobe canethgw +mkcegwdev -cegw --listen udp@127.0.0.1:10501 -cegw --add -s can@vcan0 -d udp@127.0.0.1:10502 -cegw --add -s udp@127.0.0.1:10502 -d can@vcan0 +cegw vcan0 127.0.0.1:10501 127.0.0.1:10502 & +JOB=$! sleep $SLEEP -echo "#kernel udp->can: " | tee -a result | cut -c 2- +echo "#kernel udp->can" | tee -a result | cut -c 2- cegwbench -s udp@127.0.0.1:10501 -d can@vcan0 -n $N -m $MODE -t 1 >> $RESULTFILE sleep $SLEEP -echo "#kernel can->udp: " | tee -a result | cut -c 2- +echo "#kernel can->udp" | tee -a result | cut -c 2- cegwbench -s can@vcan0 -d udp@127.0.0.1:10502 -n $N -m $MODE -t 1 >> $RESULTFILE sleep $SLEEP +kill $JOB modprobe -r canethgw #----------------- @@ -38,11 +39,11 @@ canethgw -s can@vcan0 -d udp@127.0.0.1:10502 -l udp@127.0.0.1:10501 & PID=$! sleep $SLEEP -echo "#user udp->can: " | tee -a result | cut -c 2- +echo "#user udp->can" | tee -a result | cut -c 2- cegwbench -s udp@127.0.0.1:10501 -d can@vcan0 -n $N -m $MODE -t 1 >> $RESULTFILE sleep $SLEEP -echo "#user can->udp: " | tee -a result | cut -c 2- +echo "#user can->udp" | tee -a result | cut -c 2- cegwbench -s can@vcan0 -d udp@127.0.0.1:10502 -n $N -m $MODE -t 1 >> $RESULTFILE sleep $SLEEP diff --git a/bench/ncrcv b/bench/ncrcv index f9e39d5..34a7e03 100755 --- a/bench/ncrcv +++ b/bench/ncrcv @@ -1,18 +1,11 @@ #!/bin/bash PORT=10600 -ID=`ls -1 result | tail -1 | grep -o "[0-9]*\." | sed "s/\.//"` -echo -n "nc listening on $PORT: " -nc -l -p $PORT > res +FILENAME=`date +%Y-%m-%d-%H%M%S`_bench -if [ -z $ID ]; then - let id=1 -else - let id=ID+1 -fi; +echo -n "nc listening on $PORT: " +nc -l -p $PORT > result/$FILENAME.res -FILENAME=result$id -mv res result/$FILENAME.dat cd result -sed /^#/d $FILENAME.dat > $FILENAME.raw +sed /^#/d $FILENAME.res > $FILENAME.raw echo "done" diff --git a/bench/novaboot b/bench/novaboot new file mode 100755 index 0000000..bc52ee3 --- /dev/null +++ b/bench/novaboot @@ -0,0 +1,1077 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use Getopt::Long qw(GetOptionsFromString); +use Pod::Usage; +use File::Basename; +use File::Spec; +use IO::Handle; +use Time::HiRes("usleep"); +use Socket; +use FileHandle; +use IPC::Open2; +use POSIX qw(:errno_h); +use Cwd; + +# always flush +$| = 1; + +my $invocation_dir = getcwd(); + +chomp(my $gittop = `git rev-parse --show-toplevel 2>/dev/null`); + +# Default configuration +$CFG::hypervisor = "bin/apps/hypervisor"; +$CFG::hypervisor_params = "serial"; +$CFG::server = "erwin.inf.tu-dresden.de:boot/novaboot/\$NAME"; +$CFG::server_grub_prefix = "(nd)/tftpboot/sojka/novaboot/\$NAME"; +$CFG::grub_keys = ''; #"/novaboot\n\n/\$NAME\n\n"; +$CFG::grub2_prolog = " set root='(hd0,msdos1)'"; +$CFG::genisoimage = "genisoimage"; +$CFG::iprelay_addr = '141.76.48.80:2324'; #'141.76.48.252'; +$CFG::qemu = 'qemu'; +$CFG::script_modifier = ''; # Depricated, use --scriptmod commandline option or custom_options. +@CFG::chainloaders = (); #('bin/boot/bender promisc'); +$CFG::pulsar_root = ''; +$CFG::pulsar_mac = '52-54-00-12-34-56'; +%CFG::custom_options = ('I' => '--server=erwin.inf.tu-dresden.de:~passive/boot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub-prefix=(nd)/tftpboot/passive/ --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g', + 'J' => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar=novaboot --iprelay=147.32.86.92:2324'); +$CFG::scons = "scons -j2"; + +my @qemu_flags = qw(-cpu coreduo -smp 2); +sub read_config($) { + my ($cfg) = @_; + { + package CFG; # Put config data into a separate namespace + my $rc = do($cfg); + + # Check for errors + if ($@) { + die("ERROR: Failure compiling '$cfg' - $@"); + } elsif (! defined($rc)) { + die("ERROR: Failure reading '$cfg' - $!"); + } elsif (! $rc) { + die("ERROR: Failure processing '$cfg'"); + } + } +} + +my $cfg = $ENV{'NOVABOOT_CONFIG'} || $ENV{'HOME'}."/.novaboot"; +Getopt::Long::Configure(qw/no_ignore_case pass_through/); +GetOptions ("config|c=s" => \$cfg); +if (-s $cfg) { read_config($cfg); } + +# Command line +my ($append, $bender, $builddir, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, $grub_config, $grub_prefix, $grub2_config, $help, $iprelay, $iso_image, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $qemu, $qemu_append, $qemu_flags_cmd, $rsync_flags, @scriptmod, $scons, $serial, $server); + +$rsync_flags = ''; + +Getopt::Long::Configure(qw/no_ignore_case no_pass_through/); +my %opt_spec = ( + "append|a=s" => \$append, + "bender|b" => \$bender, + "build-dir=s" => \$builddir, + "dhcp-tftp|d" => \$dhcp_tftp, + "dump" => \$dump_opt, + "dump-config" => \$dump_config, + "grub|g:s" => \$grub_config, + "grub-prefix=s" => \$grub_prefix, + "grub2:s" => \$grub2_config, + "iprelay:s" => \$iprelay, + "iso|i:s" => \$iso_image, + "name=s" => \$config_name_opt, + "no-file-gen" => \$no_file_gen, + "off" => \$off_opt, + "on" => \$on_opt, + "pulsar|p:s" => \$pulsar, + "qemu|Q=s" => \$qemu, + "qemu-append=s" => \$qemu_append, + "qemu-flags|q=s" => \$qemu_flags_cmd, + "rsync-flags=s" => \$rsync_flags, + "scons:s" => \$scons, + "scriptmod=s" => \@scriptmod, + "serial|s:s" => \$serial, + "server:s" => \$server, + "h" => \$help, + "help" => \$man, + ); +foreach my $opt(keys(%CFG::custom_options)) { + $opt_spec{$opt} = sub { GetOptionsFromString($CFG::custom_options{$opt}, %opt_spec); }; +} +GetOptions %opt_spec or pod2usage(2); +pod2usage(1) if $help; +pod2usage(-exitstatus => 0, -verbose => 2) if $man; + +$CFG::iprelay_addr = $ENV{'NOVABOOT_IPRELAY'} if $ENV{'NOVABOOT_IPRELAY'}; +if ($iprelay && $iprelay ne "on" && $iprelay ne "off") { $CFG::iprelay_addr = $iprelay; } + +if (defined $config_name_opt && scalar(@ARGV) > 1) { die "You cannot use --name with multiple scripts"; } + +if ($server) { $CFG::server = $server; } +if ($qemu) { $CFG::qemu = $qemu; } +$qemu_append ||= ''; + +if (defined $pulsar) { + $CFG::pulsar_mac = $pulsar; +} + +if ($scons) { $CFG::scons = $scons; } +if (!@scriptmod && $CFG::script_modifier) { @scriptmod = ( $CFG::script_modifier ); } +if (defined $grub_prefix) { $CFG::server_grub_prefix = $grub_prefix; } + +if ($dump_config) { + use Data::Dumper; + $Data::Dumper::Indent=0; + print "# This file is in perl syntax.\n"; + foreach my $key(sort(keys(%CFG::))) { # See "Symbol Tables" in perlmod(1) + if (defined ${$CFG::{$key}}) { print Data::Dumper->Dump([${$CFG::{$key}}], ["*$key"]); } + if (defined @{$CFG::{$key}}) { print Data::Dumper->Dump([\@{$CFG::{$key}}], ["*$key"]); } + if ( %{$CFG::{$key}}) { print Data::Dumper->Dump([\%{$CFG::{$key}}], ["*$key"]); } + print "\n"; + } + print "1;\n"; + exit; +} + +if (defined $serial) { + $serial ||= "/dev/ttyUSB0"; +} + +if (defined $grub_config) { + $grub_config ||= "menu.lst"; +} + +if (defined $grub2_config) { + $grub2_config ||= "grub.cfg"; +} + +if ($on_opt) { $iprelay="on"; } +if ($off_opt) { $iprelay="off"; } + +# Parse the config(s) +my @scripts; +my $file; +my $line; +my $EOF; +my $last_fn = ''; +my ($modules, $variables, $generated, $continuation); +while (<>) { + if ($ARGV ne $last_fn) { # New script + die "Missing EOF in $last_fn" if $file; + die "Unfinished line in $last_fn" if $line; + $last_fn = $ARGV; + push @scripts, { 'filename' => $ARGV, + 'modules' => $modules = [], + 'variables' => $variables = {}, + 'generated' => $generated = []}; + + } + chomp(); + next if /^#/ || /^\s*$/; # Skip comments and empty lines + + foreach my $mod(@scriptmod) { eval $mod; } + + print "$_\n" if $dump_opt; + + if (/^([A-Z_]+)=(.*)$/) { # Internal variable + $$variables{$1} = $2; + next; + } + if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start + push @$modules, "$1$2"; + $file = []; + push @$generated, {filename => $1, content => $file}; + $EOF = $3; + next; + } + if ($file && $_ eq $EOF) { # Heredoc end + undef $file; + next; + } + if ($file) { # Heredoc content + push @{$file}, "$_\n"; + next; + } + $_ =~ s/^[[:space:]]*// if ($continuation); + if (/\\$/) { # Line continuation + $line .= substr($_, 0, length($_)-1); + $continuation = 1; + next; + } + $continuation = 0; + $line .= $_; + $line .= " $append" if ($append && scalar(@$modules) == 0); + + if ($line =~ /^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution + push @$modules, "$1$2"; + push @$generated, {filename => $1, command => $3}; + $line = ''; + next; + } + push @$modules, $line; + $line = ''; +} +#use Data::Dumper; +#print Dumper(\@scripts); + +exit if $dump_opt; + +sub generate_configs($$$) { + my ($base, $generated, $filename) = @_; + if ($base) { $base = "$base/"; }; + foreach my $g(@$generated) { + if (exists $$g{content}) { + my $config = $$g{content}; + my $fn = $$g{filename}; + open(my $f, '>', $fn) || die("$fn: $!"); + map { s|\brom://([^ ]*)|rom://$base$1|g; print $f "$_"; } @{$config}; + close($f); + print "novaboot: Created $fn\n"; + } elsif (exists $$g{command} && ! $no_file_gen) { + $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir )); + system_verbose("( $$g{command} ) > $$g{filename}"); + } + } +} + +sub generate_grub_config($$$$;$) +{ + my ($filename, $title, $base, $modules_ref, $prepend) = @_; + if ($base) { $base = "$base/"; }; + open(my $fg, '>', $filename) or die "$filename: $!"; + print $fg "$prepend\n" if $prepend; + my $endmark = ($serial || defined $iprelay) ? ';' : ''; + print $fg "title $title$endmark\n" if $title; + #print $fg "root $base\n"; # root doesn't really work for (nd) + my $first = 1; + foreach (@$modules_ref) { + if ($first) { + $first = 0; + my ($kbin, $kcmd) = split(' ', $_, 2); + $kcmd = '' if !defined $kcmd; + print $fg "kernel ${base}$kbin $kcmd\n"; + } else { + s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0 + print $fg "module $base$_\n"; + } + } + close($fg); +} + +sub generate_grub2_config($$$$;$) +{ + my ($filename, $title, $base, $modules_ref, $prepend) = @_; + if ($base && substr($base,-1,1) ne '/') { $base = "$base/"; }; + open(my $fg, '>', $filename) or die "$filename: $!"; + print $fg "$prepend\n" if $prepend; + my $endmark = ($serial || defined $iprelay) ? ';' : ''; + $title ||= 'novaboot'; + print $fg "menuentry $title$endmark {\n"; + print $fg "$CFG::grub2_prolog\n"; + my $first = 1; + foreach (@$modules_ref) { + if ($first) { + $first = 0; + my ($kbin, $kcmd) = split(' ', $_, 2); + $kcmd = '' if !defined $kcmd; + print $fg " multiboot ${base}$kbin $kcmd\n"; + } else { + my @args = split; + # GRUB2 doesn't pass filename in multiboot info so we have to duplicate it here + $_ = join(' ', ($args[0], @args)); + #s|\brom://([^ ]*)|rom://$base$1|g; # Translate rom:// files - needed for vdisk parameter of sigma0 + print $fg " module $base$_\n"; + } + } + print $fg "}\n"; + close($fg); +} + +sub generate_pulsar_config($$) +{ + my ($filename, $modules_ref) = @_; + open(my $fg, '>', $filename) or die "$filename: $!"; + print $fg "root $CFG::pulsar_root\n" if $CFG::pulsar_root; + my $first = 1; + my ($kbin, $kcmd); + foreach (@$modules_ref) { + if ($first) { + $first = 0; + ($kbin, $kcmd) = split(' ', $_, 2); + $kcmd = '' if !defined $kcmd; + } else { + my @args = split; + print $fg "load $_\n"; + } + } + # Put kernel as last - this is needed for booting Linux and has no influence on non-Linux OSes + print $fg "exec $kbin $kcmd\n"; + close($fg); +} + +sub exec_verbose(@) +{ + print "novaboot: Running: ".join(' ', map("'$_'", @_))."\n"; + exec(@_); +} + +sub system_verbose($) +{ + my $cmd = shift; + print "novaboot: Running: $cmd\n"; + my $ret = system($cmd); + if ($ret & 0x007f) { die("Command terminated by a signal"); } + if ($ret & 0xff00) {die("Command exit with non-zero exit code"); } + if ($ret) { die("Command failure $ret"); } +} + +if (exists $variables->{WVDESC}) { + print "Testing \"$variables->{WVDESC}\" in $last_fn:\n"; +} elsif ($last_fn =~ /\.wv$/) { + print "Testing \"all\" in $last_fn:\n"; +} + +my $IPRELAY; +if (defined $iprelay) { + $CFG::iprelay_addr =~ /([.0-9]+)(:([0-9]+))?/; + my $addr = $1; + my $port = $3 || 23; + my $paddr = sockaddr_in($port, inet_aton($addr)); + my $proto = getprotobyname('tcp'); + socket($IPRELAY, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; + print "novaboot: Connecting to IP relay... "; + connect($IPRELAY, $paddr) || die "connect: $!"; + print "done\n"; + $IPRELAY->autoflush(1); + + while (1) { + print $IPRELAY "\xFF\xF6"; + alarm(20); + local $SIG{ALRM} = sub { die "Relay AYT timeout"; }; + my $ayt_reponse = ""; + my $read = sysread($IPRELAY, $ayt_reponse, 100); + alarm(0); + + chomp($ayt_reponse); + print "$ayt_reponse\n"; + if ($ayt_reponse =~ /blocking(0); + + alarm(20); # Timeout in seconds + my $giveup = 0; + local $SIG{ALRM} = sub { + if ($can_giveup) { print("Relay confirmation timeout - ignoring\n"); $giveup = 1;} + else {die "Relay confirmation timeout";} + }; + my $index; + while (($index=index($confirmation, relayconf($relay, $onoff))) < 0 && !$giveup) { + my $read = read($IPRELAY, $confirmation, 70, length($confirmation)); + if (!defined($read)) { + die("IP relay: $!") unless $! == EAGAIN; + usleep(10000); + next; + } + #use MIME::QuotedPrint; + #print "confirmation = ".encode_qp($confirmation)."\n"; + } + alarm(0); + $IPRELAY->blocking(1); + } +} + +if ($iprelay && ($iprelay eq "on" || $iprelay eq "off")) { + relay(1, 1); # Press power button + if ($iprelay eq "on") { + usleep(100000); # Short press + } else { + usleep(6000000); # Long press to switch off + } + print $IPRELAY relay(1, 0); + exit; +} + +if ($builddir) { + $CFG::builddir = $builddir; +} else { + if (! defined $CFG::builddir) { + $CFG::builddir = ( $gittop || $ENV{'HOME'}."/nul" ) . "/build"; + if (! -d $CFG::builddir) { + $CFG::builddir = $ENV{SRCDIR} = dirname(File::Spec->rel2abs( ${$scripts[0]}{filename}, $invocation_dir )); + } + } +} + +chdir($CFG::builddir) or die "Can't change directory to $CFG::builddir: $!"; +print "novaboot: Entering directory `$CFG::builddir'\n"; + +my (%files_iso, $menu_iso, $config_name, $filename); + +foreach my $script (@scripts) { + $filename = $$script{filename}; + $modules = $$script{modules}; + $generated = $$script{generated}; + $variables = $$script{variables}; + my ($server_grub_prefix); + + if (defined $config_name_opt) { + $config_name = $config_name_opt; + } else { + ($config_name = $filename) =~ s#.*/##; + } + + my $kernel; + if (exists $variables->{KERNEL}) { + $kernel = $variables->{KERNEL}; + } else { + $kernel = $CFG::hypervisor . " "; + if (exists $variables->{HYPERVISOR_PARAMS}) { + $kernel .= $variables->{HYPERVISOR_PARAMS}; + } else { + $kernel .= $CFG::hypervisor_params; + } + } + @$modules = ($kernel, @$modules); + @$modules = (@CFG::chainloaders, @$modules); + @$modules = ("bin/boot/bender", @$modules) if ($bender || defined $ENV{'NOVABOOT_BENDER'}); + + if (defined $grub_config) { + generate_configs("", $generated, $filename); + generate_grub_config($grub_config, $config_name, "", $modules); + print("GRUB menu created: $CFG::builddir/$grub_config\n"); + exit; + } + + if (defined $grub2_config && !defined $server) { + generate_configs('', $generated, $filename); + generate_grub2_config($grub2_config, $config_name, $CFG::builddir, $modules); + print("GRUB2 configuration created: $CFG::builddir/$grub2_config\n"); + exit; + } + + my $pulsar_config; + if (defined $pulsar) { + $pulsar_config = "config-$CFG::pulsar_mac"; + generate_configs('', $generated, $filename); + generate_pulsar_config($pulsar_config, $modules); + if (!defined $server) { + print("Pulsar configuration created: $CFG::builddir/$pulsar_config\n"); + exit; + } + } + + if (defined $scons) { + my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules); + # Filter-out generated files + my @to_build = grep({ my $file = $_; !scalar(grep($file eq $$_{filename}, @$generated)) } @files); + system_verbose($CFG::scons." ".join(" ", @to_build)); + } + + if (defined $server) { + ($server_grub_prefix = $CFG::server_grub_prefix) =~ s/\$NAME/$config_name/; + ($server = $CFG::server) =~ s/\$NAME/$config_name/; + my $bootloader_config; + if ($grub2_config) { + generate_configs('', $generated, $filename); + $bootloader_config ||= "grub.cfg"; + generate_grub2_config($grub2_config, $config_name, $server_grub_prefix, $modules); + } elsif (defined $pulsar) { + $bootloader_config = $pulsar_config; + } else { + generate_configs($server_grub_prefix, $generated, $filename); + $bootloader_config ||= "menu.lst"; + if (!grep { $_ eq $bootloader_config } @$modules) { + generate_grub_config($bootloader_config, $config_name, $server_grub_prefix, $modules, + $server_grub_prefix eq $CFG::server_grub_prefix ? "timeout 0" : undef); + } + } + my ($hostname, $path) = split(":", $server, 2); + if (! defined $path) { + $path = $hostname; + $hostname = ""; + } + my $files = "$bootloader_config " . join(" ", map({ ($file) = m/([^ ]*)/; $file; } @$modules)); + my $combined_menu_lst = ($CFG::server =~ m|/\$NAME$|); + map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules); + my $istty = -t STDOUT && $ENV{'TERM'} ne "dumb"; + my $progress = $istty ? "--progress" : ""; + system_verbose("rsync $progress -RLp $rsync_flags $files $server"); + my $cmd = "cd $path/.. && cat */menu.lst > menu.lst"; + if ($combined_menu_lst) { system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd); } + } + + if (defined $iso_image) { + generate_configs("(cd)", $generated, $filename); + my $menu; + generate_grub_config(\$menu, $config_name, "(cd)", $modules); + $menu_iso .= "$menu\n"; + map { ($file,undef) = split; $files_iso{$file} = 1; } @$modules; + } +} + +if (defined $iso_image) { + open(my $fh, ">menu-iso.lst"); + print $fh "timeout 5\n\n$menu_iso"; + close($fh); + my $files = "boot/grub/menu.lst=menu-iso.lst " . join(" ", map("$_=$_", keys(%files_iso))); + $iso_image ||= "$config_name.iso"; + system_verbose("$CFG::genisoimage -R -b stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -hide-rr-moved -J -joliet-long -o $iso_image -graft-points bin/boot/grub/ $files"); + print("ISO image created: $CFG::builddir/$iso_image\n"); +} + +###################################################################### +# Boot NOVA using various methods and send serial output to stdout +###################################################################### + +if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $iprelay)) { + die "You cannot do this with multiple scripts simultaneously"; +} + +if ($variables->{WVTEST_TIMEOUT}) { + print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n"; +} + +if (!(defined $dhcp_tftp || defined $serial || defined $iprelay || defined $server || defined $iso_image)) { + # Qemu + @qemu_flags = split(/ /, $variables->{QEMU_FLAGS}) if exists $variables->{QEMU_FLAGS}; + @qemu_flags = split(/ /, $qemu_flags_cmd) if $qemu_flags_cmd; + push(@qemu_flags, split(/ /, $qemu_append)); + + if (defined $iso_image) { + # Boot NOVA with grub (and test the iso image) + push(@qemu_flags, ('-cdrom', "$config_name.iso")); + } else { + # Boot NOVA without GRUB + + # Non-patched qemu doesn't like commas, but NUL can live with pluses instead of commans + foreach (@$modules) {s/,/+/g;} + generate_configs("", $generated, $filename); + + my ($kbin, $kcmd) = split(' ', shift(@$modules), 2); + $kcmd = '' if !defined $kcmd; + my $initrd = join ",", @$modules; + + push(@qemu_flags, ('-kernel', $kbin, '-append', $kcmd)); + push(@qemu_flags, ('-initrd', $initrd)) if $initrd; + } + push(@qemu_flags, qw(-serial stdio)); # Redirect serial output (for collecting test restuls) + exec_verbose(($CFG::qemu, '-name', $config_name, @qemu_flags)); +} + +my ($dhcpd_pid, $tftpd_pid); + +if (defined $dhcp_tftp) +{ + generate_configs("(nd)", $generated, $filename); + system_verbose('mkdir -p tftpboot'); + generate_grub_config("tftpboot/os-menu.lst", $config_name, "(nd)", \@$modules, "timeout 0"); + open(my $fh, '>', 'dhcpd.conf'); + my $mac = `cat /sys/class/net/eth0/address`; + chomp $mac; + print $fh "subnet 10.23.23.0 netmask 255.255.255.0 { + range 10.23.23.10 10.23.23.100; + filename \"bin/boot/grub/pxegrub.pxe\"; + next-server 10.23.23.1; +} +host server { + hardware ethernet $mac; + fixed-address 10.23.23.1; +}"; + close($fh); + system_verbose("sudo ip a add 10.23.23.1/24 dev eth0; + sudo ip l set dev eth0 up; + sudo touch dhcpd.leases"); + + $dhcpd_pid = fork(); + if ($dhcpd_pid == 0) { + # This way, the spawned server are killed when this script is killed. + exec_verbose("sudo dhcpd -d -cf dhcpd.conf -lf dhcpd.leases -pf dhcpd.pid"); + } + $tftpd_pid = fork(); + if ($tftpd_pid == 0) { + exec_verbose("sudo in.tftpd --foreground --secure -v -v -v $CFG::builddir"); + } + $SIG{TERM} = sub { print "CHILDS KILLED\n"; kill 15, $dhcpd_pid, $tftpd_pid; }; +} + +if ($serial || defined $iprelay) { + my $CONN; + if (defined $iprelay) { + print "novaboot: Reseting the test box... "; + relay(2, 1, 1); # Reset the machine + usleep(100000); + relay(2, 0); + print "done\n"; + + $CONN = $IPRELAY; + } elsif ($serial) { + system("stty -F $serial raw -crtscts -onlcr 115200"); + open($CONN, "+<", $serial) || die "open $serial: $!"; + $CONN->autoflush(1); + } + if (!defined $dhcp_tftp && $CFG::grub_keys) { + # Control grub via serial line + print "Waiting for GRUB's serial output... "; + while (<$CONN>) { + if (/Press any key to continue/) { print $CONN "\n"; last; } + } + $CFG::grub_keys =~ s/\$NAME/$config_name;/; + my @characters = split(//, $CFG::grub_keys); + foreach (@characters) { + print $CONN $_; + usleep($_ eq "\n" ? 100000 : 10000); + } + print $CONN "\n"; + print "done\n"; + } + # Pass the NOVA output to stdout. + while (<$CONN>) { + print; + } + kill 15, $dhcpd_pid, $tftpd_pid if ($dhcp_tftp); + exit; +} + +if (defined $dhcp_tftp) { + my $pid = wait(); + if ($pid == $dhcpd_pid) { print "dhcpd exited!\n"; } + elsif ($pid == $tftpd_pid) { print "tftpd exited!\n"; } + else { print "wait returned: $pid\n"; } + kill(15, 0); # Kill current process group i.e. all remaining children +} + +=head1 NAME + +novaboot - NOVA boot script interpreter + +=head1 SYNOPSIS + +B [ options ] [--] script... + +B<./script> [ options ] + +B --help + +=head1 DESCRIPTION + +This program makes it easier to boot NOVA or other operating system +(OS) in different environments. It reads a so called novaboot script +and uses it either to boot the OS in an emulator (e.g. in qemu) or to +generate the configuration for a specific bootloader and optionally to +copy the necessary binaries and other needed files to proper +locations, perhaps on a remote server. In case the system is actually +booted, its serial output is redirected to standard output if that is +possible. + +A typical way of using novaboot is to make the novaboot script +executable and set its first line to I<#!/usr/bin/env novaboot>. Then, +booting a particular OS configuration becomes the same as executing a +local program - the novaboot script. + +With C you can: + +=over 3 + +=item 1. + +Run NOVA in Qemu. This is the default action when no other action is +specified by command line switches. Thus running C +(or C<./script> as described above) will run Qemu with configuration +specified in the I