:: limine / common / protos / linux_risc.c 18.2 KB raw

1
#if defined(__riscv) || defined(__aarch64__) || defined(__loongarch__)
2
3
#include <stdint.h>
4
#include <stddef.h>
5
#include <stdnoreturn.h>
6
#include <protos/linux.h>
7
#include <fs/file.h>
8
#include <lib/libc.h>
9
#include <lib/misc.h>
10
#include <lib/term.h>
11
#include <lib/config.h>
12
#include <lib/print.h>
13
#include <lib/uri.h>
14
#include <lib/tpm.h>
15
#include <mm/pmm.h>
16
#include <sys/idt.h>
17
#include <lib/fb.h>
18
#include <lib/acpi.h>
19
#include <lib/fdt.h>
20
#include <libfdt.h>
21
22
// The following definitions and struct were copied and adapted from Linux
23
// kernel headers released under GPL-2.0 WITH Linux-syscall-note
24
// allowing their inclusion in non GPL compliant code.
25
26
#if defined(__riscv) || defined(__aarch64__)
27
struct linux_header {
28
    uint32_t code0;
29
    uint32_t code1;
30
    uint64_t text_offset;
31
    uint64_t image_size;
32
    uint64_t flags;
33
    uint32_t version;
34
    uint32_t res1;
35
    uint64_t res2;
36
    uint64_t res3;          // originally 'magic' field, deprecated
37
    uint32_t magic2;
38
    uint32_t res4;
39
} __attribute__((packed));
40
#elif defined(__loongarch__)
41
struct linux_header {
42
    uint32_t mz;
43
    uint32_t res0;
44
    uint64_t kernel_entry;
45
    uint64_t image_size;
46
    uint64_t load_offset;
47
    uint64_t res1;
48
    uint64_t res2;
49
    uint64_t res3;
50
    uint32_t magic2;       // LINUX_PE_MAGIC
51
    uint32_t pe_offset;
52
} __attribute__((packed));
53
#else
54
#error "Unknown architecture"
55
#endif
56
57
struct linux_efi_memreserve {
58
    int size;
59
    int count;
60
    uint64_t next;
61
};
62
63
struct linux_efi_boot_memmap {
64
    UINTN    map_size;
65
    UINTN    desc_size;
66
    uint32_t desc_ver;
67
    UINTN    map_key;
68
    UINTN    buff_size;
69
    EFI_MEMORY_DESCRIPTOR descs[];
70
};
71
72
struct linux_efi_initrd {
73
    UINTN base;
74
    UINTN size;
75
};
76
77
// End of Linux code
78
79
struct boot_param {
80
    void *kernel_base;
81
    size_t kernel_size;
82
    void *module_base;
83
    size_t module_size;
84
    char *cmdline;
85
    void *dtb;
86
    struct linux_efi_boot_memmap *memmap;
87
};
88
89
#if defined(__riscv)
90
#define LINUX_HEADER_MAGIC2             0x05435352
91
#define LINUX_HEADER_MAJOR_VER(ver)     (((ver) >> 16) & 0xffff)
92
#define LINUX_HEADER_MINOR_VER(ver)     (((ver) >> 0)  & 0xffff)
93
#elif defined(__aarch64__)
94
#define LINUX_HEADER_MAGIC2             0x644d5241
95
#elif defined(__loongarch__)
96
#define LINUX_HEADER_MAGIC2             0x818223cd
97
#endif
98
99
static const char *verify_kernel(struct linux_header *header) {
100
    if (header->magic2 != LINUX_HEADER_MAGIC2) {
101
        return "kernel header magic does not match";
102
    }
103
104
    // riscv-specific version requirements
105
#if defined(__riscv)
106
    printv("linux: boot protocol version %d.%d\n",
107
           LINUX_HEADER_MAJOR_VER(header->version),
108
           LINUX_HEADER_MINOR_VER(header->version));
109
    if (LINUX_HEADER_MAJOR_VER(header->version) == 0
110
     && LINUX_HEADER_MINOR_VER(header->version) < 2) {
111
        return "linux: protocols < 0.2 are not supported";
112
    }
113
#endif
114
115
    return NULL;
116
}
117
118
static void load_module(struct boot_param *p, char *config) {
119
    size_t module_count;
120
    for (module_count = 0; ; module_count++) {
121
        if (config_get_value(config, module_count, "MODULE_PATH") == NULL)
122
            break;
123
    }
124
125
    if (module_count == 0) {
126
        return;
127
    }
128
129
    struct file_handle **modules = ext_mem_alloc_counted(module_count, sizeof(struct file_handle *));
130
131
    size_t total_size = 0;
132
    for (size_t i = 0; i < module_count; i++) {
133
        char *module_path = config_get_value(config, i, "MODULE_PATH");
134
135
        print("linux: Loading module `%#`...\n", module_path);
136
137
        struct file_handle *module_file = uri_open(module_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false);
138
        if (!module_file) {
139
            panic(true, "linux: failed to open module `%s`. Is the path correct?", module_path);
140
        }
141
142
        total_size = CHECKED_ADD(total_size, module_file->size,
143
            panic(true, "linux: Total module size overflow"));
144
145
        modules[i] = module_file;
146
    }
147
148
    p->module_size = total_size;
149
    p->module_base = ext_mem_alloc_type_aligned(
150
                    ALIGN_UP(p->module_size, 4096, panic(true, "linux: Alignment overflow")),
151
                    MEMMAP_KERNEL_AND_MODULES, 4096);
152
153
    size_t offset = 0;
154
    for (size_t i = 0; i < module_count; i++) {
155
        size_t module_size = modules[i]->size;
156
        fread(modules[i], p->module_base + offset, 0, module_size);
157
        fclose(modules[i]);
158
159
        char *module_path = config_get_value(config, i, "MODULE_PATH");
160
161
        tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
162
        tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
163
                    p->module_base + offset, module_size, "module_path: ", module_path);
164
165
        printv("linux: loaded module `%s` at %p, size %U\n", module_path,
166
               p->module_base + offset, (uint64_t)module_size);
167
        offset += module_size;
168
    }
