[FREELDR] Several changes regarding chainloading and Linux boot.

- 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.
This commit is contained in:
Hermès Bélusca-Maïto 2019-09-29 19:20:15 +02:00
parent 912268762e
commit 21c51eed05
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0
12 changed files with 284 additions and 110 deletions

View file

@ -106,50 +106,58 @@ _Reboot:
/* Stop the floppy drive motor */ /* Stop the floppy drive motor */
call _DiskStopFloppyMotor call _DiskStopFloppyMotor
/* Set the function ID */ /* Set the function ID and switch to real mode (we don't return) */
mov bx, FNID_Reboot mov bx, FNID_Reboot
/* Switch to real mode (we don't return) */
jmp SwitchToReal jmp SwitchToReal
/* /*
* VOID __cdecl ChainLoadBiosBootSectorCode( * VOID __cdecl Relocator16Boot(
* IN UCHAR BootDrive OPTIONAL, * IN REGS* In,
* IN ULONG BootPartition OPTIONAL); * 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 PUBLIC _Relocator16Boot
_ChainLoadBiosBootSectorCode: _Relocator16Boot:
/* Set the boot drive */
mov dl, [esp + 4]
test dl, dl
jnz set_part
mov dl, byte ptr ds:[_FrldrBootDrive]
/* Set the boot partition */ /* Copy input registers */
set_part: mov esi, dword ptr [esp + 4]
mov eax, [esp + 8] mov edi, BSS_RegisterSet
test eax, eax mov ecx, REGS_SIZE / 4
jnz continue rep movsd
mov eax, dword ptr ds:[_FrldrBootPartition]
continue: /* Set the stack segment/offset */
/* Store the 1-byte truncated partition number in DH */ // Since BSS_CallbackReturn contains a ULONG, store in its high word
mov dh, al // 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, * Set the code segment/offset (Copy entry point)
* a drive, or a partition. If we were to stop the floppy motor, the BIOS * NOTE: We permanently *ERASE* the contents of ds:[BSS_RealModeEntry]
* wouldn't be informed and if the next read is to a floppy then the BIOS * but it is not a problem since we are going to place the machine in
* will still think the motor is on and this will result in a read error. * a permanent new CPU state.
*/ */
// call _DiskStopFloppyMotor // 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
/* Set the function ID */ /* Set the function ID and switch to real mode (we don't return) */
mov bx, FNID_ChainLoadBiosBootSectorCode mov bx, FNID_Relocator16Boot
/* Switch to real mode (we don't return) */
jmp SwitchToReal jmp SwitchToReal

View file

@ -88,3 +88,30 @@ void sound(int freq)
WRITE_PORT_UCHAR((PUCHAR)0x42, scale >> 8); WRITE_PORT_UCHAR((PUCHAR)0x42, scale >> 8);
WRITE_PORT_UCHAR((PUCHAR)0x61, READ_PORT_UCHAR((PUCHAR)0x61) | 3); 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);
}

View file

@ -19,40 +19,121 @@
#include <asm.inc> #include <asm.inc>
#include <arch/pc/x86common.h> #include <arch/pc/x86common.h>
#include <arch/pc/pcbios.h>
EXTERN _DiskStopFloppyMotor:PROC EXTERN _DiskStopFloppyMotor:PROC
EXTERN i386CallRealMode:PROC EXTERN _Relocator16Boot:PROC
EXTERN _FrldrBootDrive:BYTE
EXTERN _FrldrBootPartition:DWORD
.code32 .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 PUBLIC _BootLinuxKernel
_BootOldLinuxKernel: _BootLinuxKernel:
/* 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
/* Fall through */
PUBLIC _BootNewLinuxKernel
_BootNewLinuxKernel:
/* Stop the floppy drive motor */ /* Stop the floppy drive motor */
call _DiskStopFloppyMotor call _DiskStopFloppyMotor
mov bx, FNID_BootLinuxKernel /* Set all segment registers to 0x9000 */
call i386CallRealMode 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
/* We should never get here */ /* 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 must never get there */
int 3 int 3
END END

View file

