Forward port new boot menu features from bochs bios.

Support boot menu during last stage of post.
Improve description of option roms.
Support printf of "%.s" to force printing strings on stack.
Enhance memcpy so that it also works in real-mode.
diff --git a/Makefile b/Makefile
index cb1cdd0..3808086 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@
 # Source files
 SRC16=floppy.c disk.c system.c clock.c serial.c kbd.c mouse.c output.c \
       boot.c ata.c cdrom.c apm.c util.c pcibios.c pci.c
-SRC32=post.c output.c rombios32.c util.c ata.c kbd.c pci.c
+SRC32=post.c output.c rombios32.c util.c ata.c kbd.c pci.c boot.c post_menu.c
 TABLESRC=font.c cbt.c floppy_dbt.c
 
 cc-option = $(shell if test -z "`$(1) $(2) -S -o /dev/null -xc \
diff --git a/src/biosvar.h b/src/biosvar.h
index fe957e5..1c791dc 100644
--- a/src/biosvar.h
+++ b/src/biosvar.h
@@ -213,6 +213,7 @@
     struct ipl_entry_s table[8];
     u16 count;
     u16 sequence;
+    u16 bootfirst;
 };
 
 #define IPL_TYPE_FLOPPY      0x01
diff --git a/src/boot.c b/src/boot.c
index c1f8846..53577ec 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -26,17 +26,36 @@
     "", "Floppy","Hard Disk","CD-Rom", "Network"
 };
 
-static void
-print_boot_device(u16 type)
+void
+printf_bootdev(u16 bootdev)
 {
+    u16 type = GET_EBDA(ipl.table[bootdev].type);
+
     /* NIC appears as type 0x80 */
     if (type == IPL_TYPE_BEV)
         type = 0x4;
     if (type == 0 || type > 0x4)
         BX_PANIC("Bad drive type\n");
-    printf("Booting from %s...\n", drivetypes[type]);
+    printf("%s", drivetypes[type]);
 
-    // XXX - latest cvs has BEV description
+    /* print product string if BEV */
+    void *far_description = (void*)GET_EBDA(ipl.table[bootdev].description);
+    if (type == 4 && far_description != 0) {
+        char description[33];
+        /* first 32 bytes are significant */
+        memcpy(MAKE_FARPTR(GET_SEG(SS), &description), far_description, 32);
+        /* terminate string */
+        description[32] = 0;
+        printf(" [%.s]", description);
+    }
+}
+
+static void
+print_boot_device(u16 bootdev)
+{
+    printf("Booting from ");
+    printf_bootdev(bootdev);
+    printf("...\n");
 }
 
 //--------------------------------------------------------------------------
@@ -67,14 +86,20 @@
 
     SET_EBDA(ipl.sequence, seq_nr);
 
-    u16 bootseg;
-    u8 bootdrv = 0;
-    u16 bootdev, bootip;
-
-    bootdev = inb_cmos(CMOS_BIOS_BOOTFLAG2);
+    u16 bootdev = inb_cmos(CMOS_BIOS_BOOTFLAG2);
     bootdev |= ((inb_cmos(CMOS_BIOS_BOOTFLAG1) & 0xf0) << 4);
     bootdev >>= 4 * seq_nr;
     bootdev &= 0xf;
+
+    /* Read user selected device */
+    u16 bootfirst = GET_EBDA(ipl.bootfirst);
+    if (bootfirst != 0xFFFF) {
+        bootdev = bootfirst;
+        /* Reset boot sequence */
+        SET_EBDA(ipl.bootfirst, 0xFFFF);
+        SET_EBDA(ipl.sequence, 0xFFFF);
+    }
+
     if (bootdev == 0)
         BX_PANIC("No bootable device.\n");
 
@@ -85,12 +110,15 @@
         BX_INFO("Invalid boot device (0x%x)\n", bootdev);
         return;
     }
-    u16 type = GET_EBDA(ipl.table[bootdev].type);
 
     /* Do the loading, and set up vector as a far pointer to the boot
      * address, and bootdrv as the boot drive */
-    print_boot_device(type);
+    print_boot_device(bootdev);
 
+    u16 type = GET_EBDA(ipl.table[bootdev].type);
+
+    u16 bootseg, bootip;
+    u8 bootdrv = 0;
     struct bregs cr;
     switch(type) {
     case IPL_TYPE_FLOPPY: /* FDD */
diff --git a/src/output.c b/src/output.c
index 8983f97..3d53613 100644
--- a/src/output.c
+++ b/src/output.c
@@ -154,6 +154,14 @@
             val = va_arg(args, int);
             putc(action, val);
             break;
+        case '.':
+            // Hack to support "%.s" - meaning string on stack.
+            if (GET_VAR(CS, *(u8*)(n+1)) != 's')
+                break;
+            n++;
+            sarg = va_arg(args, const char *);
+            puts(action, sarg);
+            break;
         case 's':
             sarg = va_arg(args, const char *);
             puts_cs(action, sarg);
diff --git a/src/post.c b/src/post.c
index 5ba9a29..4734991 100644
--- a/src/post.c
+++ b/src/post.c
@@ -289,6 +289,7 @@
 
     ebda->ipl.count = ip - ebda->ipl.table;
     ebda->ipl.sequence = 0xffff;
+    ebda->ipl.bootfirst = 0xffff;
 }
 
 static void
