/*
 * Copyright (C) 2009 Juergen Beisert, Pengutronix
 *
 * In parts from the GRUB2 project:
 *
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008  Free Software Foundation, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *
 */

#include <common.h>
#include <command.h>
#include <environment.h>
#include <fs.h>
#include <errno.h>
#include <getopt.h>
#include <malloc.h>
#include <boot.h>
#include <asm/syslib.h>

/** FIXME */
#define LINUX_MAGIC_SIGNATURE       0x53726448      /* "HdrS" */

/** FIXME */
#define LINUX_FLAG_BIG_KERNEL       0x1

/** FIXME */
#define LINUX_BOOT_LOADER_TYPE      0x72

/** FIXME */
#define LINUX_DEFAULT_SETUP_SECTS	4

/** FIXME */
#define LINUX_MAX_SETUP_SECTS		64

/** FIXME */
#define LINUX_OLD_REAL_MODE_SEGMT	0x9000

/** FIXME */
#define LINUX_OLD_REAL_MODE_ADDR	(LINUX_OLD_REAL_MODE_SEGMT << 4)

/** FIXME */
#define LINUX_HEAP_END_OFFSET		(LINUX_OLD_REAL_MODE_SEGMT - 0x200)

/** FIXME */
#define LINUX_FLAG_CAN_USE_HEAP		0x80

/** Define kernel command lines's start offset in the setup segment */
#define LINUX_CL_OFFSET			0x9000

/** Define kernel command lines's end offset */
#define LINUX_CL_END_OFFSET		0x90FF

/** FIXME */
#define LINUX_CL_MAGIC			0xA33F

/** FIXME */
#define LINUX_SETUP_MOVE_SIZE		0x9100

/** Sector size */
#define DISK_SECTOR_BITS		9
#define DISK_SECTOR_SIZE		0x200

/** Where to load a bzImage */
#define LINUX_BZIMAGE_ADDR		0x100000

struct linux_kernel_header {
	/* first sector of the image */
	uint8_t code1[0x0020];
	uint16_t cl_magic;		/**< Magic number 0xA33F */
	uint16_t cl_offset;		/**< The offset of command line */
	uint8_t code2[0x01F1 - 0x0020 - 2 - 2];
	uint8_t setup_sects;		/**< The size of the setup in sectors */
	uint16_t root_flags;		/**< If the root is mounted readonly */
	uint16_t syssize;		/**< obsolete */
	uint16_t swap_dev;		/**< obsolete */
	uint16_t ram_size;		/**< obsolete */
	uint16_t vid_mode;		/**< Video mode control */
	uint16_t root_dev;		/**< Default root device number */
	uint16_t boot_flag;		/**< 0xAA55 magic number */

	/* second sector of the image */
	uint16_t jump;			/**< Jump instruction (this is code!) */
	uint32_t header;		/**< Magic signature "HdrS" */
	uint16_t version;		/**< Boot protocol version supported */
	uint32_t realmode_swtch;	/**< Boot loader hook */
	uint16_t start_sys;		/**< The load-low segment (obsolete) */
	uint16_t kernel_version;	/**< Points to kernel version string */
	uint8_t type_of_loader;		/**< Boot loader identifier */
#define LINUX_LOADER_ID_LILO		0x0
#define LINUX_LOADER_ID_LOADLIN		0x1
#define LINUX_LOADER_ID_BOOTSECT	0x2
#define LINUX_LOADER_ID_SYSLINUX	0x3
#define LINUX_LOADER_ID_ETHERBOOT	0x4
#define LINUX_LOADER_ID_ELILO		0x5
#define LINUX_LOADER_ID_GRUB		0x7
#define LINUX_LOADER_ID_UBOOT		0x8
#define LINUX_LOADER_ID_XEN		0x9
#define LINUX_LOADER_ID_GUJIN		0xa
#define LINUX_LOADER_ID_QEMU		0xb
	uint8_t loadflags;		/**< Boot protocol option flags */
	uint16_t setup_move_size;	/**< Move to high memory size */
	uint32_t code32_start;		/**< Boot loader hook */
	uint32_t ramdisk_image;		/**< initrd load address */
	uint32_t ramdisk_size;		/**< initrd size */
	uint32_t bootsect_kludge;	/**< obsolete */
	uint16_t heap_end_ptr;		/**< Free memory after setup end */
	uint8_t ext_loader_ver;		/**< boot loader's extension of the version number */
	uint8_t ext_loader_type;	/**< boot loader's extension of its type */
	char *cmd_line_ptr;		/**< Points to the kernel command line */
	uint32_t initrd_addr_max;	/**< Highest address for initrd */
#if 0
	/* for the records only. These members are defined in
	 * more recent Linux kernels
	 */
	uint32_t kernel_alignment;	/**< Alignment unit required by the kernel */
	uint8_t relocatable_kernel;	/** */
	uint8_t min_alignment;		/** */
	uint32_t cmdline_size;		/** */
	uint32_t hardware_subarch;	/** */
	uint64_t hardware_subarch_data;	/** */
	uint32_t payload_offset;	/** */
	uint32_t payload_length;	/** */
	uint64_t setup_data;		/** */
	uint64_t pref_address;		/** */
	uint32_t init_size;		/** */
#endif
} __attribute__ ((packed));