169
170
    pmm_free(modules, module_count * sizeof(struct file_handle *));
171
}
172
173
static void prepare_device_tree_blob(struct boot_param *p) {
174
    void *dtb = p->dtb;
175
    int ret;
176
177
    // Delete all /memory@... nodes. Linux will use the given UEFI memory map
178
    // instead.
179
    while (true) {
180
        int offset = fdt_subnode_offset_namelen(dtb, 0, "memory@", 7);
181
182
        if (offset == -FDT_ERR_NOTFOUND) {
183
            break;
184
        }
185
186
        if (offset < 0) {
187
            panic(true, "linux: failed to find node: '%s'", fdt_strerror(offset));
188
        }
189
190
        ret = fdt_del_node(dtb, offset);
191
        if (ret < 0) {
192
            panic(true, "linux: failed to delete memory node: '%s'", fdt_strerror(ret));
193
        }
194
    }
195
196
    if (p->module_base) {
197
        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-start", (uint64_t)p->module_base);
198
        if (ret < 0) {
199
            panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret));
200
        }
201
202
        ret = fdt_set_chosen_uint64(dtb, "linux,initrd-end", (uint64_t)(p->module_base + p->module_size));
203
        if (ret < 0) {
204
            panic(true, "linux: cannot set initrd parameter: '%s'", fdt_strerror(ret));
205
        }
206
    }
207
208
    // Set the kernel command line arguments.
209
    ret = fdt_set_chosen_string(dtb, "bootargs", p->cmdline);
210
    if (ret < 0) {
211
        panic(true, "linux: failed to set bootargs: '%s'", fdt_strerror(ret));
212
    }
213
214
    // Tell Linux about the UEFI memory map and system table.
215
    ret = fdt_set_chosen_uint64(dtb, "linux,uefi-system-table", (uint64_t)gST);
216
    if (ret < 0) {
217
        panic(true, "linux: failed to set UEFI system table pointer: '%s'", fdt_strerror(ret));
218
    }
