[FREELDR] Several changes regarding chainloading and Linux boot.
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Sun, 29 Sep 2019 17:20:15 +0000 (19:20 +0200)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Mon, 7 Oct 2019 00:54:07 +0000 (02:54 +0200)
- Introduce "Relocator16Boot()". So far its aim is just to correctly set
  the CPU state (segments, registers, flags) to what is expected by a
  given boot image before running it.
  This function can be seen as the embryonic state of a future boot relocator
  (see e.g. GRUB or SYSLINUX) that would also relocate the boot image at
  the correct places. (Such feature is needed when boot images have to
  be loaded in memory areas that cover where the boot loader is in memory.)

- Implement ChainLoadBiosBootSectorCode() around it.

- Replace BootOldLinuxKernel() and BootNewLinuxKernel() by a new
  BootLinuxKernel() function (in assembly) that relocates the kernel
  to a given position and then boot it, using Relocator16Boot().
  Ideally the relocation should be done by a future boot relocator...

Implementation notes for Relocator16Boot():
===========================================

For setting the CPU state the function is based on a similar code as the
Int386() helper, namely it takes a pointer to REGS structure and pass
this information through the 32->16 bits call before setting the CPU state
in accordance.
New stack segment/pointer and code segment/pointer are also specified.
For passing these values through the 32->16 bits call the 16-bit BSS
memory offsets "BSS_CallbackReturn" and "BSS_RealModeEntry" (respectively)
are reused.

12 files changed:
boot/freeldr/freeldr/arch/i386/entry.S
boot/freeldr/freeldr/arch/i386/i386rtl.c
boot/freeldr/freeldr/arch/i386/linux.S
boot/freeldr/freeldr/arch/powerpc/mach.c
boot/freeldr/freeldr/arch/realmode/helpers.inc
boot/freeldr/freeldr/arch/realmode/i386.S
boot/freeldr/freeldr/arch/realmode/int386.inc
boot/freeldr/freeldr/arch/realmode/linux.inc [deleted file]
boot/freeldr/freeldr/include/arch/pc/pcbios.h
boot/freeldr/freeldr/include/arch/pc/x86common.h
boot/freeldr/freeldr/include/linux.h
boot/freeldr/freeldr/linuxboot.c

index 4c95e64..9e7090f 100644 (file)
@@ -106,50 +106,58 @@ _Reboot:
     /* Stop the floppy drive motor */
     call _DiskStopFloppyMotor
 
-    /* Set the function ID */
+    /* Set the function ID and switch to real mode (we don't return) */
     mov bx, FNID_Reboot
-
-    /* Switch to real mode (we don't return) */
     jmp SwitchToReal
 
 
 /*
- * VOID __cdecl ChainLoadBiosBootSectorCode(
- *     IN UCHAR BootDrive OPTIONAL,
- *     IN ULONG BootPartition OPTIONAL);
+ * VOID __cdecl Relocator16Boot(
+ *     IN REGS*  In,
+ *     IN USHORT StackSegment,
+ *     IN USHORT StackPointer,
+ *     IN USHORT CodeSegment,
+ *     IN USHORT CodePointer);
+ *
+ * RETURNS: Nothing.
  *
- * RETURNS: Nothing
+ * NOTE: The implementation of this function is similar to that of Int386(),
+ * with the proviso that no attempt is done to save the original values of
+ * the registers since we will not need them anyway, as we do not return back
+ * to the caller but instead place the machine in a permanent new CPU state.
  */
