[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 */
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]
PUBLIC _Relocator16Boot
_Relocator16Boot:
/* 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
/* 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
// 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 */
mov bx, FNID_ChainLoadBiosBootSectorCode
/* 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

View 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);
}

View file

@ -19,40 +19,121 @@
#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:
PUBLIC _BootLinuxKernel
_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 */
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
/* 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
END

View 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();
}

View 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
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, HEX(7C00)
/* Get current EFLAGS and mask CF, ZF and SF */
pushf
pop cx
and cx, not (EFLAGS_CF or EFLAGS_ZF or EFLAGS_SF)
/* Jump to the bootsector code */
ljmp16 HEX(0000), HEX(7C00)
/* 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
/* 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
/* 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

View 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

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_
#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__ */

View file

@ -54,11 +54,10 @@
/* 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 */

View 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(

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 */
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;