/*-
 * Copyright (c) 2018-2022 Intel Corporation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

/* This is a very simple elf binary loader. It only support static elf32 binary
 * loading. So we don't need to handle elf relocation. Just need to load the
 * PT_LOAD section to correct memory address and set correct entry.
 *
 * It also prepare simple multiboot info (only memory info) to guest. If some
 * other things are necessary for guest, we could add it per requirement.
 */

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <elf.h>

#include "types.h"
#include "dm.h"
#include "vmmapi.h"
#include "sw_load.h"
#include "acpi.h"
#include "log.h"

#define	ELF_BUF_LEN			(1024UL*8UL)
#define	MULTIBOOT_HEAD_MAGIC		(0x1BADB002U)
#define	MULTIBOOT_MACHINE_STATE_MAGIC	(0x2BADB002U)
/* The max length for GDT is 8192 * 8 bytes */
#define	GDT_LOAD_OFF(ctx)		(ctx->lowmem - 64U * KB)

static char elf_path[STR_LEN];
/* Whether we need to setup multiboot info for guest */
static int multiboot_image = 0;

/* Multiboot info. Compatible with Multiboot spec 0.6.96 with vesa removed */
#define	MULTIBOOT_INFO_MEMORY		(0x00000001U)
#define	MULTIBOOT_INFO_BOOTDEV		(0x00000002U)
#define	MULTIBOOT_INFO_CMDLINE		(0x00000004U)
#define	MULTIBOOT_INFO_MODS		(0x00000008U)
#define	MULTIBOOT_INFO_AOUT_SYMS	(0x00000010U)
#define	MULTIBOOT_INFO_ELF_SHDR		(0x00000020U)
#define	MULTIBOOT_INFO_MEM_MAP		(0x00000040U)
#define	MULTIBOOT_INFO_DRIVE_INFO	(0x00000080U)
#define	MULTIBOOT_INFO_CONFIG_TABLE	(0x00000100U)
#define	MULTIBOOT_INFO_BOOT_LOADER_NAME	(0x00000200U)
#define	MULTIBOOT_INFO_BOOT_APM_TABLE	(0x00000400U)

struct multiboot_info {
	uint32_t	flags;
	uint32_t	mem_lower;
	uint32_t	mem_upper;

	uint32_t	boot_device;
	uint32_t	cmdline;
	uint32_t	mods_count;
	uint32_t	mods_addr;

	uint32_t	bin_sec[4];

	uint32_t	mmap_length;
	uint32_t	mmap_addr;

	uint32_t	drives_length;
	uint32_t	drives_addr;

	uint32_t	config_table;

	uint32_t	boot_loader_name;
};

int
acrn_parse_elf(char *arg)
{
	int err = -1;
	size_t len = strnlen(arg, STR_LEN);
	size_t elfsz;

	if (len < STR_LEN) {
		strncpy(elf_path, arg, len + 1);
		if (check_image(elf_path, 0, &elfsz) == 0) {
			elf_file_name = elf_path;
			pr_notice("SW_LOAD: get elf path %s\n", elf_path);
			err = 0;
		}
	}
	return err;
}

static int load_elf32(struct vmctx *ctx, FILE *fp, void *buf)
{
	int i;
	size_t phd_size, read_len;
	Elf32_Ehdr *elf32_header = (Elf32_Ehdr *)buf;
	Elf32_Phdr *elf32_phdr, *elf32_phdr_bk;

	phd_size = elf32_header->e_phentsize * elf32_header->e_phnum;
	elf32_phdr_bk = elf32_phdr = (Elf32_Phdr *)calloc(1, phd_size);
	if (elf32_phdr == NULL) {
		pr_err("Can't allocate memory for elf program header\n");
		return -1;
	}

	fseek(fp, elf32_header->e_phoff, SEEK_SET);
	read_len = fread((void *)elf32_phdr, 1, phd_size, fp);
	if (read_len != phd_size) {
		pr_err("can't get %ld data from elf file\n", phd_size);
	}

	for (i = 0; i < elf32_header->e_phnum; i++) {
		if (elf32_phdr->p_type == PT_LOAD) {
			if ((elf32_phdr->p_vaddr + elf32_phdr->p_memsz) >
					ctx->lowmem) {
				pr_err("No enough memory to load elf file\n");
				free(elf32_phdr_bk);
				return -1;
			}

			void *seg_ptr = ctx->baseaddr + elf32_phdr->p_vaddr;

			/* Clear the segment memory in memory.
			 * This is required for BSS section
			 */
			memset(seg_ptr, 0, elf32_phdr->p_memsz);
			fseek(fp, elf32_phdr->p_offset, SEEK_SET);
			read_len = fread(seg_ptr, 1, elf32_phdr->p_filesz, fp);
			if (read_len != elf32_phdr->p_filesz) {
				pr_err("Can't get %d data\n",
						elf32_phdr->p_filesz);
			}
		}

		elf32_phdr++;
	}

	free(elf32_phdr_bk);
	return 0;
}

