The Pedigree Project 0.1
loader-armv7.c
1/* ARMv7 UEFI handoff for the freestanding Pedigree ELF kernel. */
2#include "pedigree/kernel/processor/armv7/UefiHandoff.h"
3
4#include <stdint.h>
5
6#include "exit_boot_services.h"
7
8typedef uint16_t efi_char16_t;
9typedef struct {
10 uint32_t a;
11 uint16_t b, c;
12 uint8_t d[8];
14
15typedef struct efi_file efi_file_t;
17typedef efi_status_t (*efi_handle_protocol_t)(efi_handle_t, efi_guid_t*, void**);
18typedef efi_status_t (*efi_allocate_pages_t)(uint32_t, uint32_t, uintptr_t, uint64_t*);
19
20typedef struct {
21 uint8_t header[24];
22 void* raise_tpl;
23 void* restore_tpl;
24 efi_allocate_pages_t allocate_pages;
25 void* free_pages;
26 efi_get_memory_map_t get_memory_map;
27 void* allocate_pool;
28 void* free_pool;
29 uint8_t before_handle_protocol[9 * 4];
30 efi_handle_protocol_t handle_protocol;
31 void* reserved_after_handle_protocol;
32 void* register_protocol_notify;
33 void* locate_handle;
34 void* locate_device_path;
35 void* install_configuration_table;
36 void* load_image;
37 void* start_image;
38 void* exit;
39 void* unload_image;
40 efi_exit_boot_services_t exit_boot_services;
42
43typedef struct {
44 void* reset;
45 efi_status_t (*output_string)(void*, efi_char16_t*);
47
48struct efi_system_table {
49 uint8_t header[24];
50 efi_char16_t* firmware_vendor;
51 uint32_t firmware_revision;
52 efi_handle_t console_in_handle;
53 void* con_in;
54 efi_handle_t console_out_handle;
56 efi_handle_t standard_error_handle;
57 void* std_err;
58 void* runtime_services;
59 efi_boot_services_t* boot_services;
60 uint32_t configuration_table_entries;
61 void* configuration_table;
62};
63
64struct efi_file {
65 uint64_t revision;
66 efi_status_t (*open)(efi_file_t*, efi_file_t**, efi_char16_t*, uint64_t, uint64_t);
67 efi_status_t (*close)(efi_file_t*);
68 void* delete_file;
69 efi_status_t (*read)(efi_file_t*, uint32_t*, void*);
70 void* write;
71 void* get_position;
72 void* set_position;
73 efi_status_t (*get_info)(efi_file_t*, efi_guid_t*, uint32_t*, void*);
74};
75
76typedef struct {
77 uint64_t revision;
78 efi_status_t (*open_volume)(void*, efi_file_t**);
80
81typedef struct {
82 uint32_t revision;
83 efi_handle_t parent_handle;
84 efi_system_table_t* system_table;
85 efi_handle_t device_handle;
87
88typedef struct {
89 efi_guid_t guid;
90 void* table;
92
93typedef struct {
94 uint8_t ident[16];
95 uint16_t type, machine;
96 uint32_t version;
97 uint32_t entry, program_offset, section_offset;
98 uint32_t flags;
99 uint16_t header_size, program_size, program_count;
100 uint16_t section_size, section_count, section_string_index;
102
103typedef struct {
104 uint32_t type, offset, virtual_address, physical_address;
105 uint32_t file_size, memory_size, flags, 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 0x7fffffffULL
113#define MAX_KERNEL_FILE_SIZE (64ULL * 1024 * 1024)
114#define MAX_ROOTFS_SIZE (512ULL * 1024 * 1024)
115
116static const efi_guid_t loaded_image_guid = {
117 0x5b1b31a1, 0x9562, 0x11d2, {0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
118static const efi_guid_t file_system_guid = {
119 0x964e5b22, 0x6459, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
120static const efi_guid_t file_info_guid = {
121 0x09576e92, 0x6d3f, 0x11d2, {0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b}};
122static const efi_guid_t fdt_guid = {
123 0xb1b621d5, 0xf19c, 0x41a5, {0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0}};
124
125static void print(efi_system_table_t* table, efi_char16_t* message) {
126 if (table && table->con_out) {
127 table->con_out->output_string(table->con_out, message);
128 }
129}
130
131static int same_guid(const efi_guid_t* a, const efi_guid_t* b) {
132 if (a->a != b->a || a->b != b->b || a->c != b->c) {
133 return 0;
134 }
135 for (unsigned i = 0; i < 8; ++i) {
136 if (a->d[i] != b->d[i]) {
137 return 0;
138 }
139 }
140 return 1;
141}
142
143static void* allocate(efi_system_table_t* table, uint64_t bytes) {
144 if (!bytes || bytes > MAX_PHYSICAL_ADDRESS - 4095) {
145 return 0;
146 }
147 uint64_t address = MAX_PHYSICAL_ADDRESS;
148 const efi_status_t status = table->boot_services->allocate_pages(
149 EFI_ALLOCATE_MAX_ADDRESS, EFI_LOADER_DATA, (bytes + 4095) / 4096, &address);
150 return status == EFI_SUCCESS ? (void*)address : 0;
151}
152
153static void copy(void* destination, const void* source, uint64_t count) {
154 uint8_t* to = destination;
155 const uint8_t* from = source;
156 for (uint64_t i = 0; i < count; ++i) {
157 to[i] = from[i];
158 }
159}
160
161static void zero(void* destination, uint64_t count) {
162 uint8_t* bytes = destination;
163 for (uint64_t i = 0; i < count; ++i) {
164 bytes[i] = 0;
165 }
166}
167
168static void clean_buffer(uint32_t address, uint32_t size) {
169 uint32_t ctr;
170 __asm__ volatile("mrc p15, 0, %0, c0, c0, 1" : "=r"(ctr));
171 const uint32_t line_size = 4U << ((ctr >> 16) & 15);
172 const uint32_t end = address + size;
173 for (uint32_t cursor = address & ~(line_size - 1); cursor < end; cursor += line_size) {
174 __asm__ volatile("mcr p15, 0, %0, c7, c10, 1" : : "r"(cursor) : "memory");
175 }
176}
177
178static void* read_file(efi_system_table_t* table, efi_file_t* root, efi_char16_t* path,
179 uint64_t maximum, uint64_t* length) {
180 efi_file_t* file = 0;
181 if (root->open(root, &file, path, EFI_FILE_MODE_READ, 0) != EFI_SUCCESS) {
182 return 0;
183 }
184 uint64_t info[32];
185 uint32_t info_size = sizeof(info);
186 if (file->get_info(file, (efi_guid_t*)&file_info_guid, &info_size, info) != EFI_SUCCESS ||
187 info[1] == 0 || info[1] > maximum) {
188 print(table, (efi_char16_t*)L"UEFI: file info failed\r\n");
189 file->close(file);
190 return 0;
191 }
192 void* buffer = allocate(table, info[1] + 1);
193 if (!buffer) {
194 print(table, (efi_char16_t*)L"UEFI: file allocation failed\r\n");
195 file->close(file);
196 return 0;
197 }
198 uint64_t position = 0;
199 while (position < info[1]) {
200 uint32_t requested = info[1] - position;
201 if (file->read(file, &requested, (uint8_t*)buffer + position) != EFI_SUCCESS || !requested) {
202 print(table, (efi_char16_t*)L"UEFI: file read failed\r\n");
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 uint32_t* entry) {
216 if (length < sizeof(elf32_header_t)) {
217 return 0;
218 }
219 const elf32_header_t* header = (const elf32_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] != 1 || header->ident[5] != 1 ||
222 header->machine != 40 || header->type != 2 ||
223 header->program_size != sizeof(elf32_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 elf32_program_t* program =
231 (const elf32_program_t*)(data + header->program_offset + i * sizeof(elf32_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_buffer(address, program->memory_size);
250 if (header->entry >= address && header->entry - address < program->memory_size) {
251 entry_loaded = 1;
252 }
253 }
254 __asm__ volatile("dsb sy\n mcr p15, 0, %0, c7, c5, 0\n dsb sy\n isb" : : "r"(0) : "memory");
255 *entry = header->entry;
256 return entry_loaded;
257}
258
259static void enter_kernel(uint32_t entry, uint32_t handoff) __attribute__((noreturn));
260static void enter_kernel(uint32_t entry, uint32_t handoff) {
261 __asm__ volatile(
262 "cpsid if\n"
263 "dsb sy\n"
264 "mrc p15, 0, r12, c1, c0, 0\n"
265 "bic r12, r12, #1\n"
266 "bic r12, r12, #4\n"
267 "bic r12, r12, #0x1000\n"
268 "mcr p15, 0, r12, c1, c0, 0\n"
269 "isb\n"
270 "mov r0, %0\n"
271 "mov r1, %1\n"
272 "bx %2\n"
273 :
274 : "r"(ARMV7_UEFI_HANDOFF_MAGIC), "r"(handoff), "r"(entry)
275 : "r0", "r1", "r12", "memory");
276 for (;;) {
277 __asm__ volatile("wfi");
278 }
279}
280
281efi_status_t efi_main(efi_handle_t image, efi_system_table_t* table) {
282 print(table, (efi_char16_t*)L"Pedigree ARMv7 UEFI loader\r\n");
283
284 uint32_t fdt = 0;
285 const efi_configuration_table_t* tables = table->configuration_table;
286 for (uint32_t i = 0; i < table->configuration_table_entries; ++i) {
287 if (same_guid(&tables[i].guid, &fdt_guid)) {
288 fdt = (uint32_t)tables[i].table;
289 }
290 }
291 if (!fdt || fdt >= 0x80000000U) {
292 print(table, (efi_char16_t*)L"UEFI: no usable FDT\r\n");
293 return EFI_LOAD_ERROR;
294 }
295 const uint8_t* firmware_fdt = (const uint8_t*)fdt;
296 const uint32_t fdt_size = ((uint32_t)firmware_fdt[4] << 24) | ((uint32_t)firmware_fdt[5] << 16) |
297 ((uint32_t)firmware_fdt[6] << 8) | firmware_fdt[7];
298 if (firmware_fdt[0] != 0xd0 || firmware_fdt[1] != 0x0d || firmware_fdt[2] != 0xfe ||
299 firmware_fdt[3] != 0xed || fdt_size < 40 || fdt_size > 2 * 1024 * 1024) {
300 print(table, (efi_char16_t*)L"UEFI: invalid FDT\r\n");
301 return EFI_LOAD_ERROR;
302 }
303 void* fdt_copy = allocate(table, fdt_size);
304 if (!fdt_copy) {
305 print(table, (efi_char16_t*)L"UEFI: FDT allocation failed\r\n");
306 return EFI_LOAD_ERROR;
307 }
308 copy(fdt_copy, firmware_fdt, fdt_size);
309 fdt = (uint32_t)fdt_copy;
310 print(table, (efi_char16_t*)L"UEFI: FDT platform\r\n");
311
312 efi_loaded_image_t* loaded = 0;
313 efi_simple_file_system_t* filesystem = 0;
314 if (table->boot_services->handle_protocol(image, (efi_guid_t*)&loaded_image_guid,
315 (void**)&loaded) != EFI_SUCCESS ||
316 table->boot_services->handle_protocol(loaded->device_handle, (efi_guid_t*)&file_system_guid,
317 (void**)&filesystem) != EFI_SUCCESS) {
318 print(table, (efi_char16_t*)L"UEFI: filesystem unavailable\r\n");
319 return EFI_LOAD_ERROR;
320 }
321 efi_file_t* root = 0;
322 if (filesystem->open_volume(filesystem, &root) != EFI_SUCCESS) {
323 print(table, (efi_char16_t*)L"UEFI: cannot open ESP\r\n");
324 return EFI_LOAD_ERROR;
325 }
326
327 uint64_t kernel_length = 0, rootfs_length = 0, cmdline_length = 0;
328 uint8_t* kernel_file = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\kernel",
329 MAX_KERNEL_FILE_SIZE, &kernel_length);
330 uint8_t* rootfs = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\rootfs.img",
331 MAX_ROOTFS_SIZE, &rootfs_length);
332 char* cmdline = read_file(table, root, (efi_char16_t*)L"\\EFI\\PEDIGREE\\current\\cmdline", 4095,
333 &cmdline_length);
334 root->close(root);
335 if (!kernel_file) {
336 print(table, (efi_char16_t*)L"UEFI: kernel read failed\r\n");
337 return EFI_LOAD_ERROR;
338 }
339 if (!cmdline) {
340 print(table, (efi_char16_t*)L"UEFI: command line read failed\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 uint32_t entry = 0;
351 if (!load_kernel(table, kernel_file, kernel_length, &entry)) {
352 print(table, (efi_char16_t*)L"UEFI: cannot load ARMv7 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 armv7_uefi_handoff_t* handoff = allocate(table, 4096);
359 if (!raw_map || !normalized || !handoff) {
360 print(table, (efi_char16_t*)L"UEFI: memory map allocation failed\r\n");
361 return EFI_LOAD_ERROR;
362 }
363 print(table, (efi_char16_t*)L"UEFI: entering kernel\r\n");
364 uint32_t normalized_bytes = 0;
365 int exit_attempted = 0;
366 const efi_status_t status = exit_boot_services_with_map(
367 table->boot_services->get_memory_map, table->boot_services->exit_boot_services, image,
368 raw_map, 16 * 4096, normalized, 16 * 4096, &normalized_bytes, &exit_attempted);
369 if (status != EFI_SUCCESS) {
370 if (!exit_attempted) {
371 print(table, (efi_char16_t*)L"UEFI: ExitBootServices failed\r\n");
372 }
373 return status;
374 }
375 const uint32_t initrd_start = (uint32_t)rootfs;
376 *handoff = (armv7_uefi_handoff_t){ARMV7_UEFI_HANDOFF_MAGIC,
377 fdt,
378 initrd_start,
379 initrd_start + (uint32_t)rootfs_length,
380 (uint32_t)cmdline,
381 (uint32_t)normalized,
382 normalized_bytes};
383 clean_buffer((uint32_t)handoff, sizeof(*handoff));
384 clean_buffer(fdt, fdt_size);
385 clean_buffer((uint32_t)normalized, normalized_bytes);
386 clean_buffer((uint32_t)cmdline, (uint32_t)cmdline_length + 1);
387 if (rootfs) {
388 clean_buffer(initrd_start, (uint32_t)rootfs_length);
389 }
390 __asm__ volatile("dsb sy" : : : "memory");
391 enter_kernel(entry, (uint32_t)handoff);
392}