Index: /trunk/src/VBox/Devices/PC/BIOS/notes.txt
===================================================================
--- /trunk/src/VBox/Devices/PC/BIOS/notes.txt	(revision 60609)
+++ /trunk/src/VBox/Devices/PC/BIOS/notes.txt	(revision 60610)
@@ -22,5 +22,6 @@
 
 - DOS 4.01 (both IBM and Microsoft) calls INT 13h to read from disk with less
-  than 100 bytes of stack space early in the boot sequence.
+  than 100 bytes of stack space early in the boot sequence. This tends to be
+  a problem especially for the SATA and SCSI code paths.
 
 - Very few guests use the 32-bit PCI BIOS interface. One is OS/2 (but falls
@@ -80,4 +81,9 @@
   executed on 386+ processors.
 
+- IBM and Microsoft OS/2 1.0 use CMOS shutdown status 9 to get back from
+  protected mode without having called INT 15h/87h at all. That makes the
+  status 9 handling a public interface (just like codes 5 and 0Ah) which
+  has to be compatible with other BIOS implementations.
+
 - Windows NT 3.5 and 3.51 with MPS HAL requires that INT 15h/E820h return the
   I/O APIC range as reserved, or not return any ranges at all just below 4GB.
@@ -86,4 +92,27 @@
 
 
+
+ 286 BIOS
+ --------
+
+ For testing purposes, it's quite useful to have a BIOS that can run in a
+classic PC/AT environment with a 286 CPU. This forces various changes, not
+always obvious:
+
+ - C code can be easily compiled to produce 286-compatible object code
+
+ - 32-bit BIOS services such as APM or PCI BIOS are irrelevant
+
+ - PCI cannot be supported because it requires 32-bit port I/O
+
+ - AHCI cannot be supported because it requires 32-bit port I/O and PCI
+
+ - Switching to protected mode must be done using LMSW instead of CR0
+
+ - Switching back to real mode must reset the CPU (currently triple fault)
+   and regain control by setting up the CMOS shutdown status byte
+
+
+ 
  Notes on BIOS implementation
  ----------------------------
Index: /trunk/src/VBox/Devices/PC/BIOS/orgs.asm
===================================================================
--- /trunk/src/VBox/Devices/PC/BIOS/orgs.asm	(revision 60609)
+++ /trunk/src/VBox/Devices/PC/BIOS/orgs.asm	(revision 60610)
@@ -141,4 +141,7 @@
 extrn		_ahci_init:near
 endif
+if VBOX_BIOS_CPU ge 80286
+extrn		_int15_blkmove:near
+endif
 
 
@@ -1666,4 +1669,25 @@
 		BIOSORG_CHECK	0F859h	; fixed wrt preceding code
 int15_handler:
+
+if VBOX_BIOS_CPU ge 80286
+		cmp	ah, 87h
+		jne	not_blkmove
+		
+		;; INT 15h/87h has semi-public interface because software
+		;; may use CMOS shutdown status code 9 for its own purposes.
+		;; The stack layout has to match.
+		pusha
+		push	es
+		push	ds
+		C_SETUP
+		call	_int15_blkmove
+		pop	ds
+		pop	es
+		popa
+		iret
+not_blkmove:
+
+endif
+
 		pushf
 		push	ds
Index: /trunk/src/VBox/Devices/PC/BIOS/system.c
===================================================================
--- /trunk/src/VBox/Devices/PC/BIOS/system.c	(revision 60609)
+++ /trunk/src/VBox/Devices/PC/BIOS/system.c	(revision 60610)
@@ -65,5 +65,11 @@
 #pragma aux read_ss = "mov ax, ss" modify exact [ax] nomemory;
 
-void pm_stack_save(uint16_t cx, uint16_t es, uint16_t si);
+#if VBOX_BIOS_CPU >= 80386
+
+/* The 386+ code uses CR0 to switch to/from protected mode.
+ * Quite straightforward.
+ */
+
+void pm_stack_save(uint16_t cx, uint16_t es, uint16_t si, uint16_t frame);
 #pragma aux pm_stack_save =     \
     ".386"                      \
@@ -74,5 +80,5 @@
     "mov    ds:[467h], sp"      \
     "mov    ds:[469h], ss"      \
-    parm [cx] [es] [si] modify nomemory;
+    parm [cx] [es] [si] [ax] modify nomemory;
 
 /* Uses position independent code... because it was too hard to figure
@@ -103,12 +109,4 @@
     modify nomemory;
 
-void pm_copy(void);
-#pragma aux pm_copy =               \
-    "xor    si, si"                 \
-    "xor    di, di"                 \
-    "cld"                           \
-    "rep    movsw"                  \
-    modify nomemory;
-
 /* Restore segment limits to real mode compatible values and
  * return to real mode.
@@ -146,4 +144,79 @@
     "pop    eax"                \
     "pop    ds"                 \
+    modify nomemory;
+
+#elif VBOX_BIOS_CPU >= 80286
+
+/* The 286 code uses LMSW to switch to protected mode but it has to reset
+ * the CPU to get back to real mode. Ugly! See return_blkmove in orgs.asm
+ * for the other matching half.
+ */
+void pm_stack_save(uint16_t cx, uint16_t es, uint16_t si, uint16_t frame);
+#pragma aux pm_stack_save =     \
+    "xor    ax, ax"             \
+    "mov    ds, ax"             \
+    "mov    ds:[467h], bx"      \
+    "mov    ds:[469h], ss"      \
+    parm [cx] [es] [si] [bx] modify nomemory;
+
+/* Uses position independent code... because it was too hard to figure
+ * out how to code the far call in inline assembler.
+ * NB: Trashes MSW bits but the CPU will be reset anyway.
+ */
+void pm_enter(void);
+#pragma aux pm_enter =              \
+    ".286p"                         \
+    "call   pentry"                 \
+    "pentry:"                       \
+    "pop    di"                     \
+    "add    di, 18h"                \
+    "push   20h"                    \
+    "push   di"                     \
+    "lgdt   fword ptr es:[si+8]"    \
+    "lidt   fword ptr cs:pmode_IDT" \
+    "or     al, 1"                  \
+    "lmsw   ax"                     \
+    "retf"                          \
+    "pm_pm:"                        \
+    "mov    ax, 28h"                \
+    "mov    ss, ax"                 \
+    "mov    ax, 10h"                \
+    "mov    ds, ax"                 \
+    "mov    ax, 18h"                \
+    "mov    es, ax"                 \
+    modify nomemory;
+
+/* Set up shutdown status and reset the CPU. The POST code
+ * will regain control. Port 80h is written with status.
+ * Code 9 is written to CMOS shutdown status byte (0Fh).
+ * CPU is triple faulted.                                                    .
+ */
+void pm_exit(void);
+#pragma aux pm_exit =               \
+    "xor    ax, ax"                 \
+    "out    80h, al"                \
+    "mov    al, 0Fh"                \
+    "out    70h, al"                \
+    "mov    al, 09h"                \
+    "out    71h, al"                \
+    ".286p"                         \
+    "lidt   fword ptr cs:pmode_IDT" \
+    "int    3"                      \
+    modify nomemory;
+
+/* Dummy. Actually done in return_blkmove. */
+void pm_stack_restore(void);
+#pragma aux pm_stack_restore =  \
+    "rm_return:"                \
+    modify nomemory;
+
+#endif
+
+void pm_copy(void);
+#pragma aux pm_copy =               \
+    "xor    si, si"                 \
+    "xor    di, di"                 \
+    "cld"                           \
+    "rep    movsw"                  \
     modify nomemory;
 
@@ -283,8 +356,4 @@
 void BIOSCALL int15_function(sys_regs_t r)
 {
-    bx_bool     prev_a20_enable;
-    uint16_t    base15_00;
-    uint8_t     base23_16;
-    uint16_t    ss;
     uint16_t    bRegister;
     uint8_t     irqDisable;
@@ -392,80 +461,4 @@
         }
 
-    case 0x87:
-#if BX_CPU < 3
-        SET_AH(UNSUPPORTED_FUNCTION);
-        SET_CF();
-#endif
-        // +++ should probably have descriptor checks
-        // +++ should have exception handlers
-
-        // turn off interrupts
-        int_disable();    //@todo: aren't they disabled already?
-
-        prev_a20_enable = set_enable_a20(1); // enable A20 line
-
-        // 128K max of transfer on 386+ ???
-        // source == destination ???
-
-        // ES:SI points to descriptor table
-        // offset   use     initially  comments
-        // ==============================================
-        // 00..07   Unused  zeros      Null descriptor
-        // 08..0f   GDT     zeros      filled in by BIOS
-        // 10..17   source  ssssssss   source of data
-        // 18..1f   dest    dddddddd   destination of data
-        // 20..27   CS      zeros      filled in by BIOS
-        // 28..2f   SS      zeros      filled in by BIOS
-
-        //es:si
-        //eeee0
-        //0ssss
-        //-----
-
-        // check for access rights of source & dest here
-
-        // Initialize GDT descriptor
-        base15_00 = (ES << 4) + SI;
-        base23_16 = ES >> 12;
-        if (base15_00 < (ES<<4))
-            base23_16++;
-        write_word(ES, SI+0x08+0, 47);       // limit 15:00 = 6 * 8bytes/descriptor
-        write_word(ES, SI+0x08+2, base15_00);// base 15:00
-        write_byte(ES, SI+0x08+4, base23_16);// base 23:16
-        write_byte(ES, SI+0x08+5, 0x93);     // access
-        write_word(ES, SI+0x08+6, 0x0000);   // base 31:24/reserved/limit 19:16
-
-        // Initialize CS descriptor
-        write_word(ES, SI+0x20+0, 0xffff);// limit 15:00 = normal 64K limit
-        write_word(ES, SI+0x20+2, 0x0000);// base 15:00
-        write_byte(ES, SI+0x20+4, 0x000f);// base 23:16
-        write_byte(ES, SI+0x20+5, 0x9b);  // access
-        write_word(ES, SI+0x20+6, 0x0000);// base 31:24/reserved/limit 19:16
-
-        // Initialize SS descriptor
-        ss = read_ss();
-        base15_00 = ss << 4;
-        base23_16 = ss >> 12;
-        write_word(ES, SI+0x28+0, 0xffff);   // limit 15:00 = normal 64K limit
-        write_word(ES, SI+0x28+2, base15_00);// base 15:00
-        write_byte(ES, SI+0x28+4, base23_16);// base 23:16
-        write_byte(ES, SI+0x28+5, 0x93);     // access
-        write_word(ES, SI+0x28+6, 0x0000);   // base 31:24/reserved/limit 19:16
-
-        pm_stack_save(CX, ES, SI);
-        pm_enter();
-        pm_copy();
-        pm_exit();
-        pm_stack_restore();
-
-        set_enable_a20(prev_a20_enable);
-
-        // turn interrupts back on
-        int_enable();
-
-        SET_AH(0);
-        CLEAR_CF();
-        break;
-
     case 0x88:
         // Get the amount of extended memory (above 1M)
@@ -830,2 +823,87 @@
     }
 }
