| #include "bios.h" |
| #include "linuxboot.h" |
| #include "memaccess.h" |
| #include "ioport.h" |
| #include "start_info.h" |
| #include "string.h" |
| #include "stdio.h" |
| #include "benchmark.h" |
| |
| struct hvm_start_info start_info = {0}; |
| |
| bool parse_bzimage(struct linuxboot_args *args) |
| { |
| uint8_t *header = args->header; |
| |
| uint32_t real_addr, cmdline_addr, prot_addr, initrd_addr; |
| uint32_t setup_size; |
| uint32_t initrd_max; |
| uint16_t protocol; |
| |
| if (ldl_p(header+0x202) == 0x53726448) |
| protocol = lduw_p(header+0x206); |
| else { |
| /* assume multiboot. TODO: scan for header */ |
| return false; |
| // protocol = 0; |
| } |
| |
| if (protocol < 0x200 || !(header[0x211] & 0x01)) { |
| /* Low kernel */ |
| real_addr = 0x90000; |
| cmdline_addr = (0x9a000 - args->cmdline_size) & ~15; |
| prot_addr = 0x10000; |
| } else if (protocol < 0x202) { |
| /* High but ancient kernel */ |
| real_addr = 0x90000; |
| cmdline_addr = (0x9a000 - args->cmdline_size) & ~15; |
| prot_addr = 0x100000; |
| } else { |
| /* High and recent kernel */ |
| real_addr = 0x10000; |
| cmdline_addr = 0x20000; |
| prot_addr = 0x100000; |
| } |
| |
| if (protocol >= 0x203) |
| initrd_max = ldl_p(header+0x22c); |
| else |
| initrd_max = 0x37ffffff; |
| if (initrd_max > lowmem - 1) |
| initrd_max = lowmem - 1; |
| |
| if (protocol >= 0x202) |
| stl_p(header+0x228, cmdline_addr); |
| else { |
| stw_p(header+0x20, 0xA33F); |
| stw_p(header+0x22, cmdline_addr-real_addr); |
| } |
| |
| /* High nybble = B reserved for QEMU; low nybble is revision number. |
| * If this code is substantially changed, you may want to consider |
| * incrementing the revision. */ |
| if (protocol >= 0x200) |
| header[0x210] = 0xB0; |
| |
| /* heap */ |
| if (protocol >= 0x201) { |
| header[0x211] |= 0x80; /* CAN_USE_HEAP */ |
| stw_p(header+0x224, cmdline_addr-real_addr-0x200); |
| } |
| |
| if (args->initrd_size) |
| initrd_addr = (initrd_max - args->initrd_size) & ~4095; |
| else |
| initrd_addr = 0; |
| stl_p(header+0x218, initrd_addr); |
| stl_p(header+0x21c, args->initrd_size); |
| |
| /* load kernel and setup */ |
| setup_size = header[0x1f1]; |
| if (setup_size == 0) |
| setup_size = 4; |
| |
| args->setup_size = (setup_size+1)*512; |
| args->kernel_size = args->vmlinuz_size - setup_size; |
| args->initrd_addr = (void *)initrd_addr; |
| args->setup_addr = (void *)real_addr; |
| args->kernel_addr = (void *)prot_addr; |
| args->cmdline_addr = (void *)cmdline_addr; |
| return true; |
| } |
| |
| void boot_bzimage(struct linuxboot_args *args) |
| { |
| memcpy(args->setup_addr, args->header, sizeof(args->header)); |
| #ifdef BENCHMARK_HACK |
| /* Exit just before getting to vmlinuz, so that it is easy |
| * to time/profile the firmware. |
| */ |
| outb(LINUX_EXIT_PORT, LINUX_START_BOOT); |
| #endif |
| asm volatile( |
| "ljmp $0x18, $pm16_boot_linux - 0xf0000" |
| : : |
| "b" (((uintptr_t) args->setup_addr) >> 4), |
| "d" (args->cmdline_addr - args->setup_addr - 16)); |
| panic(); |
| } |
| |
| /* BX = address of data block |
| * DX = cmdline_addr-setup_addr-16 |
| */ |
| asm("pm16_boot_linux:" |
| ".code16;" |
| "mov $0x20, %ax; mov %ax, %ds; mov %ax, %es;" |
| "mov %ax, %fs; mov %ax, %gs; mov %ax, %ss;" |
| "xor %eax, %eax; mov %eax, %cr0;" |
| "ljmpl $0xf000, $(1f - 0xf0000); 1:" |
| "mov %bx, %ds; mov %bx, %es;" |
| "mov %bx, %fs; mov %bx, %gs; mov %bx, %ss;" |
| "mov %dx, %sp;" |
| "add $0x20, %bx; pushw %bx;" // push CS |
| "pushw %ax;" // push IP |
| "xor %ebx, %ebx;" |
| "xor %ecx, %ecx;" |
| "xor %edx, %edx;" |
| "xor %edi, %edi;" |
| "xor %ebp, %ebp;" |
| "lret;" |
| ".code32"); |