summaryrefslogtreecommitdiff
path: root/src/sd-boot/linux.c
blob: 809c69310e32bca9275a52bbcd610202ba96826e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/

/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Copyright (C) 2015 Kay Sievers <kay@vrfy.org>
 */

#include <efi.h>
#include <efilib.h>

#include "util.h"
#include "linux.h"

#define SETUP_MAGIC             0x53726448      /* "HdrS" */
struct SetupHeader {
        UINT8 boot_sector[0x01f1];
        UINT8 setup_secs;
        UINT16 root_flags;
        UINT32 sys_size;
        UINT16 ram_size;
        UINT16 video_mode;
        UINT16 root_dev;
        UINT16 signature;
        UINT16 jump;
        UINT32 header;
        UINT16 version;
        UINT16 su_switch;
        UINT16 setup_seg;
        UINT16 start_sys;
        UINT16 kernel_ver;
        UINT8 loader_id;
        UINT8 load_flags;
        UINT16 movesize;
        UINT32 code32_start;
        UINT32 ramdisk_start;
        UINT32 ramdisk_len;
        UINT32 bootsect_kludge;
        UINT16 heap_end;
        UINT8 ext_loader_ver;
        UINT8 ext_loader_type;
        UINT32 cmd_line_ptr;
        UINT32 ramdisk_max;
        UINT32 kernel_alignment;
        UINT8 relocatable_kernel;
        UINT8 min_alignment;
        UINT16 xloadflags;
        UINT32 cmdline_size;
        UINT32 hardware_subarch;
        UINT64 hardware_subarch_data;
        UINT32 payload_offset;
        UINT32 payload_length;
        UINT64 setup_data;
        UINT64 pref_address;
        UINT32 init_size;
        UINT32 handover_offset;
} __attribute__((packed));

#ifdef __x86_64__
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup);
static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) {
        handover_f handover;

        asm volatile ("cli");
        handover = (handover_f)((UINTN)setup->code32_start + 512 + setup->handover_offset);
        handover(image, ST, setup);
}
#else
typedef VOID(*handover_f)(VOID *image, EFI_SYSTEM_TABLE *table, struct SetupHeader *setup) __attribute__((regparm(0)));
static inline VOID linux_efi_handover(EFI_HANDLE image, struct SetupHeader *setup) {
        handover_f handover;

        handover = (handover_f)((UINTN)setup->code32_start + setup->handover_offset);
        handover(image, ST, setup);
}
#endif

EFI_STATUS linux_exec(EFI_HANDLE *image,
                      CHAR8 *cmdline, UINTN cmdline_len,
                      UINTN linux_addr,
                      UINTN initrd_addr, UINTN initrd_size) {
        struct SetupHeader *image_setup;
        struct SetupHeader *boot_setup;
        EFI_PHYSICAL_ADDRESS addr;
        EFI_STATUS err;

        image_setup = (struct SetupHeader *)(linux_addr);
        if (image_setup->signature != 0xAA55 || image_setup->header != SETUP_MAGIC)
                return EFI_LOAD_ERROR;

        if (image_setup->version < 0x20b || !image_setup->relocatable_kernel)
                return EFI_LOAD_ERROR;

        addr = 0x3fffffff;
        err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData,
                                EFI_SIZE_TO_PAGES(0x4000), &addr);
        if (EFI_ERROR(err))
                return err;
        boot_setup = (struct SetupHeader *)(UINTN)addr;
        ZeroMem(boot_setup, 0x4000);
        CopyMem(boot_setup, image_setup, sizeof(struct SetupHeader));
        boot_setup->loader_id = 0xff;

        boot_setup->code32_start = (UINT32)linux_addr + (image_setup->setup_secs+1) * 512;

        if (cmdline) {
                addr = 0xA0000;
                err = uefi_call_wrapper(BS->AllocatePages, 4, AllocateMaxAddress, EfiLoaderData,
                                        EFI_SIZE_TO_PAGES(cmdline_len + 1), &addr);
                if (EFI_ERROR(err))
                        return err;
                CopyMem((VOID *)(UINTN)addr, cmdline, cmdline_len);
                ((CHAR8 *)addr)[cmdline_len] = 0;
                boot_setup->cmd_line_ptr = (UINT32)addr;
        }

        boot_setup->ramdisk_start = (UINT32)initrd_addr;
        boot_setup->ramdisk_len = (UINT32)initrd_size;

        linux_efi_handover(image, boot_setup);
        return EFI_LOAD_ERROR;
}