summaryrefslogtreecommitdiffstats
path: root/qemu/roms/openbios/arch/amd64/linux_load.c
blob: f1ed98df18de89312f05df73dd5385083ce87de4 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
/*
 * Linux/i386 loader
 * Supports bzImage, zImage and Image format.
 *
 * Based on work by Steve Gehlbach.
 * Portions are taken from mkelfImage.
 *
 * 2003-09 by SONE Takeshi
 */

#include "config.h"
#include "kernel/kernel.h"
#include "libopenbios/bindings.h"
#include "libopenbios/sys_info.h"
#include "context.h"
#include "segment.h"
#include "loadfs.h"

#define printf printk
#define debug printk
#define strtoull_with_suffix strtol

#define LINUX_PARAM_LOC 0x90000
#define COMMAND_LINE_LOC 0x91000
#define GDT_LOC 0x92000
#define STACK_LOC 0x93000

#define E820MAX	32		/* number of entries in E820MAP */
struct e820entry {
	unsigned long long addr;	/* start of memory segment */
	unsigned long long size;	/* size of memory segment */
	unsigned long type;		/* type of memory segment */
#define E820_RAM	1
#define E820_RESERVED	2
#define E820_ACPI	3 /* usable as RAM once ACPI tables have been read */
#define E820_NVS	4
};

/* The header of Linux/i386 kernel */
struct linux_header {
    uint8_t  reserved1[0x1f1];		/* 0x000 */
    uint8_t  setup_sects;		/* 0x1f1 */
    uint16_t root_flags;		/* 0x1f2 */
    uint8_t  reserved2[6];		/* 0x1f4 */
    uint16_t vid_mode;			/* 0x1fa */
    uint16_t root_dev;			/* 0x1fc */
    uint16_t boot_sector_magic;		/* 0x1fe */
    /* 2.00+ */
    uint8_t  reserved3[2];		/* 0x200 */
    uint8_t  header_magic[4];		/* 0x202 */
    uint16_t protocol_version;		/* 0x206 */
    uint32_t realmode_swtch;		/* 0x208 */
    uint16_t start_sys;			/* 0x20c */
    uint16_t kver_addr;			/* 0x20e */
    uint8_t  type_of_loader;		/* 0x210 */
    uint8_t  loadflags;			/* 0x211 */
    uint16_t setup_move_size;		/* 0x212 */
    uint32_t code32_start;		/* 0x214 */
    uint32_t ramdisk_image;		/* 0x218 */
    uint32_t ramdisk_size;		/* 0x21c */
    uint8_t  reserved4[4];		/* 0x220 */
    /* 2.01+ */
    uint16_t heap_end_ptr;		/* 0x224 */
    uint8_t  reserved5[2];		/* 0x226 */
    /* 2.02+ */
    uint32_t cmd_line_ptr;		/* 0x228 */
    /* 2.03+ */
    uint32_t initrd_addr_max;		/* 0x22c */
} __attribute__ ((packed));


/* Paramters passed to 32-bit part of Linux
 * This is another view of the structure above.. */
struct linux_params {
    uint8_t  orig_x;			/* 0x00 */
    uint8_t  orig_y;			/* 0x01 */
    uint16_t ext_mem_k;			/* 0x02 -- EXT_MEM_K sits here */
    uint16_t orig_video_page;		/* 0x04 */
    uint8_t  orig_video_mode;		/* 0x06 */
    uint8_t  orig_video_cols;		/* 0x07 */
    uint16_t unused2;			/* 0x08 */
    uint16_t orig_video_ega_bx;		/* 0x0a */
    uint16_t unused3;			/* 0x0c */
    uint8_t  orig_video_lines;		/* 0x0e */
    uint8_t  orig_video_isVGA;		/* 0x0f */
    uint16_t orig_video_points;		/* 0x10 */