219
220
    // Report UEFI Secure Boot state via the /chosen FDT property. Values
221
    // match Linux's efi_secureboot_mode enum: 2 = disabled, 3 = enabled.
222
    ret = fdt_set_chosen_uint32(dtb, "linux,uefi-secure-boot", secure_boot_active ? 3 : 2);
223
    if (ret < 0) {
224
        panic(true, "linux: failed to set UEFI secure boot state: '%s'", fdt_strerror(ret));
225
    }
226
}
227
228
static void add_framebuffer(struct fb_info *fb) {
229
    struct screen_info *screen_info;
230
231
    EFI_STATUS alloc_ret = gBS->AllocatePool(EfiLoaderData, sizeof(*screen_info), (void **)&screen_info);
232
    if (alloc_ret != EFI_SUCCESS) {
233
        panic(true, "linux: failed to allocate screen info table");
234
    }
235
    memset(screen_info, 0, sizeof(*screen_info));
236
237
    screen_info->capabilities   = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS;
238
    screen_info->flags          = VIDEO_FLAGS_NOCURSOR;
239
    screen_info->lfb_base       = (uint32_t)fb->framebuffer_addr;
240
    screen_info->ext_lfb_base   = (uint32_t)(fb->framebuffer_addr >> 32);
241
    screen_info->lfb_size       = fb->framebuffer_pitch * fb->framebuffer_height;
242
    screen_info->lfb_width      = fb->framebuffer_width;
243
    screen_info->lfb_height     = fb->framebuffer_height;
244
    screen_info->lfb_depth      = fb->framebuffer_bpp;
245
    screen_info->lfb_linelength = fb->framebuffer_pitch;
246
    screen_info->red_size       = fb->red_mask_size;
247
    screen_info->red_pos        = fb->red_mask_shift;
248
    screen_info->green_size     = fb->green_mask_size;
249
    screen_info->green_pos      = fb->green_mask_shift;
250
    screen_info->blue_size      = fb->blue_mask_size;
251
    screen_info->blue_pos       = fb->blue_mask_shift;
252
253
    screen_info->orig_video_isVGA = VIDEO_TYPE_EFI;
254
255
    EFI_GUID screen_info_table_guid = {0xe03fc20a, 0x85dc, 0x406e, {0xb9, 0x0e, 0x4a, 0xb5, 0x02, 0x37, 0x1d, 0x95}};
256
    EFI_STATUS ret = gBS->InstallConfigurationTable(&screen_info_table_guid, screen_info);
257
258
    if (ret != EFI_SUCCESS) {
259
        panic(true, "linux: failed to install screen info configuration table: '%X'", (uint64_t)ret);
260
    }
261
}
262
263
static void prepare_efi_tables(struct boot_param *p, char *config) {
264
    (void)p;
265
    EFI_STATUS ret = 0;
266
267
    {
268
        size_t req_width = 0, req_height = 0, req_bpp = 0;
269
270
        char *resolution = config_get_value(config, 0, "RESOLUTION");
271
        if (resolution != NULL) {
272
            parse_resolution(&req_width, &req_height, &req_bpp, resolution);
273
        }
274
275
        struct fb_info *fbs;
276
        size_t fbs_count;
277
278
        term_notready();
279
280
        fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp, false, false);
281
282
        // TODO(qookie): Let the user pick a framebuffer if there's > 1
283
        if (fbs_count > 0) {
284
            add_framebuffer(&fbs[0]);
285
        }
286
    }
287
288
289
    {
290
        struct linux_efi_memreserve *rsv;
291
292
        ret = gBS->AllocatePool(EfiLoaderData, sizeof(*rsv), (void **)&rsv);
293
        if (ret != EFI_SUCCESS) {
294
            panic(true, "linux: failed to allocate memory reservation table");
295
        }
296
        memset(rsv, 0, sizeof(*rsv));
297
298
        rsv->size = 0;
299
        rsv->count = 0;
300
        rsv->next = 0;
301
302
        EFI_GUID memreserve_table_guid = {0x888eb0c6, 0x8ede, 0x4ff5, {0xa8, 0xf0, 0x9a, 0xee, 0x5c, 0xb9, 0x77, 0xc2}};
303
        ret = gBS->InstallConfigurationTable(&memreserve_table_guid, rsv);
304
305
        if (ret != EFI_SUCCESS) {
306
            panic(true, "linux: failed to install memory reservation configuration table: '%X'", (uint64_t)ret);
307
        }
308
    }
