/*
 * LoongArch kexec PE format binary image support.
 *
 * Copyright (C) 2022 Loongson Technology Corporation Limited.
 *   Youling Tang <tangyouling@loongson.cn>
 *
 * derived from kexec-image-arm64.c
 *
 * This source code is licensed under the GNU General Public License,
 * Version 2.  See the file COPYING for more details.
 */

#define _GNU_SOURCE

#include <limits.h>
#include <errno.h>
#include <elf.h>

#include "kexec.h"
#include "kexec-elf.h"
#include "image-header.h"
#include "kexec-syscall.h"
#include "crashdump-loongarch.h"
#include "kexec-loongarch.h"
#include "arch/options.h"

#include <pe.h>

static inline int loongarch_pe_check_machine(const struct pe_hdr *pe_hdr)
{
	if (!pe_hdr)
		return 0;

	return (pe_hdr->machine == IMAGE_FILE_MACHINE_LOONGARCH64);
}

int pei_loongarch_probe(const char *kernel_buf, off_t kernel_size)
{
	const struct loongarch_image_header *h;
	const struct pe_hdr *pe_hdr;

	if (kernel_size < sizeof(struct loongarch_image_header)) {
		dbgprintf("%s: No loongarch image header.\n", __func__);
		return -1;
	}

	h = (const struct loongarch_image_header *)(kernel_buf);

	if (!loongarch_header_check_pe_sig(h)) {
		dbgprintf("%s: Bad loongarch PE image header.\n", __func__);
		return -1;
	}

	pe_hdr = (const struct pe_hdr *)(kernel_buf + get_pehdr_offset(kernel_buf));
	if (!loongarch_pe_check_machine(pe_hdr)) {
		dbgprintf("%s: Bad loongarch pe_hdr machine.\n", __func__);
		return -1;
	}

	return 0;
}

int pei_loongarch_load(int argc, char **argv, const char *buf,
	off_t len, struct kexec_info *info)
{
	int result;
	unsigned long hole_min = 0;
	unsigned long kernel_segment, kernel_entry;
	const struct loongarch_image_header *header;

	header = (const struct loongarch_image_header *)(buf);

	if (info->file_mode)
		return prepare_kexec_file_options(info);

	if (loongarch_process_image_header(header))
		return EFAILED;

	kernel_segment = loongarch_locate_kernel_segment(info);

	if (kernel_segment == ULONG_MAX) {
		dbgprintf("%s: Kernel segment is not allocated\n", __func__);
		result = EFAILED;
		goto exit;
	}

	kernel_entry = virt_to_phys(loongarch_header_kernel_entry(header));

	if (info->kexec_flags & KEXEC_ON_CRASH)
		/*
		 * offset addresses in order to load vmlinux.efi into
		 * crash kernel's memory.
		 */
		kernel_entry += crash_reserved_mem[usablemem_rgns.size - 1].start;

	dbgprintf("%s: kernel_segment: %016lx\n", __func__, kernel_segment);
	dbgprintf("%s: kernel_entry:   %016lx\n", __func__, kernel_entry);
	dbgprintf("%s: image_size:     %016lx\n", __func__,
		loongarch_mem.image_size);
	dbgprintf("%s: text_offset:    %016lx\n", __func__,
		loongarch_mem.text_offset);
	dbgprintf("%s: phys_offset:    %016lx\n", __func__,
		loongarch_mem.phys_offset);
	dbgprintf("%s: PE format:      %s\n", __func__,
		(loongarch_header_check_pe_sig(header) ? "yes" : "no"));

	/* Get kernel entry point */
	info->entry = (void *)kernel_entry;

	hole_min = kernel_segment + loongarch_mem.image_size;

	/* Create and initialize elf core header segment */
	if (info->kexec_flags & KEXEC_ON_CRASH) {
		result = load_crashdump_segments(info);
		if (result) {
			dbgprintf("%s: Creating eflcorehdr failed.\n",
								__func__);
			goto exit;
		}
	}

	/* Fixme: Loading a non-relocation kernel will cause the second kernel to fail
	   to start in KEXEC_ON_CRASH */

	/* Load the kernel */
	add_segment(info, buf, len, kernel_segment, loongarch_mem.image_size);

	/* Prepare and load initrd and cmdline data */
	result = loongarch_load_other_segments(info, hole_min);
	if (result) {
		fprintf(stderr, "kexec: Load initrd and cmdline segments failed.\n");
		goto exit;
	}

exit:
	if (result)
		fprintf(stderr, "kexec: load failed.\n");

	return result;
}

void pei_loongarch_usage(void)
{
	printf(
"     An LoongArch PE format binary image, uncompressed, little endian.\n"
"     Typically a vmlinux.efi file.\n\n");
}