    /* VESA graphic mode -- linear frame buffer */
    uint16_t lfb_width;			/* 0x12 */
    uint16_t lfb_height;		/* 0x14 */
    uint16_t lfb_depth;			/* 0x16 */
    uint32_t lfb_base;			/* 0x18 */
    uint32_t lfb_size;			/* 0x1c */
    uint16_t cl_magic;			/* 0x20 */
#define CL_MAGIC_VALUE 0xA33F
    uint16_t cl_offset;			/* 0x22 */
    uint16_t lfb_linelength;		/* 0x24 */
    uint8_t  red_size;			/* 0x26 */
    uint8_t  red_pos;			/* 0x27 */
    uint8_t  green_size;		/* 0x28 */
    uint8_t  green_pos;			/* 0x29 */
    uint8_t  blue_size;			/* 0x2a */
    uint8_t  blue_pos;			/* 0x2b */
    uint8_t  rsvd_size;			/* 0x2c */
    uint8_t  rsvd_pos;			/* 0x2d */
    uint16_t vesapm_seg;		/* 0x2e */
    uint16_t vesapm_off;		/* 0x30 */
    uint16_t pages;			/* 0x32 */
    uint8_t  reserved4[12];		/* 0x34 -- 0x3f reserved for future expansion */

    //struct apm_bios_info apm_bios_info;	/* 0x40 */
    uint8_t  apm_bios_info[0x40];
    //struct drive_info_struct drive_info;	/* 0x80 */
    uint8_t  drive_info[0x20];
    //struct sys_desc_table sys_desc_table;	/* 0xa0 */
    uint8_t  sys_desc_table[0x140];
    uint32_t alt_mem_k;			/* 0x1e0 */
    uint8_t  reserved5[4];		/* 0x1e4 */
    uint8_t  e820_map_nr;		/* 0x1e8 */
    uint8_t  reserved6[9];		/* 0x1e9 */
    uint16_t mount_root_rdonly;		/* 0x1f2 */
    uint8_t  reserved7[4];		/* 0x1f4 */
    uint16_t ramdisk_flags;		/* 0x1f8 */
#define RAMDISK_IMAGE_START_MASK  	0x07FF
#define RAMDISK_PROMPT_FLAG		0x8000
#define RAMDISK_LOAD_FLAG		0x4000
    uint8_t  reserved8[2];		/* 0x1fa */
    uint16_t orig_root_dev;		/* 0x1fc */
    uint8_t  reserved9[1];		/* 0x1fe */
    uint8_t  aux_device_info;		/* 0x1ff */
    uint8_t  reserved10[2];		/* 0x200 */
    uint8_t  param_block_signature[4];	/* 0x202 */
    uint16_t param_block_version;	/* 0x206 */
    uint8_t  reserved11[8];		/* 0x208 */
    uint8_t  loader_type;		/* 0x210 */
#define LOADER_TYPE_LOADLIN         1
#define LOADER_TYPE_BOOTSECT_LOADER 2
#define LOADER_TYPE_SYSLINUX        3
#define LOADER_TYPE_ETHERBOOT       4
#define LOADER_TYPE_KERNEL          5
    uint8_t  loader_flags;		/* 0x211 */
    uint8_t  reserved12[2];		/* 0x212 */
    uint32_t kernel_start;		/* 0x214 */
    uint32_t initrd_start;		/* 0x218 */
    uint32_t initrd_size;		/* 0x21c */
    uint8_t  reserved12_5[8];		/* 0x220 */
    uint32_t cmd_line_ptr;		/* 0x228 */
    uint8_t  reserved13[164];		/* 0x22c */
    struct e820entry e820_map[E820MAX];	/* 0x2d0 */
    uint8_t  reserved16[688];		/* 0x550 */
#define COMMAND_LINE_SIZE 256
    /* Command line is copied here by 32-bit i386/kernel/head.S.
     * So I will follow the boot protocol, rather than putting it
     * directly here. --ts1 */
    uint8_t  command_line[COMMAND_LINE_SIZE]; /* 0x800 */
    uint8_t  reserved17[1792];		/* 0x900 - 0x1000 */
};