+
+#if VBOX_BIOS_CPU >= 80286
+
+#undef  FLAGS
+#define FLAGS   r.ra.flags.u.r16.flags
+
+/* Function 0x87 handled separately due to specific stack layout requirements. */
+void BIOSCALL int15_blkmove(disk_regs_t r)
+{
+    bx_bool     prev_a20_enable;
+    uint16_t    base15_00;
+    uint8_t     base23_16;
+    uint16_t    ss;
+
+    // +++ should probably have descriptor checks
+    // +++ should have exception handlers
+
+    // turn off interrupts
+    int_disable();    //@todo: aren't they disabled already?
+
+    prev_a20_enable = set_enable_a20(1); // enable A20 line
+
+    // 128K max of transfer on 386+ ???
+    // source == destination ???
+
+    // ES:SI points to descriptor table
+    // offset   use     initially  comments
+    // ==============================================
+    // 00..07   Unused  zeros      Null descriptor
+    // 08..0f   GDT     zeros      filled in by BIOS
+    // 10..17   source  ssssssss   source of data
+    // 18..1f   dest    dddddddd   destination of data
+    // 20..27   CS      zeros      filled in by BIOS
+    // 28..2f   SS      zeros      filled in by BIOS
+
+    //es:si
+    //eeee0
+    //0ssss
+    //-----
+
+    // check for access rights of source & dest here
+
+    // Initialize GDT descriptor
+    base15_00 = (ES << 4) + SI;
+    base23_16 = ES >> 12;
+    if (base15_00 < (ES<<4))
+        base23_16++;
+    write_word(ES, SI+0x08+0, 47);       // limit 15:00 = 6 * 8bytes/descriptor
+    write_word(ES, SI+0x08+2, base15_00);// base 15:00
+    write_byte(ES, SI+0x08+4, base23_16);// base 23:16
+    write_byte(ES, SI+0x08+5, 0x93);     // access
+    write_word(ES, SI+0x08+6, 0x0000);   // base 31:24/reserved/limit 19:16
+
+    // Initialize CS descriptor
+    write_word(ES, SI+0x20+0, 0xffff);// limit 15:00 = normal 64K limit
+    write_word(ES, SI+0x20+2, 0x0000);// base 15:00
+    write_byte(ES, SI+0x20+4, 0x000f);// base 23:16
+    write_byte(ES, SI+0x20+5, 0x9b);  // access
+    write_word(ES, SI+0x20+6, 0x0000);// base 31:24/reserved/limit 19:16
+
+    // Initialize SS descriptor
+    ss = read_ss();
+    base15_00 = ss << 4;
+    base23_16 = ss >> 12;
+    write_word(ES, SI+0x28+0, 0xffff);   // limit 15:00 = normal 64K limit
+    write_word(ES, SI+0x28+2, base15_00);// base 15:00
+    write_byte(ES, SI+0x28+4, base23_16);// base 23:16
+    write_byte(ES, SI+0x28+5, 0x93);     // access
+    write_word(ES, SI+0x28+6, 0x0000);   // base 31:24/reserved/limit 19:16
+
+    pm_stack_save(CX, ES, SI, (uint16_t)(void __near *)&r);
+    pm_enter();
+    pm_copy();
+    pm_exit();
+    pm_stack_restore();
+
+    set_enable_a20(prev_a20_enable);
+
+    // turn interrupts back on
+    int_enable();
+
+    SET_AH(0);
+    CLEAR_CF();
+}
+#endif
