/*
 * Copyright 2015 Google Inc.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <assert.h>
#include <libpayload.h>
#include <stdint.h>

#include "vboot/boot.h"
#include "vboot/boot_policy.h"

/*
 * Boot policy settings. By default, we select CrOS image type and command line
 * as the one filled by the signer. This is important to maintain consistency
 * with current boards.
 */
static struct boot_policy boot_policy = {
	.img_type = KERNEL_IMAGE_CROS,
	.cmd_line_loc = CMD_LINE_SIGNER,
};

static struct {
	size_t count;
	const struct boot_policy *policy;
} curr_policy = {
	.count = 1,
	.policy = &boot_policy,
};

typedef int (*fill_bootinfo_fnptr)(struct boot_info *bi,
				   VbSelectAndLoadKernelParams *kparams);

/************************* CrOS Image Parsing ****************************/

static int fill_info_cros(struct boot_info *bi,
			  VbSelectAndLoadKernelParams *kparams)
{
	bi->kernel = kparams->kernel_buffer;
	bi->loader = (uint8_t *)bi->kernel +
		(kparams->bootloader_address - 0x100000);
	bi->params = (uint8_t *)bi->loader - CrosParamSize;

	if (boot_policy.cmd_line_loc == CMD_LINE_SIGNER)
		bi->cmd_line = (char *)bi->params - CmdLineSize;
	else
		bi->cmd_line = NULL;

	return 0;
}

/************************* Bootimg Parsing *******************************/
#define BOOTIMG_MAGIC			"ANDROID!"
#define BOOTIMG_MAGIC_SIZE		8
#define BOOTIMG_NAME_SIZE		16
#define BOOTIMG_ARGS_SIZE		512
#define BOOTIMG_EXTRA_ARGS_SIZE	1024
#define BOOTIMG_ID_SIZE		8

struct bootimg_hdr {
	uint8_t magic[BOOTIMG_MAGIC_SIZE];
	uint32_t kernel_size;
	uint32_t kernel_addr;
	uint32_t ramdisk_size;
	uint32_t ramdisk_addr;
	uint32_t second_size;
	uint32_t second_addr;
	uint32_t tags_addr;
	uint32_t page_size;
	uint32_t unused[2];
	uint8_t name[BOOTIMG_NAME_SIZE];
	uint8_t cmdline[BOOTIMG_ARGS_SIZE];
	uint32_t id[BOOTIMG_ID_SIZE];
	uint8_t extra_cmdline[BOOTIMG_EXTRA_ARGS_SIZE];
};

void *bootimg_get_kernel_ptr(void *img, size_t image_size)
{
	struct bootimg_hdr *hdr = img;
	void *kernel;

	if (image_size < sizeof(struct bootimg_hdr)) {
		printf("Bootimg: Header size error!\n");
		return NULL;
	}

	if (memcmp(hdr->magic, BOOTIMG_MAGIC, BOOTIMG_MAGIC_SIZE)) {
		printf("Bootimg: BOOTIMG_MAGIC mismatch!\n");
		return NULL;
	}

	if (image_size <= hdr->page_size) {
		printf("Bootimg: Header page size error!\n");
		return NULL;
	}

	kernel = (void *)((uintptr_t)hdr + hdr->page_size);

	return kernel;
}