uint64_t forced_memsize;

/* Load the first part the file and check if it's Linux */
static uint32_t load_linux_header(struct linux_header *hdr)
{
    int load_high;
    uint32_t kern_addr;

    if (lfile_read(hdr, sizeof *hdr) != sizeof *hdr) {
	debug("Can't read Linux header\n");
	return 0;
    }
    if (hdr->boot_sector_magic != 0xaa55) {
	debug("Not a Linux kernel image\n");
	return 0;
    }

    /* Linux is found. Print some information */
    if (memcmp(hdr->header_magic, "HdrS", 4) != 0) {
	/* This may be floppy disk image or something.
	 * Perform a simple (incomplete) sanity check. */
	if (hdr->setup_sects >= 16
		|| file_size() - (hdr->setup_sects<<9) >= 512<<10) {
	    debug("This looks like a bootdisk image but not like Linux...\n");
	    return 0;
	}

	printf("Possible very old Linux");
	/* This kernel does not even have a protocol version.
	 * Force the value. */
	hdr->protocol_version = 0; /* pre-2.00 */
    } else
	printf("Found Linux");
    if (hdr->protocol_version >= 0x200 && hdr->kver_addr) {
	char kver[256];
	file_seek(hdr->kver_addr + 0x200);
	if (lfile_read(kver, sizeof kver) != 0) {
	    kver[255] = 0;
	    printf(" version %s", kver);
	}
    }
    debug(" (protocol %#x)", hdr->protocol_version);
    load_high = 0;
    if (hdr->protocol_version >= 0x200) {
	debug(" (loadflags %#x)", hdr->loadflags);
	load_high = hdr->loadflags & 1;
    }
    if (load_high) {
	printf(" bzImage");
	kern_addr = 0x100000;
    } else {
	printf(" zImage or Image");
	kern_addr = 0x1000;
    }
    printf(".\n");

    return kern_addr;
}

/* Set up parameters for 32-bit kernel */
static void
init_linux_params(struct linux_params *params, struct linux_header *hdr)
{
    debug("Setting up paramters at %#lx\n", virt_to_phys(params));
    memset(params, 0, sizeof *params);

    /* Copy some useful values from header */
    params->mount_root_rdonly = hdr->root_flags;
    params->orig_root_dev = hdr->root_dev;

    /* Video parameters.
     * This assumes we have VGA in standard 80x25 text mode,
     * just like our vga.c does.
     * Cursor position is filled later to allow some more printf's. */
    params->orig_video_mode = 3;
    params->orig_video_cols = 80;
    params->orig_video_lines = 25;
    params->orig_video_isVGA = 1;
    params->orig_video_points = 16;

    params->loader_type = 0xff; /* Unregistered Linux loader */
}

/* Memory map */
static void
set_memory_size(struct linux_params *params, struct sys_info *info)
{
    int i;
    uint64_t end;
    uint32_t ramtop = 0;
    struct e820entry *linux_map;
    struct memrange *filo_map;

    linux_map = params->e820_map;
    filo_map = info->memrange;
    for (i = 0; i < info->n_memranges; i++, linux_map++, filo_map++) {
	if (i < E820MAX) {
	    /* Convert to BIOS e820 style */
	    linux_map->addr = filo_map->base;
	    linux_map->size = filo_map->size;
	    linux_map->type = E820_RAM;
	    debug("%016Lx - %016Lx\n", linux_map->addr,
		    linux_map->addr + linux_map->size);
	    params->e820_map_nr = i+1;
	}

	/* Find out top of RAM. XXX This ignores hole above 1MB */
	end = filo_map->base + filo_map->size;
	if (end < (1ULL << 32)) { /* don't count memory above 4GB */
	    if (end > ramtop)
		ramtop = (uint32_t) end;
	}
    }
    debug("ramtop=%#x\n", ramtop);
    /* Size of memory above 1MB in KB */
    params->alt_mem_k = (ramtop - (1<<20)) >> 10;
    /* old style, 64MB max */
    if (ramtop >= (64<<20))
	params->ext_mem_k = (63<<10);
    else
	params->ext_mem_k = params->alt_mem_k;
    debug("ext_mem_k=%d, alt_mem_k=%d\n", params->ext_mem_k, params->alt_mem_k);
}

