]> rtime.felk.cvut.cz Git - novaboot.git/blobdiff - novaboot
Suppress Expect warning: handle id(3) is not a tty...
[novaboot.git] / novaboot
index 05323cae562484cd89cd30f8fd8943ce5a86ab64..38189bb9df4e6bc281bc4bba3288efbf5d8208bb 100755 (executable)
--- a/novaboot
+++ b/novaboot
@@ -47,7 +47,7 @@ $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',
-    "novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar --iprelay=147.32.86.92:2324',
+    "novabox" => '--server=rtime.felk.cvut.cz:/srv/tftp/novaboot --rsync-flags="--chmod=Dg+s,ug+w,o-w,+rX --rsync-path=\"umask 002 && rsync\"" --pulsar --iprelay=147.32.87.159:2324',
     "localhost" => '--scriptmod=s/console=tty[A-Z0-9,]+// --server=/boot/novaboot/$NAME --grub2 --grub-prefix=/boot/novaboot/$NAME --grub2-prolog="  set root=\'(hd0,msdos1)\'"',
     "ryu" =>  '--uboot --uboot-init="mw f0000b00 \${psc_cfg}; sleep 1" --uboot-addr kernel=800000 --uboot-addr ramdisk=b00000 --uboot-addr fdt=7f0000',
     "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\""',
@@ -120,7 +120,7 @@ read_config($_) foreach $cfg or @cfgs;
 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, $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, $rom_prefix, $rsync_flags, @scriptmod, $scons, $serial, $server, $stty, $tftp, $tftp_port, $uboot, %uboot_addr, $uboot_cmd, @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 = (
@@ -200,6 +200,7 @@ my %opt_spec;
     "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,
@@ -215,7 +216,7 @@ my %opt_spec;
     "no-uboot"      => sub { undef $uboot; },
     "uboot-addr=s"   => \%uboot_addr,
     "uboot-cmd=s"    => \$uboot_cmd,
-    "uboot-init=s"   => \@uboot_init,
+    "uboot-init=s"   => sub { push @uboot_init, { command => $_[1] }; },
     "h"             => \$help,
     "help"          => \$man,
     );
@@ -234,6 +235,11 @@ my %opt_spec;
        push(@target_expanded, @$remaining_args);
        $t = $explicit_target;
     }
+
+    (undef, my @args) = @ARGV;
+    @args = (@target_expanded, @args);
+    print STDERR "novaboot: Effective options: @args\n";
+
     Getopt::Long::Configure(qw/no_ignore_case no_pass_through/);
     GetOptionsFromArray(\@target_expanded, %opt_spec) or die ("Error in target definition");
 }
@@ -295,7 +301,7 @@ my @scripts;
 my $file;
 my $EOF;
 my $last_fn = '';
-my ($modules, $variables, $generated, $continuation) = ([], {}, []);
+my ($modules, $variables, $generated, $copy, $chainload, $continuation) = ([], {}, [], []);
 my $skip_reading = defined($on_opt) || defined($off_opt);
 while (!$skip_reading && ($_ = <>)) {
     if ($ARGV ne $last_fn) { # New script
@@ -305,7 +311,10 @@ while (!$skip_reading && ($_ = <>)) {
        push @scripts, { 'filename' => $ARGV,
                         'modules' => $modules = [],
                         'variables' => $variables = {},
-                        'generated' => $generated = []};
+                        'generated' => $generated = [],
+                        'copy' => $copy = [],
+                        'chainload' => $chainload = [],
+       };
 
     }
     chomp();
@@ -338,36 +347,49 @@ 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 (s/^chld *//) {         # Chainload line
+       push @$chainload, 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;
     }
 
@@ -518,26 +540,30 @@ sub generate_grub2_config($$$$;$$)
     return $filename;
 }
 
