>From e6e69250a434e92962a8609dbb39dbb54f80911f Mon Sep 17 00:00:00 2001
From: Steve Goodrich <steve.goodrich@se-eng.com>
Date: Fri, 13 Jul 2012 07:27:48 -0600
Subject: [PATCH] Enable bootorder failover

Legacy MBRs on HDDs and USB MSCs allow booting only from device id 0x80.
This is OK for platforms that have only one HDD or USB device to boot from,
but causes problems if the end user wants to have more than one HDD or USB
MSC device in the boot order (and if those devices are present, but not
bootable).

This patch allows proper failover from one HDD/USB MSC device to the next.
Assume the platform has the following devices in its bootorder file:
  HDD1   (present, no MBR/not bootable)
  HDD2   (present but unreadable)
  Floppy (not present)
  USB1   (present, no MBR/not bootable)
  USB2   (present, bootable)
The current/previous bootorder handling would set HDD1 as drive 0x80, and
it would be the only HDD or USB upon which booting would be attempted.
Since it has no MBR, the system would not boot.

With the code in this patch, the system would behave like this:
* Assign HDD1 as device 0x80 and attempt to boot
* boot fails because the device is not bootable
* Rotate the boot order to be HDD2, Floppy, USB1, USB2, HDD1, making HDD2
  device 0x80
* Attempt to boot
* boot fails because the device is unreadable
* Reset the boot order to its original while we try (and fail) booting from
  the Floppy
* Rotate the boot order to be USB1, USB2, HDD1, HDD2, Floppy, making USB1
  device 0x80
* Attempt to boot
* boot fails because the device has no MBR
* Rotate the boot order to be USB2, HDD1, HDD2, Floppy, USB1, making USB2
  device 0x80
* Attempt to boot
* boot succeeds with the current device order (USB2=0x80, HDD1=0x81, HDD2=
  0x82, Floppy=0x00, USB1=0x83)
---------------------------------------------------------------------------

Signed-off-by: Steve Goodrich <steve.goodrich@se-eng.com>
---
 src/Kconfig   |    9 ++++++
 src/biosvar.h |   13 +++++++++
 src/block.c   |   81 +++++++++++++++++++++++++++++++++++++++++---------------
 src/boot.c    |   57 ++++++++++++++++++++++++++++++++++++---
 src/post.c    |    2 +-
 5 files changed, 134 insertions(+), 28 deletions(-)

diff --git a/src/Kconfig b/src/Kconfig
index 8932c9e..8aeed02 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -60,6 +60,15 @@ menu "General Features"
             Support controlling of the boot order via the fw_cfg/CBFS
             "bootorder" file.
 
+    config BOOTORDER_FAILOVER
+        depends on BOOTORDER
+        bool "Bootorder failover"
+        default y
+        help
+            Fail over to the next device in the order if the current device
+            fails to boot.  For HDD and USB MSC devices, sets each boot device
+            as drive 0x80 (in turn) until one of them boots.
+
     config COREBOOT_FLASH
         depends on COREBOOT
         bool "coreboot CBFS support"
diff --git a/src/biosvar.h b/src/biosvar.h
index f0a0fd2..9715a69 100644
--- a/src/biosvar.h
+++ b/src/biosvar.h
@@ -181,6 +181,19 @@ struct extended_bios_data_area_s {
     u8 other2[0xC4];
 
     // 0x121 - Begin custom storage.
+#if CONFIG_BOOTORDER_FAILOVER
+    // EBDA variables to support failing over to the next HDD or USB MSC 
+    // device
+
+    // total number of fdpts in the backup list
+    u8 num_backup_fdpt;
+
+    // current fdpt being used for boot
+    u8 starting_backup_fdpt;
+
+    // a list of one fdpt for each HDD or USB device found
+    struct fdpt_s backup_fdpt[CONFIG_MAX_EXTDRIVE];
+#endif
 } PACKED;
 
 // The initial size and location of EBDA