@ -533,11 +533,13 @@ void WRITE_PORT_UCHAR(PUCHAR Address, UCHAR Value) {
SetPhysByte(((ULONG)Address)+0x80000000, Value); SetPhysByte(((ULONG)Address)+0x80000000, Value);
} }
void BootOldLinuxKernel( unsigned long size ) { VOID __cdecl BootLinuxKernel(
ofw_exit(); IN ULONG KernelSize,
} IN PVOID KernelCurrentLoadAddress,
IN PVOID KernelTargetLoadAddress,
void BootNewLinuxKernel() { IN UCHAR DriveNumber,
IN ULONG PartitionNumber)
{
ofw_exit(); ofw_exit();
} }

View file

@ -128,7 +128,7 @@ Reboot:
ljmp16 HEX(0F000), HEX(0FFF0) ljmp16 HEX(0F000), HEX(0FFF0)
ChainLoadBiosBootSectorCode: Relocator16Boot:
cli cli
/* Disable A20 address line */ /* Disable A20 address line */
@ -138,14 +138,54 @@ ChainLoadBiosBootSectorCode:
mov ax, HEX(0003) mov ax, HEX(0003)
int HEX(10) int HEX(10)
/* Load segment registers */ /* Get current EFLAGS and mask CF, ZF and SF */
xor ax, ax pushf
mov ds, ax pop cx
mov es, ax and cx, not (EFLAGS_CF or EFLAGS_ZF or EFLAGS_SF)
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, HEX(7C00)
/* Jump to the bootsector code */ /* Get flags CF, ZF and SF from the REGS structure */
ljmp16 HEX(0000), HEX(7C00) 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
/* 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

View file

@ -8,7 +8,7 @@
.code16 .code16
/* fat helper code */ /* FAT helper code */
#include "fathelp.inc" #include "fathelp.inc"
.org 512 .org 512
@ -143,11 +143,10 @@ pm_entrypoint:
CallbackTable: CallbackTable:
.word Int386 .word Int386
.word Reboot .word Reboot
.word ChainLoadBiosBootSectorCode .word Relocator16Boot
.word PxeCallApi .word PxeCallApi
.word PnpBiosGetDeviceNodeCount .word PnpBiosGetDeviceNodeCount
.word PnpBiosGetDeviceNode .word PnpBiosGetDeviceNode
.word BootLinuxKernel
/* 16-bit stack pointer */ /* 16-bit stack pointer */
@ -194,16 +193,16 @@ gdtptr:
/* Real-mode IDT pointer */ /* Real-mode IDT pointer */
rmode_idtptr: rmode_idtptr:
.word HEX(3ff) /* Limit */ .word HEX(3FF) /* Limit */
.long 0 /* Base Address */ .long 0 /* Base Address */
#include "int386.inc" #include "int386.inc"
#include "helpers.inc" #include "helpers.inc"
#include "pxe.inc" #include "pxe.inc"
#include "pnp.inc" #include "pnp.inc"
#include "linux.inc"
.org (FREELDR_PE_BASE - FREELDR_BASE - 1) .org (FREELDR_PE_BASE - FREELDR_BASE - 1)
.byte 0 .byte 0
.endcode16 .endcode16
END END

View file

@ -1,10 +1,6 @@
#include "../../include/arch/pc/pcbios.h" #include "../../include/arch/pc/pcbios.h"
#define EFLAGS_CF HEX(01)
#define EFLAGS_ZF HEX(40)
#define EFLAGS_SF HEX(80)
Int386: Int386:
/* Save all registers + segment registers */ /* Save all registers + segment registers */
push ds push ds

View file

@ -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)

View file

@ -1,6 +1,12 @@
#ifndef _PCBIOS_H_ #ifndef _PCBIOS_H_
#define _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__ #ifndef __ASM__
#define MAX_BIOS_DESCRIPTORS 80 #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) // If CF is set then the call failed (usually)
#define INT386_SUCCESS(regs) ((regs.x.eflags & EFLAGS_CF) == 0) #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 UCHAR BootDrive OPTIONAL,
IN ULONG BootPartition OPTIONAL); IN ULONG BootPartition OPTIONAL);
VOID __cdecl Reboot(VOID); // Implemented in boot.S VOID __cdecl Relocator16Boot(
VOID DetectHardware(VOID); // Implemented in hardware.c IN REGS* In,
IN USHORT StackSegment,
IN USHORT StackPointer,
IN USHORT CodeSegment,
IN USHORT CodePointer);
VOID __cdecl Reboot(VOID);
VOID DetectHardware(VOID);
#endif /* ! __ASM__ */ #endif /* ! __ASM__ */

