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 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 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 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 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 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 430 static int linux_execinitrd(struct preloaded_file *pf) 431 { 432 return (EFTYPE); 433 } 434