petitboot: Add fixes for PS3
authorGeoff Levand <geoffrey.levand@am.sony.com>
Fri, 10 Jul 2009 01:15:45 +0000 (01:15 +0000)
committerGeoff Levand <geoffrey.levand@am.sony.com>
Fri, 10 Jul 2009 01:15:45 +0000 (01:15 +0000)
SVN-Revision: 16763

utils/petitboot/patches/010-petitboot-fixups.diff [new file with mode: 0644]

diff --git a/utils/petitboot/patches/010-petitboot-fixups.diff b/utils/petitboot/patches/010-petitboot-fixups.diff
new file mode 100644 (file)
index 0000000..f82dd29
--- /dev/null
@@ -0,0 +1,3075 @@
+ 34 files changed, 1687 insertions(+), 235 deletions(-)
+
+diff --git a/Makefile.in b/Makefile.in
+index 01771db..23135aa 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -21,7 +21,12 @@ ENABLE_PS3 = @ENABLE_PS3@
+ # other programs
+ INSTALL = @INSTALL@
++INSTALL_DATA = @INSTALL_DATA@
++INSTALL_PROGRAM = @INSTALL_PROGRAM@
++INSTALL_SCRIPT = @INSTALL_SCRIPT@
++INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+ SHELL = @SHELL@
++STRIP = @STRIP@
+ # paths
+ prefix = @prefix@
+@@ -34,5 +39,6 @@ localstatedir = @localstatedir@
+ builddir = @builddir@
+ srcdir = @srcdir@
+ top_srcdir = @top_srcdir@
++mandir = @mandir@
+ include $(top_srcdir)/rules.mk
+diff --git a/configure.ac b/configure.ac
+index cb43f8c..fa40f34 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -35,9 +35,7 @@ AC_ARG_ENABLE([ps3],
+       [],
+       [enable_ps3=check])
+-AS_IF([test "x$enable_ps3" != xno],
+-      [AC_SUBST([ENABLE_PS3], ["y"])],
+-      [AC_SUBST([ENABLE_PS3], ["n"])])
++AS_IF([test "x$enable_ps3" != xno], [AC_SUBST([ENABLE_PS3], ["y"])], [])
+ AC_ARG_WITH([twin],
+       [AS_HELP_STRING([--with-twin],
+diff --git a/discover/kboot-parser.c b/discover/kboot-parser.c
+index e688c22..7c7cb5d 100644
+--- a/discover/kboot-parser.c
++++ b/discover/kboot-parser.c
+@@ -133,10 +133,11 @@ static int kboot_parse(struct discover_context *dc)
+       conf = talloc_zero(dc, struct conf_context);
+       if (!conf)
+-              return -1;
++              return 0;
+       conf->dc = dc;
+       conf->global_options = kboot_global_options,
++      conf_init_global_options(conf);
+       conf->conf_files = kboot_conf_files,
+       conf->process_pair = kboot_process_pair;
+       conf->parser_info = (void *)kboot_ignored_names,
+diff --git a/discover/parser-conf.c b/discover/parser-conf.c
+index 14f847d..88e96b7 100644
+--- a/discover/parser-conf.c
++++ b/discover/parser-conf.c
+@@ -121,6 +121,18 @@ int conf_param_in_list(const char *const *list, const char *param)
+ }
+ /**
++ * conf_init_global_options - Zero the global option table.
++ */
++
++void conf_init_global_options(struct conf_context *conf)
++{
++      int i;
++
++      for (i = 0; conf->global_options[i].name; i++)
++              conf->global_options[i].value = NULL;
++}
++
++/**
+  * conf_set_global_option - Set a value in the global option table.
+  *
+  * Check if an option (name=value) is a global option. If so, store it in
+@@ -136,7 +148,7 @@ int conf_set_global_option(struct conf_context *conf, const char *name,
+               if (streq(name, conf->global_options[i].name)) {
+                       conf->global_options[i].value
+                               = talloc_strdup(conf, value);
+-                      pb_log("%s: %s:%s\n", __func__, name, value);
++                      pb_log("%s: @%s@%s@\n", __func__, name, value);
+                       return 1;
+               }
+       }
+@@ -158,8 +170,11 @@ const char *conf_get_global_option(struct conf_context *conf,
+       int i;
+       for (i = 0; conf->global_options[i].name ;i++)
+-              if (streq(name, conf->global_options[i].name))
++              if (streq(name, conf->global_options[i].name)) {
++                      pb_log("%s: @%s@%s@\n", __func__, name,
++                              conf->global_options[i].value);
+                       return conf->global_options[i].value;
++              }
+       assert(0 && "unknown global name");
+       return NULL;
+diff --git a/discover/parser-conf.h b/discover/parser-conf.h
+index 09015d1..3325faf 100644
+--- a/discover/parser-conf.h
++++ b/discover/parser-conf.h
+@@ -41,6 +41,7 @@ struct conf_context {
+ int conf_parse(struct conf_context *conf);
+ char *conf_get_param_pair(char *str, char **name_out, char **value_out,
+               char terminator);
++void conf_init_global_options(struct conf_context *conf);
+ const char *conf_get_global_option(struct conf_context *conf,
+       const char *name);
+ int conf_set_global_option(struct conf_context *conf, const char *name,
+diff --git a/discover/parser.c b/discover/parser.c
+index 2b4ddd2..8f2735c 100644
+--- a/discover/parser.c
++++ b/discover/parser.c
+@@ -13,16 +13,16 @@ extern struct parser __start_parsers[], __stop_parsers[];
+ void iterate_parsers(struct discover_context *ctx)
+ {
+       struct parser *parser;
++      unsigned int count = 0;
+       pb_log("trying parsers for %s\n", ctx->device_path);
+       for (parser = __start_parsers; parser < __stop_parsers; parser++) {
+               pb_log("\ttrying parser '%s'\n", parser->name);
+-              /* just use a dummy device path for now */
+-              if (parser->parse(ctx))
+-                      return;
++              count += parser->parse(ctx);
+       }
+-      pb_log("\tno boot_options found\n");
++      if (!count)
++              pb_log("\tno boot_options found\n");
+ }
+ static int compare_parsers(const void *a, const void *b)
+diff --git a/discover/paths.c b/discover/paths.c
+index 8e2a361..fe7a876 100644
+--- a/discover/paths.c
++++ b/discover/paths.c
+@@ -81,14 +81,6 @@ char *parse_device_path(void *alloc_ctx,
+       if (is_prefix(dev_str, "/dev/"))
+               dev_str += strlen("/dev/");
+-      /* PS3 hack: if we're reading from a ps3dx device, and we refer to
+-       * a sdx device, remap to ps3dx */
+-      if (cur_dev && is_prefix(cur_dev, "/dev/ps3d")
+-                      && is_prefix(dev_str, "sd")) {
+-              snprintf(tmp, 255, "ps3d%s", dev_str + 2);
+-              dev_str = tmp;
+-      }
+-
+       return join_paths(alloc_ctx, "/dev", dev_str);
+ }
+diff --git a/discover/pb-discover.c b/discover/pb-discover.c
+index d7ea0ca..bd515e3 100644
+--- a/discover/pb-discover.c
++++ b/discover/pb-discover.c
+@@ -1,5 +1,10 @@
++#if defined(HAVE_CONFIG_H)
++#include "config.h"
++#endif
++
+ #include <assert.h>
++#include <getopt.h>
+ #include <stdlib.h>
+ #include <signal.h>
+@@ -11,6 +16,79 @@
+ #include "discover-server.h"
+ #include "device-handler.h"
++static void print_version(void)
++{
++      printf("pb-discover (" PACKAGE_NAME ") " PACKAGE_VERSION "\n");
++}
++
++static void print_usage(void)
++{
++      print_version();
++      printf(
++"Usage: pb-discover [-h, --help] [-l, --log log-file] [-V, --version]\n");
++}
++
++/**
++ * enum opt_value - Tri-state options variables.
++ */
++
++enum opt_value {opt_undef = 0, opt_yes, opt_no};
++
++/**
++ * struct opts - Values from command line options.
++ */
++
++struct opts {
++      enum opt_value show_help;
++      const char *log_file;
++      enum opt_value show_version;
++};
++
++/**
++ * opts_parse - Parse the command line options.
++ */
++
++static int opts_parse(struct opts *opts, int argc, char *argv[])
++{
++      static const struct option long_options[] = {
++              {"help",           no_argument,       NULL, 'h'},
++              {"log",            required_argument, NULL, 'l'},
++              {"version",        no_argument,       NULL, 'V'},
++              { NULL, 0, NULL, 0},
++      };
++      static const char short_options[] = "hl:V";
++      static const struct opts default_values = {
++              .log_file = "pb-discover.log",
++      };
++
++      *opts = default_values;
++
++      while (1) {
++              int c = getopt_long(argc, argv, short_options, long_options,
++                      NULL);
++
++              if (c == EOF)
++                      break;
++
++              switch (c) {
++              case 'h':
++                      opts->show_help = opt_yes;
++                      break;
++              case 'l':
++                      opts->log_file = optarg;
++                      break;
++              case 'V':
++                      opts->show_version = opt_yes;
++                      break;
++              default:
++                      opts->show_help = opt_yes;
++                      return -1;
++              }
++      }
++
++      return optind != argc;
++}
++
+ static int running;
+ static void sigint_handler(int __attribute__((unused)) signum)
+@@ -18,15 +96,31 @@ static void sigint_handler(int __attribute__((unused)) signum)
+       running = 0;
+ }
+-int main(void)
++int main(int argc, char *argv[])
+ {
+       struct device_handler *handler;
+       struct discover_server *server;
++      struct opts opts;
+       struct udev *udev;
+       struct user_event *uev;
+       FILE *log;
+-      log = fopen("pb-discover.log", "a");
++      if (opts_parse(&opts, argc, argv)) {
++              print_usage();
++              return EXIT_FAILURE;
++      }
++
++      if (opts.show_help == opt_yes) {
++              print_usage();
++              return EXIT_SUCCESS;
++      }
++
++      if (opts.show_version == opt_yes) {
++              print_version();
++              return EXIT_SUCCESS;
++      }
++
++      log = fopen(opts.log_file, "a");
+       assert(log);
+       pb_log_set_stream(log);
+diff --git a/discover/yaboot-parser.c b/discover/yaboot-parser.c
+index d9f2aff..6101cd8 100644
+--- a/discover/yaboot-parser.c
++++ b/discover/yaboot-parser.c
+@@ -15,7 +15,6 @@ struct yaboot_state {
+       struct boot_option *opt;
+       const char *desc_image;
+       char *desc_initrd;
+-      int found_suse;
+       int globals_done;
+       const char *const *known_names;
+ };
+@@ -56,6 +55,19 @@ static void yaboot_process_pair(struct conf_context *conf, const char *name,
+               char *value)
+ {
+       struct yaboot_state *state = conf->parser_info;
++      struct fixed_pair {
++              const char *image;
++              const char *initrd;
++      };
++      static const struct fixed_pair suse_fp32 = {
++              .image = "/suseboot/vmlinux32",
++              .initrd = "/suseboot/initrd32",
++      };
++      static const struct fixed_pair suse_fp64 = {
++              .image = "/suseboot/vmlinux64",
++              .initrd = "/suseboot/initrd64",
++      };
++      const struct fixed_pair *suse_fp;
+       /* fixup for bare values */
+@@ -73,32 +85,70 @@ static void yaboot_process_pair(struct conf_context *conf, const char *name,
+       /* image */
+       if (streq(name, "image")) {
++              const char *g_boot = conf_get_global_option(conf, "boot");
++              const char *g_part = conf_get_global_option(conf, "partition");
++
++              /* First finish any previous image. */
++
+               if (state->opt->boot_image_file)
+                       yaboot_finish(conf);
+-              state->opt->boot_image_file = resolve_path(state->opt, value,
+-                      conf->dc->device_path);
+-              state->desc_image = talloc_strdup(state->opt, value);
++              /* Then start the new image. */
++
++              if (g_boot && g_part) {
++                      char* dev = talloc_asprintf(NULL, "%s%s", g_boot,
++                              g_part);
++
++                      state->opt->boot_image_file = resolve_path(state->opt,
++                              value, dev);
++                      state->desc_image = talloc_asprintf(state->opt,
++                              "%s%s", dev, value);
++                      talloc_free(dev);
++              } else if (g_boot) {
++                      state->opt->boot_image_file = resolve_path(state->opt,
++                              value, g_boot);
++                      state->desc_image = talloc_asprintf(state->opt,
++                              "%s%s", g_boot, value);
++              } else {
++                      state->opt->boot_image_file = resolve_path(state->opt,
++                              value, conf->dc->device_path);
++                      state->desc_image = talloc_strdup(state->opt, value);
++              }
++
+               return;
+       }
+-      if (streq(name, "image[32bit]") || streq(name, "image[64bit]")) {
+-              state->found_suse = 1;
++      /* Special processing for SUSE install CD. */
++
++      if (streq(name, "image[32bit]"))
++              suse_fp = &suse_fp32;
++      else if (streq(name, "image[64bit]"))
++              suse_fp = &suse_fp64;
++      else
++              suse_fp = NULL;
++
++      if (suse_fp) {
++              /* First finish any previous image. */
+               if (state->opt->boot_image_file)
+                       yaboot_finish(conf);
++              /* Then start the new image. */
++
+               if (*value == '/') {
+                       state->opt->boot_image_file = resolve_path(state->opt,
+                               value, conf->dc->device_path);
+                       state->desc_image = talloc_strdup(state->opt, value);
+               } else {
+-                      char *s;
+-                      asprintf(&s, "/suseboot/%s", value);
+                       state->opt->boot_image_file = resolve_path(state->opt,
+-                              s, conf->dc->device_path);
+-                      state->desc_image = talloc_strdup(state->opt, s);
+-                      free(s);
++                              suse_fp->image, conf->dc->device_path);
++                      state->desc_image = talloc_strdup(state->opt,
++                              suse_fp->image);
++
++                      state->opt->initrd_file = resolve_path(state->opt,
++                              suse_fp->initrd, conf->dc->device_path);
++                      state->desc_initrd = talloc_asprintf(state, "initrd=%s",
++                              suse_fp->initrd);
+               }
+               return;
+@@ -112,19 +162,28 @@ static void yaboot_process_pair(struct conf_context *conf, const char *name,
+       /* initrd */
+       if (streq(name, "initrd")) {
+-              if (!state->found_suse || (*value == '/')) {
++              const char *g_boot = conf_get_global_option(conf, "boot");
++              const char *g_part = conf_get_global_option(conf, "partition");
++
++              if (g_boot && g_part) {
++                      char* dev = talloc_asprintf(NULL, "%s%s", g_boot,
++                              g_part);
++
+                       state->opt->initrd_file = resolve_path(state->opt,
+-                              value, conf->dc->device_path);
+-                      state->desc_initrd = talloc_asprintf(state, "initrd=%s",
+-                              value);
++                              value, dev);
++                      state->desc_initrd = talloc_asprintf(state,
++                              "initrd=%s%s", dev, value);
++                      talloc_free(dev);
++              } else if (g_boot) {
++                      state->opt->initrd_file = resolve_path(state->opt,
++                              value, g_boot);
++                      state->desc_initrd = talloc_asprintf(state,
++                              "initrd=%s%s", g_boot, value);
+               } else {
+-                      char *s;
+-                      asprintf(&s, "/suseboot/%s", value);
+                       state->opt->initrd_file = resolve_path(state->opt,
+-                              s, conf->dc->device_path);
++                              value, conf->dc->device_path);
+                       state->desc_initrd = talloc_asprintf(state, "initrd=%s",
+-                              s);
+-                      free(s);
++                              value);
+               }
+               return;
+       }
+@@ -236,10 +295,11 @@ static int yaboot_parse(struct discover_context *dc)
+       conf = talloc_zero(dc, struct conf_context);
+       if (!conf)
+-              return -1;
++              return 0;
+       conf->dc = dc;
+       conf->global_options = yaboot_global_options,
++      conf_init_global_options(conf);
+       conf->conf_files = yaboot_conf_files,
+       conf->process_pair = yaboot_process_pair;
+       conf->finish = yaboot_finish;
+diff --git a/lib/system/system.c b/lib/system/system.c
+index 380dded..7371445 100644
+--- a/lib/system/system.c
++++ b/lib/system/system.c
+@@ -3,6 +3,7 @@
+ #include "config.h"
+ #endif
++#include <assert.h>
+ #include <errno.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -19,6 +20,7 @@ const struct pb_system_apps pb_system_apps = {
+       .cp = "/bin/cp",
+       .kexec = "/sbin/kexec",
+       .mount = "/bin/mount",
++      .shutdown = "/sbin/shutdown",
+       .sftp = "/usr/bin/sftp",
+       .tftp = "/usr/bin/tftp",
+       .umount = "/bin/umount",
+@@ -104,13 +106,13 @@ int pb_rmdir_recursive(const char *base, const char *dir)
+ int pb_run_cmd(const char *const *cmd_argv)
+ {
+-      int status;
+-      pid_t pid;
+ #if defined(DEBUG)
+       enum {do_debug = 1};
+ #else
+       enum {do_debug = 0};
+ #endif
++      int status;
++      pid_t pid;
+       if (do_debug) {
+               const char *const *p = cmd_argv;
+@@ -125,12 +127,23 @@ int pb_run_cmd(const char *const *cmd_argv)
+               pb_log("%s: %s\n", __func__, cmd_argv[0]);
+       pid = fork();
++
+       if (pid == -1) {
+               pb_log("%s: fork failed: %s\n", __func__, strerror(errno));
+               return -1;
+       }
+       if (pid == 0) {
++              int log = fileno(pb_log_get_stream());
++
++              /* Redirect child output to log. */
++
++              status = dup2(log, STDOUT_FILENO);
++              assert(status != -1);
++
++              status = dup2(log, STDERR_FILENO);
++              assert(status != -1);
++
+               execvp(cmd_argv[0], (char *const *)cmd_argv);
+               pb_log("%s: exec failed: %s\n", __func__, strerror(errno));
+               exit(EXIT_FAILURE);
+diff --git a/lib/system/system.h b/lib/system/system.h
+index 47c7c02..1918309 100644
+--- a/lib/system/system.h
++++ b/lib/system/system.h
+@@ -5,6 +5,7 @@ struct pb_system_apps {
+       const char *cp;
+       const char *kexec;
+       const char *mount;
++      const char *shutdown;
+       const char *sftp;
+       const char *tftp;
+       const char *umount;
+diff --git a/man/pb-cui.8 b/man/pb-cui.8
+new file mode 100644
+index 0000000..e671c20
+--- /dev/null
++++ b/man/pb-cui.8
+@@ -0,0 +1,67 @@
++.\" Copyright (C) 2009 Sony Computer Entertainment Inc.
++.\" Copyright 2009 Sony Corp.
++.\"
++.\" This program is free software; you can redistribute it and/or modify
++.\" it under the terms of the GNU General Public License as published by
++.\" the Free Software Foundation; version 2 of the License.
++.\"
++.\" This program is distributed in the hope that it will be useful,
++.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
++.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++.\" GNU General Public License for more details.
++.\"
++.\" You should have received a copy of the GNU General Public License
++.\" along with this program; if not, write to the Free Software
++.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++.\"
++.\" Maintainer's Notes:
++.\"  * For syntax help see the man pages for 'mdoc' and 'mdoc.samples'.
++.\"  * To check syntax use this:
++.\"    'groff -C -mtty-char -Tutf8 -man pb-cui.8'.
++.\"  * To check format use this: 'less pb-cui.8'.
++.\"
++.Dd ""
++.Dt pb-cui 8
++.Os
++.\"
++.Sh NAME
++.\" ====
++.Nm pb-cui
++.Nd Petitboot ncurses bootloader UI
++.\"
++.Sh SYNOPSIS
++.\" ========
++.Nm
++.Op Fl h, -help
++.Op Fl l, -log Ar log-file
++.Op Fl V, -version
++.\"
++.Sh DESCRIPTION
++.\" ===========
++pb-cui is an ncurses based interface to the Petitboot bootloader.
++.Pp
++Petitboot is a Linux kexec based bootloader that supports loading Linux
++kernel and initrd images from any device that can be mounted by Linux.
++It can also load images from the network using the
++HTTP, HTTPS, NFS, SFTP, and TFTP
++protocols.
++.\"
++.Sh OPTIONS
++.\" =======
++.Bl -tag -width indent
++.\"
++.It Fl h, -help
++Print a help message.
++.\"
++.It Fl l, -log Ar log-file
++Log messages to the file
++.Ar log-file .
++The default log is a file pb-cui.log in the directory where pb-cui
++is started.  New messages are appended to an existing log file.
++.\"
++.It Fl V, -version
++Display the program version number.
++.El
++.Sh SEE ALSO
++.\" ========
++.Xr petitboot 8 , Xr pb-discover 8 , Xr pb-event 8
+diff --git a/man/pb-discover.8 b/man/pb-discover.8
+new file mode 100644
+index 0000000..9123e0f
+--- /dev/null
++++ b/man/pb-discover.8
+@@ -0,0 +1,44 @@
++.\" Copyright (C) 2009 Sony Computer Entertainment Inc.
++.\" Copyright 2009 Sony Corp.
++.\"
++.\" This program is free software; you can redistribute it and/or modify
++.\" it under the terms of the GNU General Public License as published by
++.\" the Free Software Foundation; version 2 of the License.
++.\"
++.\" This program is distributed in the hope that it will be useful,
++.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
++.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++.\" GNU General Public License for more details.
++.\"
++.\" You should have received a copy of the GNU General Public License
++.\" along with this program; if not, write to the Free Software
++.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++.\"
++.\" Maintainer's Notes:
++.\"  * For syntax help see the man pages for 'mdoc' and 'mdoc.samples'.
++.\"  * To check syntax use this:
++.\"    'groff -C -mtty-char -Tutf8 -man pb-discover.8'.
++.\"  * To check format use this: 'less pb-discover.8'.
++.\"
++.Dd ""
++.Dt pb-discover 8
++.Os
++.\"
++.Sh NAME
++.\" ====
++.Nm pb-discover
++.Nd The dynamic device discovery daemon of the Petitboot bootloader
++.\"
++.Sh SYNOPSIS
++.\" ========
++.Nm
++.\"
++.Sh DESCRIPTION
++.\" ===========
++pb-discover maintains a dynamic list of boot options available to
++the system.  On startup, the petitboot user interface clients connect to
++pb-discover daemon and receive boot option information.
++.\"
++.Sh SEE ALSO
++.\" ========
++.Xr petitboot 8 , Xr pb-cui 8 , Xr pb-event 8
+diff --git a/man/pb-event.8 b/man/pb-event.8
+new file mode 100644
+index 0000000..dc123e1
+--- /dev/null
++++ b/man/pb-event.8
+@@ -0,0 +1,43 @@
++.\" Copyright (C) 2009 Sony Computer Entertainment Inc.
++.\" Copyright 2009 Sony Corp.
++.\"
++.\" This program is free software; you can redistribute it and/or modify
++.\" it under the terms of the GNU General Public License as published by
++.\" the Free Software Foundation; version 2 of the License.
++.\"
++.\" This program is distributed in the hope that it will be useful,
++.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
++.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++.\" GNU General Public License for more details.
++.\"
++.\" You should have received a copy of the GNU General Public License
++.\" along with this program; if not, write to the Free Software
++.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++.\"
++.\" Maintainer's Notes:
++.\"  * For syntax help see the man pages for 'mdoc' and 'mdoc.samples'.
++.\"  * To check syntax use this:
++.\"    'groff -C -mtty-char -Tutf8 -man pb-event.8'.
++.\"  * To check format use this: 'less pb-event.8'.
++.\"
++.Dd ""
++.Dt pb-event 8
++.Os
++.\"
++.Sh NAME
++.\" ====
++.Nm pb-event
++.Nd Event helper for the Petitboot bootloader
++.\"
++.Sh SYNOPSIS
++.\" ========
++.Nm
++.\"
++.Sh DESCRIPTION
++.\" ===========
++The pb-event utility is used to send user mode events to pb-discover, the
++petitboot device discovery daemon.
++.\"
++.Sh SEE ALSO
++.\" ========
++.Xr petitboot 8 , Xr pb-cui 8 , Xr pb-discover 8
+diff --git a/man/petitboot.8 b/man/petitboot.8
+new file mode 100644
+index 0000000..9dc2222
+--- /dev/null
++++ b/man/petitboot.8
+@@ -0,0 +1,47 @@
++.\" Copyright (C) 2009 Sony Computer Entertainment Inc.
++.\" Copyright 2009 Sony Corp.
++.\"
++.\" This program is free software; you can redistribute it and/or modify
++.\" it under the terms of the GNU General Public License as published by
++.\" the Free Software Foundation; version 2 of the License.
++.\"
++.\" This program is distributed in the hope that it will be useful,
++.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
++.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++.\" GNU General Public License for more details.
++.\"
++.\" You should have received a copy of the GNU General Public License
++.\" along with this program; if not, write to the Free Software
++.\" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++.\"
++.\" Maintainer's Notes:
++.\"  * For syntax help see the man pages for 'mdoc' and 'mdoc.samples'.
++.\"  * To check syntax use this:
++.\"    'groff -C -mtty-char -Tutf8 -man petitboot.8'.
++.\"  * To check format use this: 'less petitboot.8'.
++.\"
++.Dd ""
++.Dt petitboot 8
++.Os
++.\"
++.Sh NAME
++.\" ====
++.Nm petitboot
++.Nd The Petitboot bootloader
++.\"
++.Sh DESCRIPTION
++.\" ===========
++Petitboot is a platform independent bootloader based on Linux kexec.
++Petitboot can load Linux kernel and initrd images from any device that
++can be mounted by Linux, and can also load images from the network
++using the
++HTTP, HTTPS, NFS, SFTP, and TFTP
++protocols.
++.Pp
++Petitboot looks for bootloader configuration files on mountable devices
++in the system, and can also be configured to use boot information
++from a DHCP server.
++.\"
++.Sh SEE ALSO
++.\" ========
++.Xr pb-cui 8 , Xr pb-discover 8 , Xr pb-event 8
+diff --git a/rules.mk b/rules.mk
+index e743338..3b1dcfe 100644
+--- a/rules.mk
++++ b/rules.mk
+@@ -19,8 +19,8 @@ parser_test = test/parser-test
+ # install targets and components
+ daemons = $(pb_discover)
+ parsers = event kboot yaboot
+-uis = $(pb_cui) $(pb_test)
+-tests = $(parser_test)
++uis = $(pb_cui)
++tests = $(parser_test) $(pb_test)
+ utils = $(pb_event)
+ ifeq ($(PBTWIN),y)
+@@ -29,6 +29,7 @@ endif
+ # other to install
+ artwork = background.jpg cdrom.png hdd.png usbpen.png tux.png cursor.gz
++man8 = pb-cui.8 pb-discover.8 pb-event.8 petitboot.8
+ rules = utils/99-petitboot.rules
+ udhcpc = utils/udhcpc
+@@ -48,10 +49,11 @@ discover_objs = discover/event.o discover/user-event.o discover/udev.o \
+       discover/parser-utils.o
+ # client objs
+-ui_common_objs = ui/common/discover-client.o ui/common/loader.o \
+-      ui/common/ui-system.o ui/common/url.o
+-ncurses_objs = ui/ncurses/nc-scr.o ui/ncurses/nc-menu.o \
+-       ui/ncurses/nc-ked.o ui/ncurses/nc-cui.o
++ui_common_objs = ui/common/discover-client.o ui/common/joystick.o \
++      ui/common/loader.o ui/common/ui-system.o ui/common/timer.o \
++      ui/common/url.o
++ncurses_objs = ui/ncurses/nc-scr.o ui/ncurses/nc-menu.o ui/ncurses/nc-ked.o \
++      ui/ncurses/nc-cui.o
+ twin_objs = ui/twin/pb-twin.o
+ # Makefiles
+@@ -68,6 +70,7 @@ client_objs = $(lib_objs) $(ui_common_objs)
+ all: $(uis) $(daemons) $(utils)
+ # ncurses cui
++pb_cui_objs-y$(ENABLE_PS3) += ui/ncurses/pb-cui.o
+ pb_cui_objs-$(ENABLE_PS3) += ui/ncurses/ps3-cui.o ui/common/ps3.o
+ pb_cui_ldflags-$(ENABLE_PS3) += -lps3-utils
+@@ -120,13 +123,16 @@ parser-test: $(parser_test)
+ install: all $(rules) $(udhcpc)
+       $(INSTALL) -d $(DESTDIR)$(sbindir)/
+-      $(INSTALL) $(daemons) $(uis) $(utils) $(DESTDIR)$(sbindir)/
++      $(INSTALL_PROGRAM) $(daemons) $(uis) $(utils) $(DESTDIR)$(sbindir)/
+       $(INSTALL) -d $(DESTDIR)$(pkgdatadir)/artwork/
+-      $(INSTALL) $(addprefix $(top_srcdir)/ui/twin/artwork/,$(artwork)) \
++      $(INSTALL_DATA) $(addprefix $(top_srcdir)/ui/twin/artwork/,$(artwork)) \
+               $(DESTDIR)$(pkgdatadir)/artwork/
+       $(INSTALL) -d $(DESTDIR)$(pkgdatadir)/utils
+-      $(INSTALL) -m 644 $(top_srcdir)/$(rules) $(DESTDIR)$(pkgdatadir)/utils
+-      $(INSTALL) -m 644 $(top_srcdir)/$(udhcpc) $(DESTDIR)$(pkgdatadir)/utils
++      $(INSTALL_DATA) $(top_srcdir)/$(rules) $(DESTDIR)$(pkgdatadir)/utils
++      $(INSTALL_DATA) $(top_srcdir)/$(udhcpc) $(DESTDIR)$(pkgdatadir)/utils
++      $(INSTALL) -d $(DESTDIR)$(mandir)/man8/
++      $(INSTALL_DATA) $(addprefix $(top_srcdir)/man/, $(man8)) \
++              $(DESTDIR)$(mandir)/man8/
+ dist: $(PACKAGE)-$(VERSION).tar.gz
+diff --git a/ui/common/joystick.c b/ui/common/joystick.c
+new file mode 100644
+index 0000000..94c85fe
+--- /dev/null
++++ b/ui/common/joystick.c
+@@ -0,0 +1,106 @@
++/*
++ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
++ *  Copyright 2009 Sony Corp.
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; version 2 of the License.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ */
++
++#if defined(HAVE_CONFIG_H)
++#include "config.h"
++#endif
++
++#define _GNU_SOURCE
++#include <assert.h>
++#include <errno.h>
++#include <fcntl.h>
++#include <string.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++
++#include "log/log.h"
++#include "talloc/talloc.h"
++#include "joystick.h"
++
++/**
++ * pjs_process_event - Read joystick event and map to UI key code.
++ *
++ * Returns a map routine UI key code or zero.
++ */
++
++int pjs_process_event(const struct pjs *pjs)
++{
++      int result;
++      struct js_event e;
++
++      assert(pjs->fd);
++
++      result = read(pjs->fd, &e, sizeof(e));
++
++      if (result != sizeof(e)) {
++              pb_log("%s: read failed: %s\n", __func__, strerror(errno));
++              return 0;
++      }
++
++      return pjs->map(&e);
++}
++
++/**
++ * pjs_destructor - The talloc destructor for a joystick handler.
++ */
++
++static int pjs_destructor(void *arg)
++{
++      struct pjs *pjs = pjs_from_arg(arg);
++
++      close(pjs->fd);
++      pjs->fd = 0;
++
++      return 0;
++}
++
++/**
++ * pjs_init - Initialize the joystick event handler.
++ */
++
++struct pjs *pjs_init(void *ctx, int (*map)(const struct js_event *))
++{
++      static const char dev_name[] = "/dev/input/js0";
++      struct pjs *pjs;
++
++      pjs = talloc_zero(ctx, struct pjs);
++
++      if (!pjs)
++              return NULL;
++
++      pjs->map = map;
++      pjs->fd = open(dev_name, O_RDONLY | O_NONBLOCK);
++
++      if (pjs->fd < 0) {
++              pb_log("%s: open %s failed: %s\n", __func__, dev_name,
++                      strerror(errno));
++              goto out_err;
++      }
++
++      talloc_set_destructor(pjs, pjs_destructor);
++
++      pb_log("%s: using %s\n", __func__, dev_name);
++
++      return pjs;
++
++out_err:
++      close(pjs->fd);
++      pjs->fd = 0;
++      talloc_free(pjs);
++      return NULL;
++}
+diff --git a/ui/common/joystick.h b/ui/common/joystick.h
+new file mode 100644
+index 0000000..cf3fc45
+--- /dev/null
++++ b/ui/common/joystick.h
+@@ -0,0 +1,47 @@
++/*
++ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
++ *  Copyright 2009 Sony Corp.
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; version 2 of the License.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ */
++
++#if !defined(_PB_JOYSTICK_H)
++#define _PB_JOYSTICK_H
++
++#include <linux/joystick.h>
++
++/**
++ * struct pjs - Petitboot joystick event handler.
++ * @map: Routine to map from a Linux struct js_event to a ui key code.
++ */
++
++struct pjs {
++      int fd;
++      int (*map)(const struct js_event *e);
++};
++
++struct pjs *pjs_init(void *ctx, int (*map)(const struct js_event *));
++int pjs_process_event(const struct pjs *pjs);
++
++static inline struct pjs *pjs_from_arg(void *arg)
++{
++      return arg;
++}
++
++static inline int pjs_get_fd(const struct pjs *pjs)
++{
++      return pjs->fd;
++}
++
++#endif
+diff --git a/ui/common/loader.c b/ui/common/loader.c
+index babca28..5c69533 100644
+--- a/ui/common/loader.c
++++ b/ui/common/loader.c
+@@ -227,7 +227,7 @@ enum wget_flags {
+ static char *pb_load_wget(void *ctx, struct pb_url *url, enum wget_flags flags)
+ {
+       int result;
+-      const char *argv[6];
++      const char *argv[7];
+       const char **p;
+       char *local;
+@@ -238,12 +238,15 @@ static char *pb_load_wget(void *ctx, struct pb_url *url, enum wget_flags flags)
+       p = argv;
+       *p++ = pb_system_apps.wget;                     /* 1 */
+-      *p++ = "-O";                                    /* 2 */
+-      *p++ = local;                                   /* 3 */
+-      *p++ = url->full;                               /* 4 */
++#if !defined(DEBUG)
++      *p++ = "--quiet";                               /* 2 */
++#endif
++      *p++ = "-O";                                    /* 3 */
++      *p++ = local;                                   /* 4 */
++      *p++ = url->full;                               /* 5 */
+       if (flags & wget_no_check_certificate)
+-              *p++ = "--no-check-certificate";        /* 5 */
+-      *p++ = NULL;                                    /* 6 */
++              *p++ = "--no-check-certificate";        /* 6 */
++      *p++ = NULL;                                    /* 7 */
+       result = pb_run_cmd(argv);
+@@ -260,16 +263,22 @@ fail:
+ /**
+  * pb_load_file - Loads a remote file and returns the local file path.
+  * @ctx: The talloc context to associate with the returned string.
++ * @remote: The remote file URL.
++ * @tempfile: An optional variable pointer to be set when a temporary local
++ *  file is created.
+  *
+  * Returns the local file path in a talloc'ed character string on success,
+  * or NULL on error.
+  */
+-char *pb_load_file(void *ctx, const char *remote)
++char *pb_load_file(void *ctx, const char *remote, unsigned int *tempfile)
+ {
+       char *local;
+       struct pb_url *url = pb_url_parse(NULL, remote);
++      if (tempfile)
++              *tempfile = 0;
++
+       if (!url)
+               return NULL;
+@@ -277,19 +286,28 @@ char *pb_load_file(void *ctx, const char *remote)
+       case pb_url_ftp:
+       case pb_url_http:
+               local = pb_load_wget(ctx, url, 0);
++              if (tempfile && local)
++                      *tempfile = 1;
+               break;
+       case pb_url_https:
+-              local = pb_load_wget(ctx, url,
+-                      wget_no_check_certificate);
++              local = pb_load_wget(ctx, url, wget_no_check_certificate);
++              if (tempfile && local)
++                      *tempfile = 1;
+               break;
+       case pb_url_nfs:
+               local = pb_load_nfs(ctx, url);
++              if (tempfile && local)
++                      *tempfile = 1;
+               break;
+       case pb_url_sftp:
+               local = pb_load_sftp(ctx, url);
++              if (tempfile && local)
++                      *tempfile = 1;
+               break;
+       case pb_url_tftp:
+               local = pb_load_tftp(ctx, url);
++              if (tempfile && local)
++                      *tempfile = 1;
+               break;
+       default:
+               local = talloc_strdup(ctx, url->full);
+diff --git a/ui/common/loader.h b/ui/common/loader.h
+index b06bb43..42d4d4b 100644
+--- a/ui/common/loader.h
++++ b/ui/common/loader.h
+@@ -19,6 +19,6 @@
+ #if !defined(_PB_FILE_LOADER_H)
+ #define _PB_FILE_LOADER_H
+-char *pb_load_file(void *ctx, const char *remote);
++char *pb_load_file(void *ctx, const char *remote, unsigned int *tempfile);
+ #endif
+diff --git a/ui/common/ps3.c b/ui/common/ps3.c
+index 5c83289..cb1c8d1 100644
+--- a/ui/common/ps3.c
++++ b/ui/common/ps3.c
+@@ -49,6 +49,11 @@ static const struct os_area_db_id id_flags =
+       .owner = OS_AREA_DB_OWNER_PETITBOOT, /* 3 */
+       .key = 3,
+ };
++static const struct os_area_db_id id_timeout =
++{
++      .owner = OS_AREA_DB_OWNER_PETITBOOT, /* 3 */
++      .key = 4,
++};
+ struct ps3_flash_ctx {
+       FILE *dev;
+@@ -59,6 +64,8 @@ struct ps3_flash_ctx {
+ static void ps3_flash_close(struct ps3_flash_ctx *fc)
+ {
++      assert(fc->dev);
++
+       fclose(fc->dev);
+       fc->dev = NULL;
+ }
+@@ -104,19 +111,19 @@ int ps3_flash_get_values(struct ps3_flash_values *values)
+       struct ps3_flash_ctx fc;
+       uint64_t tmp;
+-      memset(values, 0, sizeof(*values));
+-
+       result = ps3_flash_open(&fc, "r");
+       if (result)
+-              return -1;
++              goto done;
+       result = os_area_db_read(&fc.db, &fc.header, fc.dev);
++      ps3_flash_close(&fc);
++
+       if (result) {
+               pb_log("%s: os_area_db_read failed: %s\n", __func__,
+                       strerror(errno));
+-              goto fail;
++              goto done;
+       }
+       sum = result = os_area_db_get(&fc.db, &id_default_item, &tmp);
+@@ -124,21 +131,25 @@ int ps3_flash_get_values(struct ps3_flash_values *values)
+       if (!result)
+               values->default_item = (uint32_t)tmp;
+-      sum += result = os_area_db_get(&fc.db, &id_video_mode, &tmp);
++      result = os_area_db_get(&fc.db, &id_timeout, &tmp);
+       if (!result)
+-              values->video_mode = (uint16_t)tmp;
++              values->timeout = (uint8_t)tmp;
++      sum += result = os_area_db_get(&fc.db, &id_video_mode, &tmp);
+-      pb_log("%s: default_item: %u\n", __func__, values->default_item);
+-      pb_log("%s: video_mode:   %u\n", __func__, values->video_mode);
++      if (!result)
++              values->video_mode = (uint16_t)tmp;
+-      ps3_flash_close(&fc);
+-      return !!sum;
++done:
++      pb_log("%s: default_item: %x\n", __func__,
++              (unsigned int)values->default_item);
++      pb_log("%s: timeout: %u\n", __func__,
++              (unsigned int)values->timeout);
++      pb_log("%s: video_mode:   %u\n", __func__,
++              (unsigned int)values->video_mode);
+-fail:
+-      ps3_flash_close(&fc);
+-      return -1;
++      return (result || sum) ? -1 : 0;
+ }
+ /**
+@@ -177,6 +188,8 @@ int ps3_flash_set_values(const struct ps3_flash_values *values)
+               }
+       }
++      /* timeout is currently read-only, set with ps3-bl-option */
++
+       result = os_area_db_set_32(&fc.db, &id_default_item,
+               values->default_item);
+       result += os_area_db_set_16(&fc.db, &id_video_mode,
+diff --git a/ui/common/ps3.h b/ui/common/ps3.h
+index 8a7fe1c..5ba8afe 100644
+--- a/ui/common/ps3.h
++++ b/ui/common/ps3.h
+@@ -33,9 +33,14 @@ enum ps3_flash_flags {
+       ps3_flag_telnet = 1,
+ };
++enum ps3_timeouts {
++      ps3_timeout_forever = 255,
++};
++
+ /**
+  * struct ps3_flash_values - Values from PS3 flash memory.
+  * @default_item: The default menu item.
++ * @timeout: The timeout in seconds.
+  * @video_mode: The default video_mode.
+  * @flags: Logical OR of enum ps3_flash_flags.
+  */
+@@ -44,6 +49,13 @@ struct ps3_flash_values {
+       uint32_t default_item;
+       uint16_t video_mode;
+       /* uint16_t flags; */
++      uint8_t timeout;
++};
++
++static const struct ps3_flash_values ps3_flash_defaults = {
++      .default_item = 0,
++      .video_mode = 1,
++      .timeout = ps3_timeout_forever,
+ };
+ int ps3_flash_get_values(struct ps3_flash_values *values);
+diff --git a/ui/common/timer.c b/ui/common/timer.c
+new file mode 100644
+index 0000000..954a18a
+--- /dev/null
++++ b/ui/common/timer.c
+@@ -0,0 +1,139 @@
++/*
++ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
++ *  Copyright 2009 Sony Corp.
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; version 2 of the License.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ */
++
++#if defined(HAVE_CONFIG_H)
++#include "config.h"
++#endif
++
++#define _GNU_SOURCE
++#include <assert.h>
++#include <limits.h>
++#include <unistd.h>
++
++#include "log/log.h"
++#include "timer.h"
++
++/**
++ * ui_timer_init - Initialize the timer for use.
++ * @seconds: The final timeout value in seconds.
++ */
++
++void ui_timer_init(struct ui_timer *timer, unsigned int seconds)
++{
++      pb_log("%s: %u\n", __func__, seconds);
++      assert(!timer->disabled);
++      timer->timeout = seconds;
++}
++
++/**
++ * ui_timer_next - Calculate the next timer interval.
++ */
++
++static unsigned int ui_timer_next(unsigned int seconds)
++{
++      unsigned int next;
++
++      if (seconds == 0) {
++              next = 0;
++              goto done;
++      }
++
++      if (seconds <= 10) {
++              next = 1;
++              goto done;
++      }
++
++      if (seconds <= 60) {
++              next = seconds % 5;
++              next = next ? next : 5;
++              goto done;
++      }
++
++      next = seconds % 10;
++      next = next ? next : 10;
++
++done:
++      pb_log("%s: %u = %u\n", __func__, seconds, next);
++      return next;
++}
++
++/**
++ * ui_timer_kick - Kickstart the next timer interval.
++ */
++
++void ui_timer_kick(struct ui_timer *timer)
++{
++      unsigned int next;
++
++      if(timer->disabled)
++              return;
++
++      if (timer->update_display)
++              timer->update_display(timer, timer->timeout);
++
++      next = ui_timer_next(timer->timeout);
++      timer->timeout -= next;
++
++      if (next) {
++              alarm(next);
++              return;
++      }
++
++      pb_log("%s: timed out\n", __func__);
++
++      ui_timer_disable(timer);
++      timer->handle_timeout(timer);
++}
++
++/**
++ * ui_timer_disable - Stop and disable the timer.
++ */
++
++void ui_timer_disable(struct ui_timer *timer)
++{
++      if (timer->disabled)
++              return;
++
++      pb_log("%s\n", __func__);
++      timer->disabled = 1;
++      timer->timeout = UINT_MAX;
++      alarm(0);
++}
++
++/**
++ * ui_timer_sigalrm
++ *
++ * Called at SIGALRM.
++ */
++
++void ui_timer_sigalrm(struct ui_timer *timer)
++{
++      timer->signaled = 1;
++}
++
++/**
++ * ui_timer_process_sig - Process a timer signal
++ */
++
++void ui_timer_process_sig(struct ui_timer *timer)
++{
++      while (timer->signaled) {
++              timer->signaled = 0;
++              ui_timer_kick(timer);
++      }
++}
+diff --git a/ui/common/timer.h b/ui/common/timer.h
+new file mode 100644
+index 0000000..781b442
+--- /dev/null
++++ b/ui/common/timer.h
+@@ -0,0 +1,42 @@
++/*
++ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
++ *  Copyright 2009 Sony Corp.
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; version 2 of the License.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ */
++
++#if !defined(_PB_UI_TIMER_H)
++#define _PB_UI_TIMER_H
++
++#include <signal.h>
++
++/**
++ * struct ui_timer - UI timeout.
++ */
++
++struct ui_timer {
++      unsigned int timeout;
++      unsigned int disabled;
++      sig_atomic_t signaled;
++      void (*update_display)(struct ui_timer *timer, unsigned int timeout);
++      void (*handle_timeout)(struct ui_timer *timer);
++};
++
++void ui_timer_init(struct ui_timer *timer, unsigned int seconds);
++void ui_timer_kick(struct ui_timer *timer);
++void ui_timer_disable(struct ui_timer *timer);
++void ui_timer_sigalrm(struct ui_timer *timer);
++void ui_timer_process_sig(struct ui_timer *timer);
++
++#endif
+diff --git a/ui/common/ui-system.c b/ui/common/ui-system.c
+index 3f54191..0140f0e 100644
+--- a/ui/common/ui-system.c
++++ b/ui/common/ui-system.c
+@@ -33,46 +33,80 @@
+ #include "ui-system.h"
+ /**
+- * run_kexec_local - Final kexec helper.
++ * kexec_load - kexec load helper.
+  * @l_image: The local image file for kexec to execute.
+  * @l_initrd: Optional local initrd file for kexec --initrd, can be NULL.
+  * @args: Optional command line args for kexec --append, can be NULL.
+  */
+-static int run_kexec_local(const char *l_image, const char *l_initrd,
++static int kexec_load(const char *l_image, const char *l_initrd,
+       const char *args)
+ {
+       int result;
+-      const char *argv[8];
++      const char *argv[6];
+       const char **p;
++      char *s_initrd = NULL;
++      char *s_args = NULL;
+       p = argv;
+-      *p++ = pb_system_apps.kexec;            /* 1 */
++      *p++ = pb_system_apps.kexec;    /* 1 */
++      *p++ = "-l";                    /* 2 */
+       if (l_initrd) {
+-              *p++ = "--initrd";              /* 2 */
+-              *p++ = l_initrd;                /* 3 */
++              s_initrd = talloc_asprintf(NULL, "--initrd=%s", l_initrd);
++              assert(s_initrd);
++              *p++ = s_initrd;         /* 3 */
+       }
+       if (args) {
+-              *p++ = "--append";              /* 4 */
+-              *p++ = args;                    /* 5 */
++              s_args = talloc_asprintf(NULL, "--append=%s", args);
++              assert(s_args);
++              *p++ = s_args;          /* 4 */
+       }
+-      /* First try by telling kexec to run shutdown */
++      *p++ = l_image;                 /* 5 */
++      *p++ = NULL;                    /* 6 */
+-      *(p + 0) = l_image;
+-      *(p + 1) = NULL;
++      result = pb_run_cmd(argv);
++
++      if (result)
++              pb_log("%s: failed: (%d)\n", __func__, result);
++
++      talloc_free(s_initrd);
++      talloc_free(s_args);
++
++      return result;
++}
++
++/**
++ * kexec_reboot - Helper to boot the new kernel.
++ *
++ * Must only be called after a successful call to kexec_load().
++ */
++
++static int kexec_reboot(void)
++{
++      int result;
++      const char *argv[4];
++      const char **p;
++
++      /* First try running shutdown.  Init scripts should run 'exec -e' */
++
++      p = argv;
++      *p++ = pb_system_apps.shutdown; /* 1 */
++      *p++ =  "-r";                   /* 2 */
++      *p++ =  "now";                  /* 3 */
++      *p++ =  NULL;                   /* 4 */
+       result = pb_run_cmd(argv);
+-      /* kexec will return zero on success */
+-      /* On error, force a kexec with the -f option */
++      /* On error, force a kexec with the -e option */
+       if (result) {
+-              *(p + 0) = "-f";                /* 6 */
+-              *(p + 1) = l_image;             /* 7 */
+-              *(p + 2) = NULL;                /* 8 */
++              p = argv;
++              *p++ = pb_system_apps.kexec;    /* 1 */
++              *p++ = "-e";                    /* 2 */
++              *p++ = NULL;                    /* 3 */
+               result = pb_run_cmd(argv);
+       }
+@@ -85,38 +119,51 @@ static int run_kexec_local(const char *l_image, const char *l_initrd,
+ /**
+  * pb_run_kexec - Run kexec with the supplied boot options.
+- *
+- * For the convenience of the user, tries to load both files before
+- * returning error.
+  */
+ int pb_run_kexec(const struct pb_kexec_data *kd)
+ {
+       int result;
+-      char *l_image;
+-      char *l_initrd;
++      char *l_image = NULL;
++      char *l_initrd = NULL;
++      unsigned int clean_image = 0;
++      unsigned int clean_initrd = 0;
+       pb_log("%s: image:  '%s'\n", __func__, kd->image);
+       pb_log("%s: initrd: '%s'\n", __func__, kd->initrd);
+       pb_log("%s: args:   '%s'\n", __func__, kd->args);
+-      if (kd->image)
+-              l_image = pb_load_file(NULL, kd->image);
+-      else {
+-              l_image = NULL;
+-              pb_log("%s: error null image\n", __func__);
++      result = -1;
++
++      if (kd->image) {
++              l_image = pb_load_file(NULL, kd->image, &clean_image);
++              if (!l_image)
++                      goto no_load;
++      }
++
++      if (kd->initrd) {
++              l_initrd = pb_load_file(NULL, kd->initrd, &clean_initrd);
++              if (!l_initrd)
++                      goto no_load;
+       }
+-      l_initrd = kd->initrd ? pb_load_file(NULL, kd->initrd) : NULL;
++      if (!l_image && !l_initrd)
++              goto no_load;
++
++      result = kexec_load(l_image, l_initrd, kd->args);
+-      if (!l_image || (kd->initrd && !l_initrd))
+-              result = -1;
+-      else
+-              result = run_kexec_local(l_image, l_initrd, kd->args);
++no_load:
++      if (clean_image)
++              unlink(l_image);
++      if (clean_initrd)
++              unlink(l_initrd);
+       talloc_free(l_image);
+       talloc_free(l_initrd);
++      if (!result)
++              result = kexec_reboot();
++
+       return result;
+ }
+diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
+index 2a4c971..aed5ff7 100644
+--- a/ui/ncurses/nc-cui.c
++++ b/ui/ncurses/nc-cui.c
+@@ -30,7 +30,6 @@
+ #include "ui/common/discover-client.h"
+ #include "nc-cui.h"
+-
+ static struct cui_opt_data *cod_from_item(struct pmenu_item *item)
+ {
+       return item->data;
+@@ -61,6 +60,35 @@ void cui_resize(struct cui *cui)
+ }
+ /**
++ * cui_make_item_name - Format the menu item name srting.
++ *
++ * Returns a talloc string.
++ */
++
++static char *cui_make_item_name(struct pmenu_item *i, struct cui_opt_data *cod)
++{
++      char *name;
++
++      assert(cod->name);
++      assert(cod->kd);
++
++      name = talloc_asprintf(i, "%s:", cod->name);
++
++      if (cod->kd->image)
++              name = talloc_asprintf_append(name, " %s", cod->kd->image);
++
++      if (cod->kd->initrd)
++              name = talloc_asprintf_append(name, " initrd=%s",
++                      cod->kd->initrd);
++
++      if (cod->kd->args)
++              name = talloc_asprintf_append(name, " %s", cod->kd->args);
++
++      DBGS("@%s@\n", name);
++      return name;
++}
++
++/**
+  * cui_on_exit - A generic main menu ESC callback.
+  */
+@@ -109,8 +137,8 @@ static int cui_run_kexec(struct pmenu_item *item)
+       assert(cui->current == &cui->main->scr);
+       assert(cui->on_kexec);
+-      pb_log("%s: %s\n", __func__, cod->dev->name, cod->opt->name);
+-      nc_scr_status_printf(cui->current, "Booting %s...", cod->opt->name);
++      pb_log("%s: %s\n", __func__, cod->name);
++      nc_scr_status_printf(cui->current, "Booting %s...", cod->name);
+       def_prog_mode();
+@@ -141,9 +169,10 @@ static void cui_ked_on_exit(struct ked *ked, enum ked_result ked_result,
+ {
+       struct cui *cui = cui_from_arg(ked->scr.ui_ctx);
+-      if (ked_result == ked_update || ked_result == ked_boot) {
++      if (ked_result == ked_update) {
+               struct pmenu_item *i = pmenu_find_selected(cui->main);
+               struct cui_opt_data *cod = cod_from_item(i);
++              char *name;
+               assert(kd);
+@@ -151,7 +180,13 @@ static void cui_ked_on_exit(struct ked *ked, enum ked_result ked_result,
+               talloc_free(cod->kd);
+               cod->kd = kd;
+-              pb_log("%s: updating opt '%s'\n", __func__, cod->opt->name);
++              name = cui_make_item_name(i, cod);
++              pmenu_item_replace(i, name);
++
++              /* FIXME: need to make item visible somehow */
++              set_current_item(cui->main->ncm, i->nci);
++
++              pb_log("%s: updating opt '%s'\n", __func__, cod->name);
+               pb_log(" image  '%s'\n", cod->kd->image);
+               pb_log(" initrd '%s'\n", cod->kd->initrd);
+               pb_log(" args   '%s'\n", cod->kd->args);
+@@ -160,20 +195,15 @@ static void cui_ked_on_exit(struct ked *ked, enum ked_result ked_result,
+       cui_set_current(cui, &cui->main->scr);
+       talloc_free(ked);
+-
+-      if (ked_result == ked_boot) {
+-              struct pmenu_item *i = pmenu_find_selected(cui->main);
+-              i->on_execute(i);
+-      }
+ }
+ int cui_ked_run(struct pmenu_item *item)
+ {
+       struct cui *cui = cui_from_item(item);
++      struct cui_opt_data *cod = cod_from_item(item);
+       struct ked *ked;
+-      ked = ked_init(cui, cod_from_item(item)->kd, cui_ked_on_exit);
+-
++      ked = ked_init(cui, cod->kd, cui_ked_on_exit);
+       cui_set_current(cui, &ked->scr);
+       return 0;
+@@ -200,14 +230,43 @@ struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr)
+       return old;
+ }
++/**
++ * cui_process_key - Process input on stdin.
++ */
++
+ static int cui_process_key(void *arg)
+ {
+       struct cui *cui = cui_from_arg(arg);
+       assert(cui->current);
++
++      ui_timer_disable(&cui->timer);
+       cui->current->process_key(cui->current);
++
++      return 0;
++}
++
++/**
++ * cui_process_js - Process joystick events.
++ */
++
++static int cui_process_js(void *arg)
++{
++      struct cui *cui = cui_from_arg(arg);
++      int c;
++
++      c = pjs_process_event(cui->pjs);
++
++      if (c) {
++              ungetch(c);
++              cui_process_key(arg);
++      }
++
+       return 0;
+ }
++/**
++ * cui_client_process_socket - Process a socket event from the discover server.
++ */
+ static int cui_client_process_socket(void *arg)
+ {
+@@ -218,6 +277,24 @@ static int cui_client_process_socket(void *arg)
+ }
+ /**
++ * cui_handle_timeout - Handle the timeout.
++ */
++
++static void cui_handle_timeout(struct ui_timer *timer)
++{
++      struct cui *cui = cui_from_timer(timer);
++      struct pmenu_item *i = pmenu_find_selected(cui->main);
++
++#if defined(DEBUG)
++      {
++              struct cui_opt_data *cod = cod_from_item(i);
++              assert(cod && (cod->opt_hash == cui->default_item));
++      }
++#endif
++      i->on_execute(i);
++}
++
++/**
+  * cui_handle_resize - Handle the term resize.
+  */
+@@ -243,6 +320,46 @@ static void cui_handle_resize(struct cui *cui)
+ }
+ /**
++ * cui_on_open - Open new item callback.
++ */
++
++void cui_on_open(struct pmenu *menu)
++{
++      unsigned int insert_pt;
++      struct pmenu_item *i;
++      struct cui_opt_data *cod;
++
++      menu->scr.unpost(&menu->scr);
++
++      /* This disconnects items array from menu. */
++
++      set_menu_items(menu->ncm, NULL);
++
++      /* Insert new items at insert_pt. */
++
++      insert_pt = pmenu_grow(menu, 1);
++      i = pmenu_item_alloc(menu);
++
++      i->on_edit = cui_ked_run;
++      i->on_execute = cui_run_kexec;
++      i->data = cod = talloc_zero(i, struct cui_opt_data);
++
++      cod->name = talloc_asprintf(i, "User item %u:", insert_pt);
++      cod->kd = talloc_zero(i, struct pb_kexec_data);
++
++      pmenu_item_setup(menu, i, insert_pt, talloc_strdup(i, cod->name));
++
++      /* Re-attach the items array. */
++
++      set_menu_items(menu->ncm, menu->items);
++
++      menu->scr.post(&menu->scr);
++      set_current_item(menu->ncm, i->nci);
++
++      i->on_edit(i);
++}
++
++/**
+  * cui_device_add - Client device_add callback.
+  *
+  * Creates menu_items for all the device boot_options and inserts those
+@@ -284,23 +401,11 @@ static int cui_device_add(struct device *dev, void *arg)
+               struct pmenu_item *i;
+               struct cui_opt_data *cod;
+               char *name;
+-              char *description;
+               /* Save the item in opt->ui_info for cui_device_remove() */
+               opt->ui_info = i = pmenu_item_alloc(cui->main);
+-              name = talloc_asprintf(i, "%s: %s", opt->name,
+-                      opt->description);
+-
+-              description = talloc_asprintf(i,
+-                      " kexec: image='%s' initrd='%s' args='%s'",
+-                      opt->boot_image_file,
+-                      opt->initrd_file,
+-                      opt->boot_args);
+-
+-              pmenu_item_setup(cui->main, i, insert_pt, name, description);
+-
+               i->on_edit = cui_ked_run;
+               i->on_execute = cui_run_kexec;
+               i->data = cod = talloc(i, struct cui_opt_data);
+@@ -308,23 +413,29 @@ static int cui_device_add(struct device *dev, void *arg)
+               cod->dev = dev;
+               cod->opt = opt;
+               cod->opt_hash = pb_opt_hash(dev, opt);
++              cod->name = opt->name;
+               cod->kd = talloc(i, struct pb_kexec_data);
+               cod->kd->image = talloc_strdup(cod->kd, opt->boot_image_file);
+               cod->kd->initrd = talloc_strdup(cod->kd, opt->initrd_file);
+               cod->kd->args = talloc_strdup(cod->kd, opt->boot_args);
+-              insert_pt++;
+-
+-              /* If this is the default_item select it. */
++              name = cui_make_item_name(i, cod);
++              pmenu_item_setup(cui->main, i, insert_pt, name);
+-              if (cod->opt_hash == cui->default_item)
+-                      selected = i->nci;
++              insert_pt++;
+-              pb_log("%s: adding opt '%s'\n", __func__, cod->opt->name);
++              pb_log("%s: adding opt '%s'\n", __func__, cod->name);
+               pb_log("   image  '%s'\n", cod->kd->image);
+               pb_log("   initrd '%s'\n", cod->kd->initrd);
+               pb_log("   args   '%s'\n", cod->kd->args);
++
++              /* If this is the default_item select it and start timer. */
++
++              if (cod->opt_hash == cui->default_item) {
++                      selected = i->nci;
++                      ui_timer_kick(&cui->timer);
++              }
+       }
+       /* Re-attach the items array. */
+@@ -340,6 +451,9 @@ static int cui_device_add(struct device *dev, void *arg)
+                       item_count(cui->main->ncm) + 1);
+       }
++      /* FIXME: need to make item visible somehow */
++      menu_driver(cui->main->ncm, REQ_SCR_UPAGE);
++      menu_driver(cui->main->ncm, REQ_SCR_DPAGE);
+       set_current_item(cui->main->ncm, selected);
+       if (cui->current == &cui->main->scr)
+@@ -375,9 +489,15 @@ static void cui_device_remove(struct device *dev, void *arg)
+       list_for_each_entry(&dev->boot_options, opt, list) {
+               struct pmenu_item *i = pmenu_item_from_arg(opt->ui_info);
++              struct cui_opt_data *cod = cod_from_item(i);
+-              assert(pb_protocol_device_cmp(dev, cod_from_item(i)->dev));
++              assert(pb_protocol_device_cmp(dev, cod->dev));
+               pmenu_remove(cui->main, i);
++
++              /* If this is the default_item disable timer. */
++
++              if (cod->opt_hash == cui->default_item)
++                      ui_timer_disable(&cui->timer);
+       }
+       /* Re-attach the items array. */
+@@ -397,10 +517,6 @@ static void cui_device_remove(struct device *dev, void *arg)
+               cui->current->post(cui->current);
+ }
+-/**
+- * cui_client_process_socket - Process a socket event from the discover server.
+- */
+-
+ static struct discover_client_ops cui_client_ops = {
+       .device_add = cui_device_add,
+       .device_remove = cui_device_remove,
+@@ -417,7 +533,8 @@ static struct discover_client_ops cui_client_ops = {
+  */
+ struct cui *cui_init(void* platform_info,
+-      int (*on_kexec)(struct cui *, struct cui_opt_data *))
++      int (*on_kexec)(struct cui *, struct cui_opt_data *),
++      int (*js_map)(const struct js_event *e))
+ {
+       struct cui *cui;
+       struct discover_client *client;
+@@ -434,6 +551,7 @@ struct cui *cui_init(void* platform_info,
+       cui->c_sig = pb_cui_sig;
+       cui->platform_info = platform_info;
+       cui->on_kexec = on_kexec;
++      cui->timer.handle_timeout = cui_handle_timeout;
+       /* Loop here for scripts that just started the server. */
+@@ -462,6 +580,15 @@ struct cui *cui_init(void* platform_info,
+       waiter_register(STDIN_FILENO, WAIT_IN, cui_process_key, cui);
++      if (js_map) {
++
++              cui->pjs = pjs_init(cui, js_map);
++
++              if (cui->pjs)
++                      waiter_register(pjs_get_fd(cui->pjs), WAIT_IN,
++                              cui_process_js, cui);
++      }
++
+       return cui;
+ fail_client_init:
+@@ -501,6 +628,8 @@ int cui_run(struct cui *cui, struct pmenu *main, unsigned int default_item)
+               if (cui->abort)
+                       break;
++              ui_timer_process_sig(&cui->timer);
++
+               while (cui->resize) {
+                       cui->resize = 0;
+                       cui_handle_resize(cui);
+diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h
+index 668776e..94fef6b 100644
+--- a/ui/ncurses/nc-cui.h
++++ b/ui/ncurses/nc-cui.h
+@@ -21,15 +21,19 @@
+ #include <signal.h>
++#include "ui/common/joystick.h"
++#include "ui/common/timer.h"
+ #include "nc-menu.h"
+ #include "nc-ked.h"
+-
+ struct cui_opt_data {
++      const char *name;
++      struct pb_kexec_data *kd;
++
++      /* optional data */
+       const struct device *dev;
+       const struct boot_option *opt;
+       uint32_t opt_hash;
+-      struct pb_kexec_data *kd;
+ };
+ /**
+@@ -49,13 +53,16 @@ struct cui {
+       sig_atomic_t resize;
+       struct nc_scr *current;
+       struct pmenu *main;
++      struct ui_timer timer;
++      struct pjs *pjs;
+       void *platform_info;
+       unsigned int default_item;
+       int (*on_kexec)(struct cui *cui, struct cui_opt_data *cod);
+ };
+ struct cui *cui_init(void* platform_info,
+-      int (*on_kexec)(struct cui *, struct cui_opt_data *));
++      int (*on_kexec)(struct cui *, struct cui_opt_data *),
++      int (*js_map)(const struct js_event *e));
+ struct nc_scr *cui_set_current(struct cui *cui, struct nc_scr *scr);
+ int cui_run(struct cui *cui, struct pmenu *main, unsigned int default_item);
+ int cui_ked_run(struct pmenu_item *item);
+@@ -65,6 +72,7 @@ int cui_ked_run(struct pmenu_item *item);
+ void cui_abort(struct cui *cui);
+ void cui_resize(struct cui *cui);
+ void cui_on_exit(struct pmenu *menu);
++void cui_on_open(struct pmenu *menu);
+ int cui_run_cmd(struct pmenu_item *item);
+ static inline struct cui *cui_from_arg(void *arg)
+@@ -85,4 +93,15 @@ static inline struct cui *cui_from_item(struct pmenu_item *item)
+       return cui_from_pmenu(item->pmenu);
+ }
++static inline struct cui *cui_from_timer(struct ui_timer *timer)
++{
++      struct cui *cui;
++
++      cui = (struct cui *)((char *)timer
++              - (size_t)&((struct cui *)0)->timer);
++      assert(cui->c_sig == pb_cui_sig);
++
++      return cui;
++}
++
+ #endif
+diff --git a/ui/ncurses/nc-ked.c b/ui/ncurses/nc-ked.c
+index 3bdbd6c..806d389 100644
+--- a/ui/ncurses/nc-ked.c
++++ b/ui/ncurses/nc-ked.c
+@@ -48,12 +48,15 @@ static struct ked *ked_from_arg(void *arg)
+  * @req: An ncurses request or char to send to form_driver().
+  */
+-static void ked_move_cursor(struct ked *ked, int req)
++static int ked_move_cursor(struct ked *ked, int req)
+ {
++      int result;
++
+       wchgat(ked->scr.sub_ncw, 1, ked_attr_field_selected, 0, 0);
+-      form_driver(ked->ncf, req);
++      result = form_driver(ked->ncf, req);
+       wchgat(ked->scr.sub_ncw, 1, ked->attr_cursor, 0, 0);
+       wrefresh(ked->scr.main_ncw);
++      return result;
+ }
+ /**
+@@ -93,12 +96,15 @@ static void ked_insert_mode_tog(struct ked *ked)
+  * @req: An ncurses request to send to form_driver().
+  */
+-static void ked_move_field(struct ked *ked, int req)
++static int ked_move_field(struct ked *ked, int req)
+ {
++      int result;
++
+       set_field_back(current_field(ked->ncf), ked_attr_field_normal);
+-      form_driver(ked->ncf, req);
++      result = form_driver(ked->ncf, req);
+       set_field_back(current_field(ked->ncf), ked_attr_field_selected);
+       ked_move_cursor(ked, REQ_END_FIELD);
++      return result;
+ }
+ static int ked_post(struct nc_scr *scr)
+@@ -143,9 +149,10 @@ static char *ked_chomp(char *s)
+       for (; s < s_end; s++)
+               if (*s != ' ' && *s != '\t')
+                       break;
+-      start = s;
+-      for (++s; s < s_end; s++)
++      start = end = s;
++
++      for (; s < s_end; s++)
+               if (*s != ' ' && *s != '\t')
+                       end = s;
+       *(end + 1) = 0;
+@@ -183,41 +190,33 @@ static struct pb_kexec_data *ked_prepare_data(struct ked *ked)
+ static void ked_process_key(struct nc_scr *scr)
+ {
+       struct ked *ked = ked_from_scr(scr);
++      struct pb_kexec_data *kd;
+       while (1) {
+               int c = getch();
++              if (c == ERR)
++                      return;
++
++              /* DBGS("%d (%o)\n", c, c); */
++
+               switch (c) {
+               default:
+                       ked_move_cursor(ked, c);
+-              break;
+-              case ERR:
+-                      return;
++                      break;
+               /* hot keys */
+-              case 2: { /* CTRL-B */
+-                      struct pb_kexec_data *kd;
+-
+-                      form_driver(ked->ncf, REQ_VALIDATION);
+-                      kd = ked_prepare_data(ked);
+-                      ked->on_exit(ked, ked_boot, kd);
+-                      nc_flush_keys();
+-                      return;
+-              }
+               case 27: /* ESC */
+                       ked->on_exit(ked, ked_cancel, NULL);
+                       nc_flush_keys();
+                       return;
+               case '\n':
+-              case '\r': {
+-                      struct pb_kexec_data *kd;
+-
++              case '\r':
+                       form_driver(ked->ncf, REQ_VALIDATION);
+                       kd = ked_prepare_data(ked);
+                       ked->on_exit(ked, ked_update, kd);
+                       nc_flush_keys();
+                       return;
+-              }
+               /* insert mode */
+               case KEY_IC:
+@@ -252,8 +251,8 @@ static void ked_process_key(struct nc_scr *scr)
+                       ked_move_cursor(ked, REQ_RIGHT_CHAR);
+                       break;
+               case KEY_BACKSPACE:
+-                      ked_move_cursor(ked, REQ_LEFT_CHAR);
+-                      ked_move_cursor(ked, REQ_DEL_CHAR);
++                      if (ked_move_cursor(ked, REQ_LEFT_CHAR) == E_OK)
++                              ked_move_cursor(ked, REQ_DEL_CHAR);
+                       break;
+               case KEY_DC:
+                       ked_move_cursor(ked, REQ_DEL_CHAR);
+@@ -325,7 +324,7 @@ struct ked *ked_init(void *ui_ctx, const struct pb_kexec_data *kd,
+       ked->scr.frame.title = talloc_strdup(ked, "Petitboot Option Editor");
+       ked->scr.frame.help = talloc_strdup(ked,
+-              "ESC=cancel, Enter=accept, Ctrl-b=boot");
++              "ESC=cancel, Enter=accept");
+       ked->on_exit = on_exit;
+diff --git a/ui/ncurses/nc-ked.h b/ui/ncurses/nc-ked.h
+index 36ed4f1..62fddd6 100644
+--- a/ui/ncurses/nc-ked.h
++++ b/ui/ncurses/nc-ked.h
+@@ -20,6 +20,7 @@
+ #define _PB_NC_KED_H
+ #include <assert.h>
++#include <linux/input.h> /* This must be included before ncurses.h */
+ #include <form.h>
+ #include "pb-protocol/pb-protocol.h"
+@@ -40,13 +41,11 @@ enum ked_attr_cursor {
+  * enum ked_result - Result code for ked:on_exit().
+  * @ked_cancel: The user canceled the operation.
+  * @ked_update: The args were updated.
+- * @ked_boot: The user requested a boot of this item.
+  */
+ enum ked_result {
+       ked_cancel,
+       ked_update,
+-      ked_boot,
+ };
+ /**
+diff --git a/ui/ncurses/nc-menu.c b/ui/ncurses/nc-menu.c
+index 0a76b11..f96eb82 100644
+--- a/ui/ncurses/nc-menu.c
++++ b/ui/ncurses/nc-menu.c
+@@ -91,17 +91,17 @@ struct pmenu_item *pmenu_item_alloc(struct pmenu *menu)
+ }
+ struct pmenu_item *pmenu_item_setup(struct pmenu *menu, struct pmenu_item *i,
+-      unsigned int index, const char *name,
+-      const char *description)
++      unsigned int index, const char *name)
+ {
+       assert(i);
++      assert(name);
+       if (!i)
+               return NULL;
+       i->i_sig = pb_item_sig;
+       i->pmenu = menu;
+-      i->nci = new_item(name, description);
++      i->nci = new_item(name, NULL);
+       if (!i->nci) {
+               talloc_free(i);
+@@ -115,6 +115,67 @@ struct pmenu_item *pmenu_item_setup(struct pmenu *menu, struct pmenu_item *i,
+       return i;
+ }
++static int pmenu_item_get_index(const struct pmenu_item *item)
++{
++      unsigned int i;
++
++      for (i = 0; i < item->pmenu->item_count; i++)
++              if (item->pmenu->items[i] == item->nci)
++                      return i;
++
++      pb_log("%s: not found: %p %s\n", __func__, item,
++              (item ? item->nci->name.str : "(null)"));
++      return -1;
++}
++
++/**
++ * pmenu_item_replace - Replace the menu item with a new one.
++ *
++ * Use this routine to change a menu item's text.
++ */
++
++int pmenu_item_replace(struct pmenu_item *i, const char *name)
++{
++      struct pmenu *menu;
++      ITEM *nci;
++      int index;
++
++      assert(name);
++      assert(i->nci);
++
++      menu = i->pmenu;
++      index = pmenu_item_get_index(i);
++
++      if (index < 0) {
++              assert(0 && "get_index failed");
++              return -1;
++      }
++
++      nci = new_item(name, NULL);
++
++      if (!nci) {
++              assert(0 && "new_item failed");
++              return -1;
++      }
++
++      set_item_userptr(nci, i);
++
++      menu->scr.unpost(&menu->scr);
++      set_menu_items(menu->ncm, NULL);
++
++      // FIXME: need to assure item name is a talloc string.
++      /* talloc_free((char *)item_name(i->nci)); */
++
++      free_item(i->nci);
++      menu->items[index] = nci;
++      i->nci = nci;
++
++      set_menu_items(menu->ncm, menu->items);
++      menu->scr.post(&menu->scr);
++
++      return 0;
++}
++
+ /**
+  * pmenu_move_cursor - Move the cursor.
+  * @req: An ncurses request or char to send to menu_driver().
+@@ -143,7 +204,7 @@ static void pmenu_process_key(struct nc_scr *scr)
+               if (c == ERR)
+                       return;
+-              /* DBGS("%d (%o)\n", c, c); */
++              if (1) DBGS("%d (%o)\n", c, c);
+               if (menu->hot_key)
+                       c = menu->hot_key(menu, item, c);
+@@ -174,13 +235,16 @@ static void pmenu_process_key(struct nc_scr *scr)
+               case '\t':
+                       pmenu_move_cursor(menu, REQ_DOWN_ITEM);
+                       break;
+-
+               case KEY_LEFT:
+-              case 'E':
+               case 'e':
+                       if (item->on_edit)
+                               item->on_edit(item);
+                       break;
++              case 'o':
++                      DBGS("on_open: %p\n", menu->on_open);
++                      if (menu->on_open)
++                              menu->on_open(menu);
++                      break;
+               case '\n':
+               case '\r':
+                       if (item->on_execute)
+@@ -228,19 +292,6 @@ unsigned int pmenu_grow(struct pmenu *menu, unsigned int count)
+       return tmp;
+ }
+-static int pmenu_item_get_index(const struct pmenu_item *item)
+-{
+-      unsigned int i;
+-
+-      for (i = 0; i < item->pmenu->item_count; i++)
+-              if (item->pmenu->items[i] == item->nci)
+-                      return i;
+-
+-      pb_log("%s: not found: %p %s\n", __func__, item,
+-              (item ? item->nci->name.str : "(null)"));
+-      return -1;
+-}
+-
+ /**
+  * pmenu_remove - Remove an item from the item array.
+  *
+@@ -262,6 +313,9 @@ int pmenu_remove(struct pmenu *menu, struct pmenu_item *item)
+       if (index < 0)
+               return -1;
++      free_item(item->nci);
++      talloc_free(item);
++
+       /* Note that items array has a null terminator. */
+       menu->insert_pt--;
+@@ -349,7 +403,7 @@ void pmenu_delete(struct pmenu *menu)
+       menu->scr.sig = pb_removed_sig;
+       for (i = item_count(menu->ncm); i; i--)
+-              free_item(menu->items[i]);
++              free_item(menu->items[i - 1]);
+       free_menu(menu->ncm);
+       delwin(menu->scr.sub_ncw);
+diff --git a/ui/ncurses/nc-menu.h b/ui/ncurses/nc-menu.h
+index b487df9..4abec6f 100644
+--- a/ui/ncurses/nc-menu.h
++++ b/ui/ncurses/nc-menu.h
+@@ -20,6 +20,7 @@
+ #define _PB_NC_MENU_H
+ #include <assert.h>
++#include <linux/input.h> /* This must be included before ncurses.h */
+ #include <menu.h>
+ #include "log/log.h"
+@@ -45,7 +46,8 @@ struct pmenu_item {
+ struct pmenu_item *pmenu_item_alloc(struct pmenu *menu);
+ struct pmenu_item *pmenu_item_setup(struct pmenu *menu, struct pmenu_item *i,
+-      unsigned int index, const char *name, const char *description);
++      unsigned int index, const char *name);
++int pmenu_item_replace(struct pmenu_item *i, const char *name);
+ void pmenu_item_delete(struct pmenu_item *item);
+ static inline struct pmenu_item *pmenu_item_from_arg(void *arg)
+@@ -57,10 +59,9 @@ static inline struct pmenu_item *pmenu_item_from_arg(void *arg)
+ }
+ static inline struct pmenu_item *pmenu_item_init(struct pmenu *menu,
+-      unsigned int index, const char *name, const char *description)
++      unsigned int index, const char *name)
+ {
+-      return pmenu_item_setup(menu, pmenu_item_alloc(menu), index, name,
+-              description);
++      return pmenu_item_setup(menu, pmenu_item_alloc(menu), index, name);
+ }
+ /**
+@@ -77,6 +78,7 @@ struct pmenu {
+       unsigned int insert_pt;
+       int (*hot_key)(struct pmenu *menu, struct pmenu_item *item, int c);
+       void (*on_exit)(struct pmenu *menu);
++      void (*on_open)(struct pmenu *menu);
+ };
+ struct pmenu *pmenu_init(void *ui_ctx, unsigned int item_count,
+diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h
+index c08fcd4..2374c20 100644
+--- a/ui/ncurses/nc-scr.h
++++ b/ui/ncurses/nc-scr.h
+@@ -19,6 +19,7 @@
+ #if !defined(_PB_NC_SCR_H)
+ #define _PB_NC_SCR_H
++#include <linux/input.h> /* This must be included before ncurses.h */
+ #include <ncurses.h>
+ #define DBG(fmt, args...) pb_log("DBG: " fmt, ## args)
+diff --git a/ui/ncurses/pb-cui.c b/ui/ncurses/pb-cui.c
+new file mode 100644
+index 0000000..972490a
+--- /dev/null
++++ b/ui/ncurses/pb-cui.c
+@@ -0,0 +1,289 @@
++/*
++ * Petitboot generic ncurses bootloader UI
++ *
++ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
++ *  Copyright 2009 Sony Corp.
++ *
++ *  This program is free software; you can redistribute it and/or modify
++ *  it under the terms of the GNU General Public License as published by
++ *  the Free Software Foundation; version 2 of the License.
++ *
++ *  This program is distributed in the hope that it will be useful,
++ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++ *  GNU General Public License for more details.
++ *
++ *  You should have received a copy of the GNU General Public License
++ *  along with this program; if not, write to the Free Software
++ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ */
++
++#if defined(HAVE_CONFIG_H)
++#include "config.h"
++#endif
++
++#define _GNU_SOURCE
++#include <errno.h>
++#include <getopt.h>
++#include <signal.h>
++#include <stdlib.h>
++#include <string.h>
++#include <sys/time.h>
++
++#include "log/log.h"
++#include "talloc/talloc.h"
++#include "waiter/waiter.h"
++#include "ui/common/discover-client.h"
++#include "nc-cui.h"
++
++static void print_version(void)
++{
++      printf("pb-cui (" PACKAGE_NAME ") " PACKAGE_VERSION "\n");
++}
++
++static void print_usage(void)
++{
++      print_version();
++      printf(
++"Usage: pb-cui [-h, --help] [-l, --log log-file] [-V, --version]\n");
++}
++
++/**
++ * enum opt_value - Tri-state options variables.
++ */
++
++enum opt_value {opt_undef = 0, opt_yes, opt_no};
++
++/**
++ * struct opts - Values from command line options.
++ */
++
++struct opts {
++      enum opt_value show_help;
++      const char *log_file;
++      enum opt_value show_version;
++};
++
++/**
++ * opts_parse - Parse the command line options.
++ */
++
++static int opts_parse(struct opts *opts, int argc, char *argv[])
++{
++      static const struct option long_options[] = {
++              {"help",    no_argument,       NULL, 'h'},
++              {"log",     required_argument, NULL, 'l'},
++              {"version", no_argument,       NULL, 'V'},
++              { NULL,     0,                 NULL, 0},
++      };
++      static const char short_options[] = "hl:V";
++      static const struct opts default_values = {
++              .log_file = "pb-cui.log",
++      };
++
++      *opts = default_values;
++
++      while (1) {
++              int c = getopt_long(argc, argv, short_options, long_options,
++                      NULL);
++
++              if (c == EOF)
++                      break;
++
++              switch (c) {
++              case 'h':
++                      opts->show_help = opt_yes;
++                      break;
++              case 'l':
++                      opts->log_file = optarg;
++                      break;
++              case 'V':
++                      opts->show_version = opt_yes;
++                      break;
++              default:
++                      opts->show_help = opt_yes;
++                      return -1;
++              }
++      }
++
++      return 0;
++}
++
++/**
++ * struct pb_cui - Main cui program instance.
++ * @mm: Main menu.
++ * @svm: Set video mode menu.
++ */
++
++struct pb_cui {
++      struct pmenu *mm;
++      struct cui *cui;
++};
++
++static struct pb_cui *pb_from_cui(struct cui *cui)
++{
++      struct pb_cui *pb;
++
++      assert(cui->c_sig == pb_cui_sig);
++      pb = cui->platform_info;
++      assert(pb->cui->c_sig == pb_cui_sig);
++      return pb;
++}
++
++/**
++ * pb_kexec_cb - The kexec callback.
++ */
++
++static int pb_kexec_cb(struct cui *cui, struct cui_opt_data *cod)
++{
++      struct pb_cui *pb = pb_from_cui(cui);
++
++      pb_log("%s: %s\n", __func__, cod->name);
++
++      assert(pb->cui->current == &pb->cui->main->scr);
++
++      return pb_run_kexec(cod->kd);
++}
++
++/**
++ * pb_mm_init - Setup the main menu instance.
++ */
++
++static struct pmenu *pb_mm_init(struct pb_cui *pb_cui)
++{
++      int result;
++      struct pmenu *m;
++      struct pmenu_item *i;
++
++      m = pmenu_init(pb_cui->cui, 1, cui_on_exit);
++
++      if (!m) {
++              pb_log("%s: failed\n", __func__);
++              return NULL;
++      }
++
++      m->on_open = cui_on_open;
++
++      m->scr.frame.title = talloc_strdup(m, "Petitboot");
++      m->scr.frame.help = talloc_strdup(m,
++              "ESC=exit, Enter=accept, e=edit, o=open");
++      m->scr.frame.status = talloc_strdup(m, "Welcome to Petitboot");
++
++      i = pmenu_item_init(m, 0, "Exit to Shell");
++      i->on_execute = pmenu_exit_cb;
++
++      result = pmenu_setup(m);
++
++      if (result) {
++              pb_log("%s:%d: pmenu_setup failed: %s\n", __func__, __LINE__,
++                      strerror(errno));
++              goto fail_setup;
++      }
++
++      menu_opts_off(m->ncm, O_SHOWDESC);
++      set_menu_mark(m->ncm, " *");
++      set_current_item(m->ncm, i->nci);
++
++      return m;
++
++fail_setup:
++      talloc_free(m);
++      return NULL;
++}
++
++static struct pb_cui pb;
++
++static void sig_handler(int signum)
++{
++      DBGS("%d\n", signum);
++
++      switch (signum) {
++      case SIGALRM:
++              if (pb.cui)
++                      ui_timer_sigalrm(&pb.cui->timer);
++              break;
++      case SIGWINCH:
++              if (pb.cui)
++                      cui_resize(pb.cui);
++              break;
++      default:
++              assert(0 && "unknown sig");
++              /* fall through */
++      case SIGINT:
++      case SIGHUP:
++      case SIGTERM:
++              if (pb.cui)
++                      cui_abort(pb.cui);
++              break;
++      }
++}
++
++/**
++ * main - cui bootloader main routine.
++ */
++
++int main(int argc, char *argv[])
++{
++      static struct sigaction sa;
++      static struct opts opts;
++      int result;
++      int cui_result;
++      FILE *log;
++
++      result = opts_parse(&opts, argc, argv);
++
++      if (result) {
++              print_usage();
++              return EXIT_FAILURE;
++      }
++
++      if (opts.show_help == opt_yes) {
++              print_usage();
++              return EXIT_SUCCESS;
++      }
++
++      if (opts.show_version == opt_yes) {
++              print_version();
++              return EXIT_SUCCESS;
++      }
++
++      log = fopen(opts.log_file, "a");
++      assert(log);
++      pb_log_set_stream(log);
++
++#if defined(DEBUG)
++      pb_log_always_flush(1);
++#endif
++
++      pb_log("--- pb-cui ---\n");
++
++      sa.sa_handler = sig_handler;
++      result = sigaction(SIGALRM, &sa, NULL);
++      result += sigaction(SIGHUP, &sa, NULL);
++      result += sigaction(SIGINT, &sa, NULL);
++      result += sigaction(SIGTERM, &sa, NULL);
++      result += sigaction(SIGWINCH, &sa, NULL);
++
++      if (result) {
++              pb_log("%s sigaction failed.\n", __func__);
++              return EXIT_FAILURE;
++      }
++
++      pb.cui = cui_init(&pb, pb_kexec_cb, NULL);
++
++      if (!pb.cui)
++              return EXIT_FAILURE;
++
++      pb.mm = pb_mm_init(&pb);
++      ui_timer_disable(&pb.cui->timer);
++
++      cui_result = cui_run(pb.cui, pb.mm, 0);
++
++      pmenu_delete(pb.mm);
++
++      talloc_free(pb.cui);
++
++      pb_log("--- end ---\n");
++
++      return cui_result ? EXIT_FAILURE : EXIT_SUCCESS;
++}
+diff --git a/ui/ncurses/ps3-cui.c b/ui/ncurses/ps3-cui.c
+index f166c88..d9a66fa 100644
+--- a/ui/ncurses/ps3-cui.c
++++ b/ui/ncurses/ps3-cui.c
+@@ -21,9 +21,7 @@
+ /*
+  * TODO
+  * removable media event
+- * resize after video mode change
+  * ncurses mouse support
+- * timeout
+  */
+ #if defined(HAVE_CONFIG_H)
+@@ -36,6 +34,7 @@
+ #include <signal.h>
+ #include <stdlib.h>
+ #include <string.h>
++#include <sys/time.h>
+ #include "log/log.h"
+ #include "talloc/talloc.h"
+@@ -53,7 +52,8 @@ static void print_usage(void)
+ {
+       print_version();
+       printf(
+-"Usage: pb-cui [-h, --help] [-l, --log log-file] [-V, --version]\n");
++"Usage: pb-cui [-h, --help] [-l, --log log-file] [-r, --reset-defaults]\n"
++"              [-t, --timeout] [-V, --version]\n");
+ }
+ /**
+@@ -69,6 +69,8 @@ enum opt_value {opt_undef = 0, opt_yes, opt_no};
+ struct opts {
+       enum opt_value show_help;
+       const char *log_file;
++      enum opt_value reset_defaults;
++      enum opt_value use_timeout;
+       enum opt_value show_version;
+ };
+@@ -79,12 +81,14 @@ struct opts {
+ static int opts_parse(struct opts *opts, int argc, char *argv[])
+ {
+       static const struct option long_options[] = {
+-              {"help",    no_argument,       NULL, 'h'},
+-              {"log",     required_argument, NULL, 'l'},
+-              {"version", no_argument,       NULL, 'V'},
+-              { NULL,     0,                 NULL, 0},
++              {"help",           no_argument,       NULL, 'h'},
++              {"log",            required_argument, NULL, 'l'},
++              {"reset-defaults", no_argument,       NULL, 'r'},
++              {"timeout",        no_argument,       NULL, 't'},
++              {"version",        no_argument,       NULL, 'V'},
++              { NULL, 0, NULL, 0},
+       };
+-      static const char short_options[] = "hl:V";
++      static const char short_options[] = "hl:trV";
+       static const struct opts default_values = {
+               .log_file = "pb-cui.log",
+       };
+@@ -105,6 +109,12 @@ static int opts_parse(struct opts *opts, int argc, char *argv[])
+               case 'l':
+                       opts->log_file = optarg;
+                       break;
++              case 't':
++                      opts->use_timeout = opt_yes;
++                      break;
++              case 'r':
++                      opts->reset_defaults = opt_yes;
++                      break;
+               case 'V':
+                       opts->show_version = opt_yes;
+                       break;
+@@ -114,7 +124,7 @@ static int opts_parse(struct opts *opts, int argc, char *argv[])
+               }
+       }
+-      return 0;
++      return optind != argc;
+ }
+ /**
+@@ -147,6 +157,83 @@ static struct ps3_cui *ps3_from_item(struct pmenu_item *item)
+ }
+ /**
++ * ps3_sixaxis_map - Map a Linux joystick event to an ncurses key code.
++ *
++ */
++
++static int ps3_sixaxis_map(const struct js_event *e)
++{
++#if 0
++      static const int axis_map[] = {
++              0,              /*   0  Left thumb X    */
++              0,              /*   1  Left thumb Y    */
++              0,              /*   2  Right thumb X   */
++              0,              /*   3  Right thumb Y   */
++              0,              /*   4  nothing         */
++              0,              /*   5  nothing         */
++              0,              /*   6  nothing         */
++              0,              /*   7  nothing         */
++              0,              /*   8  Dpad Up         */
++              0,              /*   9  Dpad Right      */
++              0,              /*  10  Dpad Down       */
++              0,              /*  11  Dpad Left       */
++              0,              /*  12  L2              */
++              0,              /*  13  R2              */
++              0,              /*  14  L1              */
++              0,              /*  15  R1              */
++              0,              /*  16  Triangle        */
++              0,              /*  17  Circle          */
++              0,              /*  18  Cross           */
++              0,              /*  19  Square          */
++              0,              /*  20  nothing         */
++              0,              /*  21  nothing         */
++              0,              /*  22  nothing         */
++              0,              /*  23  nothing         */
++              0,              /*  24  nothing         */
++              0,              /*  25  nothing         */
++              0,              /*  26  nothing         */
++              0,              /*  27  nothing         */
++      };
++#endif
++      static const int button_map[] = {
++              0,              /*   0  Select          */
++              0,              /*   1  L3              */
++              0,              /*   2  R3              */
++              0,              /*   3  Start           */
++              KEY_UP,         /*   4  Dpad Up         */
++              0,              /*   5  Dpad Right      */
++              KEY_DOWN,       /*   6  Dpad Down       */
++              0,              /*   7  Dpad Left       */
++              KEY_UP,         /*   8  L2              */
++              KEY_DOWN,       /*   9  R2              */
++              KEY_HOME,       /*  10  L1              */
++              KEY_END,        /*  11  R1              */
++              0,              /*  12  Triangle        */
++              0,              /*  13  Circle          */
++              13,             /*  14  Cross           */
++              0,              /*  15  Square          */
++              0,              /*  16  PS Button       */
++              0,              /*  17  nothing         */
++              0,              /*  18  nothing         */
++      };
++
++      if (!e->value)
++              return 0;
++
++      if (e->type == JS_EVENT_BUTTON
++              && e->number < sizeof(button_map) / sizeof(button_map[0]))
++              return button_map[e->number];
++
++#if 0
++      if (e->type == JS_EVENT_AXIS
++              && e->number < sizeof(axis_map) / sizeof(axis_map[0]))
++              return axis_map[e->number];
++#endif
++
++      return 0;
++}
++
++/**
+  * ps3_set_mode - Set video mode helper.
+  *
+  * Runs ps3_set_video_mode().
+@@ -183,22 +270,51 @@ static int ps3_svm_cb(struct pmenu_item *item)
+  * ps3_kexec_cb - The kexec callback.
+  *
+  * Writes config data to PS3 flash then calls pb_run_kexec().
++ * Adds a video mode arg to the kernel command line if needed.
+  */
+ static int ps3_kexec_cb(struct cui *cui, struct cui_opt_data *cod)
+ {
+       struct ps3_cui *ps3 = ps3_from_cui(cui);
++      int result;
++      int altered_args;
++      char *orig_args;
+-      pb_log("%s: %s:%s\n", __func__, cod->dev->name, cod->opt->name);
++      pb_log("%s: %s\n", __func__, cod->name);
+       assert(ps3->cui->current == &ps3->cui->main->scr);
+-      if (cui->default_item != cod->opt_hash || ps3->dirty_values) {
++      /* Save values to flash if needed */
++
++      if ((cod->opt_hash && cod->opt_hash != cui->default_item)
++              || ps3->dirty_values) {
+               ps3->values.default_item = cod->opt_hash;
+               ps3_flash_set_values(&ps3->values);
+       }
+-      return pb_run_kexec(cod->kd);
++      /* Add a default kernel video mode. */
++
++      if (!cod->kd->args) {
++              altered_args = 1;
++              orig_args = NULL;
++              cod->kd->args = talloc_asprintf(NULL, "video=ps3fb:mode:%u",
++                      (unsigned int)ps3->values.video_mode);
++      } else if (!strstr(cod->kd->args, "video=")) {
++              altered_args = 1;
++              orig_args = cod->kd->args;
++              cod->kd->args = talloc_asprintf(NULL, "%s video=ps3fb:mode:%u",
++                      orig_args, (unsigned int)ps3->values.video_mode);
++      } else
++              altered_args = 0;
++
++      result = pb_run_kexec(cod->kd);
++
++      if (altered_args) {
++              talloc_free(cod->kd->args);
++              cod->kd->args = orig_args;
++      }
++
++      return result;
+ }
+ /**
+@@ -283,6 +399,21 @@ static int ps3_hot_key(struct pmenu __attribute__((unused)) *menu,
+ }
+ /**
++ * ps3_timer_update - Timer callback.
++ */
++
++static void ps3_timer_update(struct ui_timer *timer, unsigned int timeout)
++{
++      struct ps3_cui *ps3 = ps3_from_cui(cui_from_timer(timer));
++
++      //FIXME: make scr:timer.
++      // nc_scr_timer_update(&ps3.mm->scr, timeout);
++
++      nc_scr_status_printf(&ps3->mm->scr,
++              "Welcome to Petitboot (timeout %u sec)", timeout);
++}
++
++/**
+  * ps3_mm_init - Setup the main menu instance.
+  */
+@@ -291,8 +422,7 @@ static struct pmenu *ps3_mm_init(struct ps3_cui *ps3_cui)
+       int result;
+       struct pmenu *m;
+       struct pmenu_item *i;
+-      static const char *const bgo[] =
+-              {"/usr/sbin/ps3-boot-game-os-NOT", NULL};
++      static const char *const bgo[] = {"/usr/sbin/ps3-boot-game-os", NULL};
+       m = pmenu_init(ps3_cui->cui, 3, cui_on_exit);
+@@ -302,22 +432,26 @@ static struct pmenu *ps3_mm_init(struct ps3_cui *ps3_cui)
+       }
+       m->hot_key = ps3_hot_key;
++      m->on_open = cui_on_open;
++
++#if defined(DEBUG)
++      m->scr.frame.title = talloc_strdup(m,
++              "Petitboot PS3 (" PACKAGE_VERSION ")");
++#else
+       m->scr.frame.title = talloc_strdup(m, "Petitboot PS3");
++#endif
+       m->scr.frame.help = talloc_strdup(m,
+-              "ESC=exit, Enter=accept, E,e=edit");
++              "ESC=exit, Enter=accept, e=edit, o=open");
+       m->scr.frame.status = talloc_strdup(m, "Welcome to Petitboot");
+-      i = pmenu_item_init(m, 0, "Boot GameOS",
+-              "Reboot the PS3 into the GameOS");
++      i = pmenu_item_init(m, 0, "Boot GameOS");
+       i->on_execute = cui_run_cmd;
+       i->data = (void *)bgo;
+-      i = pmenu_item_init(m, 1, "Set Video Mode",
+-              "Display a video mode selection menu");
++      i = pmenu_item_init(m, 1, "Set Video Mode");
+       i->on_execute = ps3_mm_to_svm_cb;
+-      i = pmenu_item_init(m, 2, "Exit to Shell",
+-              "Exit petitboot and return to a shell prompt");
++      i = pmenu_item_init(m, 2, "Exit to Shell");
+       i->on_execute = pmenu_exit_cb;
+       result = pmenu_setup(m);
+@@ -360,53 +494,51 @@ static struct pmenu *ps3_svm_init(struct ps3_cui *ps3_cui)
+       m->scr.frame.title = talloc_strdup(m, "Select PS3 Video Mode");
+       m->scr.frame.help = talloc_strdup(m, "ESC=exit, Enter=accept");
+-      i = pmenu_item_init(m, 0, "auto detect",
+-              "Auto detect the best HDMI video mode");
++      i = pmenu_item_init(m, 0, "auto detect");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)0;
+-      i = pmenu_item_init(m, 1, "480i    (576 x 384)", NULL);
++      i = pmenu_item_init(m, 1, "480i    (576 x 384)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)1;
+-      i = pmenu_item_init(m, 2, "480p    (576 x 384)", NULL);
++      i = pmenu_item_init(m, 2, "480p    (576 x 384)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)2;
+-      i = pmenu_item_init(m, 3, "576i    (576 x 460)", NULL);
++      i = pmenu_item_init(m, 3, "576i    (576 x 460)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)6;
+-      i = pmenu_item_init(m, 4, "576p    (576 x 460)", NULL);
++      i = pmenu_item_init(m, 4, "576p    (576 x 460)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)7;
+-      i = pmenu_item_init(m, 5, "720p   (1124 x 644)", NULL);
++      i = pmenu_item_init(m, 5, "720p   (1124 x 644)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)3;
+-      i = pmenu_item_init(m, 6, "1080i  (1688 x 964)", NULL);
++      i = pmenu_item_init(m, 6, "1080i  (1688 x 964)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)4;
+-      i = pmenu_item_init(m, 7, "1080p  (1688 x 964)", NULL);
++      i = pmenu_item_init(m, 7, "1080p  (1688 x 964)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)5;
+-      i = pmenu_item_init(m, 8, "wxga   (1280 x 768)", NULL);
++      i = pmenu_item_init(m, 8, "wxga   (1280 x 768)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)11;
+-      i = pmenu_item_init(m, 9, "sxga   (1280 x 1024)", NULL);
++      i = pmenu_item_init(m, 9, "sxga   (1280 x 1024)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)12;
+-      i = pmenu_item_init(m, 10, "wuxga  (1920 x 1200)", NULL);
++      i = pmenu_item_init(m, 10, "wuxga  (1920 x 1200)");
+       i->on_execute = ps3_svm_cb;
+       i->data = (void *)13;
+-      i = pmenu_item_init(m, 11, "Return",
+-              "Return to the main menu");
++      i = pmenu_item_init(m, 11, "Return");
+       i->on_execute = ps3_svm_to_mm_cb;
+       result = pmenu_setup(m);
+@@ -434,6 +566,10 @@ static void sig_handler(int signum)
+       DBGS("%d\n", signum);
+       switch (signum) {
++      case SIGALRM:
++              if (ps3.cui)
++                      ui_timer_sigalrm(&ps3.cui->timer);
++              break;
+       case SIGWINCH:
+               if (ps3.cui)
+                       cui_resize(ps3.cui);
+@@ -491,8 +627,9 @@ int main(int argc, char *argv[])
+       pb_log("--- pb-cui ---\n");
+       sa.sa_handler = sig_handler;
+-      result = sigaction(SIGINT, &sa, NULL);
++      result = sigaction(SIGALRM, &sa, NULL);
+       result += sigaction(SIGHUP, &sa, NULL);
++      result += sigaction(SIGINT, &sa, NULL);
+       result += sigaction(SIGTERM, &sa, NULL);
+       result += sigaction(SIGWINCH, &sa, NULL);
+@@ -501,7 +638,10 @@ int main(int argc, char *argv[])
+               return EXIT_FAILURE;
+       }
+-      ps3.dirty_values = ps3_flash_get_values(&ps3.values);
++      ps3.values = ps3_flash_defaults;
++
++      if (opts.reset_defaults != opt_yes)
++              ps3.dirty_values = ps3_flash_get_values(&ps3.values);
+       result = ps3_get_video_mode(&mode);
+@@ -515,7 +655,7 @@ int main(int argc, char *argv[])
+       if (!result && (ps3.values.video_mode != (uint16_t)mode))
+               ps3_set_video_mode(ps3.values.video_mode);
+-      ps3.cui = cui_init(&ps3, ps3_kexec_cb);
++      ps3.cui = cui_init(&ps3, ps3_kexec_cb, ps3_sixaxis_map);
+       if (!ps3.cui)
+               return EXIT_FAILURE;
+@@ -523,6 +663,14 @@ int main(int argc, char *argv[])
+       ps3.mm = ps3_mm_init(&ps3);
+       ps3.svm = ps3_svm_init(&ps3);
++      if (opts.use_timeout != opt_yes
++              || ps3.values.timeout == ps3_timeout_forever)
++              ui_timer_disable(&ps3.cui->timer);
++      else {
++              ps3.cui->timer.update_display = ps3_timer_update;
++              ui_timer_init(&ps3.cui->timer, ps3.values.timeout);
++      }
++
+       cui_result = cui_run(ps3.cui, ps3.mm, ps3.values.default_item);
+       pmenu_delete(ps3.mm);