]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
doc: Add complex example of U-Boot interaction
[novaboot.git] / novaboot
index 2589374fb4b654376c7197affee78284ec560f08..e224300ff0c1216cce84a84e27f645f113e16790 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -13,6 +13,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+## Initialization
+
 use strict;
 use warnings;
 use warnings (exists $ENV{NOVABOOT_TEST} ? (FATAL => 'all') : ());
@@ -25,7 +27,7 @@ use Time::HiRes("usleep");
 use Socket;
 use FileHandle;
 use IPC::Open2;
-use POSIX qw(:errno_h);
+use POSIX qw(:errno_h sysconf);
 use Cwd qw(getcwd abs_path);
 use Expect;
 
@@ -41,7 +43,7 @@ $CFG::hypervisor = "";
 $CFG::hypervisor_params = "serial";
 $CFG::genisoimage = "genisoimage";
 $CFG::qemu = 'qemu-system-i386 -cpu coreduo -smp 2';
-$CFG::default_target = 'qemu';
+$CFG::default_target = '';
 %CFG::targets = (
     'qemu' => '--qemu',
     "tud" => '--server=erwin.inf.tu-dresden.de:~sojka/boot/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --grub --grub-prefix=(nd)/tftpboot/sojka/novaboot --grub-preamble="timeout 0" --concat --iprelay=141.76.48.80:2324 --scriptmod=s/\\\\bhostserial\\\\b/hostserialpci/g',
@@ -51,9 +53,15 @@ $CFG::default_target = 'qemu';
     "ryuglab" => '--target ryu --server=pc-sojkam.felk.cvut.cz:/srv/tftp --remote-cmd="ssh -tt pc-sojkam.felk.cvut.cz \"sterm -d -s 115200 /dev/ttyUSB0\""',
     "ryulocal" => '--target ryu --dhcp-tftp --serial --reset-cmd="if which dtrrts; then dtrrts $NB_SERIAL 0 1; sleep 0.1; dtrrts $NB_SERIAL 1 1; fi"',
     );
-chomp(my $nproc = `nproc`);
-$CFG::scons = "scons -j$nproc";
-$CFG::make = "make -j$nproc";
+
+{
+    my %const;
+    $const{linux}->{_SC_NPROCESSORS_CONF} = 83;
+    my $nproc = sysconf($const{$^O}->{_SC_NPROCESSORS_CONF});
+
+    $CFG::scons = "scons -j$nproc";
+    $CFG::make = "make -j$nproc";
+}
 
 my $builddir;
 