@@ -340,7 +341,7 @@
 
         u16 desc = *(u16*)&rom[0x1a+0x10];
         if (desc)
-            ip->description = (FARPTR_TO_SEG(rom) << 16) | desc;
+            ip->description = (u32)MAKE_FARPTR(FARPTR_TO_SEG(rom), desc);
 
         ebda->ipl.count++;
     }
@@ -385,6 +386,8 @@
 
     rom_scan(0xc8000, 0xe0000);
 
+    interactive_bootmenu();
+
     // reset the memory (some boot loaders such as syslinux suppose
     // that the memory is set to zero)
     memset((void*)0x40000, 0, 0x40000); // XXX - shouldn't use globals
diff --git a/src/post_menu.c b/src/post_menu.c
new file mode 100644
index 0000000..91b18fe
--- /dev/null
+++ b/src/post_menu.c
@@ -0,0 +1,81 @@
+// Menu presented during final phase of "post".
+//
+// Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
+// Copyright (C) 2002  MandrakeSoft S.A.
+//
+// This file may be distributed under the terms of the GNU GPLv3 license.
+
+#include "biosvar.h" // GET_EBDA
+#include "util.h" // usleep
+
+static u8
+check_for_keystroke()
+{
+    struct bregs br;
+    memset(&br, 0, sizeof(br));
+    br.ah = 1;
+    call16_int(0x16, &br);
+    return !(br.flags & F_ZF);
+}
+
+static u8
+get_keystroke()
+{
+    struct bregs br;
+    memset(&br, 0, sizeof(br));
+    call16_int(0x16, &br);
+    return br.ah;
+}
+
+static void
+udelay_and_check_for_keystroke(u32 usec, int count)
+{
+    int i;
+    for (i = 1; i <= count; i++) {
+        usleep(usec);
+        if (check_for_keystroke())
+            break;
+    }
+}
+
+void
+interactive_bootmenu()
+{
+    while (check_for_keystroke())
+        get_keystroke();
+
+    printf("Press F12 for boot menu.\n\n");
+
+    udelay_and_check_for_keystroke(500000, 5);
+    if (! check_for_keystroke())
+        return;
+    u8 scan_code = get_keystroke();
+    if (scan_code != 0x58)
+        /* not F12 */
+        return;
+
+    while (check_for_keystroke())
+        get_keystroke();
+
+    printf("Select boot device:\n\n");
+
+    int count = GET_EBDA(ipl.count);
+    int i;
+    for (i = 0; i < count; i++) {
+        printf("%d. ", i+1);
+        printf_bootdev(i);
+        printf("\n");
+    }
+
+    for (;;) {
+        scan_code = get_keystroke();
+        if (scan_code == 0x01 || scan_code == 0x58)
+            /* ESC or F12 */
+            break;
+        if (scan_code <= count + 1) {
+            SET_EBDA(ipl.bootfirst, scan_code - 1);
+            break;
+        }
+    }
+    printf("\n");
+}
diff --git a/src/romlayout.S b/src/romlayout.S
index c5f48eb..baf68f3 100644
--- a/src/romlayout.S
+++ b/src/romlayout.S
@@ -411,6 +411,7 @@
         IRQ_TRAMPOLINE 10
         IRQ_TRAMPOLINE 13
         IRQ_TRAMPOLINE 15
+        IRQ_TRAMPOLINE 16
         IRQ_TRAMPOLINE 18
         IRQ_TRAMPOLINE 19
         IRQ_TRAMPOLINE 1c
diff --git a/src/util.c b/src/util.c
index 438bf75..845609b 100644
--- a/src/util.c
+++ b/src/util.c
@@ -31,15 +31,18 @@
 }
 
 void *
-memcpy(void *d1, const void *s1, size_t len)
+memcpy(void *far_d1, const void *far_s1, size_t len)
 {
-    u8 *d = d1;
-    const u8 *s = s1;
+    u8 *d = far_d1;
+    u8 *s = (u8*)far_s1;
 
-    while (len--)
-        *d++ = *s++;
+    while (len--) {
+        SET_FARPTR(*d, GET_FARPTR(*s));
+        d++;
+        s++;
+    }
 
-    return d1;
+    return far_d1;
 }
 
 void
diff --git a/src/util.h b/src/util.h
index 1566adf..dff1a11 100644
--- a/src/util.h
+++ b/src/util.h
@@ -154,4 +154,10 @@
 // rombios32.c
 void rombios32_init(void);
 
+// boot.c
+void printf_bootdev(u16 bootdev);
+
+// post_menu.c
+void interactive_bootmenu();
+
 #endif // util.h