-PUBLIC _ChainLoadBiosBootSectorCode
-_ChainLoadBiosBootSectorCode:
-    /* Set the boot drive */
-    mov dl, [esp + 4]
-    test dl, dl
-    jnz set_part
-    mov dl, byte ptr ds:[_FrldrBootDrive]
-
-    /* Set the boot partition */
-set_part:
-    mov eax, [esp + 8]
-    test eax, eax
-    jnz continue
-    mov eax, dword ptr ds:[_FrldrBootPartition]
-continue:
-    /* Store the 1-byte truncated partition number in DH */
-    mov dh, al
+PUBLIC _Relocator16Boot
+_Relocator16Boot:
+
+    /* Copy input registers */
+    mov esi, dword ptr [esp + 4]
+    mov edi, BSS_RegisterSet
+    mov ecx, REGS_SIZE / 4
+    rep movsd
+
+    /* Set the stack segment/offset */
+    // Since BSS_CallbackReturn contains a ULONG, store in its high word
+    // the stack segment and in its low word the stack offset.
+    mov ax, word ptr [esp + 8]
+    shl eax, 16
+    mov ax, word ptr [esp + 12]
+    mov dword ptr ds:[BSS_CallbackReturn], eax
 
     /*
-     * Don't stop the floppy drive motor when we are just booting a bootsector,
-     * a drive, or a partition. If we were to stop the floppy motor, the BIOS
-     * wouldn't be informed and if the next read is to a floppy then the BIOS
-     * will still think the motor is on and this will result in a read error.
+     * Set the code segment/offset (Copy entry point)
+     * NOTE: We permanently *ERASE* the contents of ds:[BSS_RealModeEntry]
+     * but it is not a problem since we are going to place the machine in
+     * a permanent new CPU state.
      */
-    // call _DiskStopFloppyMotor
-
-    /* Set the function ID */
-    mov bx, FNID_ChainLoadBiosBootSectorCode
+    // Since BSS_RealModeEntry contains a ULONG, store in its high word
+    // the code segment and in its low word the code offset.
+    mov ax, word ptr [esp + 16]
+    shl eax, 16
+    mov ax, word ptr [esp + 20]
+    mov dword ptr ds:[BSS_RealModeEntry], eax
 
-    /* Switch to real mode (we don't return) */
+    /* Set the function ID and switch to real mode (we don't return) */
+    mov bx, FNID_Relocator16Boot
     jmp SwitchToReal
 
 
index 90da33d..e4ef701 100644 (file)
@@ -88,3 +88,30 @@ void sound(int freq)
     WRITE_PORT_UCHAR((PUCHAR)0x42, scale >> 8);
     WRITE_PORT_UCHAR((PUCHAR)0x61, READ_PORT_UCHAR((PUCHAR)0x61) | 3);
 }
+
+VOID __cdecl ChainLoadBiosBootSectorCode(
+    IN UCHAR BootDrive OPTIONAL,
+    IN ULONG BootPartition OPTIONAL)
+{
+    REGS Regs;
+
+    RtlZeroMemory(&Regs, sizeof(Regs));
+
+    /* Set the boot drive and the boot partition */
+    Regs.b.dl = (UCHAR)(BootDrive ? BootDrive : FrldrBootDrive);
+    Regs.b.dh = (UCHAR)(BootPartition ? BootPartition : FrldrBootPartition);
+
+    /*
+     * Don't stop the floppy drive motor when we are just booting a bootsector,
+     * a drive, or a partition. If we were to stop the floppy motor, the BIOS
+     * wouldn't be informed and if the next read is to a floppy then the BIOS
+     * will still think the motor is on and this will result in a read error.
+     */
+    // DiskStopFloppyMotor();
+
+    Relocator16Boot(&Regs,
+                    /* Stack segment:pointer */
+                    0x0000, 0x7C00,
+                    /* Code segment:pointer */
+                    0x0000, 0x7C00);
+}
index d2b187c..cc18346 100644 (file)
 
 #include <asm.inc>
 #include <arch/pc/x86common.h>
+#include <arch/pc/pcbios.h>
 
 EXTERN _DiskStopFloppyMotor:PROC
-EXTERN i386CallRealMode:PROC
+EXTERN _Relocator16Boot:PROC
+EXTERN _FrldrBootDrive:BYTE
+EXTERN _FrldrBootPartition:DWORD
 
 .code32
 
+Regs:
+    .space REGS_SIZE
+
 /*
- *  VOID BootOldLinuxKernel(ULONG KernelSize);
+ * VOID __cdecl BootLinuxKernel(
+ *     IN ULONG KernelSize,
+ *     IN PVOID KernelCurrentLoadAddress,
+ *     IN PVOID KernelTargetLoadAddress,
+ *     IN UCHAR DriveNumber,
+ *     IN ULONG PartitionNumber);
  */
