Switching from 64-bit UEFI to protected mode

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
rdos
Member
Member
Posts: 3303
Joined: Wed Oct 01, 2008 1:55 pm

Switching from 64-bit UEFI to protected mode

Post by rdos »

I'm making some progress on my UEFI loaders (both 32 and 64 bit versions). The hardest one is the 64-bit version since it will need to switch back to protected mode without paging in order to load my kernel.

Another problem is the mixed-bitness involved. I cannot link 32-bit (or 16-bit) code into a 64-bit UEFI image, so I need to load it (at a fixed location) from disc.

Here is my current attempt for 64-bit mode:

Code: Select all

param_struc     STRUC

lfb_base        DD ?,?          ; address to LFB for printing
uefi_data       DD ?,?          ; address to UEFI data (yet to be specified) 

param_struc     ENDS


Code32 segment byte public use32 'code32'


    org 0110000h                ; put the code at a fixed position. 

;
; UEFI entrypoint
;

    db 0Ebh                        ; jmp init64
    db 38h + SIZE param_struc

param   param_struc <>

rom_gdt:
gdt0:
    dw 0
    dd 0
    dw 0
gdt8:                          ; selector for setting up IDT
    dw 10h*8-1
    dd 92000000h
    dw 0
gdt10:                        ; selector for setting up GDT
    dw 28h-1
    dd 92000000h + OFFSET rom_gdt
    dw 0
gdt18:                        ; flat code selector
    dw 0FFFFh
    dd 9A000000h
    dw 0CFh
gdt20:                        ; flat data selector
    dw 0FFFFh
    dd 92000000h
    dw 0CFh

gdt_ptr:                      ; used for loading GDT
    dw 28h-1
    dq OFFSET rom_gdt

prot_ptr:                     ; used for indirect call to 32-bit mode
    dd OFFSET prot_init
    dw 18h

init64:
    db 0FAh    ; cli
    db 0Fh      ; lgdt gdt_ptr
    db 01h
    db 15h
    dd 0FFFFFFE8h
;
    db 0FFh    ; call far prot_init
    db 1Dh
    dd 0FFFFFFECh

prot_init:
    mov eax,20h
    mov ds,ax
    mov es,ax
    mov fs,ax
    mov gs,ax
    mov ss,ax
    mov esp,120000h
;
; disable paging
;
    mov eax,cr0 
    and eax,7FFFFFFFh
    mov cr0,eax
;
; disable long mode
;
    mov ecx,IA32_EFER
    rdmsr
    and eax,0FFFFFEFFh   
    wrmsr
;
; draw some pattern if successful
;
    mov edi,cs:param.lfb_base
    mov ecx,10000h
    mov eax,80706050h
    rep stosd

stopl:
    jmp stopl

The UEFI part looks something like this:

Code: Select all


#define RDOS_LOADER 0x110000

char str[256];
CHAR16 wstr[256];

EFI_INPUT_KEY Key;

EFI_PHYSICAL_ADDRESS RdosLoaderBase = RDOS_LOADER;
unsigned int RdosLoaderPages = 16;

unsigned int LoaderEntry = (unsigned int)RDOS_LOADER;
void (*StartLoaderProc)();

struct LoaderParam
{
    EFI_PHYSICAL_ADDRESS Lfb;
    EFI_PHYSICAL_ADDRESS Param;
};

static void ConvertToWide(CHAR16 *dest, const char *src)
{
    int i = 0;

    while (src[i])
    {
        dest[i] = (CHAR16)src[i];
        i++;
    }
    dest[i] = 0;
}

static int LoadBootLoader(EFI_FILE_IO_INTERFACE *Fs)
{
    int ok = 0;

    printf("Loading boot-loader <");
    strcpy(str, "efi\\rdos\\boot64.bin");
    ConvertToWide(wstr, str);
    ST->ConOut->OutputString(ST->ConOut, wstr);
    printf(">\n\r");

    if (Fs->OpenVolume(Fs, &Root) == EFI_SUCCESS)
    {
        if (Root->Open(Root, &FileHandle, wstr, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY | EFI_FILE_HIDDEN | EFI_FILE_SYSTEM) == EFI_SUCCESS)
        {
            if (BS->AllocatePages(AllocateAddress, EfiRuntimeServicesData, RdosLoaderPages, &RdosLoaderBase) == EFI_SUCCESS)
            {
                if (FileHandle->Read(FileHandle, 0x10000, RdosLoaderBase) == EFI_SUCCESS)
                    ok = 1;
                else
                {
                    BS->FreePages(RdosLoaderBase, RdosLoaderPages);
                    printf("Failed to read RDOS loader\n\r");
                }
            }
            else
                printf("Failed to allocate fixed memory for RDOS loader\n\r");

            FileHandle->Close(FileHandle);
        }
        else
            printf("Cannot open loader file\n\r");
            
        Root->Close(Root);
    }
    else
        printf("Cannot open volume\n\r");

    return ok;
}