309
310
    if (p->module_base) {
311
        struct linux_efi_initrd *initrd_table;
312
313
        ret = gBS->AllocatePool(EfiLoaderData, sizeof(*initrd_table), (void **)&initrd_table);
314
        if (ret != EFI_SUCCESS) {
315
            panic(true, "linux: failed to allocate Linux initrd table");
316
        }
317
318
        initrd_table->base = (UINTN)p->module_base;
319
        initrd_table->size = p->module_size;
320
321
        EFI_GUID initrd_table_guid = { 0x5568e427, 0x68fc, 0x4f3d, { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68}};
322
        ret = gBS->InstallConfigurationTable(&initrd_table_guid, initrd_table);
323
        if (ret != EFI_SUCCESS) {
324
            panic(true, "linux: failed to install initrd\n");
325
        }
326
    }
327
328
    {
329
        size_t buff_size = sizeof(struct linux_efi_boot_memmap) + efi_mmap_size + 4096;
330
331
        ret = gBS->AllocatePool(EfiLoaderData, buff_size, (void **)&p->memmap);
332
        if (ret != EFI_SUCCESS) {
333
            panic(true, "linux: failed to allocate UEFI memory map");
334
        }
335
336
        p->memmap->buff_size = buff_size;
337
338
        EFI_GUID memmap_table_guid = { 0x800f683f, 0xd08b, 0x423a, { 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4}};
339
        ret = gBS->InstallConfigurationTable(&memmap_table_guid, p->memmap);
340
        if (ret != EFI_SUCCESS) {
341
            panic(true, "linux: failed to install UEFI memory map");
342
        }
343
    }
344
345
    linux_install_efi_tpm_event_log();
346
    efi_exit_boot_services();