-PUBLIC _BootOldLinuxKernel
-_BootOldLinuxKernel:
-
-    /* First we have to copy the kernel down from 0x100000 to 0x10000 */
-    /* The reason we can overwrite low memory is because this code */
-    /* executes between 0000:8000 and 0000:FFFF. That leaves space for */
-    /* 32k of code before we start interfering with Linux kernel address space. */
-
-    /* Get KernelSize in ECX and move the kernel down */
-    mov ecx, [esp + 4]
-    mov esi, HEX(100000)
-    mov edi, HEX(10000)
-    rep movsb
+PUBLIC _BootLinuxKernel
+_BootLinuxKernel:
 
-    /* Fall through */
-
-PUBLIC _BootNewLinuxKernel
-_BootNewLinuxKernel:
     /* Stop the floppy drive motor */
     call _DiskStopFloppyMotor
 
-    mov bx, FNID_BootLinuxKernel
-    call i386CallRealMode
+    /* Set all segment registers to 0x9000 */
+    mov ax, HEX(9000)
+    mov word ptr [Regs + REGS_DS], ax
+    mov word ptr [Regs + REGS_ES], ax
+    mov word ptr [Regs + REGS_FS], ax
+    mov word ptr [Regs + REGS_GS], ax
+
+    /* Set the boot drive */
+    xor edx, edx
+    mov dl, byte ptr [esp + 16]
+    test dl, dl
+    jnz set_part
+    mov dl, byte ptr ds:[_FrldrBootDrive]
+
+    /* Set the boot partition */
+set_part:
+    mov eax, dword ptr [esp + 20]
+    test eax, eax
+    jnz continue
+    mov eax, dword ptr ds:[_FrldrBootPartition]
+continue:
+    /* Store the 1-byte truncated partition number in DH */
+    mov dh, al
+
+    mov dword ptr [Regs + REGS_EDX], edx
+
+    /*
+     * Relocate the kernel image to its final destination (can be as low as 0x10000).
+     * The reason we can overwrite low memory is because this code executes
+     * between 0000:8000 and 0000:FFFF. That leaves space for 32k of code
+     * before we start interfering with Linux kernel address space.
+     */
+
+    /* Get KernelSize in ECX */
+    mov ecx, dword ptr [esp + 4]
+    test ecx, ecx   // If size is zero, do not perform relocations
+    jz after_reloc
+
+    /* Load the source and target addresses */
+    mov esi, dword ptr [esp +  8] // HEX(100000) // LINUX_KERNEL_LOAD_ADDRESS
+    mov edi, dword ptr [esp + 12] // HEX(10000)
+
+//
+// FIXME: Support relocating *upwards*, overlapping regions, aligned addresses,
+// etc... !! See memmove code.
+//
+    /* Check how we should perform relocation */
+    cmp edi, esi
+    je after_reloc  // target == source: do not perform relocations
+    ja reloc_up     // target  > source: relocate up
+//  jb reloc_down   // target  < source: relocate down (default)
+
+reloc_down:
+    /* Move the kernel down - Start with low addresses and increment them */
+    cld
+#if 0
+    rep movsb
+#else
+    mov edx, ecx            // Copy the total number of bytes in EDX
+    and edx, HEX(0FFFFFFFC) // Number of bytes we copy using DWORDs
+    xor edx, ecx            // Number of remaining bytes to copy after the DWORDs
+    shr ecx, 2      // Count number of DWORDs
+    rep movsd       // Move DWORDs
+    mov ecx, edx    // Count number of remaining bytes
+    rep movsb       // Move bytes
+#endif
+    jmp after_reloc
+
+reloc_up:
+    /* Move the kernel up - Start with high addresses and decrement them */
+    std
+    add esi, ecx
+    add edi, ecx
+    dec esi
+    dec edi
+    rep movsb
+    // jmp after_reloc
+
+after_reloc:
+
+    push HEX(0000) // CodePointer
+    push HEX(9020) // CodeSegment
+    push HEX(9000) // StackPointer
+    push HEX(9000) // StackSegment
+    mov eax, offset Regs
+    push eax
+    call _Relocator16Boot
 
