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,10 +31,6 @@ | ||
31 | #define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" | 31 | #define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" |
32 | #define LINUX_BOOT_FILENAME "linux_boot.bin" | 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 | /* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables. */ | 34 | /* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables. */ |
39 | #define ACPI_DATA_SIZE 0x10000 | 35 | #define ACPI_DATA_SIZE 0x10000 |
40 | 36 | ||
@@ -350,6 +346,61 @@ void bochs_bios_init(void) | @@ -350,6 +346,61 @@ void bochs_bios_init(void) | ||
350 | register_ioport_write(0x503, 1, 1, bochs_bios_write, NULL); | 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 | int load_kernel(const char *filename, uint8_t *addr, | 405 | int load_kernel(const char *filename, uint8_t *addr, |
355 | uint8_t *real_addr) | 406 | uint8_t *real_addr) |
@@ -370,7 +421,7 @@ int load_kernel(const char *filename, uint8_t *addr, | @@ -370,7 +421,7 @@ int load_kernel(const char *filename, uint8_t *addr, | ||
370 | if (read(fd, real_addr + 512, setup_sects * 512) != | 421 | if (read(fd, real_addr + 512, setup_sects * 512) != |
371 | setup_sects * 512) | 422 | setup_sects * 512) |
372 | goto fail; | 423 | goto fail; |
373 | - | 424 | + |
374 | /* load 32 bit code */ | 425 | /* load 32 bit code */ |
375 | size = read(fd, addr, 16 * 1024 * 1024); | 426 | size = read(fd, addr, 16 * 1024 * 1024); |
376 | if (size < 0) | 427 | if (size < 0) |
@@ -382,6 +433,169 @@ int load_kernel(const char *filename, uint8_t *addr, | @@ -382,6 +433,169 @@ int load_kernel(const char *filename, uint8_t *addr, | ||
382 | return -1; | 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 | static void main_cpu_reset(void *opaque) | 599 | static void main_cpu_reset(void *opaque) |
386 | { | 600 | { |
387 | CPUState *env = opaque; | 601 | CPUState *env = opaque; |
@@ -453,9 +667,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, | @@ -453,9 +667,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, | ||
453 | int pci_enabled) | 667 | int pci_enabled) |
454 | { | 668 | { |
455 | char buf[1024]; | 669 | char buf[1024]; |
456 | - int ret, linux_boot, initrd_size, i; | 670 | + int ret, linux_boot, i; |
457 | ram_addr_t ram_addr, vga_ram_addr, bios_offset, vga_bios_offset; | 671 | ram_addr_t ram_addr, vga_ram_addr, bios_offset, vga_bios_offset; |
458 | - ram_addr_t initrd_offset; | ||
459 | int bios_size, isa_bios_size, vga_bios_size; | 672 | int bios_size, isa_bios_size, vga_bios_size; |
460 | PCIBus *pci_bus; | 673 | PCIBus *pci_bus; |
461 | int piix3_devfn = -1; | 674 | int piix3_devfn = -1; |
@@ -570,81 +783,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, | @@ -570,81 +783,8 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, | ||
570 | 783 | ||
571 | bochs_bios_init(); | 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 | cpu_irq = qemu_allocate_irqs(pic_irq_request, first_cpu, 1); | 789 | cpu_irq = qemu_allocate_irqs(pic_irq_request, first_cpu, 1); |
650 | i8259 = i8259_init(cpu_irq[0]); | 790 | i8259 = i8259_init(cpu_irq[0]); |
pc-bios/Makefile
@@ -6,16 +6,9 @@ include ../config-host.mak | @@ -6,16 +6,9 @@ include ../config-host.mak | ||
6 | DEFINES= | 6 | DEFINES= |
7 | 7 | ||
8 | TARGETS= | 8 | TARGETS= |
9 | -ifeq ($(ARCH),i386) | ||
10 | -TARGETS+=linux_boot.bin | ||
11 | -endif | ||
12 | 9 | ||
13 | all: $(TARGETS) | 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 | %.o: %.S | 12 | %.o: %.S |
20 | $(CC) $(DEFINES) -c -o $@ $< | 13 | $(CC) $(DEFINES) -c -o $@ $< |
21 | 14 |