rulimine/PROTOCOL.md

5.8 KiB

The Limine Boot Protocol

The Limine boot protocol is a modern, minimal, fast, and extensible boot protocol, with a focus on backwards and forwards compatibility, created from the experienced gained by working on the stivale boot protocols.

This file serves as the official centralised collection of features that the Limine boot protocol is composed of. Other bootloaders may support extra unofficial features, but it is strongly recommended to avoid fragmentation and submit new features by opening a pull request to this repository.

Features

The protocol is centered around the concept of request/response - collectively named "features" - where the kernel requests some action or information from the bootloader, and the bootloader responds accordingly, if it is capable of doing so.

In C terms, a feature is composed of 2 structure: the request, and the response.

A request has 3 mandatory members at the beginning of the structure:

struct limine_example_request {
    uint64_t id[4];
    uint64_t revision;
    struct limine_example_response *response;
    ... optional members follow ...
};
  • id - The ID of the request. This is an 8-byte aligned magic number that the bootloader will scan for inside the executable file to find requests. Requests may be located anywhere inside the executable as long as they are 8-byte aligned. There may only be 1 of the same request. The bootloader will refuse to boot an executable with multiple of the same request IDs.
  • revision - The revision of the request that the kernel provides. This is bumped whenever new members or functionality are added to the request structure. Bootloaders process requests in a backwards compatible manner, always. This means that if the bootloader does not support the revision of the request, it will process the request as if were the highest revision that the bootloader supports.
  • response - This field is filled in by the bootloader at load time, with a pointer to the response structure, if the request was successfully processed. If the request is unsupported or was not successfully processed, this field is left untouched, meaning that if it was set to NULL, it will stay that way.

A response has only 1 mandatory member at the beginning of the structure:

struct limine_example_response {
    uint64_t revision;
    ... optional members follow ...
};
  • revision - Like for requests, bootloaders will instead mark responses with a revision number. This revision is not coupled between requests and responses, as they are bumped individually when new members are added or functionality is changed. Bootloaders will set the revision to the one they provide, and this is always backwards compatible, meaning higher revisions support all that lower revisions do.

This is all there is to features. For a list of official Limine features, read the "Feature List" section below.

Executable memory layout

The protocol mandates kernels to load themselves at or above 0xffffffff80000000. Lower half kernels are not supported.

At handoff, the kernel will be properly loaded and mapped with appropriate MMU permissions at the requested virtual memory address (provided it is at or above 0xffffffff80000000).

No specific physical memory placement is guaranteed. In order to determine where the kernel is loaded in physical memory, see the Kernel Address feature below.

Alongside the loaded kernel, the bootloader will set up memory mappings as such:

 Base Physical Address -                    Size                    ->  Virtual address
  0x0000000000001000   - 4 GiB plus any additional memory map entry -> 0x0000000000001000
  0x0000000000000000   - 4 GiB plus any additional memory map entry -> HHDM start

Where HHDM start is returned by the Higher Half Direct Map feature (see below). These mappings are supervisor, read, write, execute (-rwx).

The bootloader page tables are in bootloader-reclaimable memory (see Memory Map feature below), and their specific layout is undefined as long as they provide the above memory mappings.

If the kernel is a position independent executable, the bootloader is free to relocate it as it sees fit, potentially performing KASLR (as specified by the config).

Entry machine state

x86_64

rip will be the entry point as defined as part of the executable file format, unless the an Entry Point feature is requested (see below), in which case, the value of rip is going to be taken from there.

At entry all segment registers are loaded as 64 bit code/data segments, limits and bases are ignored since this is 64-bit mode.

The GDT register is loaded to point to a GDT, in bootloader-reserved memory, with at least the following entries, starting at offset 0:

  • Null descriptor
  • 16-bit code descriptor. Base = 0, limit = 0xffff. Readable.
  • 16-bit data descriptor. Base = 0, limit = 0xffff. Writable.
  • 32-bit code descriptor. Base = 0, limit = 0xffffffff. Readable.
  • 32-bit data descriptor. Base = 0, limit = 0xffffffff. Writable.
  • 64-bit code descriptor. Base and limit irrelevant. Readable.
  • 64-bit data descriptor. Base and limit irrelevant. Writable.

The IDT is in an undefined state. Kernel must load its own.

IF flag, VM flag, and direction flag are cleared on entry. Other flags undefined.

PG is enabled (cr0), PE is enabled (cr0), PAE is enabled (cr4), LME is enabled (EFER). If 5-level paging is requested and available, then 5-level paging is enabled (LA57 bit in cr4). The NX bit will be enabled (NX bit in EFER).

The A20 gate is opened.

Legacy PIC and IO APIC IRQs are all masked.

If booted by EFI/UEFI, boot services are exited.

rsp is set to point to a stack, in bootloader-reserved memory, which is at least 8KiB (8192 bytes) in size. An invalid return address of 0 is pushed to the stack before jumping to the kernel.

All other general purpose registers are set to 0.