/* This is -1. Keep this value in sync with the kernel */
#define NORMAL_VGA	0xffff		/* 80x25 mode */
#define ASK_VGA		0xfffd		/* ask for it at bootup */

/**
 * Load an x86 Linux kernel bzImage and start it
 * @param argc parameter count
 * @param argv list of parameter
 *
 * Loads an x86 bzImage, checks for its integrity, stores the two parts
 * (setup = 'real mode code' and kernel = 'protected mode code') to their
 * default locations, switches back to real mode and runs the setup code.
 */
static int do_linux16(int argc, char *argv[])
{
	struct linux_kernel_header *lh = NULL;
	int rc, opt;
	unsigned setup_sects;
	unsigned real_mode_size;
	int vid_mode = NORMAL_VGA;
	size_t image_size;
	const char *cmdline = linux_bootargs_get();
	const char *kernel_file;

	while((opt = getopt(argc, argv, "v:")) > 0) {
		switch(opt) {
		case 'v':
			vid_mode = simple_strtoul(optarg, NULL, 0);
			if (vid_mode == 0) {
				if (!strcmp(optarg, "ask"))
					vid_mode = ASK_VGA;
				else {
					printf("Unknown video mode: %s\n", optarg);
					return 1;
				}
			}
			break;
		}
	}

	if (optind == argc) {
		printf("No kernel filename given\n");
		return 1;
	}
	kernel_file = argv[optind];

	lh = read_file(kernel_file, &image_size);
	if (lh == NULL) {
		printf("Cannot read file '%s'\n", argv[1]);
		return 1;
	}

	if (lh->boot_flag != 0xaa55) {
		printf("File '%s' has invalid magic number\n", argv[1]);
		rc = 1;
		goto on_error;
	}

	if (lh->setup_sects > LINUX_MAX_SETUP_SECTS) {
		printf("File '%s' contains too many setup sectors\n", argv[1]);
		rc = 1;
		goto on_error;
	}

	setup_sects = lh->setup_sects;

	printf("Found a %d.%d image header\n", lh->version >> 8, lh->version & 0xFF);

	if (lh->header == LINUX_MAGIC_SIGNATURE && lh->version >= 0x0200) {
		/* kernel is recent enough */
		;
		if (!(lh->loadflags & LINUX_FLAG_BIG_KERNEL)) {
			printf("Cannot load a classic zImage. Use a bzImage instead\n");
			goto on_error;
		}
		lh->type_of_loader = LINUX_BOOT_LOADER_TYPE;	/* TODO */

		if (lh->version >= 0x0201) {
			lh->heap_end_ptr = LINUX_HEAP_END_OFFSET;
			lh->loadflags |= LINUX_FLAG_CAN_USE_HEAP;
		}

		if (lh->version >= 0x0202)
			lh->cmd_line_ptr = (void*)(LINUX_OLD_REAL_MODE_ADDR + LINUX_CL_OFFSET);	/* FIXME */
		else {
			lh->cl_magic = LINUX_CL_MAGIC;
			lh->cl_offset = LINUX_CL_OFFSET;
			lh->setup_move_size = LINUX_SETUP_MOVE_SIZE;
		}
	} else {
		printf("Kernel too old to handle\n");
		rc = 1;
		goto on_error;
	}

	if (strlen(cmdline) >= (LINUX_CL_END_OFFSET -  LINUX_CL_OFFSET)) {
		printf("Kernel command line exceeds the available space\n");
		rc = 1;
		goto on_error;
	}

	/*
	 * The kernel does not check for the "vga=<val>" kernel command line
	 * parameter anymore. It expects this kind of information in the
	 * boot parameters instead.
	 */
	if (vid_mode != NORMAL_VGA)
		lh->vid_mode = vid_mode;

	/* If SETUP_SECTS is not set, set it to the default.  */
	if (setup_sects == 0) {
		printf("Fixing setup sector count\n");
		setup_sects = LINUX_DEFAULT_SETUP_SECTS;
	}

	if (setup_sects >= 15) {
		void *src = lh;
		if (lh->kernel_version != 0)
			printf("Kernel version: '%s'\n",
			       (char *)src + lh->kernel_version + DISK_SECTOR_SIZE);
	}

	/*
	 * Size of the real mode part to handle in a separate way
	 */
	real_mode_size = (setup_sects << DISK_SECTOR_BITS) + DISK_SECTOR_SIZE;

	/*
	 *                real mode space                     hole            extended memory
	 * |---------------------------------------------->|----------->|------------------------------>
	 * 0                                            0xa0000     0x100000
	 *  <-1-|----------2-----------><-3-   |
	 *    0x7e00                        0x90000
	 *                                <-4--|-5-->                   |---------6------------->
	 *
	 * 1) real mode stack
	 * 2) barebox code
	 * 3) flat mode stack
	 * 4) realmode stack when starting a Linux kernel
	 * 5) Kernel's real mode setup code
	 * 6) compressed kernel image
	 */
	/*
	 * Parts of the image we know:
	 * - real mode part
	 * - kernel payload
	 */
	/*
	 * NOTE: This part is dangerous, as it copies some image content to
	 * various locations in the main memory. This could overwrite important
	 * data of the running barebox (hopefully not)
	 */
	/* copy the real mode part of the image to the 9th segment */
	memcpy((void*)LINUX_OLD_REAL_MODE_ADDR, lh, LINUX_SETUP_MOVE_SIZE);

	/* TODO add 'BOOT_IMAGE=<file>' and 'auto' if no user intervention was done (in front of all other params) */
	/* copy also the command line into this area */
	memcpy((void*)(LINUX_OLD_REAL_MODE_ADDR + LINUX_CL_OFFSET), cmdline, strlen(cmdline) + 1);
	printf("Using kernel command line: '%s'\n", cmdline);

	/* copy the compressed image part to its final address the setup code expects it
	 * Note: The protected mode part starts at offset (setup_sects + 1) * 512
	 */
	memcpy((void*)LINUX_BZIMAGE_ADDR, ((void*)lh) + real_mode_size, image_size - real_mode_size);

	/*
	 * switch back to real mode now and start the real mode part of the
	 * image at address "(LINUX_OLD_REAL_MODE_ADDR >> 4) + 0x20:0x0000"
	 * which means "0x9020:0x000" -> 0x90200
	 */
	bios_start_linux(LINUX_OLD_REAL_MODE_SEGMT);	/* does not return */

on_error:
	if (lh != NULL)
		free(lh);

	return rc;
}

