xref: /illumos-gate/usr/src/boot/i386/libi386/linux.c (revision 22028508fd28d36ff74dc02c5774a8ba1f0db045)
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