/*
 * Parse command line
 * Some parameters, like initrd=<file>, are not passed to kernel,
 * we are responsible to process them.
 * Parameters for kernel are copied to kern_cmdline. Returns name of initrd.
 */
static char *parse_command_line(const char *orig_cmdline, char *kern_cmdline)
{
    const char *start, *sep, *end, *val;
    char name[64];
    int len;
    int k_len;
    int to_kern;
    char *initrd = 0;
    int toolong = 0;

    forced_memsize = 0;

    if (!orig_cmdline) {
	*kern_cmdline = 0;
	return 0;
    }

    k_len = 0;
    debug("original command line: \"%s\"\n", orig_cmdline);
    debug("kernel command line at %#lx\n", virt_to_phys(kern_cmdline));

    start = orig_cmdline;
    while (*start == ' ')
	start++;
    while (*start) {
	end = strchr(start, ' ');
	if (!end)
	    end = start + strlen(start);
	sep = strchr(start, '=');
	if (!sep || sep > end)
	    sep = end;
	len = sep - start;
	if (len >= sizeof(name))
	    len = sizeof(name) - 1;
	memcpy(name, start, len);
	name[len] = 0;

	if (*sep == '=') {
	    val = sep + 1;
	    len = end - val;
	} else {
	    val = 0;
	    len = 0;
	}

	/* Only initrd= and mem= are handled here. vga= is not,
	 * which I believe is a paramter to the realmode part of Linux,
	 * which we don't execute. */
	if (strcmp(name, "initrd") == 0) {
	    if (!val)
		printf("Missing filename to initrd parameter\n");
	    else {
		initrd = malloc(len + 1);
		memcpy(initrd, val, len);
		initrd[len] = 0;
		debug("initrd=%s\n", initrd);
	    }
	    /* Don't pass this to kernel */
	    to_kern = 0;
	} else if (strcmp(name, "mem") == 0) {
	    if (!val)
		printf("Missing value for mem parameter\n");
	    else {
		forced_memsize = strtoull_with_suffix(val, (char**)&val, 0);
		if (forced_memsize == 0)
		    printf("Invalid mem option, ignored\n");
		if (val != end) {
		    printf("Garbage after mem=<size>, ignored\n");
		    forced_memsize = 0;
		}
		debug("mem=%Lu\n", forced_memsize);
	    }
	    /* mem= is for both loader and kernel */
	    to_kern = 1;
	} else
	    to_kern = 1;

	if (to_kern) {
	    /* Copy to kernel command line buffer */
	    if (k_len != 0)
		kern_cmdline[k_len++] = ' '; /* put separator */
	    len = end - start;
	    if (k_len + len >= COMMAND_LINE_SIZE) {
		len = COMMAND_LINE_SIZE - k_len - 1;
		if (!toolong) {
		    printf("Kernel command line is too long; truncated to "
			    "%d bytes\n", COMMAND_LINE_SIZE-1);
		    toolong = 1;
		}
	    }
	    memcpy(kern_cmdline + k_len, start, len);
	    k_len += len;
	}

	start = end;
	while (*start == ' ')
	    start++;
    }
    kern_cmdline[k_len] = 0;
    debug("kernel command line (%d bytes): \"%s\"\n", k_len, kern_cmdline);

    return initrd;
}