347
}
348
349
static void prepare_mmap(struct boot_param *p) {
350
    {
351
        void *dtb = p->dtb;
352
        int ret = fdt_set_chosen_uint64(dtb, "linux,uefi-mmap-start", (uint64_t)efi_mmap);
353
        if (ret < 0) {
354
            panic(true, "linux: failed to set UEFI memory map pointer: '%s'", fdt_strerror(ret));
355
        }
356
357
        ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-size", efi_mmap_size);
358
        if (ret < 0) {
359
            panic(true, "linux: failed to set UEFI memory map size: '%s'", fdt_strerror(ret));
360
        }
361
362
        ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-size", efi_desc_size);
363
        if (ret < 0) {
364
            panic(true, "linux: failed to set UEFI memory map descriptor size: '%s'", fdt_strerror(ret));
365
        }
366
367
        ret = fdt_set_chosen_uint32(dtb, "linux,uefi-mmap-desc-ver", efi_desc_ver);
368
        if (ret < 0) {
369
            panic(true, "linux: failed to set UEFI memory map descriptor version: '%s'", fdt_strerror(ret));
370
        }
371
    }
372
373
    p->memmap->map_size  = efi_mmap_size;
374
    p->memmap->desc_size = efi_desc_size;
375
    p->memmap->desc_ver  = efi_desc_ver;
376
    p->memmap->map_key   = efi_mmap_key;
377
378
    size_t efi_mmap_entry_count = efi_mmap_size / efi_desc_size;
379
    for (size_t i = 0; i < efi_mmap_entry_count; i++) {
380
        EFI_MEMORY_DESCRIPTOR *entry = (void *)efi_mmap + i * efi_desc_size;
381
382
        if (entry->Attribute & EFI_MEMORY_RUNTIME) {
383
	    // LoongArch kernel requires the virtual address stays in the
384
	    // privileged, direct-mapped window
385
	    #if defined(__loongarch__)
386
       	        entry->VirtualStart = entry->PhysicalStart | (0x8ULL << 60);
387
            #else
388
	        entry->VirtualStart = entry->PhysicalStart;
389
	    #endif
390
        }
391
    }
392
393
    memcpy(&p->memmap->descs, efi_mmap, efi_mmap_size);
394
395
    EFI_STATUS status = gRT->SetVirtualAddressMap(efi_mmap_size, efi_desc_size, efi_desc_ver, efi_mmap);
396
    if (status != EFI_SUCCESS) {
397
        panic(false, "linux: failed to set UEFI virtual address map: '%X'", (uint64_t)status);
398
    }
399
}
400
401
noreturn static void jump_to_kernel(struct boot_param *p) {
402
#if defined(__riscv)
403
    printv("linux: bsp hart %U, device tree blob at %p\n", (uint64_t)bsp_hartid, p->dtb);
404
405
    void (*kernel_entry)(uint64_t hartid, uint64_t dtb) = p->kernel_base;
406
    asm ("csrci   sstatus, 0x2\n\t"
407
         "csrw    sie, zero\n\t"
408
         "csrw    satp, zero\n\t"
409
         "sfence.vma\n\t"
410
         "fence.i\n\t");
411
    kernel_entry(bsp_hartid, (uint64_t)p->dtb);
412
#elif defined(__aarch64__)
413
    printv("linux: device tree blob at %p\n", p->dtb);
414
415
    void (*kernel_entry)(uint64_t dtb, uint64_t res0, uint64_t res1, uint64_t res2) = p->kernel_base;
416
417
    // Clean caches for the loaded kernel image
418
    clean_dcache_poc((uintptr_t)p->kernel_base, (uintptr_t)p->kernel_base + p->kernel_size);
419
    inval_icache_pou((uintptr_t)p->kernel_base, (uintptr_t)p->kernel_base + p->kernel_size);
420
421
    asm ("msr daifset, 0xF");
422
423
    // Disable MMU
424
    if (current_el() == 2) {
425
        uint64_t sctlr;
426
        asm volatile ("mrs %0, sctlr_el2" : "=r"(sctlr));
427
        sctlr &= ~1;
428
        asm volatile ("msr sctlr_el2, %0" :: "r"(sctlr));
429
    } else {
430
        uint64_t sctlr;
431
        asm volatile ("mrs %0, sctlr_el1" : "=r"(sctlr));
432
        sctlr &= ~1;
433
        asm volatile ("msr sctlr_el1, %0" :: "r"(sctlr));
434
    }
435
    asm volatile ("isb");
436
437
    kernel_entry((uint64_t)p->dtb, 0, 0, 0);
438
#elif defined(__loongarch__)
439
// LoongArch kernel used to store virtual address in header.kernel_entry
440
// clearing the high 16bits ensures compatibility
441
#define TO_PHYS(addr) ((addr) & ((1ULL << 48) - 1))
442
#define CSR_DMW_PLV0  1ULL
443
#define CSR_DMW0_VSEG 0x8000ULL
444
#define CSR_DMW0_BASE (CSR_DMW0_VSEG << 48)
445
#define CSR_DMW0_INIT (CSR_DMW0_BASE | CSR_DMW_PLV0)
446
#define CSR_DMW1_MAT  (1 << 4)
447
#define CSR_DMW1_VSEG 0x9000ULL
448
#define CSR_DMW1_BASE (CSR_DMW1_VSEG << 48)
449
#define CSR_DMW1_INIT (CSR_DMW1_BASE | CSR_DMW1_MAT | CSR_DMW_PLV0)
450
#define CSR_DMW2_VSEG 0xa000ULL
451
#define CSR_DMW2_MAT  (2 << 4)
452
#define CSR_DMW2_BASE (CSR_DMW2_VSEG << 48)
453
#define CSR_DMW2_INIT (CSR_DMW2_BASE | CSR_DMW2_MAT | CSR_DMW_PLV0)
454
#define CSR_DMW3_INIT 0
455
456
    struct linux_header *header = p->kernel_base;
457
    void (*kernel_entry)(uint64_t efi_boot, uint64_t cmdline, uint64_t st);
458
    kernel_entry = p->kernel_base + (TO_PHYS(header->kernel_entry) - header->load_offset);
459
460
    asm volatile ("csrxchg $r0, %0, 0x0" :: "r" (0x4) : "memory");
461
    asm volatile ("csrwr   %0,  0x180"   :: "r" (CSR_DMW0_INIT) : "memory");
462
    asm volatile ("csrwr   %0,  0x181"   :: "r" (CSR_DMW1_INIT) : "memory");
463
    asm volatile ("csrwr   %0,  0x182"   :: "r" (CSR_DMW2_INIT) : "memory");
464
    asm volatile ("csrwr   %0,  0x183"   :: "r" (CSR_DMW3_INIT) : "memory");
465
    kernel_entry(1, (uint64_t)p->cmdline, (uint64_t)gST);
466
#endif
467
    __builtin_unreachable();
468
}
469
470
noreturn void linux_load(char *config, char *cmdline) {
471
    struct boot_param p;
472
    memset(&p, 0, sizeof(p));
473
    p.cmdline = cmdline;
474
475
    if (cmdline != NULL) {
476
        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
477
                    cmdline, strlen(cmdline), "cmdline: ", cmdline);
478
    }