diff --git a/src/block.c b/src/block.c
index 7a62787..bebda51 100644
--- a/src/block.c
+++ b/src/block.c
@@ -17,6 +17,10 @@
 u8 FloppyCount VAR16VISIBLE;
 u8 CDCount;
 struct drive_s *IDMap[3][CONFIG_MAX_EXTDRIVE] VAR16VISIBLE;
+#if CONFIG_BOOTORDER_FAILOVER
+// list of drive_s pointers in the original order they were defined
+struct drive_s *HDDMap[CONFIG_MAX_EXTDRIVE];
+#endif
 u8 *bounce_buf_fl VAR16VISIBLE;
 struct dpte_s DefaultDPTE VARLOW;
 
@@ -171,22 +175,9 @@ setup_translation(struct drive_s *drive_g)
  * Drive mapping
  ****************************************************************/
 
-// Fill in Fixed Disk Parameter Table (located in ebda).
+// Fill in a single FDPT entry
 static void
-fill_fdpt(struct drive_s *drive_g, int hdid)
-{
-    if (hdid > 1)
-        return;
-
-    u16 nlc   = GET_GLOBAL(drive_g->lchs.cylinders);
-    u16 nlh   = GET_GLOBAL(drive_g->lchs.heads);
-    u16 nlspt = GET_GLOBAL(drive_g->lchs.spt);
-
-    u16 npc   = GET_GLOBAL(drive_g->pchs.cylinders);
-    u16 nph   = GET_GLOBAL(drive_g->pchs.heads);
-    u16 npspt = GET_GLOBAL(drive_g->pchs.spt);
-
-    struct fdpt_s *fdpt = &get_ebda_ptr()->fdpt[hdid];
+fill_single_fdpt(struct fdpt_s *fdpt, u16 nlc, u16 nlh, u16 nlspt, u16 npc, u16 nph, u16 npspt) {
     fdpt->precompensation = 0xffff;
     fdpt->drive_control_byte = 0xc0 | ((nph > 8) << 3);
     fdpt->landing_zone = npc;
@@ -207,13 +198,36 @@ fill_fdpt(struct drive_s *drive_g, int hdid)
         // Checksum structure.
         fdpt->checksum -= checksum(fdpt, sizeof(*fdpt));
     }
+}
+
+// Fill in Fixed Disk Parameter Table (located in ebda).
+static void
+fill_fdpt(struct drive_s *drive_g, int hdid)
+{
+    u16 nlc   = GET_GLOBAL(drive_g->lchs.cylinders);
+    u16 nlh   = GET_GLOBAL(drive_g->lchs.heads);
+    u16 nlspt = GET_GLOBAL(drive_g->lchs.spt);
+
+    u16 npc   = GET_GLOBAL(drive_g->pchs.cylinders);
+    u16 nph   = GET_GLOBAL(drive_g->pchs.heads);
+    u16 npspt = GET_GLOBAL(drive_g->pchs.spt);
+
+#if CONFIG_BOOTORDER_FAILOVER
+    // fill in the backup FDPT list
+    fill_single_fdpt(&get_ebda_ptr()->backup_fdpt[hdid], nlc, nlh, nlspt, npc, nph, npspt);
+    get_ebda_ptr()->num_backup_fdpt++;
+#endif
 
-    if (hdid == 0)
-        SET_IVT(0x41, SEGOFF(get_ebda_seg(), offsetof(
-                                 struct extended_bios_data_area_s, fdpt[0])));
-    else
-        SET_IVT(0x46, SEGOFF(get_ebda_seg(), offsetof(
-                                 struct extended_bios_data_area_s, fdpt[1])));
+    if (hdid <= 1) {
+        fill_single_fdpt(&get_ebda_ptr()->fdpt[hdid], nlc, nlh, nlspt, npc, nph, npspt);
+
+        if (hdid == 0)
+            SET_IVT(0x41, SEGOFF(get_ebda_seg(), offsetof(
+                                     struct extended_bios_data_area_s, fdpt[0])));
+        else
+            SET_IVT(0x46, SEGOFF(get_ebda_seg(), offsetof(
+                                     struct extended_bios_data_area_s, fdpt[1])));
+    }
 }
 
 // Find spot to add a drive