static void StartLoader()
{
    StartLoaderProc = (void *)LoaderEntry;
    LoaderData = (struct LoaderParam *)LoaderParamPos;

    LoaderData->Lfb = LfbBase;
    (*StartLoaderProc)();
}

    if (LoadBootLoader(Fs))
    {
        if (BS->GetMemoryMap(&MemMapSize, MemMap, &MapKey, &MemDescrSize, &MemDescrVersion) == EFI_SUCCESS)
        {
            if (BS->ExitBootServices(ImageHandle, MapKey) == EFI_SUCCESS)
                StartLoader();
            else
                printf("Exit boot services failed\n\r");
            }
            else
                printf("Get memory map failed\n\r");

            BS->FreePages(RdosLoaderBase, RdosLoaderPages);
        }

        BS->FreePages(RdosImageBase, RdosImagePages);
    }

In order to be able to use my standard tools (wasm32), and not jwasm, I think the above flat code could be binary-coded and then a new code selector with a base of 0x110000 could be setup so no relocations would be needed in the loader itself.

Thoughts?
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: Switching from 64-bit UEFI to protected mode

Post by BrightLight »

Why do you need a 64-bit UEFI boot loader to load a 32-bit kernel? UEFI implementations also have a legacy BIOS; they can boot an ordinary boot sector.
You know your OS is advanced when you stop using the Intel programming guide as a reference.
rdos
Member
Member
Posts: 3303
Joined: Wed Oct 01, 2008 1:55 pm

Re: Switching from 64-bit UEFI to protected mode

Post by rdos »

omarrx024 wrote:Why do you need a 64-bit UEFI boot loader to load a 32-bit kernel? UEFI implementations also have a legacy BIOS; they can boot an ordinary boot sector.
Most modern computers only have 64-bit UEFI and possibly legacy boot. It's only very cheap Intel Atom-based computers that still use 32-bit UEFI (which is why I need to support that as well).

The legacy BIOS will probably disappear, and especially in laptops and cheaper hardware.

Also, the VBE interface is often broken in modern computers, but hopefully GOP will still work and return a usable LFB-buffer.

I suspect that BIOS hand-off will get a lot simpler with UEFI-booting since the ExitBootServices is supposed to hand-over the hardware to the OS.

BTW, I already support legacy-BIOS boot.
User avatar
BrightLight
Member
Member
Posts: 901
Joined: Sat Dec 27, 2014 9:11 am
Location: Maadi, Cairo, Egypt
Contact:

Re: Switching from 64-bit UEFI to protected mode

Post by BrightLight »

It seems that you notice 32-bit software is getting old. 64-bit programming is mostly the same as 32-bit programming: the difference is just in the paging structures.
You know your OS is advanced when you stop using the Intel programming guide as a reference.
rdos
Member
Member
Posts: 3303
Joined: Wed Oct 01, 2008 1:55 pm

Re: Switching from 64-bit UEFI to protected mode

Post by rdos »

omarrx024 wrote:It seems that you notice 32-bit software is getting old. 64-bit programming is mostly the same as 32-bit programming: the difference is just in the paging structures.
I have a long-term goal to support a mixed 64-bit and 32-bit environment, but right now I just want to be able to boot and run RDOS as a 32-bit OS even on platforms that comes-up only in 64-bit mode. I also want to solve the problem with video-drivers by using the LFB that GOP in UEFI supports, which hopefully means I don't need to directly program the graphics chips any time soon.

For the 64-bit code, I'm waiting for a usable 64-bit OpenWatcom port. I've tried porting GCC, but it's just too much hassle and work.
kzinti
Member
Member
Posts: 898
Joined: Mon Feb 02, 2015 7:11 pm

Re: Switching from 64-bit UEFI to protected mode

Post by kzinti »

I am about to attempt the same thing. Right now I can start a 32 or 64 bits kernel from a 32 bits multiboot loader (and this should work with EFI 32 as-is).

I want my 64 bits EFI loader to be able to do the same (i.e.: start a 32 bits kernel). As long as the kernel and the EFI bootloader are both located under 4GB (or if you use PAE), it should be doable.

But yeah, the tools do get in the way. I use nasm and it won't let me add 32 bits code to an elf64 file or 64 bits code to an elf32 file. I ended up having to do this to jump from 32 bits to 64 bits:

Code: Select all

    ; Far jump into long mode. Note that it is impossible to do an absolute jump
    ; to a 64-bit address from a 32 bits code segment. So we will jump to a 32 bits
    ; address in a 64-bit code segment first and then jump to the kernel.
    jmp GDT_BOOT_CODE:enter_long_mode


enter_long_mode:

    ; Jump to kernel using an absolute jump

    ; nasm won't let us use 64 bits instruction in 32 bits,
    ; so we enter the instructions using db.

    db 0x67, 0x48, 0x8b, 0x4c, 0x24, 0x0c   ; mov rcx, [esp+12]
    db 0xff, 0xe1                           ; jmp rcx
Post Reply