479
480
    struct file_handle *kernel_file;
481
482
    char *kernel_path = config_get_value(config, 0, "PATH");
483
    if (kernel_path == NULL) {
484
        kernel_path = config_get_value(config, 0, "KERNEL_PATH");
485
    }
486
    if (kernel_path == NULL) {
487
        panic(true, "linux: Kernel path not specified");
488
    }
489
490
    print("linux: Loading kernel `%#`...\n", kernel_path);
491
492
    if ((kernel_file = uri_open(kernel_path, MEMMAP_BOOTLOADER_RECLAIMABLE, false)) == NULL) {
493
        panic(true, "linux: failed to open kernel `%s`. Is the path correct?", kernel_path);
494
    }
495
496
    p.kernel_size = kernel_file->size;
497
498
    if (p.kernel_size < sizeof(struct linux_header)) {
499
        panic(true, "linux: kernel too small to contain a valid header");
500
    }
501
502
    struct linux_header tmp_hdr;
503
    fread(kernel_file, &tmp_hdr, 0, sizeof(tmp_hdr));
504
505
    const char *reason = verify_kernel(&tmp_hdr);
506
    if (reason)
507
        panic(true, "linux: invalid kernel image: %s", reason);
508
509
    // Use image_size from kernel header for total memory including BSS
510
    size_t kernel_alloc_size = p.kernel_size;
511
    if (tmp_hdr.image_size > kernel_alloc_size) {
512
        kernel_alloc_size = tmp_hdr.image_size;
513
    }
514
515
#if defined(__riscv) || defined(__aarch64__)
516
    size_t text_offset = tmp_hdr.text_offset;
517
#else
518
    size_t text_offset = 0;
519
#endif
520
521
    p.kernel_base = ext_mem_alloc_type_aligned(
522
                ALIGN_UP(CHECKED_ADD(text_offset, kernel_alloc_size, panic(true, "linux: Kernel size overflow")), 4096, panic(true, "linux: Alignment overflow")),
523
                MEMMAP_KERNEL_AND_MODULES, 2 * 1024 * 1024);
524
    p.kernel_base += text_offset;
525
    fread(kernel_file, p.kernel_base, 0, p.kernel_size);
526
    fclose(kernel_file);
527
    printv("linux: loaded kernel `%s` at %p, size %U\n", kernel_path, p.kernel_base, (uint64_t)p.kernel_size);
528
529
    tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
530
    tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
531
                p.kernel_base, p.kernel_size, "path: ", kernel_path);
532
533
    load_module(&p, config);
534
535
    p.dtb = get_device_tree_blob(config, 0x1000, true);
536
537
    prepare_device_tree_blob(&p);
538
539
    prepare_efi_tables(&p, config);
540
541
    prepare_mmap(&p);
542
543
    jump_to_kernel(&p);
544
}
545
546
#endif
tab: 248 wrap: offon