-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloader.cpp
177 lines (143 loc) · 6.05 KB
/
loader.cpp
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include <cstring>
#include <fstream>
#include <iostream>
#include "linuxboot.h"
#include "runslice.h"
static void fill_e820_table(const Options& options, uintptr_t mmconfig_base, boot_params& params)
{
constexpr int E820_RAM = 1;
constexpr int E820_RESERVED = 2;
// Size of the PCIe MMCONFIG region, assuming that it is contiguous for all buses.
constexpr size_t MMCONFIG_SIZE = 0x20000000;
// XXX: In order to boot secondary CPUs, Linux needs a small region of real-mode (<1MiB PA)
// memory, allocated on boot by reserve_real_mode(). We also happen to know that after boot,
// Linux unconditionally reserves (and thus avoids touching) the first 1MiB of memory, so we
// should be safe to use it here.
params.e820_table[0] = { .addr = 0, .size = 639 * 1024, .type = E820_RAM };
params.e820_table[1] = { .addr = mmconfig_base, .size = MMCONFIG_SIZE, .type = E820_RESERVED };
params.e820_table[2] = { .addr = options.rambase, .size = options.ramsize, .type = E820_RAM };
params.e820_entries = 3;
static_assert(3 <= E820_MAX_ENTRIES_ZEROPAGE);
}
bool read_to_devmem(std::ifstream& file, uint64_t offset, void* dest, size_t size)
{
// Linux doesn't permit I/O directly to a mapping of /dev/mem, so we must use a temporary
// buffer.
if (!file.seekg(offset, std::ios::beg))
return false;
while (size > 0) {
char buf[0x8000];
const size_t chunk = std::min(size, sizeof(buf));
if (!file.read(buf, chunk))
return false;
memcpy(dest, buf, chunk);
dest = static_cast<char*>(dest) + chunk;
size -= chunk;
}
return true;
}
bool load_linux(
const Options& options,
void* slice_ram,
uintptr_t& kernel_entry_phys, // Out: entry point at which to jump to kernel in 64-bit mode
uintptr_t& kernel_entry_arg) // Out: argument to be passed to kernel entry (in RSI)
{
static constexpr size_t header_offset = offsetof(boot_params, hdr);
static_assert(header_offset == 0x1f1);
setup_header header;
// open the kernel image, and determine its size
std::ifstream kernel_file(options.kernel_path, std::ios::binary | std::ios::in);
if (!kernel_file.is_open()) {
perror("Failed to open kernel image");
return false;
}
kernel_file.seekg(0, std::ios::end);
size_t kernel_file_size = kernel_file.tellg();
if (kernel_file_size < header_offset + sizeof(header))
{
std::cerr << "Invalid kernel image: file is far too small" << std::endl;
return false;
}
// Read and check the setup header.
if (!kernel_file.seekg(header_offset)
|| !kernel_file.read(reinterpret_cast<char*>(&header), sizeof(header))) {
perror("Failed to read kernel image header");
return false;
}
if (header.header != 0x53726448 || header.version < 0x20c || header.setup_sects == 0) {
std::cerr << "Invalid or too old kernel image" << std::endl;
return false;
}
if (!header.relocatable_kernel ||
!(header.xloadflags & (XLF_KERNEL_64 | XLF_CAN_BE_LOADED_ABOVE_4G))) {
std::cerr << "Kernel image is not relocatable" << std::endl;
return false;
}
// Sanity-check size of file.
size_t kernel_image_offset = 512 * ((header.setup_sects ? header.setup_sects : 4) + 1);
if (kernel_image_offset >= kernel_file_size) {
std::cerr << "Invalid kernel image (file has been truncated)" << std::endl;
return false;
}
// Load the kernel first.
uintptr_t loadaddr_phys = ALIGN_UP(options.rambase, header.kernel_alignment);
printf("Loading Linux at 0x%lx\n", loadaddr_phys);
char* loadaddr_virt = reinterpret_cast<char*>(slice_ram) + (loadaddr_phys - options.rambase);
if (!read_to_devmem(kernel_file, kernel_image_offset, loadaddr_virt, kernel_file_size - kernel_image_offset)) {
perror("Failed to read kernel image");
return false;
}
kernel_entry_phys = loadaddr_phys + 0x200;
// Leave space required for early boot code.
{
size_t header_size = ALIGN_UP(header.init_size, 0x1000);
loadaddr_phys += header_size;
loadaddr_virt += header_size;
}
// Boot params ("zero page") follows the kernel.
struct boot_params *boot_params = reinterpret_cast<struct boot_params*>(loadaddr_virt);
kernel_entry_arg = loadaddr_phys;
memset(boot_params, 0, sizeof(*boot_params));
boot_params->hdr = header;
loadaddr_phys += sizeof(*boot_params);
loadaddr_virt += sizeof(*boot_params);
// ACPI tables.
uintptr_t mmconfig_base = 0;
boot_params->acpi_rsdp_addr = build_acpi(options, loadaddr_phys, loadaddr_virt, mmconfig_base);
if (boot_params->acpi_rsdp_addr == 0)
{
return false;
}
// Command line.
if (options.kernel_cmdline) {
strcpy(loadaddr_virt, options.kernel_cmdline);
boot_params->hdr.cmd_line_ptr = static_cast<uint32_t>(loadaddr_phys);
boot_params->ext_cmd_line_ptr = loadaddr_phys >> 32;
size_t cmdline_size = ALIGN_UP(strlen(options.kernel_cmdline) + 1, 8);
loadaddr_phys += cmdline_size;
loadaddr_virt += cmdline_size;
}
// Load initrd if present.
if (options.initrd_path)
{
loadaddr_phys = ALIGN_UP(loadaddr_phys, 0x1000);
loadaddr_virt = reinterpret_cast<char*>(slice_ram) + (loadaddr_phys - options.rambase);
std::ifstream initrd_file(options.initrd_path, std::ios::binary | std::ios::in);
if (!initrd_file.is_open()) {
perror("Failed to open initrd");
return false;
}
initrd_file.seekg(0, std::ios::end);
size_t initrd_size = initrd_file.tellg();
if (!read_to_devmem(initrd_file, 0, loadaddr_virt, initrd_size)) {
perror("Failed to read initrd");
return false;
}
boot_params->hdr.ramdisk_size = initrd_size;
boot_params->hdr.ramdisk_image = static_cast<uint32_t>(loadaddr_phys);
boot_params->ext_ramdisk_image = loadaddr_phys >> 32;
}
boot_params->hdr.type_of_loader = 0xff;
fill_e820_table(options, mmconfig_base, *boot_params);
return true;
}