The Pedigree Project 0.1
loader-arm64.c
1/* ARM64 UEFI handoff for the freestanding Pedigree ELF kernel. */
2#include <stdint.h>
3
4#include "exit_boot_services.h"
5
6typedef uint16_t efi_char16_t;
7typedef struct {
8 uint32_t a;
9 uint16_t b, c;
10 uint8_t d[8];
12
13typedef struct efi_file efi_file_t;
15typedef efi_status_t (*efi_handle_protocol_t)(efi_handle_t, efi_guid_t*, void**);
16typedef efi_status_t (*efi_allocate_pages_t)(uint32_t, uint32_t, uint64_t, uint64_t*);
17
18typedef struct {
19 uint8_t header[24];
20 void* raise_tpl;
21 void* restore_tpl;
22 efi_allocate_pages_t allocate_pages;
23 void* free_pages;
24 efi_get_memory_map_t get_memory_map;
25 void* allocate_pool;
26 void* free_pool;
27 uint8_t before_handle_protocol[9 * 8];
28 efi_handle_protocol_t handle_protocol;
29 void* reserved_after_handle_protocol;
30 void* register_protocol_notify;
31 void* locate_handle;
32 void* locate_device_path;
33 void* install_configuration_table;
34 void* load_image;
35 void* start_image;
36 void* exit;
37 void* unload_image;
38 efi_exit_boot_services_t exit_boot_services;
40
41typedef struct {
42 void* reset;
43 efi_status_t (*output_string)(void*, efi_char16_t*);
45
47 uint8_t header[24];
48 efi_char16_t* firmware_vendor;
49 uint32_t firmware_revision;
50 efi_handle_t console_in_handle;
51 void* con_in;
52 efi_handle_t console_out_handle;
54 efi_handle_t standard_error_handle;
55 void* std_err;
56 void* runtime_services;
57 efi_boot_services_t* boot_services;
58 uint64_t configuration_table_entries;
59 void* configuration_table;
60};
61
62struct efi_file {
63 uint64_t revision;
64 efi_status_t (*open)(efi_file_t*, efi_file_t**, efi_char16_t*, uint64_t, uint64_t);
65 efi_status_t (*close)(efi_file_t*);
66 void* delete_file;
67 efi_status_t (*read)(efi_file_t*, uint64_t*, void*);
68 void* write;
69 void* get_position;
70 void* set_position;
71 efi_status_t (*get_info)(efi_file_t*, efi_guid_t*, uint64_t*, void*);
72};
73
74typedef struct {
75 uint64_t revision;
76 efi_status_t (*open_volume)(void*, efi_file_t**);
78
79typedef struct {
80 uint32_t revision;
81 uint32_t reserved;
82 efi_handle_t parent_handle;
83 efi_system_table_t* system_table;
84 efi_handle_t device_handle;
86
87typedef struct {
88 efi_guid_t guid;
89 void* table;
91
92typedef struct {
93 uint8_t ident[16];
94 uint16_t type, machine;
95 uint32_t version;
96 uint64_t entry, program_offset, section_offset;
97 uint32_t flags;
98 uint16_t header_size, program_size, program_count;
99 uint16_t section_size, section_count, section_string_index;
101
102typedef struct {
103 uint32_t type, flags;
104 uint64_t offset, virtual_address, physical_address;
105 uint64_t file_size, memory_size, alignment;
107
108#define EFI_LOADER_DATA 2
109#define EFI_ALLOCATE_MAX_ADDRESS 1
110#define EFI_ALLOCATE_ADDRESS 2
111#define EFI_FILE_MODE_READ 1
112#define MAX_PHYSICAL_ADDRESS 0xffffffffULL
113#define MAX_KERNEL_FILE_SIZE (64ULL * 1024 * 1024)
114#define MAX_ROOTFS_SIZE (512ULL * 1024 * 1024)
115#define KERNEL_HANDOFF_MAGIC 0x5045444947524545ULL
116
117static const efi_guid_t loaded_image_guid = {
118 0x5b1b31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
119static const efi_guid_t file_system_guid = {
120 0x964e5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
121static const efi_guid_t file_info_guid = {
122 0x09576e92, 0x6d3f, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
123static const efi_guid_t fdt_guid = {
124 0xb1b621d5, 0xf19c, 0x41a5, {0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0}};
125static const efi_guid_t acpi_guid = {
126 0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81}};
127
128static void print(efi_system_table_t* table, efi_char16_t* message) {
129 if (table && table->con_out) {
130 table->con_out->output_string(table->con_out, message);
131 }
132}
133
134static int same_guid(const efi_guid_t* a, const efi_guid_t* b) {
135 if (a->a != b->a || a->b != b->b || a->c != b->c) {
136 return 0;
137 }
138 for (unsigned i = 0; i < 8; ++i) {
139 if (a->d[i] != b->d[i]) {
140 return 0;
141 }
142 }
143 return 1;
144}
145
146static void* allocate(efi_system_table_t* table, uint64_t bytes) {
147 if (!bytes || bytes > MAX_PHYSICAL_ADDRESS - 4095) {
148 return 0;
149 }
150 uint64_t address = MAX_PHYSICAL_ADDRESS;
151 const efi_status_t status = table->boot_services->allocate_pages(
152 EFI_ALLOCATE_MAX_ADDRESS, EFI_LOADER_DATA, (bytes + 4095) / 4096, &address);
153 return status == EFI_SUCCESS ? (void*)address : 0;
154}
155
156static void copy(void* destination, const void* source, uint64_t count) {
157 uint8_t* to = destination;
158 const uint8_t* from = source;
159 for (uint64_t i = 0; i < count; ++i) {
160 to[i] = from[i];
161 }
162}
163
164static void zero(void* destination, uint64_t count) {
165 uint8_t* bytes = destination;
166 for (uint64_t i = 0; i < count; ++i) {
167 bytes[i] = 0;
168 }
169}
170
171static void clean_kernel_segment(uint64_t address, uint64_t size) {
172 uint64_t ctr;
173 __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr));
174 const uint64_t line_size = 4ULL << ((ctr >> 16) & 15);
175 const uint64_t end = address + size;
176 for (uint64_t cursor = address & ~(line_size - 1); cursor < end; cursor += line_size) {
177 __asm__ volatile("dc cvac, %0" : : "r"(cursor) : "memory");
178 }
179}
180
181static void* read_file(efi_system_table_t* table, efi_file_t* root, efi_char16_t* path,
182 uint64_t maximum, uint64_t* length) {
183 efi_file_t* file = 0;
184 if (root->open(root, &file, path, EFI_FILE_MODE_READ, 0) != EFI_SUCCESS) {
185 return 0;
186 }
187 uint64_t info[32];
188 uint64_t info_size = sizeof(info);
189 if (file->get_info(file, (efi_guid_t*)&file_info_guid, &info_size, info) != EFI_SUCCESS ||
190 info[1] == 0 || info[1] > maximum) {
191 file->close(file);
192 return 0;
193 }
194 void* buffer = allocate(table, info[1] + 1);
195 if (!buffer) {
196 file->close(file);
197 return 0;
198 }
199 uint64_t position = 0;
200 while (position < info[1]) {
201 uint64_t requested = info[1] - position;
202 if (file->read(file, &requested, (uint8_t*)buffer + position) != EFI_SUCCESS || !requested) {
203 file->close(file);
204 return 0;
205 }
206 position += requested;
207 }
208 ((uint8_t*)buffer)[position] = 0;
209 file->close(file);
210 *length = position;
211 return buffer;
212}
213
214static int load_kernel(efi_system_table_t* table, const uint8_t* data, uint64_t length,
215 uint64_t* entry) {
216 if (length < sizeof(elf64_header_t)) {
217 return 0;
218 }
219 const elf64_header_t* header = (const elf64_header_t*)data;
220 if (header->ident[0] != 0x7f || header->ident[1] != 'E' || header->ident[2] != 'L' ||
221 header->ident[3] != 'F' || header->ident[4] != 2 || header->ident[5] != 1 ||
222 header->machine != 183 || header->type != 2 ||
223 header->program_size != sizeof(elf64_program_t) || !header->program_count ||
224 header->program_offset > length ||
225 header->program_count > (length - header->program_offset) / header->program_size) {
226 return 0;
227 }
228 int entry_loaded = 0;
229 for (uint16_t i = 0; i < header->program_count; ++i) {
230 const elf64_program_t* program =
231 (const elf64_program_t*)(data + header->program_offset + i * sizeof(elf64_program_t));
232 if (program->type != 1) {
233 continue;
234 }
235 if (!program->memory_size || program->file_size > program->memory_size ||
236 program->offset > length || program->file_size > length - program->offset ||
237 (program->physical_address & 4095) || program->physical_address > MAX_PHYSICAL_ADDRESS ||
238 program->memory_size > MAX_PHYSICAL_ADDRESS - program->physical_address) {
239 return 0;
240 }
241 uint64_t address = program->physical_address;
242 if (table->boot_services->allocate_pages(EFI_ALLOCATE_ADDRESS, EFI_LOADER_DATA,
243 (program->memory_size + 4095) / 4096,
244 &address) != EFI_SUCCESS) {
245 return 0;
246 }
247 copy((void*)address, data + program->offset, program->file_size);
248 zero((uint8_t*)address + program->file_size, program->memory_size - program->file_size);
249 clean_kernel_segment(address, program->memory_size);
250 if (header->entry >= address && header->entry - address < program->memory_size) {
251 entry_loaded = 1;
252 }
253 }
254 __asm__ volatile(
255 "dsb ish\n"
256 "ic iallu\n"
257 "dsb ish\n"
258 "isb\n"
259 :
260 :
261 : "memory");
262 *entry = header->entry;
263 return entry_loaded;
264}
265
266static void enter_kernel(uint64_t entry, uint64_t fdt, uint64_t initrd_start, uint64_t initrd_end,
267 uint64_t command_line, uint64_t memory_map, uint64_t memory_map_bytes,
268 uint64_t acpi_rsdp) __attribute__((noreturn));
269static void enter_kernel(uint64_t entry, uint64_t fdt, uint64_t initrd_start, uint64_t initrd_end,
270 uint64_t command_line, uint64_t memory_map, uint64_t memory_map_bytes,
271 uint64_t acpi_rsdp) {
272 __asm__ volatile(
273 "msr daifset, #0xf\n"
274 "dsb sy\n"
275 "mrs x7, sctlr_el1\n"
276 "bic x7, x7, #1\n"
277 "msr sctlr_el1, x7\n"
278 "isb\n"
279 "mov x0, %0\n"
280 "mov x1, %1\n"
281 "mov x2, %2\n"
282 "mov x3, %3\n"
283 "mov x4, %4\n"
284 "mov x5, %5\n"
285 "mov x6, %6\n"
286 "mov x7, %7\n"
287 "br %8\n"
288 :
289 : "r"(fdt), "r"(initrd_start), "r"(initrd_end), "r"(command_line), "r"(KERNEL_HANDOFF_MAGIC),
290 "r"(memory_map), "r"(memory_map_bytes), "r"(acpi_rsdp), "r"(entry)
291 : "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "memory");
292 for (;;) {
293 __asm__ volatile("wfe");
294 }
295}
296
297efi_status_t efi_main(efi_handle_t image, efi_system_table_t* table) {
298 print(table, (efi_char16_t*)L"Pedigree ARM64 UEFI loader\r\n");
299
300 uint64_t fdt = 0, acpi_rsdp = 0;
301 const efi_configuration_table_t* tables = table->configuration_table;
302 for (uint64_t i = 0; i < table->configuration_table_entries; ++i) {
303 if (same_guid(&tables[i].guid, &fdt_guid)) {
304 fdt = (uint64_t)tables[i].table;
305 } else if (same_guid(&tables[i].guid, &acpi_guid)) {
306 acpi_rsdp = (uint64_t)tables[i].table;
307 }
308 }
309 if (!fdt && !acpi_rsdp) {
310 print(table, (efi_char16_t*)L"UEFI: no FDT or ACPI table\r\n");
311 return EFI_LOAD_ERROR;
312 }
313 print(table,
314 fdt ? (efi_char16_t*)L"UEFI: FDT platform\r\n" : (efi_char16_t*)L"UEFI: ACPI platform\r\n");
315
316 efi_loaded_image_t* loaded = 0;
317 efi_simple_file_system_t* filesystem = 0;
318 if (table->boot_services->handle_protocol(image, (efi_guid_t*)&loaded_image_guid,
319 (void**)&loaded) != EFI_SUCCESS ||
320 table->boot_services->handle_protocol(loaded->device_handle, (efi_guid_t*)&file_system_guid,
321 (void**)&filesystem) != EFI_SUCCESS) {
322 print(table, (efi_char16_t*)L"UEFI: filesystem unavailable\r\n");
323 return EFI_LOAD_ERROR;
324 }
325 efi_file_t* root = 0;
326 if (filesystem->open_volume(filesystem, &root) != EFI_SUCCESS) {
327 print(table, (efi_char16_t*)L"UEFI: cannot open ESP\r\n");
328 return EFI_LOAD_ERROR;
329 }
330
331 uint64_t kernel_length = 0, rootfs_length = 0, cmdline_length = 0;
332 uint8_t* kernel_file = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\kernel",
333 MAX_KERNEL_FILE_SIZE, &kernel_length);
334 uint8_t* rootfs = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\rootfs.img",
335 MAX_ROOTFS_SIZE, &rootfs_length);
336 char* cmdline = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\cmdline", 4095,
337 &cmdline_length);
338 root->close(root);
339 if (!kernel_file || !cmdline) {
340 print(table, (efi_char16_t*)L"UEFI: kernel or command line missing\r\n");
341 return EFI_LOAD_ERROR;
342 }
343 for (uint64_t i = 0; i < cmdline_length; ++i) {
344 if (!cmdline[i] || cmdline[i] == '\r' || cmdline[i] == '\n') {
345 cmdline[i] = 0;
346 break;
347 }
348 }
349
350 uint64_t entry = 0;
351 if (!load_kernel(table, kernel_file, kernel_length, &entry)) {
352 print(table, (efi_char16_t*)L"UEFI: cannot load ARM64 kernel\r\n");
353 return EFI_LOAD_ERROR;
354 }
355
356 void* raw_map = allocate(table, 16 * 4096);
357 bootstrap_memory_map_entry_t* normalized = allocate(table, 16 * 4096);
358 if (!raw_map || !normalized) {
359 print(table, (efi_char16_t*)L"UEFI: memory map allocation failed\r\n");
360 return EFI_LOAD_ERROR;
361 }
362 print(table, (efi_char16_t*)L"UEFI: entering kernel\r\n");
363 uint32_t normalized_bytes = 0;
364 int exit_attempted = 0;
365 const efi_status_t status = exit_boot_services_with_map(
366 table->boot_services->get_memory_map, table->boot_services->exit_boot_services, image,
367 raw_map, 16 * 4096, normalized, 16 * 4096, &normalized_bytes, &exit_attempted);
368 if (status != EFI_SUCCESS) {
369 if (!exit_attempted) {
370 print(table, (efi_char16_t*)L"UEFI: ExitBootServices failed\r\n");
371 }
372 return status;
373 }
374 const uint64_t initrd_start = (uint64_t)rootfs;
375 enter_kernel(entry, fdt, initrd_start, initrd_start + rootfs_length, (uint64_t)cmdline,
376 (uint64_t)normalized, normalized_bytes, acpi_rsdp);
377}