static int
acrn_load_elf(struct vmctx *ctx, char *elf_file_name, unsigned long *entry,
		uint32_t *multiboot_flags)
{
	int i, ret = 0;
	FILE *fp;
	size_t read_len = 0;
	unsigned int *ptr32;
	char *elf_buf;
	Elf32_Ehdr *elf_ehdr;


	elf_buf = calloc(1, ELF_BUF_LEN);
	if (elf_buf == NULL) {
		pr_err("Can't allocate elf buf\r\n");
		return -1;
	}

	fp = fopen(elf_file_name, "r");
	if (fp == NULL) {
		pr_err("Can't open elf file: %s\r\n", elf_file_name);
		free(elf_buf);
		return -1;
	}

	read_len = fread(elf_buf, 1, ELF_BUF_LEN, fp);
	if (read_len != ELF_BUF_LEN) {
		pr_err("Can't get %ld data from elf file\n",
				ELF_BUF_LEN);
	}

	/* Scan the first 8k to detect whether the elf needs multboot
	 * info prepared.
	 */
	ptr32 = (unsigned int *) elf_buf;
	for (i = 0; i <= ((ELF_BUF_LEN/4) - 3); i++) {
		if (ptr32[i] == MULTIBOOT_HEAD_MAGIC) {
			int j = 0;
			unsigned int sum = 0;

			/* According to multiboot spec 0.6.96 sec 3.1.2.
			 * There are three u32:
			 *  offset   field
			 *    0      multiboot_head_magic
			 *    4      flags
			 *    8      checksum
			 * The sum of these three u32 should be u32 zero.
			 */
			for (j = 0; j < 3; j++) {
				sum += ptr32[j + i];
			}

			if (0 == sum) {
				multiboot_image = 1;
				*multiboot_flags = ptr32[i + 1];
			}
			break;
		}
	}

	elf_ehdr = (Elf32_Ehdr *) elf_buf;

	if ((elf_ehdr->e_ident[EI_MAG0] != ELFMAG0) ||
		(elf_ehdr->e_ident[EI_MAG1] != ELFMAG1) ||
		(elf_ehdr->e_ident[EI_MAG2] != ELFMAG2) ||
		(elf_ehdr->e_ident[EI_MAG3] != ELFMAG3)) {
		pr_err("This is not elf file\n");
		fclose(fp);
		free(elf_buf);

		return -1;
	}

	if (elf_ehdr->e_ident[EI_CLASS] == ELFCLASS32) {
		ret = load_elf32(ctx, fp, elf_buf);
	} else {
		pr_err("No available 64bit elf loader ready yet\n");
		fclose(fp);
		free(elf_buf);
		return -1;
	}

	*entry = elf_ehdr->e_entry;
	fclose(fp);
	free(elf_buf);

	return ret;
}

/* Where we put multboot info to */
#define	MULTIBOOT_OFFSET	(0x20000)
static const uint64_t acrn_init_gdt[] = {
	0x0UL,
	0x00CF9B000000FFFFUL,	/* Linear Code */
	0x00CF93000000FFFFUL,	/* Linear Data */
};

int
acrn_sw_load_elf(struct vmctx *ctx)
{
	int ret;
	uint32_t multiboot_flags = 0;
	uint64_t entry = 0;
	struct multiboot_info *mi;

	ret = acrn_load_elf(ctx, elf_file_name, &entry, &multiboot_flags);
	if (ret < 0)
		return ret;

	/* set guest bsp state. Will call hypercall set bsp state
	 * after bsp is created.
	 */
	memset(&ctx->bsp_regs, 0, sizeof( struct acrn_vcpu_regs));
	ctx->bsp_regs.vcpu_id = 0;

	memcpy(ctx->baseaddr + GDT_LOAD_OFF(ctx), &acrn_init_gdt,
			sizeof(acrn_init_gdt));
	ctx->bsp_regs.vcpu_regs.gdt.limit = sizeof(acrn_init_gdt) - 1;
	ctx->bsp_regs.vcpu_regs.gdt.base = GDT_LOAD_OFF(ctx);

	/* CR0_ET | CR0_NE | CR0_PE */
	ctx->bsp_regs.vcpu_regs.cr0 = 0x31U;

	ctx->bsp_regs.vcpu_regs.cs_ar = 0xCF9BU;
	ctx->bsp_regs.vcpu_regs.cs_sel = 0x8U;
	ctx->bsp_regs.vcpu_regs.cs_limit = 0xFFFFFFFFU;

	ctx->bsp_regs.vcpu_regs.ds_sel = 0x10U;
	ctx->bsp_regs.vcpu_regs.ss_sel = 0x10U;
	ctx->bsp_regs.vcpu_regs.es_sel = 0x10U;
	ctx->bsp_regs.vcpu_regs.gs_sel = 0x10U;
	ctx->bsp_regs.vcpu_regs.fs_sel = 0x10U;

	ctx->bsp_regs.vcpu_regs.rip = entry;
	ctx->bsp_regs.vcpu_regs.gprs.rax = MULTIBOOT_MACHINE_STATE_MAGIC;

	if (multiboot_image == 1) {
		mi = (struct multiboot_info *)(ctx->baseaddr + MULTIBOOT_OFFSET);
		memset(mi, 0, sizeof(*mi));

		if (multiboot_flags == (1 << 1)) {
			/* Now, we only support elf binary request multiboot
			 * info with memory info filled case.
			 *
			 * TODO:
			 * For other elf image with multiboot enabled, they
			 * may need more fileds initialized here. We will add
			 * them here per each requirement.
			 */
			mi->flags = MULTIBOOT_INFO_MEMORY;
			mi->mem_lower = 0;
			mi->mem_upper = GDT_LOAD_OFF(ctx) / 1024U;
			ctx->bsp_regs.vcpu_regs.gprs.rbx = MULTIBOOT_OFFSET;
		} else {
			pr_err("Invalid multiboot header in elf binary\n");
			return -1;
		}
	}

	return 0;
}