-sub generate_pulsar_config($$)
+sub generate_pulsar_config($$$)
 {
-    my ($filename, $modules_ref) = @_;
+    my ($filename, $modules_ref, $chainload_ref) = @_;
     open(my $fg, '>', $filename) or die "$filename: $!";
     print $fg "root $pulsar_root\n" if defined $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;
-           s|\brom://|$rom_prefix|g;
-           print $fg "load $_\n";
+    if (scalar(@$chainload_ref) > 0) {
+       print $fg "chld $$chainload_ref[0]\n";
+    } else {
+       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;
+               s|\brom://|$rom_prefix|g;
+               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";
     }
-    # 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);
     print("novaboot: Created $builddir/$filename\n");
     return $filename;
@@ -795,6 +821,13 @@ 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;
@@ -845,11 +878,16 @@ foreach my $script (@scripts) {
     my @bootloader_configs;
     push @bootloader_configs, generate_grub_config($grub_config, $config_name, $prefix, $modules, $grub_preamble) if (defined $grub_config);
     push @bootloader_configs, generate_grub2_config($grub2_config, $config_name, $prefix, $modules, $grub_preamble, $grub2_prolog) if (defined $grub2_config);
-    push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules) if (defined $pulsar);
+    push @bootloader_configs, generate_pulsar_config('config-'.($pulsar||'novaboot'), $modules, $chainload) if (defined $pulsar);
 
 ### Run scons or make
     {
-       my @files = map({ ($file) = m/([^ ]*)/; $file; } @$modules);
+       my @all;
+       push @all, @$modules;
+       push @all, @$copy;
+       push @all, @$chainload;
+       my @files = map({ ($file) = m/([^ ]*)/; $file; } @all);
+
        # Filter-out generated files
        my @to_build = grep({ my $file = $_; !scalar(grep($file eq ($$_{filename} || ''), @$generated)) } @files);
 
@@ -866,7 +904,7 @@ 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" : "";
@@ -969,7 +1007,9 @@ if (defined $qemu) {
            push(@qemu_flags, ('-dtb', $dtb)) if $dtb;
        }
     }
-    push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
+    if (!grep /^-serial$/, @qemu_flags) {
+       push(@qemu_flags,  qw(-serial stdio)); # Redirect serial output (for collecting test restuls)
+    }
     unshift(@qemu_flags, ('-name', $config_name));
     print STDERR "novaboot: Running: ".shell_cmd_string($qemu, @qemu_flags)."\n";
     $exp = Expect->spawn(($qemu, @qemu_flags)) || die("exec() failed: $!");
@@ -1072,15 +1112,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 $netif`)[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;
        }
@@ -1091,7 +1133,18 @@ 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);
+       }
     }
 
     # Load files if there are some load lines in the script
@@ -1169,7 +1222,10 @@ if ($interaction && defined $exp) {
        #use Data::Dumper;
        #print Dumper(\@expect_raw);
        $exp->expect(undef, @expect_raw) if @expect_raw;
+
+       $^W = 0; # Suppress Expect warning: handle id(3) is not a tty. Not changing mode at /usr/share/perl5/Expect.pm line 393, <> line 8.
        Expect::interconnect(@inputs) unless defined($exp->exitstatus);
+       $^W = 1;
     }
 }
 
@@ -1229,7 +1285,7 @@ Simple examples of using C<novaboot>:
 
 =item 1.
 
-Run an OS in Qemu. This is can be specified with the B<--qemu> option.
+Run an OS in Qemu. This can be specified with the B<--qemu> option.
 Thus running
 
  novaboot --qemu myos
@@ -1394,9 +1450,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>
 
@@ -1682,7 +1739,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
 
@@ -1711,6 +1768,11 @@ 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 resetting of the target.
@@ -1867,7 +1929,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.
@@ -1882,20 +1951,59 @@ 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<chld>
+
+Chainload another bootloader. Instead of loading multiboot modules
+identified with C<load> keyword, run another bootloader. This is
+currently supported only by pulsar and can be used to load e.g. Grub
+as in the example below:
+
+ chld boot/grub/i386-pc/core.0
+
+
+=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):
 
@@ -1921,6 +2029,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:
@@ -2057,7 +2187,8 @@ 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
 
@@ -2065,6 +2196,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