static int fill_info_bootimg(struct boot_info *bi,
			     VbSelectAndLoadKernelParams *kparams)
{
	struct bootimg_hdr *hdr = kparams->kernel_buffer;
	uintptr_t kernel;
	uintptr_t ramdisk_addr;
	uint32_t kernel_size;

	bi->kernel = bootimg_get_kernel_ptr(hdr, kparams->kernel_buffer_size);

	if (bi->kernel == NULL)
		return -1;

	kernel = (uintptr_t)bi->kernel;

	if (boot_policy.cmd_line_loc == CMD_LINE_BOOTIMG_HDR)
		bi->cmd_line = (char *)hdr->cmdline;
	else
		bi->cmd_line = NULL;

	/*
	 * kernel_size should be aligned up to page_size since ramdisk always
	 * starts in a new page. The align up operation is performed on
	 * kernel_size rather than (kernel + hdr->kernel_size) because hdr might
	 * not be on an aligned boundary itself and thus aligning (kernel +
	 * hdr->kernel_size) can erroneously make ramdisk_addr point to an
	 * address beyond the ramdisk start.
	 * Aligning kernel_size to hdr->page_size gives us the size of kernel in
	 * multiple of pages.
	 */
	kernel_size = ALIGN_UP(hdr->kernel_size, hdr->page_size);

	if (kernel_size < kparams->kernel_buffer_size) {
		ramdisk_addr = kernel + kernel_size;

		bi->ramdisk_size = hdr->ramdisk_size;
		bi->ramdisk_addr = (void *)ramdisk_addr;
		printf("Bootimg: Ramdisk is present\n");
	} else {
		bi->ramdisk_size = 0;
		bi->ramdisk_addr = NULL;
		printf("Bootimg: No ramdisk present\n");
	}

	return 0;
}

/*
 * Table describing allowed command line locations and parsing functions for a
 * given image type.
 */
static const struct {
	uint32_t allowed_cmdline;
	fill_bootinfo_fnptr fn;
} img_type_info[] = {
	[KERNEL_IMAGE_CROS] = {CMD_LINE_SIGNER | CMD_LINE_DTB, fill_info_cros},
	[KERNEL_IMAGE_BOOTIMG] = {CMD_LINE_SIGNER | CMD_LINE_BOOTIMG_HDR |
				  CMD_LINE_DTB, fill_info_bootimg},
};

/* Returns 0 if img type allows given command line location, else -1 */
static int sanity_check_policy(kernel_img_type_t type,
			       cmd_line_loc_t cmd_line_loc)
{
	/*
	 * If given location for cmd line is allowed for this image type, return
	 * 0.
	 */
	if ((img_type_info[type].allowed_cmdline & cmd_line_loc)
	    == cmd_line_loc)
		return 0;

	/* Not allowed: return -1 */
	return -1;
}

/*
 * Allows board to set a boot policy based on:
 * a) Image type that the board supports - CrOS / Bootimg
 * b) Location of command line
 *
 * Returns 0 on success, -1 on error. Error can occur if a particular image type
 * does not support provided location for command line.
 */
int set_boot_policy(const struct boot_policy *policy, size_t count)
{
	int i;

	for (i = 0; i < count; i++) {
		if (sanity_check_policy(policy[i].img_type,
					policy[i].cmd_line_loc) == -1) {
			printf("Boot Policy: Invalid type-cmdline combo\n");
			return -1;
		}
	}

	curr_policy.count = count;
	curr_policy.policy = policy;

	printf("Boot policy: Policy set by board\n");

	return 0;
}

/*
 * Depending upon the boot policy set by the board for supported image type,
 * this function selects the appropriate function pointer to fill up the
 * boot_info structure.
 *
 * Returns 0 on success, -1 on error. Error can occur if the image type handler
 * is not able to setup the boot info structure for any reason.
 *
 * This function assumes that the board provides a prioritized list of image
 * types that it wants to boot. In the default case, we let only CrOS images
 * with cmd line filled by signer boot.
 */
int fill_boot_info(struct boot_info *bi, VbSelectAndLoadKernelParams *kparams)
{
	int i;

	uint32_t type = GET_KERNEL_IMG_TYPE(kparams->flags);

	for (i = 0; i < curr_policy.count; i++) {
		if (type == curr_policy.policy[i].img_type)
			break;
	}

	if (i == curr_policy.count) {
		printf("Boot policy: Invalid image type %x!\n", type);
		return -1;
	}

	boot_policy.img_type = curr_policy.policy[i].img_type;
	boot_policy.cmd_line_loc = curr_policy.policy[i].cmd_line_loc;
+
	printf("Boot policy: Match for type %x with cmdline %x\n",
	       boot_policy.img_type, boot_policy.cmd_line_loc);

	return img_type_info[boot_policy.img_type].fn(bi, kparams);
}