/* Set command line location */
static void set_command_line_loc(struct linux_params *params,
	struct linux_header *hdr)
{
    if (hdr->protocol_version >= 0x202) {
	/* new style */
	params->cmd_line_ptr = COMMAND_LINE_LOC;
    } else {
	/* old style */
	params->cl_magic = CL_MAGIC_VALUE;
	params->cl_offset = COMMAND_LINE_LOC - LINUX_PARAM_LOC;
    }
}

/* Load 32-bit part of kernel */
static int load_linux_kernel(struct linux_header *hdr, uint32_t kern_addr)
{
    uint32_t kern_offset, kern_size;

    if (hdr->setup_sects == 0)
	hdr->setup_sects = 4;
    kern_offset = (hdr->setup_sects + 1) * 512;
    file_seek(kern_offset);
    kern_size = file_size() - kern_offset;
    debug("offset=%#x addr=%#x size=%#x\n", kern_offset, kern_addr, kern_size);

#if 0
    if (using_devsize) {
	printf("Attempt to load up to end of device as kernel; "
		"specify the image size\n");
	return 0;
    }
#endif

    printf("Loading kernel... ");
    if (lfile_read(phys_to_virt(kern_addr), kern_size) != kern_size) {
	printf("Can't read kernel\n");
	return 0;
    }
    printf("ok\n");

    return kern_size;
}

static int load_initrd(struct linux_header *hdr, struct sys_info *info,
	uint32_t kern_end, struct linux_params *params, const char *initrd_file)
{
    uint32_t max;
    uint32_t start, end, size;
    uint64_t forced;
    extern char _start[], _end[];

    if (!file_open(initrd_file)) {
	printf("Can't open initrd: %s\n", initrd_file);
	return -1;
    }

#if 0
    if (using_devsize) {
	printf("Attempt to load up to end of device as initrd; "
		"specify the image size\n");
	return -1;
    }
#endif

    size = file_size();


    /* Find out the kernel's restriction on how high the initrd can be
     * placed */
    if (hdr->protocol_version >= 0x203)
	max = hdr->initrd_addr_max;
    else
	max = 0x38000000; /* Hardcoded value for older kernels */

    /* FILO itself is at the top of RAM. (relocated)
     * So, try putting initrd just below us. */
    end = virt_to_phys(_start);
    if (end > max)
	end = max;

    /* If "mem=" option is given, we have to put the initrd within
     * the specified range. */
    if (forced_memsize) {
	forced = forced_memsize;
	if (forced > max)
	    forced = max;
	/* If the "mem=" is lower, it's easy */
	if (forced <= end)
	    end = forced;
	else {
	    /* Otherwise, see if we can put it above us */
	    if (virt_to_phys(_end) + size <= forced)
		end = forced; /* Ok */
	}
    }

    start = end - size;
    start &= ~0xfff; /* page align */
    end = start + size;

    debug("start=%#x end=%#x\n", start, end);

    if (start < kern_end) {
	printf("Initrd is too big to fit in memory\n");
	return -1;
    }

    printf("Loading initrd... ");
    if (lfile_read(phys_to_virt(start), size) != size) {
	printf("Can't read initrd\n");
	return -1;
    }
    printf("ok\n");

    params->initrd_start = start;
    params->initrd_size = size;

    return 0;
}

