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 = (
"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,
"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,
);
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
push @scripts, { 'filename' => $ARGV,
'modules' => $modules = [],
'variables' => $variables = {},
- 'generated' => $generated = []};
+ 'generated' => $generated = [],
+ 'copy' => $copy = [],
+ };
}
chomp();
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;
}
};
}
+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;
$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" : "";
$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;
}
}
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
=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
=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>
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
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.
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.
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):
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:
=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
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