docs: Initial Limine protocol draft
This commit is contained in:
parent
b2238c152e
commit
2fa2fb2220
137
PROTOCOL.md
Normal file
137
PROTOCOL.md
Normal file
@ -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
Block a user