static void hardware_setup(void)
{
    /* Disable nmi */
    outb(0x80, 0x70);

    /* Make sure any coprocessor is properly reset.. */
    outb(0, 0xf0);
    outb(0, 0xf1);

    /* we're getting screwed again and again by this problem of the 8259.
     * so we're going to leave this lying around for inclusion into
     * crt0.S on an as-needed basis.
     *
     * well, that went ok, I hope. Now we have to reprogram the interrupts :-(
     * we put them right after the intel-reserved hardware interrupts, at
     * int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
     * messed this up with the original PC, and they haven't been able to
     * rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
     * which is used for the internal hardware interrupts as well. We just
     * have to reprogram the 8259's, and it isn't fun.
     */

    outb(0x11, 0x20);		/* initialization sequence to 8259A-1 */
    outb(0x11, 0xA0);		/* and to 8259A-2 */

    outb(0x20, 0x21);		/* start of hardware int's (0x20) */
    outb(0x28, 0xA1);		/* start of hardware int's 2 (0x28) */

    outb(0x04, 0x21);		/* 8259-1 is master */
    outb(0x02, 0xA1);		/* 8259-2 is slave */

    outb(0x01, 0x21);		/* 8086 mode for both */
    outb(0x01, 0xA1);

    outb(0xFF, 0xA1);		/* mask off all interrupts for now */
    outb(0xFB, 0x21);		/* mask all irq's but irq2 which is cascaded */
}

/* Start Linux */
static int start_linux(uint32_t kern_addr, struct linux_params *params)
{
    struct segment_desc *linux_gdt;
    struct context *ctx;
    //extern int cursor_x, cursor_y;

    ctx = init_context(phys_to_virt(STACK_LOC), 4096, 0);

    /* Linux expects GDT being in low memory */
    linux_gdt = phys_to_virt(GDT_LOC);
    memset(linux_gdt, 0, 13*sizeof(struct segment_desc));
    /* Normal kernel code/data segments */
    linux_gdt[2] = gdt[FLAT_CODE];
    linux_gdt[3] = gdt[FLAT_DATA];
    /* 2.6 kernel uses 12 and 13, but head.S uses backward-compatible
     * segments (2 and 3), so it SHOULD not be a problem.
     * However, some distro kernels (eg. RH9) with backported threading
     * patch use 12 and 13 also when booting... */
    linux_gdt[12] = gdt[FLAT_CODE];
    linux_gdt[13] = gdt[FLAT_DATA];
    ctx->gdt_base = GDT_LOC;
    ctx->gdt_limit = 14*8-1;
    ctx->cs = 0x10;
    ctx->ds = 0x18;
    ctx->es = 0x18;
    ctx->fs = 0x18;
    ctx->gs = 0x18;
    ctx->ss = 0x18;

    /* Parameter location */
    ctx->esi = virt_to_phys(params);

    /* Entry point */
    ctx->eip = kern_addr;

    debug("eip=%#x\n", kern_addr);
    printf("Jumping to entry point...\n");

#ifdef VGA_CONSOLE
    /* Update VGA cursor position.
     * This must be here because the printf changes the value! */
    params->orig_x = cursor_x;
    params->orig_y = cursor_y;
#endif

    /* Go... */
    ctx = switch_to(ctx);

    /* It's impossible but... */
    printf("Returned with eax=%#x\n", ctx->eax);

    return ctx->eax;
}

int linux_load(struct sys_info *info, const char *file, const char *cmdline)
{
    struct linux_header hdr;
    struct linux_params *params;
    uint32_t kern_addr, kern_size;
    char *initrd_file = 0;

    if (!file_open(file))
	return -1;

    kern_addr = load_linux_header(&hdr);
    if (kern_addr == 0)
	return LOADER_NOT_SUPPORT;

    params = phys_to_virt(LINUX_PARAM_LOC);
    init_linux_params(params, &hdr);
    set_memory_size(params, info);
    initrd_file = parse_command_line(cmdline, phys_to_virt(COMMAND_LINE_LOC));
    set_command_line_loc(params, &hdr);

    kern_size = load_linux_kernel(&hdr, kern_addr);
    if (kern_size == 0) {
	if (initrd_file)
	    free(initrd_file);
	return -1;
    }

    if (initrd_file) {
	if (load_initrd(&hdr, info, kern_addr+kern_size, params, initrd_file)
		!= 0) {
	    free(initrd_file);
	    return -1;
	}
	free(initrd_file);
    }

    hardware_setup();

    start_linux(kern_addr, params);
    return 0;
}