Ultra loader protocol defines a set of services and interfaces used by a bootloader to load a kernel/binary file.
The protocol doesn't require any kind of sections residing inside the kernel binary and fully depends on loader-specific configuration file to define all necessary load options and parameters. This allows to dynamically change all the parameters without having to recompile the kernel as well as being hybrid ultra and any other protocol compatible at the same time.
After handoff, the kernel is provided with a boot context that has the following structure:
struct ultra_boot_context {
uint8_t protocol_major;
uint8_t protocol_minor;
uint16_t reserved;
uint32_t attribute_count;
struct ultra_attribute_header attributes[];
};
protocol_major
- major version of the protocol, valid versions start at1
protocol_minor
- minor version of the protocol, valid versions start at0
attribute_count
- the number of attributes in theattributes
arrayattributes
- a contiguous array of attributes provided by the loader
The following C macro can be used to retrieve the next attribute from current:
#define ULTRA_NEXT_ATTRIBUTE(current) ((struct ultra_attribute_header*)(((uint8_t*)(current)) + (current)->size))
ultra_attribute_header
and other structures are described in the following sections.
The kernel is also provided with a protocol magic number ULTRA_MAGIC
, which is defined as:
0x554c5442
- 'ULTB' in ASCII
The kernel entrypoint ends up looking something like this in C:
void kernel_entry(struct ultra_boot_context *ctx, uint32_t magic)
The way the kernel receives this magic number is platform defined and is discussed in the later sections.
In case the kernel detects an invalid magic number the ultra_boot_context*
must be considered invalid and it's recommended that the kernel aborts boot.
Current protocol version is defined as 1.0
.
This section defines various options and features defined by the protocol. The actual way to enable/set an option is loader specific and is defined by its configuration file format.
Defines various kernel binary related options.
binary
- (string) - shorthand alias forbinary/path
binary/path
- (string) - specifies the path to the kernel in a loader-defined format, if applicable.binary/allocate-anywhere
(bool, optional, default=false) - allows the kernel physical memory mappings to be allocated anywhere in memory. Only valid for 64 bit higher-half kernels.
Defines various options related to the page table properties set up at handover.
page-table/levels
- (integer, optional, default=4) - specifies the desired depth of the page tablepage-table/constraint
- (string, optional, default="maximum") - specifies the constraint for the "levels" value, one of "maximum", "at-least", "exactly"page-table/null-guard
- (bool, optional, default=false) - if set totrue
, the first physical page is not identity mapped to prevent accidentalNULL
accesses.
Defines a command line to give to the kernel. This attribute is not generated if cmdline
is set to null.
cmdline
(string, optional, default=null) - kernel command line, passed viaultra_command_line_attribute
.
Defines various options related to the address passed in the arch-specific SP register on handover.
stack
(string/unsigned) - shorthand alias forstack/size
. Also allows a literal "auto".stack/size
(unsigned, optional, default=16K) - page aligned size of the stack to allocate.stack/allocate-at
(string/unsigned, default=<implementation-defined>) - address where to allocate the stack
Defines options related to the video-mode set by the loader before handover.
video-mode
- (string, optional, default="auto") - shorthand."auto"
implies native width/height,null
or"unset"
are also allowed,ultra_framebuffer_attribute
is not generated if this is the case.video-mode/width
- (unsigned, optional, default=<native>) - requests a specific framebuffer widthvideo-mode/height
- (unsigned, optional, default=<native>) - requests a specific framebuffer heightvideo-mode/bpp
(unsigned, optional, default=32) - requests a specific framebuffer bits per pixel valuevideo-mode/format
(string, optional, default="auto") - requests a specific framebuffer format, one of"auto"
,"rgb888"
,"bgr888"
,"rgbx8888"
,"xrgb8888"
(any case). This option is not affected byconstraint
and is always matched exactly if specified.video-mode/constraint
(string, optional, default="at-least") - specifies a constraint for the video mode, one of "at-least", "exactly"
Ultra protocol offers different types of modules: classic file-backed modules as well as raw RAM allocations.
The following options must be used to request a module:
kernel-as-module
- (bool, optional, default=false) - requests the loader to pass the full kernel binary as a separate module, this can be used for parsing additional debug information, enforcing memory protection for program headers from an ELF file or any other purpose.module
- (string) - shorthand alias formodule/path
module/type
(string, optional, default="file") - one of "file" or "memory". File modules require a path argument and simply make the loader preload a file from disk into RAM. Memory modules are used to request general purpose contiguous zeroed memory allocations without any backing, and can be used for bootstrapping kernel allocators or any other purpose.module/size
(unsigned/string, optional*, default="auto") - defines the size of the module. Mandatory for "memory" modules. Can be used to truncate or extend "file" modules, if this is bigger than the file size, the rest of the memory is zeroed.module/name
- (string, optional, default=<implementation-defined>) - name of the module that the kernel receivesmodule/path
- (string, optional*) - specifies the path for the module to load in a loader-defined format, if applicablemodule/load-at
(unsigned/string, optional, default="anywhere") - specifies the load address for the module or "anywhere"
Each directive generates a separate ultra_module_info_attribute
higher-half-exclusive
- (bool, optional, default=false) - if set totrue
, no identity mappings are provided for lower half. All loader-provided attributeaddress
fields are relocated to higher half if present. Only applicable for higher-half kernels.
This section describes the system state for each supported architecture that system software must expect after gaining control.
Page size is defined as 4096 bytes.
- A20 - enabled
- R/EFLAGS - zeroed, reserved bit 1 set
- IDTR - contents are unspecified
- GDTR - set to a valid GDT with at least ring 0 flat code/data descriptors
- Long/protected mode - set as determined by the kernel binary
- CS - set to a flat ring 0 code segment
- DS, ES, FS, GS, SS - set to a flat ring 0 data segment
Higher half is defined as 0xC000'0000
The kernel is considered higher half if it wants to be loaded at or above 0xC010'0000
- Paging enabled
- EIP - set to the entrypoint as specified by the kernel binary
- EAX, ECX, EDX, EBX, EBP, ESI, EDI - zeroed
- ESP - set to a valid stack pointer as determined by the configuration, aligned according to SysV ABI
- *ESP+8 -
ULTRA_MAGIC
- *ESP+4 -
ultra_boot_context*
virtual address | physical address | length of the mapping |
---|---|---|
0x0000'0000 | 0x0000'0000 | 3 GiB |
0xC000'0000 | 0x0000'0000 | 1 GiB |
The first mapping is not provided for the higher-half-exclusive
mode.
Higher half is defined as 0xFF00'0000'0000'0000
or 0xFFFF'8000'0000'0000
, depending on page-table/levels
The kernel is considered higher half if it wants to be loaded at or above 0xFFFF'FFFF'8000'0000
- RIP - set to the entrypoint as specified by the kernel binary
- RDI -
ultra_boot_context*
- RSI -
ULTRA_MAGIC
- RAX, RCX, RDX, RBX, RBP, R8, R9, R10, R11, R12, R13, R14, R15 - zeroed
- RSP - set to a valid stack pointer as determined by the configuration, aligned according to SysV ABI
- CR3 - a valid address of a PML4 with the following mappings:
Higher half base address depends on the value of page-table/levels
with the following conditions:
0xFF00'0000'0000'0000
- 'levels' = 5 with a matching constraint0xFFFF'8000'0000'0000
- 'levels' = 4 with a matching constraint
virtual address | physical address | length of the mapping |
---|---|---|
0x0000'0000'0000'0000 | 0x0000'0000'0000'0000 | 4 GiB + any entries above |
0xFFFF'8000'0000'0000 OR 0xFF00'0000'0000'0000 |
0x0000'0000'0000'0000 | 4 GiB + any entries above |
0xFFFF'FFFF'8000'0000 | ????????????????????? | ????????????????????? |
First two mappings are guaranteed to cover the first 4 GiB of physical memory as well as any other entries present in the memory map on top of that.
The first mapping is not provided for the higher-half-exclusive
mode.
For higher half kernels loaded with allocate-anywhere
set to true
the last mapping contains the kernel binary mappings with an arbitrary
physical base picked by the loader.
For all other kernels it is a direct mapping of the first 2 GiB of physical ram.
Address pointed to by CR3 is located somewhere within ULTRA_MEMORY_TYPE_LOADER_RECLAIMABLE
.
Whether the memory is mapped using 4K/2M/4M/1G pages is unspecified.
The contents of all other registers are unspecified.
The state of all registers not listed here is unspecified.
Whether data/instruction caches are enabled is unspecified.
The kernel should not assume that any device memory is actually mapped as such.
Normal memory is guaranteed to be accessible.
-
MMU enabled
-
Exception level 1 OR 2
- If EL2:
FEAT_VHE
is supported andHCL_EL2.{E2H, TGE}
are set to{1, 1}
- If EL2:
-
Current EL stack alignment checking is enabled
-
SPSel set to
SP_ELx
-
DAIF set to
0b1111
-
NZCV set to
0b000
-
PC - set to the entrypoint as specified by the kernel binary
-
x0 -
ultra_boot_context*
-
x1 -
ULTRA_MAGIC
-
x2..x29 - zeroed
-
SP - set to a valid stack pointer as determined by the configuration, aligned according to the aarch64 ABI
-
TTBR{0,1}_ELx - a valid address of a page table with the following mappings:
Higher half is defined as 0xFFF0'0000'0000'0000
or 0xFFFF'0000'0000'0000
, depending on page-table/levels
The kernel is considered higher half if it wants to be loaded at or above 0xFFFF'FFFF'8000'0000
Higher half base address depends on the value of page-table/levels
with the following conditions:
0xFFF0'0000'0000'0000
- 'levels' = 5 with a matching constraint0xFFFF'0000'0000'0000
- 'levels' = 4 with a matching constraint
virtual address | physical address | length of the mapping |
---|---|---|
0x0000'0000'0000'0000 | 0x0000'0000'0000'0000 | 4 GiB + any entries above |
0xFFFF'0000'0000'0000 OR 0xFFF0'0000'0000'0000 |
0x0000'0000'0000'0000 | 4 GiB + any entries above |
0xFFFF'FFFF'8000'0000 | ????????????????????? | ????????????????????? |
First two mappings are guaranteed to cover the first 4 GiB of physical memory as well as any other entries present in the memory map on top of that.
The first mapping is not provided for the higher-half-exclusive
mode.
For higher half kernels loaded with allocate-anywhere
set to true
the last mapping contains the kernel binary mappings with an arbitrary
physical base picked by the loader.
For all other kernels it is a direct mapping of the first 2 GiB of physical ram.
Address pointed to by TTBR{0/1}_ELx is located somewhere within ULTRA_MEMORY_TYPE_LOADER_RECLAIMABLE
.
The granule size, as well as the OA size of the mapped pages is unspecified and is up to the implementation.
The way the loader provides various information to the kernel is via attributes.
Guarantees about the attribute array & attributes:
- All attributes are guaranteed to be aligned on an 8 (or more if required) byte boundary.
- All attributes and
address
fields, as well as any loader-allocated memory that might be accessed by the kernel is guaranteed to be within the mapped address space range. E.g for a 32-bit higher half exclusive kernel all attributes and modules are guaranteed to be allocated under 1 GiB of physical memory so that they can be safely accessed by the kernel. - The location of any specific attribute within the array is not fixed unless specified otherwise (in the attribute description).
- All attributes of the same type are guaranteed to be a contiguous stream.
- Every attribute type can only appear in the attribute array once unless specified otherwise (in the attribute description).
- All attribute types are guaranteed to have
ultra_attribute_header
as the first member, this includes both current & future attributes. - It is safe to ignore any unknown attribute types.
- All currently defined attributes are guaranteed to keep the same layout
in the future protocol versions, but might have new members added at the end,
which shouldn't affect any valid software as the header
size
field will be adjusted accordingly.
Every attribute has a distinct type and starts with the following header:
struct ultra_attribute_header {
uint32_t type;
uint32_t size;
};
type
- one of the following values (each type is detailed in the following sections):
#define ULTRA_ATTRIBUTE_INVALID 0
#define ULTRA_ATTRIBUTE_PLATFORM_INFO 1
#define ULTRA_ATTRIBUTE_KERNEL_INFO 2
#define ULTRA_ATTRIBUTE_MEMORY_MAP 3
#define ULTRA_ATTRIBUTE_MODULE_INFO 4
#define ULTRA_ATTRIBUTE_COMMAND_LINE 5
#define ULTRA_ATTRIBUTE_FRAMEBUFFER_INFO 6
size
- size of the entire attribute including the header. This size is often used for calculating the number of entries in a variable length attribute. This isn't always possible, because sometimes the size is increased on purpose to align entries to 8 bytes. Attributes like this are explicitly documented and provide a separate member that indicates the actual size of the variable size field.
This section describes all currently implemented attribute types and their structure.
Reserved. If encountered, must be considered a fatal error.
This attribute is guaranteed to be the first entry in the attribute array.
This attribute provides various information about the platform and has the following structure:
struct platform_info_attribute {
struct ultra_attribute_header header;
uint32_t platform_type;
uint16_t loader_major;
uint16_t loader_minor;
char loader_name[32];
uint64_t acpi_rsdp_address;
uint64_t higher_half_base;
uint8_t page_table_depth;
uint8_t reserved[7];
uint64_t dtb_address;
uint64_t smbios_address;
};
header
- standard attribute headerplatform_type
- one of the following values:
#define ULTRA_PLATFORM_INVALID 0
#define ULTRA_PLATFORM_BIOS 1
#define ULTRA_PLATFORM_UEFI 2
loader_major
- major version of the loaderloader_minor
- minor version of the loaderloader_name
- null-terminated ASCII string with a human-readable name of the loaderacpi_rsdp_address
- physical address of the RSDP structure, 0 if not applicable or not presenthigher_half_base
- virtual address of the higher half basepage_table_depth
- currently configured page table depthreserved
- reserved for future usedtb_address
- physical address of the device tree blob, 0 if not applicable or not presentsmbios_address
- physical address of the SMBIOS entry point structure (either 32 or 64 bits as indicated by the signature), 0 if not applicable or not present
Reserved. If encountered, must be considered a fatal error.
The kernel was loaded using the BIOS services and platform.
The kernel was loaded using the UEFI services and platform.
Reserved for future use, must be ignored by the kernel.
This attribute is guaranteed to be the second entry in the attribute array.
This attribute provides various information about the kernel binary and has the following structure:
struct ultra_kernel_info_attribute {
struct ultra_attribute_header header;
uint64_t physical_base;
uint64_t virtual_base;
uint64_t size;
uint64_t partition_type;
// only valid if partition_type == PARTITION_TYPE_GPT
struct ultra_guid disk_guid;
struct ultra_guid partition_guid;
// always valid
uint32_t disk_index;
uint32_t partition_index;
char fs_path[ULTRA_PATH_MAX];
};
header
- standard attribute headerphysical_base
- physical address of the kernel base, page alignedvirtual_base
- virtual address of the kernel base, page alignedsize
- number of bytes taken by the kernel, page alignedpartition_type
- one of the following values:
#define ULTRA_PARTITION_TYPE_INVALID 0
#define ULTRA_PARTITION_TYPE_RAW 1
#define ULTRA_PARTITION_TYPE_MBR 2
#define ULTRA_PARTITION_TYPE_GPT 3
disk_guid
- GUID of the disk that the kernel was loaded from, only valid forULTRA_PARTITION_TYPE_GPT
partiton_guid
- GUID of the partition that the kernel was loaded from, only valid forULTRA_PARTITION_TYPE_GPT
disk_index
- index of the disk the kernel was loaded frompartition_index
- index of the partition the kernel was loaded from, index >= 4 implies EBR partition N - 4 for an MBR diskfs_path
- null terminated UTF-8 string, absolute POSIX path to the kernel binary on the partition
ULTRA_PATH_MAX
is defined as 256
including the terminating NULL character.
Reserved. If encountered, must be considered a fatal error.
Unpartitioned device without an MBR/GPT header, the entire disk treated as one file system.
Standard MBR partition, either MBR or EBR.
Standard GPT partition, whether the device also contains a valid MBR is unspecified as GPT always takes precedence.
ultra_guid
is defined as:
struct ultra_guid {
uint32_t data1;
uint16_t data2;
uint16_t data3;
uint8_t data4[8];
};
Note that the structure is only guaranteed to be 8 byte aligned within ultra_kernel_info_attribute
.
Reserved for future use, must be ignored by the kernel.
This attributes provides a physical memory map of the entire system. It has the following format:
struct ultra_memory_map_attribute {
struct ultra_attribute_header header;
struct ultra_memory_map_entry entries[];
};
header
- standard attribute headerentries
- an array of non-overlapping and sorted in ascending order by address entries, where each entry has the following structure:
struct ultra_memory_map_entry {
uint64_t physical_address;
uint64_t size;
uint64_t type;
};
physical_address
- first byte of the physical range covered by this entrysize
- size of this rangetype
- one of the following values:
#define ULTRA_MEMORY_TYPE_INVALID 0x00000000
#define ULTRA_MEMORY_TYPE_FREE 0x00000001
#define ULTRA_MEMORY_TYPE_RESERVED 0x00000002
#define ULTRA_MEMORY_TYPE_RECLAIMABLE 0x00000003
#define ULTRA_MEMORY_TYPE_NVS 0x00000004
#define ULTRA_MEMORY_TYPE_LOADER_RECLAIMABLE 0xFFFF0001
#define ULTRA_MEMORY_TYPE_MODULE 0xFFFF0002
#define ULTRA_MEMORY_TYPE_KERNEL_STACK 0xFFFF0003
#define ULTRA_MEMORY_TYPE_KERNEL_BINARY 0xFFFF0004
Reserved. If encountered, must be considered a fatal error.
General purpose memory free for use by the kernel.
Memory reserved by the firmware.
Memory tagged as reclaimable by the firmware. Usually contains ACPI tables.
Same as MEMORY_TYPE_RESERVED. Consult the ACPI specification for more information.
Memory reserved by the bootloader. Contains temporary GDT, all attributes, memory map itself, loader code, and everything else the loader has allocated the memory for. The actual location of all the aforementioned structures within this range is unspecified.
Can be reclaimed by the kernel when it no longer needs the loader-provided structures.
Memory region containing one or more kernel modules
Memory region reserved by the loader for the kernel stack. Not necessarily the same value as SP for higher half kernels.
Memory region reserved by the loader for the loaded kernel binary (not the ELF copy).
Reserved for future use. Must be considered same as ULTRA_MEMORY_TYPE_RESERVED
if encountered by the kernel.
The number of memory map entries can be calculated using the following C macro:
#define ULTRA_MEMORY_MAP_ENTRY_COUNT(header) ((((header).size) - sizeof(struct ultra_attribute_header)) / sizeof(struct ultra_memory_map_entry))
Every loaded kernel module gets a respective attribute of this type, this means
ultra_boot_context
must contain as many attributes of this type as there are modules.
This attribute provides information necessary to locate a kernel module in memory and has the following structure:
struct ultra_module_info_attribute {
struct ultra_attribute_header header;
uint32_t reserved;
uint32_t type;
char name[64];
uint64_t address;
uint64_t size;
};
header
- standard attribute headerreserved
- for use by future versions of the protocol, must be ignoredtype
- type of this module, can be one of the following values:
#define ULTRA_MODULE_TYPE_INVALID 0
#define ULTRA_MODULE_TYPE_FILE 1
#define ULTRA_MODULE_TYPE_MEMORY 2
Reserved. If encountered, must be considered a fatal error.
File-backed module.
Contiguous zeroed memory module.
name
- null-terminated ASCII name of the module, as specified in the configuration file or"__KERNEL__"
for the autogenerated kernel binary module (if enabled)address
- address of the loaded module, page alignedsize
- size of the module as specified inmodule/size
, the actual size in RAM is this value rounded up to page size
This attribute forwards the configuration file command line to the kernel if one was provided and has the following structure:
struct ultra_command_line_attribute {
struct ultra_attribute_header header;
char text[];
};
header
- standard attribute headertext
- null terminated ASCII command line, as specified in the configuration file
Length of the command line is not artificially limited in any way but must fit in the header.size
field,
which is 32 bits wide.
Note that header.size
is not necessarily equal to the length of the command line
because of alignment reasons.
This attributes provides framebuffer information if one was requested and has the following structure:
struct ultra_framebuffer_attribute {
struct ultra_attribute_header header;
struct ultra_framebuffer fb;
};
header
- standard attribute headerfb
- describes the allocated framebuffer and has the following structure:
struct ultra_framebuffer {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint16_t bpp;
uint16_t format;
uint64_t physical_address;
};
width
- the number of visible pixels per row in the framebufferheight
- the number of rows of visible pixels in the framebufferpitch
- the number of bytes that each row takes in memorybpp
- width in bits of each visible pixelformat
- the format of the allocated framebuffer, and is one of the following values:
#define ULTRA_FB_FORMAT_INVALID 0
#define ULTRA_FB_FORMAT_RGB888 1
#define ULTRA_FB_FORMAT_BGR888 2
#define ULTRA_FB_FORMAT_RGBX8888 3
#define ULTRA_FB_FORMAT_XRGB8888 4
Framebuffer format types are very similar to DRM_FORMAT_*
and are defined as follows:
Reserved. If encountered, must be considered a fatal error.
Standard RBG format.
Layout of each pixel (low to high memory address):
bits | 0 ... 8 | 8 ... 16 | 16 ... 24 |
---|---|---|---|
color | BLUE | GREEN | RED |
bpp
must be set to 24.
Standard BGR format.
Layout of each pixel (low to high memory address):
bits | 0 ... 8 | 8 ... 16 | 16 ... 24 |
---|---|---|---|
color | RED | GREEN | BLUE |
bpp
must be set to 24.
Standard RGBX format padded to 32 bits.
Layout of each pixel (low to high memory address):
bits | 0 ... 8 | 8 ... 16 | 16 ... 24 | 24 ... 32 |
---|---|---|---|---|
color | UNUSED | BLUE | GREEN | RED |
bpp
must be set to 32.
Standard RGBX format padded to 32 bits.
Layout of each pixel (low to high memory address):
bits | 0 ... 8 | 8 ... 16 | 16 ... 24 | 24 ... 32 |
---|---|---|---|---|
color | BLUE | GREEN | RED | UNUSED |
bpp
must be set to 32.
physical_address
- address of the allocated framebuffer
All structure definitions and macros can be found in the ultra_protocol.h
file
compatible with both C and C++.
The current reference implementation of the protocol in C can be found at https://github.com/UltraOS/Hyper