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