BAREBOX_CMD_HELP_START(linux16)
BAREBOX_CMD_HELP_USAGE("linux16 <file> [-v <mode>]\n")
BAREBOX_CMD_HELP_SHORT("Boot a kernel <file> on x86 via real mode code.\n")
BAREBOX_CMD_HELP_OPT  ("-v <mode>",   "VESA video mode number or 'ask'\n")
BAREBOX_CMD_HELP_END

/**
 * @page linux16_command

<p>Only kernel images in bzImage format are supported by now. See \ref
x86_boot_preparation for more info about how to use this command.</p>

<p>For the video mode refer the Linux kernel documentation
'Documentation/fb/vesafb.txt' for correct VESA mode numbers. If the keyword
'ask' instead of a number is given, the starting kernel will ask for a number.
</p>
 */

BAREBOX_CMD_START(linux16)
	.cmd		= do_linux16,
	.usage		= "boot a linux kernel",
	BAREBOX_CMD_HELP(cmd_linux16_help)
BAREBOX_CMD_END

/**
 * @file
 * @brief Boot support for Linux on x86
 */

/**
 * @page x86_boot_preparation Linux Preparation on x86
 *
 * Due to some real mode constraints, starting Linux is somehow tricky.
 * Currently only @p bzImages are supported, because @p zImages would
 * interfere with the @a barebox runtime.
 * Also older load header versions than 2.00 aren't supported.
 *
 * The memory layout immediately before starting the Linux kernel:
 *
@verbatim
                  real mode space                     hole            extended memory
   |---------------------------------------------->|----------->|------------------------------>
   0  0x7e00                        0x90000     0xa0000     0x100000
    <-1-|----------2-----------><-3-   |
                                  <-4--|-5-->                   |---------6------------->
@endverbatim
 *
 * @li 1 = @a barebox's real mode stack
 * @li 2 = @a barebox's code
 * @li 3 = @a barebox's flat mode stack
 * @li 4 = real mode stack, when starting the Linux kernel
 * @li 5 = Kernel's real mode setup code
 * @li 6 = compressed kernel image
 *
 * A more detailed memory layout for kernel's real mode setup code
 *
@verbatim

   0x90000                                    0x97fff   0x99000              0x990ff
   ---|------------------------------------------|----------------|--------------------|
      |<-------- max setup code size ----------->|<--heap/stack-->|<-- command line -->|

@endverbatim
 *
 * The regular entry point into the setup code is 0x90200 (2nd sector)
 *
 * To start the kernel, it's own setup code will be called. To do so, it
 * must be called in real mode. So, @a barebox switches back to real mode
 * a last time and does a jump to the setup code entry point. Now its up to
 * the setup code to deflate the kernel, switching to its own protected mode
 * setup and starting the kernel itself.
 *
 * @note This scenario only works, if a BIOS is still present. In this case
 * there is no need for @a barebox to forward any system related information
 * to the kernel. Everything is detected by kernel's setup code.
 *
 */