@@ -235,8 +249,12 @@ map_hd_drive(struct drive_s *drive_g)
     ASSERT32FLAT();
     struct bios_data_area_s *bda = MAKE_FLATPTR(SEG_BDA, 0);
     int hdid = bda->hdcount;
-    dprintf(3, "Mapping hd drive %p to %d\n", drive_g, hdid);
+    dprintf(3, "Mapping hd drive %p to %d: 0x%x\n", drive_g, hdid, EXTSTART_HD + bda->hdcount);
     add_drive(IDMap[EXTTYPE_HD], &bda->hdcount, drive_g);
+#if CONFIG_BOOTORDER_FAILOVER
+    // copy the original drive_s pointer to our own copy of the list
+    HDDMap[hdid] = IDMap[EXTTYPE_HD][hdid];
+#endif
 
     // Setup disk geometry translation.
     setup_translation(drive_g);
@@ -245,6 +263,25 @@ map_hd_drive(struct drive_s *drive_g)
     fill_fdpt(drive_g, hdid);
 }
 
+#if CONFIG_BOOTORDER_FAILOVER
+// Modify the IDMap list to start from the given offset
+// This function "rotates" the IDMap[EXTTYPE_HD] list so the desired drive
+// is at the correction position to be drive 0x80.
+void
+set_starting_hd_drive(int offset)
+{
+    ASSERT32FLAT();
+    int i;
+    struct bios_data_area_s *bda = MAKE_FLATPTR(SEG_BDA, 0);
+    int count = bda->hdcount;   // "count" is the number of drives in the list
+    
+    // copy the entry (pointer) from (i+offset) to i (wrap at "count" items)
+    for (i = 0; i < count; i++) {
+        IDMap[EXTTYPE_HD][i] = HDDMap[(i + offset) % count];
+    }
+}
+#endif
+
 // Map a cd
 void
 map_cd_drive(struct drive_s *drive_g)
diff --git a/src/boot.c b/src/boot.c
index 3ca7960..40fcc11 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -14,7 +14,9 @@
 #include "paravirt.h" // qemu_cfg_show_boot_menu
 #include "pci.h" // pci_bdf_to_*
 #include "usb.h" // struct usbdevice_s
