aboutsummaryrefslogtreecommitdiff
path: root/linuxboot.c
blob: 251bcb6216ccb6642d0355cce8046dfdfecd7bc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#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");