docs: Initial Limine protocol draft
This commit is contained in:
parent
b2238c152e
commit
2fa2fb2220
|
@ -0,0 +1,137 @@
|
||||||
|
# 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](https://github.com/stivale).
|
||||||
|
|
||||||
|
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:
|
||||||
|
```c
|
||||||
|
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:
|
||||||
|
```c
|
||||||
|
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.
|
Loading…
Reference in New Issue