Commit 642a4f967f69a86bd59ad7c7d0adf8e80d0b4b54
1 parent
57fa1fb3
Linux loader rewrite, by H. Peter Anvin.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2835 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
2 changed files
with
222 additions
and
89 deletions
hw/pc.c
... | ... | @@ -31,10 +31,6 @@ |
31 | 31 | #define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" |
32 | 32 | #define LINUX_BOOT_FILENAME "linux_boot.bin" |
33 | 33 | |
34 | -#define KERNEL_LOAD_ADDR 0x00100000 | |
35 | -#define MAX_INITRD_LOAD_ADDR 0x38000000 | |
36 | -#define KERNEL_PARAMS_ADDR 0x00090000 | |
37 | -#define KERNEL_CMDLINE_ADDR 0x00099000 | |
38 | 34 | /* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables. */ |
39 | 35 | #define ACPI_DATA_SIZE 0x10000 |
40 | 36 | |
... | ... | @@ -350,6 +346,61 @@ void bochs_bios_init(void) |
350 | 346 | register_ioport_write(0x503, 1, 1, bochs_bios_write, NULL); |
351 | 347 | } |
352 | 348 | |
349 | +/* Generate an initial boot sector which sets state and jump to | |
350 | + a specified vector */ | |
351 | +static int generate_bootsect(uint32_t gpr[8], uint16_t segs[6], uint16_t ip) | |
352 | +{ | |
353 | + uint8_t bootsect[512], *p; | |
354 | + int i; | |
355 | + | |
356 | + if (bs_table[0] == NULL) { | |
357 | + fprintf(stderr, "A disk image must be given for 'hda' when booting " | |
358 | + "a Linux kernel\n"); | |
359 | + exit(1); | |
360 | + } | |
361 | + | |
362 | + memset(bootsect, 0, sizeof(bootsect)); | |
363 | + | |
364 | + /* Copy the MSDOS partition table if possible */ | |
365 | + bdrv_read(bs_table[0], 0, bootsect, 1); | |
366 | + | |
367 | + /* Make sure we have a partition signature */ | |
368 | + bootsect[510] = 0x55; | |
369 | + bootsect[511] = 0xaa; | |
370 | + | |
371 | + /* Actual code */ | |
372 | + p = bootsect; | |
373 | + *p++ = 0xfa; /* CLI */ | |
374 | + *p++ = 0xfc; /* CLD */ | |
375 | + | |
376 | + for (i = 0; i < 6; i++) { | |
377 | + if (i == 1) /* Skip CS */ | |
378 | + continue; | |
379 | + | |
380 | + *p++ = 0xb8; /* MOV AX,imm16 */ | |
381 | + *p++ = segs[i]; | |
382 | + *p++ = segs[i] >> 8; | |
383 | + *p++ = 0x8e; /* MOV <seg>,AX */ | |
384 | + *p++ = 0xc0 + (i << 3); | |
385 | + } | |
386 | + | |
387 | + for (i = 0; i < 8; i++) { | |
388 | + *p++ = 0x66; /* 32-bit operand size */ | |
389 | + *p++ = 0xb8 + i; /* MOV <reg>,imm32 */ | |
390 | + *p++ = gpr[i]; | |
391 | + *p++ = gpr[i] >> 8; | |
392 | + *p++ = gpr[i] >> 16; | |
393 | + *p++ = gpr[i] >> 24; | |
394 | + } | |
395 | + | |
396 | + *p++ = 0xea; /* JMP FAR */ | |
397 | + *p++ = ip; /* IP */ | |
398 | + *p++ = ip >> 8; | |
399 | + *p++ = segs[1]; /* CS */ | |
400 | + *p++ = segs[1] >> 8; | |
401 | + | |
402 | + bdrv_set_boot_sector(bs_table[0], bootsect, sizeof(bootsect)); | |
403 | +} | |
353 | 404 | |
354 | 405 | int load_kernel(const char *filename, uint8_t *addr, |
355 | 406 | uint8_t *real_addr) |
... | ... | @@ -370,7 +421,7 @@ int load_kernel(const char *filename, uint8_t *addr, |
370 | 421 | if (read(fd, real_addr + 512, setup_sects * 512) != |
371 | 422 | setup_sects * 512) |
372 | 423 | goto fail; |
373 | - | |
424 | + | |
374 | 425 | /* load 32 bit code */ |
375 | 426 | size = read(fd, addr, 16 * 1024 * 1024); |
376 | 427 | if (size < 0) |
... | ... | @@ -382,6 +433,169 @@ int load_kernel(const char *filename, uint8_t *addr, |
382 | 433 | return -1; |
383 | 434 | } |
384 | 435 | |
436 | +static long get_file_size(FILE *f) | |
437 | +{ | |
438 | + long where, size; | |
439 | + | |
440 | + /* XXX: on Unix systems, using fstat() probably makes more sense */ | |
441 | + | |
442 | + where = ftell(f); | |
443 | + fseek(f, 0, SEEK_END); | |
444 | + size = ftell(f); | |
445 | + fseek(f, where, SEEK_SET); | |
446 | + | |
447 | + return size; | |
448 | +} | |
449 | + | |
450 | +static void load_linux(const char *kernel_filename, | |
451 | + const char *initrd_filename, | |
452 | + const char *kernel_cmdline) | |
453 | +{ | |
454 | + uint16_t protocol; | |
455 | + uint32_t gpr[8]; | |
456 | + uint16_t seg[6]; | |
457 | + uint16_t real_seg; | |
458 | + int setup_size, kernel_size, initrd_size, cmdline_size; | |
459 | + uint32_t initrd_max; | |
460 | + uint8_t header[1024]; | |
461 | + uint8_t *real_addr, *prot_addr, *cmdline_addr, *initrd_addr; | |
462 | + FILE *f, *fi; | |
463 | + | |
464 | + /* Align to 16 bytes as a paranoia measure */ | |
465 | + cmdline_size = (strlen(kernel_cmdline)+16) & ~15; | |
466 | + | |
467 | + /* load the kernel header */ | |
468 | + f = fopen(kernel_filename, "rb"); | |
469 | + if (!f || !(kernel_size = get_file_size(f)) || | |
470 | + fread(header, 1, 1024, f) != 1024) { | |
471 | + fprintf(stderr, "qemu: could not load kernel '%s'\n", | |
472 | + kernel_filename); | |
473 | + exit(1); | |
474 | + } | |
475 | + | |
476 | + /* kernel protocol version */ | |
477 | + fprintf(stderr, "header magic: %#x\n", ldl_p(header+0x202)); | |
478 | + if (ldl_p(header+0x202) == 0x53726448) | |
479 | + protocol = lduw_p(header+0x206); | |
480 | + else | |
481 | + protocol = 0; | |
482 | + | |
483 | + if (protocol < 0x200 || !(header[0x211] & 0x01)) { | |
484 | + /* Low kernel */ | |
485 | + real_addr = phys_ram_base + 0x90000; | |
486 | + cmdline_addr = phys_ram_base + 0x9a000 - cmdline_size; | |
487 | + prot_addr = phys_ram_base + 0x10000; | |
488 | + } else if (protocol < 0x202) { | |
489 | + /* High but ancient kernel */ | |
490 | + real_addr = phys_ram_base + 0x90000; | |
491 | + cmdline_addr = phys_ram_base + 0x9a000 - cmdline_size; | |
492 | + prot_addr = phys_ram_base + 0x100000; | |
493 | + } else { | |
494 | + /* High and recent kernel */ | |
495 | + real_addr = phys_ram_base + 0x10000; | |
496 | + cmdline_addr = phys_ram_base + 0x20000; | |
497 | + prot_addr = phys_ram_base + 0x100000; | |
498 | + } | |
499 | + | |
500 | + fprintf(stderr, | |
501 | + "qemu: real_addr = %#zx\n" | |
502 | + "qemu: cmdline_addr = %#zx\n" | |
503 | + "qemu: prot_addr = %#zx\n", | |
504 | + real_addr-phys_ram_base, | |
505 | + cmdline_addr-phys_ram_base, | |
506 | + prot_addr-phys_ram_base); | |
507 | + | |
508 | + /* highest address for loading the initrd */ | |
509 | + if (protocol >= 0x203) | |
510 | + initrd_max = ldl_p(header+0x22c); | |
511 | + else | |
512 | + initrd_max = 0x37ffffff; | |
513 | + | |
514 | + if (initrd_max >= ram_size-ACPI_DATA_SIZE) | |
515 | + initrd_max = ram_size-ACPI_DATA_SIZE-1; | |
516 | + | |
517 | + /* kernel command line */ | |
518 | + pstrcpy(cmdline_addr, 4096, kernel_cmdline); | |
519 | + | |
520 | + if (protocol >= 0x202) { | |
521 | + stl_p(header+0x228, cmdline_addr-phys_ram_base); | |
522 | + } else { | |
523 | + stw_p(header+0x20, 0xA33F); | |
524 | + stw_p(header+0x22, cmdline_addr-real_addr); | |
525 | + } | |
526 | + | |
527 | + /* loader type */ | |
528 | + /* High nybble = B reserved for Qemu; low nybble is revision number. | |
529 | + If this code is substantially changed, you may want to consider | |
530 | + incrementing the revision. */ | |
531 | + if (protocol >= 0x200) | |
532 | + header[0x210] = 0xB0; | |
533 | + | |
534 | + /* heap */ | |
535 | + if (protocol >= 0x201) { | |
536 | + header[0x211] |= 0x80; /* CAN_USE_HEAP */ | |
537 | + stw_p(header+0x224, cmdline_addr-real_addr-0x200); | |
538 | + } | |
539 | + | |
540 | + /* load initrd */ | |
541 | + if (initrd_filename) { | |
542 | + if (protocol < 0x200) { | |
543 | + fprintf(stderr, "qemu: linux kernel too old to load a ram disk\n"); | |
544 | + exit(1); | |
545 | + } | |
546 | + | |
547 | + fi = fopen(initrd_filename, "rb"); | |
548 | + if (!fi) { | |
549 | + fprintf(stderr, "qemu: could not load initial ram disk '%s'\n", | |
550 | + initrd_filename); | |
551 | + exit(1); | |
552 | + } | |
553 | + | |
554 | + initrd_size = get_file_size(fi); | |
555 | + initrd_addr = phys_ram_base + ((initrd_max-initrd_size) & ~4095); | |
556 | + | |
557 | + fprintf(stderr, "qemu: loading initrd (%#x bytes) at %#zx\n", | |
558 | + initrd_size, initrd_addr-phys_ram_base); | |
559 | + | |
560 | + if (fread(initrd_addr, 1, initrd_size, fi) != initrd_size) { | |
561 | + fprintf(stderr, "qemu: read error on initial ram disk '%s'\n", | |
562 | + initrd_filename); | |
563 | + exit(1); | |
564 | + } | |
565 | + fclose(fi); | |
566 | + | |
567 | + stl_p(header+0x218, initrd_addr-phys_ram_base); | |
568 | + stl_p(header+0x21c, initrd_size); | |
569 | + } | |
570 | + | |
571 | + /* store the finalized header and load the rest of the kernel */ | |
572 | + memcpy(real_addr, header, 1024); | |
573 | + | |
574 | + setup_size = header[0x1f1]; | |
575 | + if (setup_size == 0) | |
576 | + setup_size = 4; | |
577 | + | |
578 | + setup_size = (setup_size+1)*512; | |
579 | + kernel_size -= setup_size; /* Size of protected-mode code */ | |
580 | + | |
581 | + if (fread(real_addr+1024, 1, setup_size-1024, f) != setup_size-1024 || | |
582 | + fread(prot_addr, 1, kernel_size, f) != kernel_size) { | |
583 | + fprintf(stderr, "qemu: read error on kernel '%s'\n", | |
584 | + kernel_filename); | |
585 | + exit(1); | |
586 | + } | |
587 | + fclose(f); | |
588 | + | |
589 | + /* generate bootsector to set up the initial register state */ | |
590 | + real_seg = (real_addr-phys_ram_base) >> 4; | |
591 | + seg[0] = seg[2] = seg[3] = seg[4] = seg[4] = real_seg; | |
592 | + seg[1] = real_seg+0x20; /* CS */ | |
593 | + memset(gpr, 0, sizeof gpr); | |
594 | + gpr[4] = cmdline_addr-real_addr-16; /* SP (-16 is paranoia) */ | |
595 | + | |
596 | + generate_bootsect(gpr, seg, 0); | |
597 | +} | |
598 | + | |
385 | 599 | static void main_cpu_reset(void *opaque) |
386 | 600 | { |
387 | 601 | CPUState *env = opaque; |
... | ... | @@ -453,9 +667,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, |
453 | 667 | int pci_enabled) |
454 | 668 | { |
455 | 669 | char buf[1024]; |
456 | - int ret, linux_boot, initrd_size, i; | |
670 | + int ret, linux_boot, i; | |
457 | 671 | ram_addr_t ram_addr, vga_ram_addr, bios_offset, vga_bios_offset; |
458 | - ram_addr_t initrd_offset; | |
459 | 672 | int bios_size, isa_bios_size, vga_bios_size; |
460 | 673 | PCIBus *pci_bus; |
461 | 674 | int piix3_devfn = -1; |
... | ... | @@ -570,81 +783,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, |
570 | 783 | |
571 | 784 | bochs_bios_init(); |
572 | 785 | |
573 | - if (linux_boot) { | |
574 | - uint8_t bootsect[512]; | |
575 | - uint8_t old_bootsect[512]; | |
576 | - | |
577 | - if (bs_table[0] == NULL) { | |
578 | - fprintf(stderr, "A disk image must be given for 'hda' when booting a Linux kernel\n"); | |
579 | - exit(1); | |
580 | - } | |
581 | - snprintf(buf, sizeof(buf), "%s/%s", bios_dir, LINUX_BOOT_FILENAME); | |
582 | - ret = load_image(buf, bootsect); | |
583 | - if (ret != sizeof(bootsect)) { | |
584 | - fprintf(stderr, "qemu: could not load linux boot sector '%s'\n", | |
585 | - buf); | |
586 | - exit(1); | |
587 | - } | |
588 | - | |
589 | - if (bdrv_read(bs_table[0], 0, old_bootsect, 1) >= 0) { | |
590 | - /* copy the MSDOS partition table */ | |
591 | - memcpy(bootsect + 0x1be, old_bootsect + 0x1be, 0x40); | |
592 | - } | |
593 | - | |
594 | - bdrv_set_boot_sector(bs_table[0], bootsect, sizeof(bootsect)); | |
595 | - | |
596 | - /* now we can load the kernel */ | |
597 | - ret = load_kernel(kernel_filename, | |
598 | - phys_ram_base + KERNEL_LOAD_ADDR, | |
599 | - phys_ram_base + KERNEL_PARAMS_ADDR); | |
600 | - if (ret < 0) { | |
601 | - fprintf(stderr, "qemu: could not load kernel '%s'\n", | |
602 | - kernel_filename); | |
603 | - exit(1); | |
604 | - } | |
605 | - | |
606 | - /* load initrd */ | |
607 | - initrd_size = 0; | |
608 | - initrd_offset = 0; | |
609 | - if (initrd_filename) { | |
610 | - initrd_size = get_image_size (initrd_filename); | |
611 | - if (initrd_size > 0) { | |
612 | - initrd_offset = (ram_size - initrd_size) & TARGET_PAGE_MASK; | |
613 | - /* Leave space for BIOS ACPI tables. */ | |
614 | - initrd_offset -= ACPI_DATA_SIZE; | |
615 | - /* Avoid the last 64k to avoid 2.2.x kernel bugs. */ | |
616 | - initrd_offset -= 0x10000; | |
617 | - if (initrd_offset > MAX_INITRD_LOAD_ADDR) | |
618 | - initrd_offset = MAX_INITRD_LOAD_ADDR; | |
619 | - | |
620 | - if (initrd_size > ram_size | |
621 | - || initrd_offset < KERNEL_LOAD_ADDR + ret) { | |
622 | - fprintf(stderr, | |
623 | - "qemu: memory too small for initial ram disk '%s'\n", | |
624 | - initrd_filename); | |
625 | - exit(1); | |
626 | - } | |
627 | - initrd_size = load_image(initrd_filename, | |
628 | - phys_ram_base + initrd_offset); | |
629 | - } | |
630 | - if (initrd_size < 0) { | |
631 | - fprintf(stderr, "qemu: could not load initial ram disk '%s'\n", | |
632 | - initrd_filename); | |
633 | - exit(1); | |
634 | - } | |
635 | - } | |
636 | - if (initrd_size > 0) { | |
637 | - stl_raw(phys_ram_base + KERNEL_PARAMS_ADDR + 0x218, initrd_offset); | |
638 | - stl_raw(phys_ram_base + KERNEL_PARAMS_ADDR + 0x21c, initrd_size); | |
639 | - } | |
640 | - pstrcpy(phys_ram_base + KERNEL_CMDLINE_ADDR, 4096, | |
641 | - kernel_cmdline); | |
642 | - stw_raw(phys_ram_base + KERNEL_PARAMS_ADDR + 0x20, 0xA33F); | |
643 | - stw_raw(phys_ram_base + KERNEL_PARAMS_ADDR + 0x22, | |
644 | - KERNEL_CMDLINE_ADDR - KERNEL_PARAMS_ADDR); | |
645 | - /* loader type */ | |
646 | - stw_raw(phys_ram_base + KERNEL_PARAMS_ADDR + 0x210, 0x01); | |
647 | - } | |
786 | + if (linux_boot) | |
787 | + load_linux(kernel_filename, initrd_filename, kernel_cmdline); | |
648 | 788 | |
649 | 789 | cpu_irq = qemu_allocate_irqs(pic_irq_request, first_cpu, 1); |
650 | 790 | i8259 = i8259_init(cpu_irq[0]); | ... | ... |
pc-bios/Makefile
... | ... | @@ -6,16 +6,9 @@ include ../config-host.mak |
6 | 6 | DEFINES= |
7 | 7 | |
8 | 8 | TARGETS= |
9 | -ifeq ($(ARCH),i386) | |
10 | -TARGETS+=linux_boot.bin | |
11 | -endif | |
12 | 9 | |
13 | 10 | all: $(TARGETS) |
14 | 11 | |
15 | -linux_boot.bin: linux_boot.o | |
16 | - ld --oformat binary -Ttext 0 -o $@ $< | |
17 | - chmod a-x $@ | |
18 | - | |
19 | 12 | %.o: %.S |
20 | 13 | $(CC) $(DEFINES) -c -o $@ $< |
21 | 14 | ... | ... |