-
+#if CONFIG_BOOTORDER_FAILOVER
+#include "biosvar.h" // struct extended_bios_data_area_s
+#endif
 
 /****************************************************************
  * Boot priority ordering
@@ -457,13 +459,19 @@ struct bev_s {
 };
 static struct bev_s BEV[20];
 static int BEVCount;
-static int HaveHDBoot, HaveFDBoot;
+static int HaveFDBoot;
+#if !CONFIG_BOOTORDER_FAILOVER
+static int HaveHDBoot;
+#endif
 
 static void
 add_bev(int type, u32 vector)
 {
+#if !CONFIG_BOOTORDER_FAILOVER
+    // for BOOTORDER_FAILOVER, allow more than one HDD/USB MSC in the bootorder
     if (type == IPL_TYPE_HARDDISK && HaveHDBoot++)
         return;
+#endif
     if (type == IPL_TYPE_FLOPPY && HaveFDBoot++)
         return;
     if (BEVCount >= ARRAY_SIZE(BEV))
@@ -502,7 +510,8 @@ boot_prep(void)
             break;
         case IPL_TYPE_HARDDISK:
             map_hd_drive(pos->drive);
-            add_bev(IPL_TYPE_HARDDISK, 0);
+            // Added "pos" so we can see the drive info later (user-friendly)
+            add_bev(IPL_TYPE_HARDDISK, (u32) pos);
             break;
         case IPL_TYPE_CDROM:
             map_cd_drive(pos->drive);
@@ -642,6 +651,25 @@ boot_fail(void)
     farcall16big(&br);
 }
 
+#if CONFIG_BOOTORDER_FAILOVER
+void set_starting_hd_drive(int offset);
+struct extended_bios_data_area_s *get_ebda_ptr();
+
+void
+set_hdd_order(int offset) {
+    struct extended_bios_data_area_s *pEbda = get_ebda_ptr();
+    
+    // set the starting HDD in the IDMap (effectively, set who is 0x80, 0x81, etc.)
+    set_starting_hd_drive(offset);
+    // copy backup_fdpt[offset] to fdpt[0]
+    memcpy(&pEbda->fdpt[0], &pEbda->backup_fdpt[offset], sizeof(struct fdpt_s));
+    if (pEbda->num_backup_fdpt > 1) {
+        // copy backup_fdpt[offset+1 % num] to fdpt[1]
+        memcpy(&pEbda->fdpt[1], &pEbda->backup_fdpt[(offset+1) % pEbda->num_backup_fdpt], sizeof(struct fdpt_s));
+    }
+}
+#endif
+
 // Determine next boot method and attempt a boot using it.
 static void
 do_boot(int seq_nr)
@@ -657,19 +685,38 @@ do_boot(int seq_nr)
     switch (ie->type) {
     case IPL_TYPE_FLOPPY:
         printf("Booting from Floppy...\n");
+#if CONFIG_BOOTORDER_FAILOVER
+        set_hdd_order(0); // use original boot order
+#endif
         boot_disk(0x00, CheckFloppySig);
         break;
     case IPL_TYPE_HARDDISK:
-        printf("Booting from Hard Disk...\n");
-        boot_disk(0x80, 1);
+        // Modified to show media we're booting from (USB MSC are not actually
+        // HDDs)
+        printf("Booting from %s\n", ((struct bootentry_s*) ie->vector)->description);
+#if CONFIG_BOOTORDER_FAILOVER
+        struct extended_bios_data_area_s *pEbda = get_ebda_ptr();
+        set_hdd_order(pEbda->starting_backup_fdpt);
+        pEbda->starting_backup_fdpt++;
+#endif
+        boot_disk(EXTSTART_HD, 1); // check signature
         break;
     case IPL_TYPE_CDROM:
+#if CONFIG_BOOTORDER_FAILOVER
+        set_hdd_order(0); // use original boot order
+#endif
         boot_cdrom((void*)ie->vector);
         break;
     case IPL_TYPE_CBFS:
+#if CONFIG_BOOTORDER_FAILOVER
+        set_hdd_order(0); // use original boot order
+#endif
         boot_cbfs((void*)ie->vector);
         break;
     case IPL_TYPE_BEV:
+#if CONFIG_BOOTORDER_FAILOVER
+        set_hdd_order(0); // use original boot order
+#endif
         boot_rom(ie->vector);
         break;
     }
diff --git a/src/post.c b/src/post.c
index 3101505..6e9c0ff 100644
--- a/src/post.c
+++ b/src/post.c
@@ -342,7 +342,7 @@ reloc_init(void)
 
     // Copy code and update relocs (init absolute, init relative, and runtime)
     dprintf(1, "Relocating low data from %p to %p (size %d)\n"
-            , datalow_start, final_datalow_start, datalow_end - datalow_start);
+            , datalow_start, final_datalow_start, (int) (datalow_end - datalow_start));
     updateRelocs(code32flat_start, _reloc_datalow_start, _reloc_datalow_end
                  , final_datalow_start - datalow_start);
     dprintf(1, "Relocating init from %p to %p (size %d)\n"
-- 
1.7.9

