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 |