@@ -61,6 +69,7 @@ sub read_config($) {
     my ($cfg) = @_;
     {
        package CFG; # Put config data into a separate namespace
+
        my $rc = do($cfg);
 
        # Check for errors
@@ -89,6 +98,10 @@ my @cfgs;
        $dir = File::Spec->catdir(@dirs[0..$#dirs-1]);
     }
     @cfgs = reverse @cfgs;
+
+    my $xdg_config_home = $ENV{'XDG_CONFIG_HOME'} || $ENV{'HOME'}.'/.config';
+    unshift(@cfgs, "$xdg_config_home/novaboot") if -r "$xdg_config_home/novaboot";
+
     $dir = $ENV{'NOVABOOT_CONFIG_DIR'} || '/etc/novaboot.d';
     if (opendir(my $dh, $dir)) {
        my @etccfg = map { "$dir/$_" } grep { /^[-_a-zA-Z0-9]+$/ && -f "$dir/$_" } readdir($dh);
@@ -104,15 +117,24 @@ read_config($_) foreach $cfg or @cfgs;
 
 ## Command line handling
 
-my $explicit_target;
+my $explicit_target = $ENV{'NOVABOOT_TARGET'};
 GetOptions ("target|t=s" => \$explicit_target);
 
-my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, @expect_raw, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $reset, $reset_cmd, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, @uboot_init);
+my ($amt, @append, $bender, @chainloaders, $concat, $config_name_opt, $dhcp_tftp, $dump_opt, $dump_config, @exiton, $exiton_timeout, @expect_raw, $final_eol, $gen_only, $grub_config, $grub_prefix, $grub_preamble, $grub2_prolog, $grub2_config, $help, $ider, $interaction, $iprelay, $iso_image, $interactive, $kernel_opt, $make, $man, $netif, $no_file_gen, $off_opt, $on_opt, $pulsar, $pulsar_root, $qemu, $qemu_append, $qemu_flags_cmd, $remote_cmd, $remote_expect, $remote_expect_silent, $reset, $reset_cmd, $reset_send, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, $uboot_cmd, @uboot_init);
 
+# Default values of certain command line options
+%uboot_addr = (
+    'kernel'  => '${kernel_addr_r}',
+    'ramdisk' => '${ramdisk_addr_r}',
+    'fdt'     => '${fdt_addr_r}',
+    );
 $rsync_flags = '';
 $rom_prefix = 'rom://';
 $stty = 'raw -crtscts -onlcr 115200';
 $reset = 1;                    # Reset target by default
+$interaction = 1;              # Perform target interaction by default
+$final_eol = 1;
+$netif = 'eth0';
 
 my @expect_seen = ();
 sub handle_expect
@@ -144,9 +166,12 @@ my %opt_spec;
     "dump"          => \$dump_opt,
     "dump-config"    => \$dump_config,
     "exiton=s"       => \@exiton,
+    "exiton-timeout=i"=> \$exiton_timeout,
+    "exiton-re=s"    => sub { my ($n, $v) = @_; push(@exiton, '-re', $v); },
     "expect=s"      => \&handle_expect,
     "expect-re=s"    => \&handle_expect,
     "expect-raw=s"   => sub { my ($n, $v) = @_; unshift(@expect_raw, eval($v)); },
+    "final-eol!"     => \$final_eol,
     "gen-only"      => \$gen_only,
     "grub|g:s"              => \$grub_config,
     "grub-preamble=s"=> \$grub_preamble,
@@ -154,12 +179,14 @@ my %opt_spec;
     "grub2:s"       => \$grub2_config,
     "grub2-prolog=s" => \$grub2_prolog,
     "ider"           => \$ider,
+    "interaction!"   => \$interaction,
     "iprelay=s"             => \$iprelay,
     "iso:s"         => \$iso_image,
     "kernel|k=s"     => \$kernel_opt,
     "interactive|i"  => \$interactive,
     "name=s"        => \$config_name_opt,
     "make|m:s"      => \$make,
+    "netif=s"       => \$netif,
     "no-file-gen"    => \$no_file_gen,
     "off"           => \$off_opt,
     "on"            => \$on_opt,
@@ -170,8 +197,10 @@ my %opt_spec;
     "qemu-flags|q=s" => \$qemu_flags_cmd,
     "remote-cmd=s"   => \$remote_cmd,
     "remote-expect=s"=> \$remote_expect,
+    "remote-expect-silent=s"=> sub { $remote_expect=$_[1]; $remote_expect_silent=1; },
     "reset!"         => \$reset,
     "reset-cmd=s"    => \$reset_cmd,
+    "reset-send=s"   => \$reset_send,
     "rsync-flags=s"  => \$rsync_flags,
     "scons:s"       => \$scons,
     "scriptmod=s"    => \@scriptmod,
@@ -184,8 +213,10 @@ my %opt_spec;
     "tftp"          => \$tftp,
     "tftp-port=i"    => \$tftp_port,
     "uboot:s"       => \$uboot,
+    "no-uboot"      => sub { undef $uboot; },
     "uboot-addr=s"   => \%uboot_addr,
-    "uboot-init=s"   => \@uboot_init,
+    "uboot-cmd=s"    => \$uboot_cmd,
+    "uboot-init=s"   => sub { push @uboot_init, { command => $_[1] }; },
     "h"             => \$help,
     "help"          => \$man,
     );
@@ -242,6 +273,16 @@ if ($ider) {
     if (!defined $amt) { die "Error: --ider requires --amt"; }
 }
 
+{
+    my %input_opts = ('--iprelay'    => \$iprelay,
+                     '--serial'     => \$serial,
+                     '--remote-cmd' => \$remote_cmd,
+                     '--amt'        => \$amt);
+    my @opts = grep(defined(${$input_opts{$_}}) , keys %input_opts);
+
+    die("novaboot: More than one target connection option: ".join(', ', @opts)) if scalar @opts > 1;
+}
+
 # Default options
 if (defined $serial) {
     $serial ||= "/dev/ttyUSB0";
@@ -255,7 +296,7 @@ my @scripts;
 my $file;
 my $EOF;
 my $last_fn = '';
-my ($modules, $variables, $generated, $continuation) = ([], {}, []);
+my ($modules, $variables, $generated, $copy, $continuation) = ([], {}, [], []);
 my $skip_reading = defined($on_opt) || defined($off_opt);
 while (!$skip_reading && ($_ = <>)) {
     if ($ARGV ne $last_fn) { # New script
@@ -265,7 +306,9 @@ while (!$skip_reading && ($_ = <>)) {
        push @scripts, { 'filename' => $ARGV,
                         'modules' => $modules = [],
                         'variables' => $variables = {},
-                        'generated' => $generated = []};
+                        'generated' => $generated = [],
+                        'copy' => $copy = [],
+       };
 
     }
     chomp();
@@ -298,36 +341,45 @@ while (!$skip_reading && ($_ = <>)) {
        push(@exiton, $2) if ($1 eq "EXITON");
        next;
     }
-    if (s/^load *//) {         # Load line
+    sub process_load_copy($) {
        die("novaboot: '$last_fn' line $.: Missing file name\n") unless /^[^ <]+/;
        if (/^([^ ]*)(.*?)[[:space:]]*<<([^ ]*)$/) { # Heredoc start
-           push @$modules, "$1$2";
            $file = [];
            push @$generated, {filename => $1, content => $file};
            $EOF = $3;
-           next;
+           return "$1$2";
        }
        if (/^([^ ]*)(.*?)[[:space:]]*< ?(.*)$/) { # Command substitution
-           push @$modules, "$1$2";
            push @$generated, {filename => $1, command => $3};
-           next;
+           return "$1$2";
        }
-       push @$modules, $_;
+       return $_;
+    }
+    if (s/^load *//) {         # Load line
+       push @$modules, process_load_copy($_);
+       next;
+    }
+    if (s/^copy *//) {         # Copy line
+       push @$copy, process_load_copy($_);
        next;
     }
     if (/^run (.*)/) {         # run line
        push @$generated, {command => $1};
        next;
     }
-    if (/^uboot(?::([0-9]+)s)? (.*)/) {        # uboot line
+    if (/^uboot(?::([0-9]+)s)? +(< *)?(.*)/) { # uboot line
        # TODO: If U-Boot supports some interactive menu, it might
        # make sense to store uboot lines per novaboot script.
-       if ($1) {               # Command with explicit timeout
-           push @uboot_init, { command => $2,
-                               timeout => $1 };
-       } else {                # Command without explicit timeout
-           push @uboot_init, $2;
+       my ($timeout, $redir, $string, $dest) = ($1, $2, $3);
+       if ($string =~ /(.*) *> *(.*)/) {
+           $string = $1;
+           $dest = $2;
        }
+       push @uboot_init, { command => $redir ? "" : $string,
+                           system =>  $redir ? $string : "",
+                           timeout => $timeout,
+                           dest => $dest,
+       };
        next;
     }
 
@@ -378,7 +430,7 @@ sub generate_configs($$$) {
        open(my $f, '>', $fn) || die("$fn: $!");
        map { s|\brom://([^ ]*)|$rom_prefix$base$1|g; print $f "$_"; } @{$config};
        close($f);
-       print "novaboot: Created $fn\n";
+       print STDERR "novaboot: Created $fn\n";
       } elsif (exists $$g{command} && ! $no_file_gen) {
        $ENV{SRCDIR} = dirname(File::Spec->rel2abs( $filename, $invocation_dir ));
        if (exists $$g{filename}) {
@@ -510,7 +562,7 @@ sub shell_cmd_string(@)
 
 sub exec_verbose(@)
 {
-    print "novaboot: Running: ".shell_cmd_string(@_)."\n";
+    print STDERR "novaboot: Running: ".shell_cmd_string(@_)."\n";
     exec(@_);
     exit(1); # should not be reached
 }
@@ -518,19 +570,25 @@ sub exec_verbose(@)
 sub system_verbose($)
 {
     my $cmd = shift;
-    print "novaboot: Running: $cmd\n";
+    print STDERR "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"); }
 }
 
+sub trim($) {
+    my ($str) = @_;
+    $str =~ s/^\s+|\s+$//g;
+    return $str
+}
+
 ## WvTest headline
 
 if (exists $variables->{WVDESC}) {
-    print "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
+    print STDERR "Testing \"$variables->{WVDESC}\" in $last_fn:\n";
 } elsif ($last_fn =~ /\.wv$/) {
-    print "Testing \"all\" in $last_fn:\n";
+    print STDERR "Testing \"all\" in $last_fn:\n";
 }
 
 ## Connect to the target and check whether it is not occupied
@@ -554,9 +612,9 @@ if (defined $iprelay) {
     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... ";
+    print STDERR "novaboot: Connecting to IP relay... ";
     connect($IPRELAY, $paddr)    || die "connect: $!";
-    print "done\n";
+    print STDERR "done\n";
     $exp = Expect->init(\*$IPRELAY);
     $exp->log_stdout(1);
 
@@ -625,7 +683,7 @@ elsif ($serial) {
     $exp = Expect->init(\*$CONN);
 }
 elsif ($remote_cmd) {
-    print "novaboot: Running: $remote_cmd\n";
+    print STDERR "novaboot: Running: $remote_cmd\n";
     $exp = Expect->spawn($remote_cmd);
 }
 elsif (defined $amt) {
@@ -723,7 +781,7 @@ END
     };
 
     my $cmd = "amtterm -u $amt_user -p $amt_password $amt_host $amt_port";
-    print "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
+    print STDERR "novaboot: Running: $cmd\n" =~ s/\Q$amt_password\E/???/r;
     $exp = Expect->spawn($cmd);
     $exp->expect(10, "RUN_SOL") || die "Expect for 'RUN_SOL' timed out";
 
@@ -732,7 +790,15 @@ END
 
 if ($remote_expect) {
     $exp || die("No serial line connection");
-    $exp->expect(10, $remote_expect) || die "Expect for '$remote_expect' timed out";
+    my $log = $exp->log_stdout;
+    if (defined $remote_expect_silent) {
+       $exp->log_stdout(0);
+    }
+    $exp->expect(180, $remote_expect) || die "Expect for '$remote_expect' timed out";
+    if (defined $remote_expect_silent) {
+       $exp->log_stdout($log);
+       print $exp->after() if $log;
+    }
 }
 
 if (defined $reset_cmd) {
@@ -741,12 +807,19 @@ if (defined $reset_cmd) {
     };
 }
 
+if (defined $reset_send) {
+    $target_reset = sub {
+       $reset_send =~ s/\\n/\n/g;
+       $exp->send($reset_send);
+    };
+}
+
 if (defined $on_opt && defined $target_power_on) {
     &$target_power_on();
     exit;
 }
 if (defined $off_opt && defined $target_power_off) {
-    print "novaboot: Switching the target off...\n";
+    print STDERR "novaboot: Switching the target off...\n";
     &$target_power_off();
     exit;
 }
@@ -754,7 +827,7 @@ if (defined $off_opt && defined $target_power_off) {
 $builddir ||= dirname(File::Spec->rel2abs( ${$scripts[0]}{filename})) if scalar @scripts;
 if (defined $builddir) {
     chdir($builddir) or die "Can't change directory to $builddir: $!";
-    print "novaboot: Entering directory `$builddir'\n";
+    print STDERR "novaboot: Entering directory `$builddir'\n";
 } else {
     $builddir = $invocation_dir;
 }
@@ -776,7 +849,7 @@ foreach my $script (@scripts) {
     if (exists $variables->{BUILDDIR}) {
        $builddir = File::Spec->rel2abs($variables->{BUILDDIR});
        chdir($builddir) or die "Can't change directory to $builddir: $!";
-       print "novaboot: Entering directory `$builddir'\n";
+       print STDERR "novaboot: Entering directory `$builddir'\n";
     }
 
     if ($grub_prefix) {
@@ -812,14 +885,16 @@ foreach my $script (@scripts) {
            $path = $hostname;
            $hostname = "";
        }
-       my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs)));
+       my $files = join(" ", map({ ($file) = m/([^ ]*)/; $file; } ( @$modules, @bootloader_configs, @$copy)));
        map({ my $file = (split)[0]; die "$file: $!" if ! -f $file; } @$modules);
        my $istty = -t STDOUT && ($ENV{'TERM'} || 'dumb') ne 'dumb';
        my $progress = $istty ? "--progress" : "";
-       system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
-       if ($server =~ m|/\$NAME$| && $concat) {
-           my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
-           system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+       if ($files) {
+           system_verbose("rsync $progress -RLp $rsync_flags $files $real_server");
+           if ($server =~ m|/\$NAME$| && $concat) {
+               my $cmd = join("; ", map { "( cd $path/.. && cat */$_ > $_ )" } @bootloader_configs);
+               system_verbose($hostname ? "ssh $hostname '$cmd'" : $cmd);
+           }
        }
     }
 
@@ -876,13 +951,7 @@ if (scalar(@scripts) > 1 && ( defined $dhcp_tftp || defined $serial || defined $
 }
 
 if ($variables->{WVTEST_TIMEOUT}) {
-    print "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
-}
-
-sub trim($) {
-    my ($str) = @_;
-    $str =~ s/^\s+|\s+$//g;
-    return $str
+    print STDERR "wvtest: timeout ", $variables->{WVTEST_TIMEOUT}, "\n";
 }
 
 ### Start in Qemu
@@ -921,7 +990,7 @@ if (defined $qemu) {
     }
     push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
     unshift(@qemu_flags, ('-name', $config_name));
-    print "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
+    print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
 }
 
@@ -937,7 +1006,7 @@ if (defined $dhcp_tftp)
     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`;
+    my $mac = `cat /sys/class/net/$netif/address`;
     chomp $mac;
     print $fh "subnet 10.23.23.0 netmask 255.255.255.0 {
                      range 10.23.23.10 10.23.23.100;
@@ -949,8 +1018,8 @@ host server {
        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;
+    system_verbose("sudo ip a add 10.23.23.1/24 dev $netif;
+           sudo ip l set dev $netif up;
            sudo touch dhcpd.leases");
 
     # We run servers by forking ourselves, because the servers end up
@@ -977,7 +1046,7 @@ if (defined $dhcp_tftp || defined $tftp) {
 ### AMT IDE-R
 if (defined $ider) {
     my $ider_cmd= "amtider -c $iso_image -u $amt_user -p $amt_password $amt_host $amt_port"  ;
-    print "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
+    print STDERR "novaboot: Running: $ider_cmd\n" =~ s/\Q$amt_password\E/???/r;
     my $ider_pid = fork();
     if ($ider_pid == 0) {
        exec($ider_cmd);
@@ -991,15 +1060,29 @@ if (defined $ider) {
 ### Reset target (IP relay, AMT, ...)
 
 if (defined $target_reset && $reset) {
-    print "novaboot: Reseting the test box... ";
+    print STDERR "novaboot: Reseting the test box... ";
     &$target_reset();
-    print "done\n";
+    print STDERR "done\n";
+    if (defined $exp) {
+       # We don't want to output anything printed by the target
+       # before reset so we clear the buffers now. This is, however,
+       # not ideal because we may loose some data that were sent
+       # after the reset. If this is a problem, one should reset and
+       # connect to serial line in atomic manner. For example, if
+       # supported by hardware, use --remote-cmd 'sterm -d ...' and
+       # do not use separate --reset-cmd.
+       my $log = $exp->log_stdout;
+       $exp->log_stdout(0);
+       $exp->expect(0); # Read data from target
+       $exp->clear_accum();    # Clear the read data
+       $exp->log_stdout($log);
+    }
 }
 
 ### U-boot conversation
 if (defined $uboot) {
     my $uboot_prompt = $uboot || '=> ';
-    print "novaboot: Waiting for U-Boot prompt...\n";
+    print STDERR "novaboot: Waiting for U-Boot prompt...\n";
     $exp || die("No serial line connection");
     $exp->log_stdout(1);
     #$exp->exp_internal(1);
@@ -1008,15 +1091,17 @@ if (defined $uboot) {
                 $uboot_prompt) || die "No U-Boot prompt deteceted";
     foreach my $cmdspec (@uboot_init) {
        my ($cmd, $timeout);
-       if (ref($cmdspec) eq "HASH") {
-           $cmd = $cmdspec->{command};
-           $timeout = $cmdspec->{timeout};
+       die "Internal error - please report a bug" unless ref($cmdspec) eq "HASH";
+
+       if ($cmdspec->{system}) {
+           $cmd = `$cmdspec->{system}`;
        } else {
-           $cmd = $cmdspec;
-           $timeout = 10;
+           $cmd = $cmdspec->{command};
        }
+       $timeout = $cmdspec->{timeout} // 10;
+
        if ($cmd =~ /\$NB_MYIP/) {
-           my $ip = (grep /inet /, `ip addr show eth0`)[0] || die "Problem determining our IP address";
+           my $ip = (grep /inet /, `ip addr show $netif`)[0] || die "Problem determining IP address of $netif";
            $ip =~ s/\s*inet ([0-9.]*).*/$1/;
            $cmd =~ s/\$NB_MYIP/$ip/g;
        }
@@ -1027,81 +1112,92 @@ if (defined $uboot) {
        }
        chomp($cmd);
        $exp->send("$cmd\n");
-       $exp->expect($timeout, $uboot_prompt) || die "U-Boot prompt timeout";
+
+       my ($matched_pattern_position, $error,
+           $successfully_matching_string,
+           $before_match, $after_match) =
+               $exp->expect($timeout, $uboot_prompt);
+       die "No U-Boot prompt: $error" if $error;
+
+       if ($cmdspec->{dest}) {
+           open(my $fh, ">", $cmdspec->{dest}) or die "Cannot open '$cmdspec->{dest}': $!";
+           print $fh $before_match;
+           close($fh);
+       }
     }
 
-    # Boot the system if there are some load lines in the script
-    if (scalar(@$modules) > 0 && !$variables->{NO_BOOT}) {
+    # Load files if there are some load lines in the script
+    if (scalar(@$modules) > 0  && !$variables->{NO_BOOT}) {
        my ($kbin, $kcmd) = split(' ', shift(@$modules), 2);
        my $dtb;
        @$modules = map { if (/\.dtb$/) { $dtb=$_; (); } else { $_ } } @$modules;
        my $initrd = shift @$modules;
 
-       my ($ramdisk_addr, $fdt_addr) = ('-', '');
-
-       die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
-       $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
-       $exp->expect(10,
-                    [qr/##/, sub { exp_continue; }],
-                    $uboot_prompt) || die "Kernel load timeout";
+       if (defined $kbin) {
+           die "No '--uboot-addr kernel' given" unless $uboot_addr{kernel};
+           $exp->send("tftpboot $uboot_addr{kernel} $prefix$kbin\n");
+           $exp->expect(10,
+                        [qr/##/, sub { exp_continue; }],
+                        $uboot_prompt) || die "Kernel load timeout";
+       }
        if (defined $dtb) {
            die "No '--uboot-addr fdt' given" unless $uboot_addr{fdt};
-           $fdt_addr = $uboot_addr{fdt};
-           $exp->send("tftpboot $fdt_addr $prefix$dtb\n");
+           $exp->send("tftpboot $uboot_addr{fdt} $prefix$dtb\n");
            $exp->expect(10,
                         [qr/##/, sub { exp_continue; }],
                         $uboot_prompt) || die "Device tree load timeout";
        }
        if (defined $initrd) {
            die "No '--uboot-addr ramdisk' given" unless $uboot_addr{ramdisk};
-           $ramdisk_addr = $uboot_addr{ramdisk};
-           $exp->send("tftpboot $ramdisk_addr $prefix$initrd\n");
+           $exp->send("tftpboot $uboot_addr{ramdisk} $prefix$initrd\n");
            $exp->expect(10,
-                        [qr/#/, sub { exp_continue; }],
+                        [qr/##/, sub { exp_continue; }],
                         $uboot_prompt) || die "Initrd load timeout";
-       }
-       $exp->send("echo $kcmd\n");
-       $exp->expect(1, '-re', qr{echo .*\n(.*)\n$uboot_prompt})  || die "Command line test timeout";
-       my $args = ($exp->matchlist)[0];
-       if ($args =~ /^setenv\s+bootargs/) {
-           $exp->send("$args\n");
        } else {
-           $exp->send("setenv bootargs $kcmd\n");
+           $uboot_addr{ramdisk} = '-';
        }
+
+       $kcmd //= '';
+       $exp->send("setenv bootargs $kcmd\n");
        $exp->expect(5, $uboot_prompt)  || die "U-Boot prompt timeout";
-       $exp->send("bootm $uboot_addr{kernel} $ramdisk_addr $fdt_addr\n");
+
+    }
+    $uboot_cmd //= $variables->{UBOOT_CMD} // 'bootm $kernel_addr $ramdisk_addr $fdt_addr';
+    if (!$variables->{NO_BOOT} && $uboot_cmd ne '') {
+       $uboot_cmd =~ s/\$kernel_addr/$uboot_addr{kernel}/g;
+       $uboot_cmd =~ s/\$ramdisk_addr/$uboot_addr{ramdisk}/g;
+       $uboot_cmd =~ s/\$fdt_addr/$uboot_addr{fdt}/g;
+
+       $exp->send($uboot_cmd . "\n");
        $exp->expect(5, "\n")  || die "U-Boot command timeout";
     }
 }
 
 ### Serial line interaction
-if (defined $exp) {
+if ($interaction && defined $exp) {
     # Serial line of the target is available
     my $interrupt = 'Ctrl-C';
     if ($interactive && !@exiton) {
        $interrupt = '"~~."';
     }
-    my $note = (-t STDIN) ? '' : '- only target->host ';
-    print "novaboot: Serial line interaction $note(press $interrupt to interrupt)...\n";
+    print STDERR "novaboot: Serial line interaction (press $interrupt to interrupt)...\n";
     $exp->log_stdout(1);
     if (@exiton) {
-       $exp->expect(undef, @expect_raw, @exiton);
+       $exp->expect($exiton_timeout, @expect_raw, @exiton) || die("exiton timeout");
     } else {
        my @inputs = ($exp);
-       if (-t STDIN) { # Set up bi-directional communication if we run on terminal
-           my $infile = new IO::File;
-           $infile->IO::File::fdopen(*STDIN,'r');
-           my $in_object = Expect->exp_init($infile);
-           $in_object->set_group($exp);
-
-           if ($interactive) {
-               $in_object->set_seq('~~\.', sub { print "novaboot: Escape sequence detected\r\n"; undef; });
-               $in_object->manual_stty(0);       # Use raw terminal mode
-           } else {
-               $in_object->manual_stty(1);       # Do not modify terminal settings
-           }
-           push(@inputs, $in_object);
+       my $infile = new IO::File;
+       $infile->IO::File::fdopen(*STDIN,'r');
+       my $in_object = Expect->exp_init($infile);
+       $in_object->set_group($exp);
+
+       if ($interactive) {
+           $in_object->set_seq('~~\.', sub { print STDERR "novaboot: Escape sequence detected\r\n"; undef; });
+           $in_object->manual_stty(0);   # Use raw terminal mode
+       } else {
+           $in_object->manual_stty(1);   # Do not modify terminal settings
        }
+       push(@inputs, $in_object);
        #use Data::Dumper;
        #print Dumper(\@expect_raw);
        $exp->expect(undef, @expect_raw) if @expect_raw;
@@ -1114,11 +1210,16 @@ if (defined $dhcp_tftp || defined $tftp) {
     die("novaboot: This should kill servers on background\n");
 }
 
+# Always finish novaboot output with newline
+print "\n" if $final_eol;
+
 ## Documentation
 
+=encoding utf8
 =head1 NAME
 
-novaboot - A tool for booting various operating systems on various hardware or in qemu
+novaboot - Boots a locally compiled operating system on a remote
+target or in qemu
 
 =head1 SYNOPSIS
 
@@ -1130,102 +1231,144 @@ B<./script> [option]...
 
 =head1 DESCRIPTION
 
-This program makes booting of an operating system (e.g. NOVA or Linux)
-as simple as running a local program. It facilitates booting on local
-or remote hosts or in emulators such as qemu. Novaboot operation is
-controlled by command line options and by a so called novaboot script,
-which can be thought as a generalization of bootloader configuration
-files (see L</"NOVABOOT SCRIPT SYNTAX">). Based on this input,
-novaboot setups everything for the target host to boot the desired
-configuration, i.e. it generates the bootloader configuration file in
-the proper format, deploy the binaries and other needed files to
-required locations, perhaps on a remote boot server and reset the
-target host. Finally, target host's serial output is redirected to
-standard output if that is possible.
-
-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.
-
-For example, with C<novaboot> you can:
+Novaboot makes booting of a locally compiled operating system (OS)
+(e.g. NOVA or Linux) on remote targets as simple as running a program
+locally. It automates things like copying OS images to a TFTP server,
+generation of bootloader configuration files, resetting of target
+hardware or redirection of target's serial line to stdin/out. Novaboot
+is highly configurable and makes it easy to boot a single image on
+different targets or different images on a single target.
+
+Novaboot operation is controlled by configuration files, command line
+options and by a so called novaboot script, which can be thought as a
+generalization of bootloader configuration files (see L</"NOVABOOT
+SCRIPT SYNTAX">). 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.
+
+Novaboot uses configuration files to, among other things, define
+command line options needed for different targets. Users typically use
+only the B<-t>/B<--target> command line option to select the target.
+Internally, this option expands to the pre-configured options.
+Configuration files are searched at multiple places, which allows to
+have per-system, per-user or per-project configurations. Configuration
+file syntax is described in section L</"CONFIGURATION FILES">.
+
+Simple examples of using C<novaboot>:
 
 =over 3
 
 =item 1.
 
-Run an OS in Qemu. This is the default action when no other action is
-specified by command line switches. Thus running C<novaboot ./script>
-(or C<./script> as described above) will run Qemu and make it boot the
-configuration specified in the F<script>.
+Run an OS in Qemu. This can be specified with the B<--qemu> option.
+Thus running
+
+ novaboot --qemu myos
+
+(or C<./myos --qemu> as described above) will run Qemu and make it
+boot the configuration specified in the F<myos> script.
 
 =item 2.
 
 Create a bootloader configuration file (currently supported
 bootloaders are GRUB, GRUB2, ISOLINUX, Pulsar and U-Boot) and copy it
-with all other files needed for booting to a remote boot server.
+with all other files needed for booting to a remote boot server. Then
+use a TCP/IP-controlled relay/serial-to-TCP converter to reset the
+target and receive its serial output.
+
+ ./myos --grub2 --server=192.168.1.1:/tftp --iprelay=192.168.1.2
 
- ./script --server=192.168.1.1:/tftp --iprelay=192.168.1.2
+Alternatively, you can put these switches to the configuration file
+and run:
 
-This command copies files to the TFTP server and uses
-TCP/IP-controlled relay to reset the target host and receive its
-serial output.
+ ./myos --target mytarget
 
 =item 3.
 
-Run DHCP and TFTP server on developer's machine to PXE-boot the target
-machine from it. E.g.
+Run DHCP and TFTP server on developer's machine to boot the target
+from it.
 
- ./script --dhcp-tftp
+ ./myos --dhcp-tftp
 
-When a PXE-bootable machine is connected via Ethernet to developer's
-machine, it will boot the configuration described in the I<script>.
+This is useful when no network infrastructure is in place and
+the target is connected directly to developer's box.
 
 =item 4.
 
-Create bootable ISO images. E.g.
+Create bootable ISO image.
 
  novaboot --iso -- script1 script2
 
-The created ISO image will use ISOLINUX bootloader installed on it and
-the boot menu will allow selecting between I<script1> and I<script2>
-configurations.
+The created ISO image will have ISOLINUX bootloader installed on it
+and the boot menu will allow selecting between I<script1> and
+I<script2> configurations.
 
 =back
 
-Note that the options needed for a specific target can be stored in a
-L</"CONFIGURATION FILE">. Then it is sufficient to use only the B<-t>
-option to specify the name of the target.
-
 =head1 PHASES AND OPTIONS
 
 Novaboot performs its work in several phases. Each phase can be
 influenced by several command line options, certain phases can be
-skipped. The list of phases (in the execution order) and the
-corresponding options follow.
+skipped. The list of phases (in the execution order) is as follows.
+
+=over
+
+=item 1. L<Configuration reading|/Configuration reading phase>
+
+=item 2. L<Command line processing|/Command line processing phase>
+
+=item 3. L<Script preprocessing|/Script preprocessing phase>
+
+=item 4. L<File generation|/File generation phase>
+
+=item 5. L<Target connection|/Target connection check>
+
+=item 6. L<File deployment|/File deployment phase>
+
+=item 7. L<Target power-on and reset|/Target power-on and reset phase>
+
+=item 8. L<Interaction with the bootloader|/Interaction with the bootloader on the target>
+
+=item 9. L<Target interaction|/Target interaction phase>
+
+=back
+
+Each phase is described in the following sections together with the
+command line options that control it.
 
 =head2 Configuration reading phase
 
 After starting, novaboot reads configuration files. Their content is
-described in section L</"CONFIGURATION FILE">. By default,
-configuration is read from two locations. First from the configuration
-directory and second from F<.novaboot> files along the path to the
-current directory. The later read files override settings from the
-former ones.
-
-Configuration directory is determined by the content of
-NOVABOOT_CONFIG_DIR environment variable defaulting to
+described in section L</"CONFIGURATION FILES">. By default,
+configuration is read from multiple locations. First from the system
+configuration directory (F</etc/novaboot.d/>), second from the user
+configuration file (F<~/.config/novaboot>) and third from F<.novaboot>
+files along the path to the current directory. Alternatively, a single
+configuration file specified with the B<-c> switch or with the
+C<NOVABOOT_CONFIG> environment variable is read. The latter read files
+override settings from the former ones.
+
+The system configuration directory is determined by the content of
+NOVABOOT_CONFIG_DIR environment variable and defaults to
 F</etc/novaboot.d>. Files in this directory with names consisting
-solely from English letters, numbers, dashes '-' and underscores '_'
+solely of English letters, numbers, dashes '-' and underscores '_'
 (note that dot '.' is not included) are read in alphabetical order.
 
-Then novaboot searches for files named F<.novaboot> starting from the
+Then, the user configuration file is read from
+F<$XDG_CONFIG_HOME/novaboot>. If C<$XDG_CONFIG_HOME> environemnt
+variable is not set F<~/.config/novaboot> is read instead.
+
+Finally, novaboot searches for files named F<.novaboot> starting from the
 directory of the novaboot script (or working directory, see bellow)
 and continuing upwards up to the root directory. The found
 configuration files are then read in the opposite order (i.e. from the
-root directory downwards). This allows to have, for example, user
-specific configuration in F<~/.novaboot> and project specific one in
-F<~/project/.novaboot>.
+root directory downwards). This allows to have, for example, a project
+specific configuration in F<~/project/.novaboot>.
+
+Note the difference between F<~/.config/novaboot> and F<~/.novaboot>.
+The former one is read always, whereas the latter only when novaboot
+script or working directory is under the C<$HOME> directory.
 
 In certain cases, the location of the novaboot script cannot be
 determined in this early phase. This happens either when the script is
@@ -1248,7 +1391,7 @@ Use the specified configuration file instead of the default one(s).
 
 =item --dump-config
 
-Dump the current configuration to stdout end exits. Useful as an
+Dump the current configuration to stdout end exit. Useful as an
 initial template for a configuration file.
 
 =item -h, --help
@@ -1258,9 +1401,13 @@ Print short (B<-h>) or long (B<--help>) help.
 =item -t, --target=I<target>
 
 This option serves as a user configurable shortcut for other novaboot
-options. The effect of this option is the same as the options stored
-in the C<%targets> configuration variable under key I<target>. See
-also L</"CONFIGURATION FILE">.
+options. The effect of this option is the same as specifying the
+options stored in the C<%targets> configuration variable under key
+I<target>. See also L</"CONFIGURATION FILES">.
+
+When this option is not given, novaboot tries to determine the target
+to use from either B<NOVABOOT_TARGET> environment variable or from
+B<$default_target> configuration file variable.
 
 =back
 
@@ -1279,9 +1426,10 @@ command line. This option can appear multiple times.
 
 =item -b, --bender
 
-Use F<bender> chainloader. Bender scans the PCI bus for PCI serial
-ports and stores the information about them in the BIOS data area for
-use by the kernel.
+Use L<bender|https://github.com/TUD-OS/morbo/blob/master/standalone/bender.c>
+chainloader. Bender scans the PCI bus for PCI serial ports and stores
+the information about them in the BIOS data area for use by the
+kernel.
 
 =item --chainloader=I<chainloader>
 
@@ -1468,9 +1616,15 @@ example C<ssh server 'cu -l /dev/ttyS0'>.
 
 =item --remote-expect=I<string>
 
-Wait for reception of I<string> after establishing the the remote
-connection before continuing.
+Wait for reception of I<string> after establishing the remote
+connection.
 
+=item --remote-expect-silent=I<string>
+
+The same as B<--remote-expect> except that the remote output is not
+echoed to stdout while waiting for the I<string>. Everything after the
+matched string is printed to stdout, so you may want to include line
+end characters in the I<string> as well.
 
 =back
 
@@ -1512,6 +1666,13 @@ to allow running the necessary commands without asking for password.
 
 Port to run the TFTP server on. Implies B<--tftp>.
 
+=item --netif=I<network interface>
+
+Network interface used to deploy files to the target. Default value is
+I<eth0>. This influences the configuration of the DHCP server started
+by B<--dhcp-tftp> and the value that B<$NB_MYIP> get replaced with in
+U-Boot conversation.
+
 =item --iso[=filename]
 
 Generates the ISO image that boots NOVA system via GRUB. If no filename
@@ -1554,7 +1715,7 @@ obtained from https://github.com/wentasah/amtterm.
 At this point, the target is reset (or switched on/off). There is
 several ways how this can be accomplished. Resetting a physical target
 can currently be accomplished by the following options: B<--amt>,
-B<--iprelay>, B<--reset-cmd>.
+B<--iprelay>, B<--reset-cmd> and B<--reset-send>.
 
 =over 8
 
@@ -1583,9 +1744,14 @@ Replace the default qemu flags (QEMU_FLAGS variable or C<-cpu coreduo
 
 Command that resets the target.
 
+=item --reset-send=I<string>
+
+Reset the target by sending the given I<string> to the remote serial
+line. "\n" sequences are replaced with newline character.
+
 =item --no-reset, --reset
 
-Disable/enable reseting of the target.
+Disable/enable resetting of the target.
 
 =back
 
@@ -1601,6 +1767,10 @@ novaboot script. I<prompt> specifies the U-Boot's prompt (default is
 Implementation of this option is currently tied to a particular board
 that we use. It may be subject to changes in the future!
 
+=item --no-uboot
+
+Disable U-Boot interaction previously enabled with B<--uboot>.
+
 =item --uboot-init
 
 Command(s) to send the U-Boot bootloader before loading the images and
@@ -1608,8 +1778,8 @@ booting them. This option can be given multiple times. After sending
 commands from each option novaboot waits for U-Boot I<prompt>.
 
 If the command contains string I<$NB_MYIP> then this string is
-replaced by IPv4 address of eth0 interface. Similarly I<$NB_PREFIX> is
-replaced with prefix given by B<--prefix>.
+replaced by IPv4 address of eth0 interface (see also B<--netif>).
+Similarly I<$NB_PREFIX> is replaced with prefix given by B<--prefix>.
 
 See also C<uboot> keyword in L</"NOVABOOT SCRIPT SYNTAX">).
 
@@ -1619,6 +1789,21 @@ Load address of U-Boot's C<tftpboot> command for loading I<name>,
 where name is one of I<kernel>, I<ramdisk> or I<fdt> (flattened device
 tree).
 
+The default addresses are ${I<name>_addr_r}, i.e. U-Boot environment
+variables used by convention for this purpose.
+
+=item --uboot-cmd=I<command>
+
+Specifies U-Boot command used to execute the OS. If the command
+contains strings C<$kernel_addr>, C<$ramdisk_addr>, C<$fdt_addr>,
+these are replaced with the addresses configured with B<--uboot-addr>.
+
+The default value is
+
+    bootm $kernel_addr $ramdisk_addr $fdt_addr
+
+or the C<UBOOT_CMD> variable if defined in the novaboot script.
+
 =back
 
 =head2 Target interaction phase
@@ -1632,13 +1817,24 @@ interactive work with the target.
 =item --exiton=I<string>
 
 When I<string> is sent by the target, novaboot exits. This option can
-be specified multiple times.
+be specified multiple times, in which case novaboot exits whenever
+either of the specified strings is sent.
 
 If I<string> is C<-re>, then the next B<--exiton>'s I<string> is
 treated as regular expression. For example:
 
     --exiton -re --exiton 'error:.*failed'
 
+=item --exiton-re=I<regex>
+
+The same as --exiton -re --exiton I<regex>.
+
+=item --exiton-timeout=I<seconds>
+
+By default B<--exiton> waits for the string match forever. When this
+option is specified, "exiton" timeouts after the specifies number of
+seconds and novaboot returns non-zero exit code.
+
 =item -i, --interactive
 
 Setup things for interactive use of target. Your terminal will be
@@ -1648,6 +1844,11 @@ special characters). This, among others, means that Ctrl-C is passed
 to the target and does no longer interrupt novaboot. Use "~~."
 sequence to exit novaboot.
 
+=item --no-interaction, --interaction
+
+Skip resp. force target interaction phase. When skipped, novaboot exits
+immediately when boot is initiated.
+
 =item --expect=I<string>
 
 When I<string> is received from the target, send the string specified
@@ -1680,6 +1881,14 @@ Similar to B<--send> but continue expecting more input.
 
 Example: C<--expect='Continue?' --sendcont='yes\n'>
 
+=item --final-eol, --no-final-eol
+
+By default, B<novaboot> always prints an end-of-line character at the
+end of its execution in order to ensure that the output of programs
+started after novaboot appears at the beginning of the line. When this
+is not desired B<--no-final-eol> option can be used to override this
+behavior.
+
 =back
 
 =head1 NOVABOOT SCRIPT SYNTAX
@@ -1696,7 +1905,14 @@ Lines of the form I<VARIABLE=...> (i.e. matching '^[A-Z_]+=' regular
 expression) assign values to internal variables. See L</VARIABLES>
 section.
 
-Lines starting with C<load> keyword represent modules to boot. The
+Otherwise, the first word on the line defines the meaning of the line.
+The following keywords are supported:
+
+=over 4
+
+=item C<load>
+
+These lines represent modules to boot. The
 word after C<load> is a file name (relative to the build directory
 (see B<--build-dir>) of the module to load and the remaining words are
 passed to it as the command line parameters.
@@ -1711,20 +1927,49 @@ that line. The SRCDIR variable in CMD's environment is set to the
 absolute path of the directory containing the interpreted novaboot
 script.
 
+=item C<copy>
+
+These lines are similar to C<load> lines. The
+file mentioned there is copied to the same place as in case of C<load>
+(e.g. tftp server), but the file is not used in the bootloader
+configuration. Such a file can be used by the target for other
+purposed than booting, e.g. at OS runtime or for firmware update.
+
+=item C<run>
+
 Lines starting with C<run> keyword contain shell commands that are run
 during file generation phase. This is the same as the "< CMD" syntax
 for C<load> keyboard except that the command's output is not
 redirected to a file. The ordering of commands is the same as they
 appear in the novaboot script.
 
-Lines starting with C<uboot> represent U-Boot commands that are sent
-to the target if B<--uboot> option is given. Having a U-Boot line in
-the novaboot script is the same as passing an equivalent
-B<--uboot-init> option to novaboot. The C<uboot> keyword can be
-suffixed with timeout specification. The syntax is C<uboot:Ns>, where
-C<N> is the whole number of seconds. If the U-Boot command prompt does
-not appear before the timeout, novaboot fails. The default timeout is
-10 seconds.
+=item C<uboot>
+
+These lines represent U-Boot commands that are sent to the target if
+B<--uboot> option is given. Having a U-Boot line in the novaboot
+script is the same as giving B<--uboot-init> option to novaboot. The
+following syntax variants are supported:
+
+
+  uboot[:<timeout>] <string> [> <file>]
+  uboot[:<timeout>] < <shell> [> <file>]
+
+C<string> is the literal U-Boot command.
+
+The C<uboot> keyword can be suffixed with timeout specification. The
+syntax is C<uboot:Ns>, where C<N> is the whole number of seconds. If
+the U-Boot command prompt does not appear before the timeout, novaboot
+fails. The default timeout is 10 seconds.
+
+In the second variant with the C<<> character the shell code is
+executed and its standard output is sent to U-Boot. Example:
+
+  uboot < printf "mmc write \$loadaddr 1 %x" $(($(/usr/bin/stat -c%s rootfs.ext4) / 512))
+
+When C<E<gt> file> part is present, the output of the U-Boot command
+is written into the given file.
+
+=back
 
 Example (Linux):
 
@@ -1750,6 +1995,28 @@ F<hello.nulconfig>. sigma0 receives some command line parameters and
 F<hello.nulconfig> file is generated on the fly from the lines between
 C<<<EOF> and C<EOF>.
 
+Example (Zynq system update via U-Boot):
+
+  #!/usr/bin/env novaboot
+
+  uboot dhcp
+
+  # Write kernel to FAT filesystem on the 1st SD card partition
+  run mkimage -f uboot-image.its image.ub
+  copy image.ub
+  uboot:60s tftpboot ${loadaddr} $NB_PREFIX/image.ub
+  uboot fatwrite mmc 0:1 ${loadaddr} image.ub $filesize
+  uboot set bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2
+
+  # Write root FS image to the 2nd SD card partition
+  copy rootfs/images/rootfs.ext4
+  uboot:60s tftpboot ${loadaddr} $NB_PREFIX/rootfs/images/rootfs.ext4
+  uboot mmc part > mmc-part.txt
+  uboot < printf "mmc write \$loadaddr %x %x" $(awk '{ if ($1 == "2") { print $2 }}' mmc-part.txt) $(($(/usr/bin/stat -L --printf=%s rootfs/images/rootfs.ext4) / 512))
+
+  UBOOT_CMD=boot
+
+
 =head2 VARIABLES
 
 The following variables are interpreted in the novaboot script:
@@ -1798,6 +2065,10 @@ QEMU_FLAGS.
 
 Use specific qemu flags (can be overridden with B<-q>).
 
+=item UBOOT_CMD
+
+See L</--uboot-cmd>.
+
 =item WVDESC
 
 Description of the WvTest-compliant program.
@@ -1811,17 +2082,19 @@ intermediate output.
 
 =back
 
-=head1 CONFIGURATION FILE
+=head1 CONFIGURATION FILES
 
 Novaboot can read its configuration from one or more files. By
-default, novaboot looks for files named F<.novaboot> as described in
+default, novaboot looks for files in F</etc/novaboot.d>, file
+F<~/.config/novaboot> and files named F<.novaboot> as described in
 L</Configuration reading phase>. Alternatively, configuration file
 location can be specified with the B<-c> switch or with the
 NOVABOOT_CONFIG environment variable. The configuration file has Perl
-syntax and should set values of certain Perl variables. The current
-configuration can be dumped with the B<--dump-config> switch. Some
-configuration variables can be overridden by environment variables
-(see below) or by command line switches.
+syntax (i.e. it is better to put C<1;> as the last line) and should set
+values of certain Perl variables. The current configuration can be
+dumped with the B<--dump-config> switch. Some configuration variables
+can be overridden by environment variables (see below) or by command
+line switches.
 
 Supported configuration variables include:
 
@@ -1835,7 +2108,8 @@ file.
 =item $default_target
 
 Default target (see below) to use when no target is explicitly
-specified on command line with the B<--target> option.
+specified with the B<--target> command line option or
+B<NOVABOOT_TARGET> environment variable.
 
 =item %targets
 
@@ -1847,8 +2121,8 @@ command line options. For instance, if the configuration file contains:
 
 then the following two commands are equivalent:
 
- ./script --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
- ./script -t mybox
+ ./myos --server=boot:/tftproot --serial=/dev/ttyUSB0 --grub
+ ./myos -t mybox
 
 =back
 
@@ -1871,9 +2145,16 @@ one(s).
 Name of the novaboot configuration directory. When not specified
 F</etc/novaboot.d> is used.
 
+=item NOVABOOT_TARGET
+
+Name of the novaboot target to use. This overrides the value of
+B<$default_target> from the configuration file and can be overriden
+with the B<--target> command line option.
+
 =item NOVABOOT_BENDER
 
-Defining this variable has the same meaning as B<--bender> option.
+Defining this variable has the same effect as using B<--bender>
+option.
 
 =back
 
@@ -1881,6 +2162,9 @@ Defining this variable has the same meaning as B<--bender> option.
 
 Michal Sojka <sojka@os.inf.tu-dresden.de>
 
+Latest novaboot version can be found at
+L<https://github.com/wentasah/novaboot>.
+
 =cut
 
 # LocalWords:  novaboot Novaboot NOVABOOT TFTP PXE DHCP filename stty