View file

@ -54,11 +54,10 @@
/* Realmode function IDs */ /* Realmode function IDs */
#define FNID_Int386 0 #define FNID_Int386 0
#define FNID_Reboot 1 #define FNID_Reboot 1
#define FNID_ChainLoadBiosBootSectorCode 2 #define FNID_Relocator16Boot 2
#define FNID_PxeCallApi 3 #define FNID_PxeCallApi 3
#define FNID_PnpBiosGetDeviceNodeCount 4 #define FNID_PnpBiosGetDeviceNodeCount 4
#define FNID_PnpBiosGetDeviceNode 5 #define FNID_PnpBiosGetDeviceNode 5
#define FNID_BootLinuxKernel 6
/* Flag Masks */ /* Flag Masks */
#define CR0_PE_SET HEX(00000001) /* OR this value with CR0 to enable pmode */ #define CR0_PE_SET HEX(00000001) /* OR this value with CR0 to enable pmode */

View file

@ -128,8 +128,13 @@ typedef struct
} LINUX_SETUPSECTOR, *PLINUX_SETUPSECTOR; } LINUX_SETUPSECTOR, *PLINUX_SETUPSECTOR;
#include <poppack.h> #include <poppack.h>
VOID __cdecl BootNewLinuxKernel(VOID); // Implemented in linux.S // Implemented in linux.S
VOID __cdecl BootOldLinuxKernel(ULONG KernelSize); // Implemented in linux.S VOID __cdecl BootLinuxKernel(
IN ULONG KernelSize,
IN PVOID KernelCurrentLoadAddress,
IN PVOID KernelTargetLoadAddress,
IN UCHAR DriveNumber,
IN ULONG PartitionNumber);
ARC_STATUS ARC_STATUS
LoadAndBootLinux( LoadAndBootLinux(

View 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 */ /* Get the kernel name */
LinuxKernelName = GetArgumentValue(Argc, Argv, "Kernel"); LinuxKernelName = GetArgumentValue(Argc, Argv, "Kernel");
if (!LinuxKernelName || !*LinuxKernelName) if (!LinuxKernelName || !*LinuxKernelName)
@ -259,11 +270,13 @@ LoadAndBootLinux(
UiUnInitialize("Booting Linux..."); UiUnInitialize("Booting Linux...");
IniCleanup(); IniCleanup();
if (LinuxSetupSector->LoadFlags & LINUX_FLAG_LOAD_HIGH) BootLinuxKernel(LinuxKernelSize, LinuxKernelLoadAddress,
BootNewLinuxKernel(); (LinuxSetupSector->LoadFlags & LINUX_FLAG_LOAD_HIGH)
else ? (PVOID)LINUX_KERNEL_LOAD_ADDRESS /* == 0x100000 */
BootOldLinuxKernel(LinuxKernelSize); : (PVOID)0x10000,
DriveNumber, PartitionNumber);
/* Must not return! */
return ESUCCESS;
LinuxBootFailed: LinuxBootFailed:
@ -407,11 +420,17 @@ static BOOLEAN LinuxReadKernel(ULONG LinuxKernelFile)
RtlStringCbPrintfA(StatusText, sizeof(StatusText), "Loading %s", LinuxKernelName); RtlStringCbPrintfA(StatusText, sizeof(StatusText), "Loading %s", LinuxKernelName);
UiDrawStatusText(StatusText); 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); LinuxKernelLoadAddress = MmAllocateMemoryAtAddress(LinuxKernelSize, (PVOID)LINUX_KERNEL_LOAD_ADDRESS, LoaderSystemCode);
if (LinuxKernelLoadAddress != (PVOID)LINUX_KERNEL_LOAD_ADDRESS) 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; LoadAddress = LinuxKernelLoadAddress;