1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2015 Toomas Soome <tsoome@me.com>
14 */
15
16 /*
17 * Primitive linux loader, at the moment only intended to load memtest86+.bin.
18 *
19 * Note the linux kernel location conflicts with loader, so we need to
20 * read in to temporary space and relocate on exec, when btx is stopped.
21 */
22 #include <sys/cdefs.h>
23 #include <sys/stat.h>
24 #include <stand.h>
25 #include <machine/metadata.h>
26 #include <machine/pc/bios.h>
27
28 #include "linux.h"
29 #include "bootstrap.h"
30 #include "libi386.h"
31 #include "btxv86.h"
32
33 static int linux_loadkernel(char *, u_int64_t, struct preloaded_file **);
34 static int linux_loadinitrd(char *, u_int64_t, struct preloaded_file **);
35 static int linux_exec(struct preloaded_file *);
36 static int linux_execinitrd(struct preloaded_file *);
37
38 struct file_format linux = { linux_loadkernel, linux_exec };
39 struct file_format linux_initrd = { linux_loadinitrd, linux_execinitrd };
40
41 uint32_t linux_text_len;
42 uint32_t linux_data_tmp_addr;
43 uint32_t linux_data_real_addr;
44 static size_t max_cmdline_size;
45
46 static void
test_addr(uint64_t addr,uint64_t length,vm_offset_t * result)47 test_addr(uint64_t addr, uint64_t length, vm_offset_t *result)
48 {
49 vm_offset_t candidate;
50
51 if (addr + length >= 0xa0000)
52 length = 0xa0000 - addr;
53
54 candidate = addr + length - (LINUX_CL_OFFSET + max_cmdline_size);
55 if (candidate > LINUX_OLD_REAL_MODE_ADDR)
56 candidate = LINUX_OLD_REAL_MODE_ADDR;
57 if (candidate < addr)
58 return;
59
60 if (candidate > *result || *result == (vm_offset_t)-1)
61 *result = candidate;
62 }
63
64 static vm_offset_t
find_real_addr(struct preloaded_file * fp)65 find_real_addr(struct preloaded_file *fp)
66 {
67 struct bios_smap *smap;
68 struct file_metadata *md;
69 int entries, i;
70 vm_offset_t candidate = -1;
71
72 md = file_findmetadata(fp, MODINFOMD_SMAP);
73 if (md == NULL) {
74 printf("no memory smap\n");
75 return (candidate);
76 }
77 entries = md->md_size / sizeof (struct bios_smap);
78 smap = (struct bios_smap *)md->md_data;
79 for (i = 0; i < entries; i++) {
80 if (smap[i].type != SMAP_TYPE_MEMORY)
81 continue;
82 if (smap[i].base >= 0xa0000)
83 continue;
84 test_addr(smap[i].base, smap[i].length, &candidate);
85 }
86 return (candidate);
87 }
88
89 static int
linux_loadkernel(char * filename,uint64_t dest,struct preloaded_file ** result)90 linux_loadkernel(char *filename, uint64_t dest, struct preloaded_file **result)
91 {
92 struct linux_kernel_header lh;
93 struct preloaded_file *fp;
94 struct stat sb;
95 ssize_t n;
96 int fd, error = 0;
97 int setup_sects, linux_big;
98 unsigned long data, text;
99 vm_offset_t mem;
100
101 if (filename == NULL)
102 return (EFTYPE);
103
104 /* is kernel already loaded? */
105 fp = file_findfile(NULL, NULL);
106 if (fp != NULL)
107 return (EFTYPE);
108
109 if ((fd = open(filename, O_RDONLY)) == -1)
110 return (errno);
111
112 if (fstat(fd, &sb) != 0) {
113 printf("stat failed\n");
114 error = errno;
115 close(fd);
116 return (error);
117 }
118
119 n = read(fd, &lh, sizeof (lh));
120 if (n != sizeof (lh)) {
121 printf("error reading kernel header\n");
122 error = EIO;
123 goto end;
124 }
125
126 if (lh.boot_flag != BOOTSEC_SIGNATURE) {
127 printf("invalid magic number\n");
128 error = EFTYPE;
129 goto end;
130 }
131
132 setup_sects = lh.setup_sects;
133 linux_big = 0;
134 max_cmdline_size = 256;
135
136 if (setup_sects > LINUX_MAX_SETUP_SECTS) {
137 printf("too many setup sectors\n");
138 error = EFTYPE;
139 goto end;
140 }
141
142 fp = file_alloc();
143 if (fp == NULL) {
144 error = ENOMEM;
145 goto end;
146 }
147
148 bios_addsmapdata(fp);
149
150 if (lh.header == LINUX_MAGIC_SIGNATURE && lh.version >= 0x0200) {
151 linux_big = lh.loadflags & LINUX_FLAG_BIG_KERNEL;
152 lh.type_of_loader = LINUX_BOOT_LOADER_TYPE;
153
154 if (lh.version >= 0x0206)
155 max_cmdline_size = lh.cmdline_size + 1;
156
157 linux_data_real_addr = find_real_addr(fp);
158 if (linux_data_real_addr == -1) {
159 printf("failed to detect suitable low memory\n");
160 file_discard(fp);
161 error = ENOMEM;
162 goto end;
163 }
164 if (lh.version >= 0x0201) {
165 lh.heap_end_ptr = LINUX_HEAP_END_OFFSET;
166 lh.loadflags |= LINUX_FLAG_CAN_USE_HEAP;
167 }
168 if (lh.version >= 0x0202) {
169 lh.cmd_line_ptr = linux_data_real_addr +
170 LINUX_CL_OFFSET;
171 } else {
172 lh.cl_magic = LINUX_CL_MAGIC;
173 lh.cl_offset = LINUX_CL_OFFSET;
174 lh.setup_move_size = LINUX_CL_OFFSET + max_cmdline_size;
175 }
176 } else {
177 /* old kernel */
178 lh.cl_magic = LINUX_CL_MAGIC;
179 lh.cl_offset = LINUX_CL_OFFSET;
180 setup_sects = LINUX_DEFAULT_SETUP_SECTS;
181 linux_data_real_addr = LINUX_OLD_REAL_MODE_ADDR;
182 }
183 if (setup_sects == 0)
184 setup_sects = LINUX_DEFAULT_SETUP_SECTS;
185
186 data = setup_sects << 9;
187 text = sb.st_size - data - 512;
188
189 /* temporary location of real mode part */
190 linux_data_tmp_addr = LINUX_BZIMAGE_ADDR + text;
191
192 if (!linux_big && text > linux_data_real_addr - LINUX_ZIMAGE_ADDR) {
193 printf("Linux zImage is too big, use bzImage instead\n");
194 file_discard(fp);
195 error = EFBIG;
196 goto end;
197 }
198 printf(" [Linux-%s, setup=0x%x, size=0x%x]\n",
199 (linux_big ? "bzImage" : "zImage"), data, text);
200
201 /* copy real mode part to place */
202 i386_copyin(&lh, linux_data_tmp_addr, sizeof (lh));
203 n = data + 512 - sizeof (lh);
204 if (archsw.arch_readin(fd, linux_data_tmp_addr+sizeof (lh), n) != n) {
205 printf("failed to read %s\n", filename);
206 file_discard(fp);
207 error = errno;
208 goto end;
209 }
210
211 /* Clear the heap space. */
212 if (lh.header != LINUX_MAGIC_SIGNATURE || lh.version < 0x0200) {
213 memset(PTOV(linux_data_tmp_addr + ((setup_sects + 1) << 9)),
214 0, (LINUX_MAX_SETUP_SECTS - setup_sects - 1) << 9);
215 }
216
217 mem = LINUX_BZIMAGE_ADDR;
218
219 if (archsw.arch_readin(fd, mem, text) != text) {
220 printf("failed to read %s\n", filename);
221 file_discard(fp);
222 error = EIO;
223 goto end;
224 }
225
226 fp->f_name = strdup(filename);
227 if (linux_big)
228 fp->f_type = strdup("Linux bzImage");
229 else
230 fp->f_type = strdup("Linux zImage");
231
232 /*
233 * NOTE: f_addr and f_size is used here as hint for module
234 * allocation, as module location will be f_addr + f_size.
235 */
236 fp->f_addr = linux_data_tmp_addr;
237 fp->f_size = LINUX_SETUP_MOVE_SIZE;
238 linux_text_len = text;
239
240 /*
241 * relocater_data is space allocated in relocater_tramp.S
242 * There is space for 3 instances + terminating zero in case
243 * all 3 entries are used.
244 */
245 if (linux_big == 0) {
246 relocater_data[0].src = LINUX_BZIMAGE_ADDR;
247 relocater_data[0].dest = LINUX_ZIMAGE_ADDR;
248 relocater_data[0].size = text;
249 relocater_data[1].src = linux_data_tmp_addr;
250 relocater_data[1].dest = linux_data_real_addr;
251 relocater_data[1].size = LINUX_SETUP_MOVE_SIZE;
252 /* make sure the next entry is zeroed */
253 relocater_data[2].src = 0;
254 relocater_data[2].dest = 0;
255 relocater_data[2].size = 0;
256 } else {
257 relocater_data[0].src = linux_data_tmp_addr;
258 relocater_data[0].dest = linux_data_real_addr;
259 relocater_data[0].size = LINUX_SETUP_MOVE_SIZE;
260 /* make sure the next entry is zeroed */
261 relocater_data[1].src = 0;
262 relocater_data[1].dest = 0;
263 relocater_data[1].size = 0;
264 }
265
266 *result = fp;
267 setenv("kernelname", fp->f_name, 1);
268 end:
269 close(fd);
270 return (error);
271 }
272
273 static int
linux_exec(struct preloaded_file * fp)274 linux_exec(struct preloaded_file *fp)
275 {
276 struct linux_kernel_header *lh = (struct linux_kernel_header *)
277 PTOV(linux_data_tmp_addr);
278 struct preloaded_file *mfp = fp->f_next;
279 char *arg, *vga;
280 char *src, *dst;
281 int linux_big;
282 uint32_t moveto, max_addr;
283 uint16_t segment;
284 struct i386_devdesc *rootdev;
285
286 if (strcmp(fp->f_type, "Linux bzImage") == 0)
287 linux_big = 1;
288 else if (strcmp(fp->f_type, "Linux zImage") == 0)
289 linux_big = 0;
290 else
291 return (EFTYPE);
292
293 i386_getdev((void **)(&rootdev), fp->f_name, NULL);
294 if (rootdev != NULL)
295 relocator_edx = bd_unit2bios(rootdev->d_unit);
296
297 /*
298 * command line
299 * if not set in fp, read from boot-args env
300 */
301 if (fp->f_args == NULL)
302 fp->f_args = getenv("boot-args");
303 arg = fp->f_args; /* it can still be NULL */
304
305 /* video mode selection */
306 if (arg && (vga = strstr(arg, "vga=")) != NULL) {
307 char *value = vga + 4;
308 uint16_t vid_mode;
309
310 if (strncmp(value, "normal", 6) < 1)
311 vid_mode = LINUX_VID_MODE_NORMAL;
312 else if (strncmp(value, "ext", 3) < 1)
313 vid_mode = LINUX_VID_MODE_EXTENDED;
314 else if (strncmp(value, "ask", 3) < 1)
315 vid_mode = LINUX_VID_MODE_ASK;
316 else {
317 long mode;
318 errno = 0;
319
320 /*
321 * libstand sets ERANGE as only error case;
322 * however, the actual value is 16bit, so
323 * additional check is needed.
324 */
325 mode = strtol(value, NULL, 0);
326 if (errno != 0 || mode >> 16 != 0 || mode == 0) {
327 printf("bad value for video mode\n");
328 return (EINTR);
329 }
330 vid_mode = (uint16_t) mode;
331 }
332 lh->vid_mode = vid_mode;
333 }
334
335 src = arg;
336 dst = (char *)PTOV(linux_data_tmp_addr + LINUX_CL_OFFSET);
337 if (src != NULL) {
338 while (*src != 0 && dst < (char *)
339 PTOV(linux_data_tmp_addr + LINUX_CL_END_OFFSET))
340 *(dst++) = *(src++);
341 }
342 *dst = 0;
343
344 /* set up module relocation */
345 if (mfp != NULL) {
346 moveto = (bios_extmem / 1024 + 0x400) << 10;
347 moveto = (moveto - mfp->f_size) & 0xfffff000;
348 max_addr = (lh->header == LINUX_MAGIC_SIGNATURE &&
349 lh->version >= 0x0203 ?
350 lh->initrd_addr_max : LINUX_INITRD_MAX_ADDRESS);
351 if (moveto + mfp->f_size >= max_addr)
352 moveto = (max_addr - mfp->f_size) & 0xfffff000;
353
354 /*
355 * XXX: Linux 2.3.xx has a bug in the memory range check,
356 * so avoid the last page.
357 * XXX: Linux 2.2.xx has a bug in the memory range check,
358 * which is worse than that of Linux 2.3.xx, so avoid the
359 * last 64kb. *sigh*
360 */
361 moveto -= 0x10000;
362
363 /* need to relocate initrd first */
364 if (linux_big == 0) {
365 relocater_data[2].src = relocater_data[1].src;
366 relocater_data[2].dest = relocater_data[1].dest;
367 relocater_data[2].size = relocater_data[1].size;
368 relocater_data[1].src = relocater_data[0].src;
369 relocater_data[1].dest = relocater_data[0].dest;
370 relocater_data[1].size = relocater_data[0].size;
371 relocater_data[0].src = mfp->f_addr;
372 relocater_data[0].dest = moveto;
373 relocater_data[0].size = mfp->f_size;
374 } else {
375 relocater_data[1].src = relocater_data[0].src;
376 relocater_data[1].dest = relocater_data[0].dest;
377 relocater_data[1].size = relocater_data[0].size;
378 relocater_data[0].src = mfp->f_addr;
379 relocater_data[0].dest = moveto;
380 relocater_data[0].size = mfp->f_size;
381 }
382 lh->ramdisk_image = moveto;
383 lh->ramdisk_size = mfp->f_size;
384 }
385
386 segment = linux_data_real_addr >> 4;
387 relocator_ds = segment;
388 relocator_es = segment;
389 relocator_fs = segment;
390 relocator_gs = segment;
391 relocator_ss = segment;
392 relocator_sp = LINUX_ESP;
393 relocator_ip = 0;
394 relocator_cs = segment + 0x20;
395 relocator_a20_enabled = 1;
396 i386_copyin(relocater, 0x600, relocater_size);
397
398 dev_cleanup();
399
400 __exec((void *)0x600);
401
402 panic("exec returned");
403
404 return (EINTR); /* not reached */
405 }
406
407 static int
linux_loadinitrd(char * filename,uint64_t dest,struct preloaded_file ** result)408 linux_loadinitrd(char *filename, uint64_t dest, struct preloaded_file **result)
409 {
410 struct preloaded_file *mfp;
411 vm_offset_t mem;
412
413 if (filename == NULL)
414 return (EFTYPE);
415
416 /* check if the kernel is loaded */
417 mfp = file_findfile(NULL, "Linux bzImage");
418 if (mfp == NULL)
419 mfp = file_findfile(NULL, "Linux zImage");
420 if (mfp == NULL)
421 return (EFTYPE);
422
423 mfp = file_loadraw(filename, "module", 0, NULL, 0);
424 if (mfp == NULL)
425 return (EFTYPE);
426 *result = mfp;
427 return (0);
428 }
429
linux_execinitrd(struct preloaded_file * pf)430 static int linux_execinitrd(struct preloaded_file *pf)
431 {
432 return (EFTYPE);
433 }
434