-    /* We should never get here */
+    /* We must never get there */
     int 3
 
 END
index 840cbfa..e5a93d9 100644 (file)
@@ -533,11 +533,13 @@ void WRITE_PORT_UCHAR(PUCHAR Address, UCHAR Value) {
     SetPhysByte(((ULONG)Address)+0x80000000, Value);
 }
 
-void BootOldLinuxKernel( unsigned long size ) {
-    ofw_exit();
-}
-
-void BootNewLinuxKernel() {
+VOID __cdecl BootLinuxKernel(
+    IN ULONG KernelSize,
+    IN PVOID KernelCurrentLoadAddress,
+    IN PVOID KernelTargetLoadAddress,
+    IN UCHAR DriveNumber,
+    IN ULONG PartitionNumber)
+{
     ofw_exit();
 }
 
index 9fbe736..922cb8d 100644 (file)
@@ -128,7 +128,7 @@ Reboot:
     ljmp16 HEX(0F000), HEX(0FFF0)
 
 
-ChainLoadBiosBootSectorCode:
+Relocator16Boot:
     cli
 
     /* Disable A20 address line */
@@ -138,14 +138,54 @@ ChainLoadBiosBootSectorCode:
     mov ax, HEX(0003)
     int HEX(10)
 
-    /* Load segment registers */
-    xor ax, ax
+    /* Get current EFLAGS and mask CF, ZF and SF */
+    pushf
+    pop cx
+    and cx, not (EFLAGS_CF or EFLAGS_ZF or EFLAGS_SF)
+
+    /* Get flags CF, ZF and SF from the REGS structure */
+    mov ax, word ptr cs:[BSS_RegisterSet + REGS_EFLAGS]
+    and ax, (EFLAGS_CF or EFLAGS_ZF or EFLAGS_SF)
+
+    /* Combine flags and set them */
+    or ax, cx
+    push ax
+    popf
+
+    /* Setup the segment registers */
+    mov ax, word ptr cs:[BSS_RegisterSet + REGS_DS]
     mov ds, ax
+    mov ax, word ptr cs:[BSS_RegisterSet + REGS_ES]
     mov es, ax
+    mov ax, word ptr cs:[BSS_RegisterSet + REGS_FS]
     mov fs, ax
+    mov ax, word ptr cs:[BSS_RegisterSet + REGS_GS]
     mov gs, ax
-    mov ss, ax
-    mov esp, HEX(7C00)
 
-    /* Jump to the bootsector code */
-    ljmp16 HEX(0000), HEX(7C00)
+    /* Patch the jump address (segment:offset) */
+    mov eax, dword ptr cs:[BSS_RealModeEntry]
+    mov dword ptr cs:[Relocator16Address], eax
+
+    /* Switch the stack (segment:offset) */
+    mov eax, dword ptr cs:[BSS_CallbackReturn]
+    shr eax, 16
+    mov ss, ax
+    mov eax, dword ptr cs:[BSS_CallbackReturn]
+    and eax, HEX(0FFFF)
+    mov esp, eax
+
+    /* Setup the registers */
+    mov eax, dword ptr cs:[BSS_RegisterSet + REGS_EAX]
+    mov ebx, dword ptr cs:[BSS_RegisterSet + REGS_EBX]
+    mov ecx, dword ptr cs:[BSS_RegisterSet + REGS_ECX]
+    mov edx, dword ptr cs:[BSS_RegisterSet + REGS_EDX]
+    mov esi, dword ptr cs:[BSS_RegisterSet + REGS_ESI]
+    mov edi, dword ptr cs:[BSS_RegisterSet + REGS_EDI]
+    // Don't setup ebp, we only use it as output! <-- FIXME!
+
+    /* Jump to the new CS:IP (e.g. jump to bootsector code...) */
+    .byte HEX(0EA) // ljmp16 segment:offset
+Relocator16Address:
+    .word HEX(7C00) // Default offset
+    .word HEX(0000) // Default segment
+    nop
index aaa261f..f76a38a 100644 (file)
@@ -8,7 +8,7 @@
 
 .code16
 
-/* fat helper code */
+/* FAT helper code */
 #include "fathelp.inc"
 
 .org 512
@@ -143,11 +143,10 @@ pm_entrypoint:
 CallbackTable:
     .word Int386
     .word Reboot
-    .word ChainLoadBiosBootSectorCode
+    .word Relocator16Boot
     .word PxeCallApi
     .word PnpBiosGetDeviceNodeCount
     .word PnpBiosGetDeviceNode
-    .word BootLinuxKernel
 
 
     /* 16-bit stack pointer */
@@ -194,16 +193,16 @@ gdtptr:
 
 /* Real-mode IDT pointer */
 rmode_idtptr:
-    .word HEX(3ff)      /* Limit */
+    .word HEX(3FF)      /* Limit */
     .long 0             /* Base Address */
 
 #include "int386.inc"
 #include "helpers.inc"
 #include "pxe.inc"
 #include "pnp.inc"
-#include "linux.inc"
 
 .org (FREELDR_PE_BASE - FREELDR_BASE - 1)
 .byte 0
 .endcode16
+
 END
index f8ab703..49a63eb 100644 (file)
@@ -1,10 +1,6 @@
 
 #include "../../include/arch/pc/pcbios.h"
 
-#define EFLAGS_CF HEX(01)
-#define EFLAGS_ZF HEX(40)
-#define EFLAGS_SF HEX(80)
-
 Int386:
     /* Save all registers + segment registers */
     push ds
diff --git a/boot/freeldr/freeldr/arch/realmode/linux.inc b/boot/freeldr/freeldr/arch/realmode/linux.inc
deleted file mode 100644 (file)
index 8b0312f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-
-BootLinuxKernel:
-    // dl must be set to the boot drive
-
-    /* Load segment registers */
-    cli
-    mov bx, HEX(9000)
-    mov ds, bx
-    mov es, bx
-    mov fs, bx
-    mov gs, bx
-    mov ss, bx
-    mov sp, HEX(9000)
-
-    ljmp16  HEX(9020), HEX(0000)
index 82fbe4e..804932d 100644 (file)
@@ -1,6 +1,12 @@
 #ifndef _PCBIOS_H_
 #define _PCBIOS_H_
 
+#ifdef __ASM__
+#define EFLAGS_CF HEX(01)
+#define EFLAGS_ZF HEX(40)
+#define EFLAGS_SF HEX(80)
+#endif
+
 #ifndef __ASM__
 
 #define MAX_BIOS_DESCRIPTORS 80
@@ -160,12 +166,19 @@ int __cdecl Int386(int ivec, REGS* in, REGS* out);
 // If CF is set then the call failed (usually)
 #define INT386_SUCCESS(regs)    ((regs.x.eflags & EFLAGS_CF) == 0)
 
-VOID __cdecl ChainLoadBiosBootSectorCode(   // Implemented in boot.S
+VOID __cdecl ChainLoadBiosBootSectorCode(
     IN UCHAR BootDrive OPTIONAL,
     IN ULONG BootPartition OPTIONAL);
 
-VOID __cdecl Reboot(VOID);                  // Implemented in boot.S
-VOID DetectHardware(VOID);                  // Implemented in hardware.c
+VOID __cdecl Relocator16Boot(
+    IN REGS*  In,
+    IN USHORT StackSegment,
+    IN USHORT StackPointer,
+    IN USHORT CodeSegment,
+    IN USHORT CodePointer);
+
+VOID __cdecl Reboot(VOID);
+VOID DetectHardware(VOID);
 
 #endif /* ! __ASM__ */
 
index 1b40ca3..e4c304b 100644 (file)
 /* Realmode function IDs */
 #define FNID_Int386 0
 #define FNID_Reboot 1
-#define FNID_ChainLoadBiosBootSectorCode 2
+#define FNID_Relocator16Boot 2
 #define FNID_PxeCallApi 3
-#define FNID_PnpBiosGetDeviceNodeCount 4
-#define FNID_PnpBiosGetDeviceNode 5
-#define FNID_BootLinuxKernel 6
+#define FNID_PnpBiosGetDeviceNodeCount  4
+#define FNID_PnpBiosGetDeviceNode       5
 
 /* Flag Masks */
 #define CR0_PE_SET    HEX(00000001)    /* OR this value with CR0 to enable pmode */
index d0b3510..c51f70d 100644 (file)
@@ -128,8 +128,13 @@ typedef struct
 } LINUX_SETUPSECTOR, *PLINUX_SETUPSECTOR;
 #include <poppack.h>
 
-VOID    __cdecl BootNewLinuxKernel(VOID);                // Implemented in linux.S
-VOID    __cdecl BootOldLinuxKernel(ULONG KernelSize);        // Implemented in linux.S
+// Implemented in linux.S
+VOID __cdecl BootLinuxKernel(
+    IN ULONG KernelSize,
+    IN PVOID KernelCurrentLoadAddress,
+    IN PVOID KernelTargetLoadAddress,
+    IN UCHAR DriveNumber,
+    IN ULONG PartitionNumber);
 
 ARC_STATUS
 LoadAndBootLinux(
index 48ebf53..fa9ad60 100644 (file)
@@ -154,6 +154,17 @@ LoadAndBootLinux(
         }
     }
 
+    /* If we haven't retrieved the BIOS drive and partition numbers above, do it now */
+    if (PartitionNumber == 0)
+    {
+        /* Retrieve the BIOS drive and partition numbers */
+        if (!DissectArcPath(BootPath, NULL, &DriveNumber, &PartitionNumber))
+        {
+            /* This is not a fatal failure, but just an inconvenience: display a message */
+            TRACE("DissectArcPath(%s) failed to retrieve BIOS drive and partition numbers.\n", BootPath);
+        }
+    }
+
     /* Get the kernel name */
     LinuxKernelName = GetArgumentValue(Argc, Argv, "Kernel");
     if (!LinuxKernelName || !*LinuxKernelName)
@@ -259,11 +270,13 @@ LoadAndBootLinux(
     UiUnInitialize("Booting Linux...");
     IniCleanup();
 
-    if (LinuxSetupSector->LoadFlags & LINUX_FLAG_LOAD_HIGH)
-        BootNewLinuxKernel();
-    else
-        BootOldLinuxKernel(LinuxKernelSize);
-
+    BootLinuxKernel(LinuxKernelSize, LinuxKernelLoadAddress,
+                    (LinuxSetupSector->LoadFlags & LINUX_FLAG_LOAD_HIGH)
+                        ? (PVOID)LINUX_KERNEL_LOAD_ADDRESS /* == 0x100000 */
+                        : (PVOID)0x10000,
+                    DriveNumber, PartitionNumber);
+    /* Must not return! */
+    return ESUCCESS;
 
 LinuxBootFailed:
 
@@ -407,11 +420,17 @@ static BOOLEAN LinuxReadKernel(ULONG LinuxKernelFile)
     RtlStringCbPrintfA(StatusText, sizeof(StatusText), "Loading %s", LinuxKernelName);
     UiDrawStatusText(StatusText);
 
-    /* Allocate memory for Linux kernel */
+    /* Try to allocate memory for the Linux kernel; if it fails, allocate somewhere else */
     LinuxKernelLoadAddress = MmAllocateMemoryAtAddress(LinuxKernelSize, (PVOID)LINUX_KERNEL_LOAD_ADDRESS, LoaderSystemCode);
     if (LinuxKernelLoadAddress != (PVOID)LINUX_KERNEL_LOAD_ADDRESS)
     {
-        return FALSE;
+        /* It's OK, let's allocate again somewhere else */
+        LinuxKernelLoadAddress = MmAllocateMemoryWithType(LinuxKernelSize, LoaderSystemCode);
+        if (LinuxKernelLoadAddress == NULL)
+        {
+            TRACE("Failed to allocate 0x%lx bytes for the kernel image.\n", LinuxKernelSize);
+            return FALSE;
+        }
     }
 
     LoadAddress = LinuxKernelLoadAddress;