/*
 * This file is part of the coreboot 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; version 2 of the License.
 *
 * 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 <console/console.h>
#include <arch/stages.h>
#include <program_loading.h>
#include <ip_checksum.h>
#include <string.h>
#include <symbols.h>

/* When the ramstage is relocatable the elf loading ensures an elf image cannot
 * be loaded over the ramstage code. */
static void jmp_payload_no_bounce_buffer(void *entry)
{
	/* Jump to kernel */
	__asm__ __volatile__(
		"	cld	\n\t"
		/* Now jump to the loaded image */
		"	call	*%0\n\t"

		/* The loaded image returned? */
		"	cli	\n\t"
		"	cld	\n\t"

		::
		"r" (entry)
		);
}

static void jmp_payload(void *entry, unsigned long buffer, unsigned long size)
{
	unsigned long lb_start, lb_size;

	lb_start = (unsigned long)&_program;
	lb_size = _program_size;

	printk(BIOS_SPEW, "entry    = 0x%08lx\n", (unsigned long)entry);
	printk(BIOS_SPEW, "lb_start = 0x%08lx\n", lb_start);
	printk(BIOS_SPEW, "lb_size  = 0x%08lx\n", lb_size);
	printk(BIOS_SPEW, "buffer   = 0x%08lx\n", buffer);

	/* Jump to kernel */
	__asm__ __volatile__(
		"	cld	\n\t"
#ifdef __x86_64__
		/* switch back to 32-bit mode */
		"       push    %4\n\t"
		"       push    %3\n\t"
		"       push    %2\n\t"
		"       push    %1\n\t"
		"       push    %0\n\t"

		/* use iret to switch to 32-bit code segment */
		"       xor     %%rax,%%rax\n\t"
		"       mov     %%ss, %%ax\n\t"
		"       push    %%rax\n\t"
		"       mov     %%rsp, %%rax\n\t"
		"       add     $8, %%rax\n\t"
		"       push    %%rax\n\t"
		"       pushfq\n\t"
		"       push    $0x10\n\t"
		"       lea     3(%%rip), %%rax\n\t"
		"       push    %%rax\n\t"
		"       iretq\n\t"
		".code32\n\t"
		/* disable paging */
		"       mov     %%cr0, %%eax\n\t"
		"       btc     $31, %%eax\n\t"
		"       mov     %%eax, %%cr0\n\t"
		/* disable long mode */
		"       mov     $0xC0000080, %%ecx\n\t"
		"       rdmsr\n\t"
		"       btc     $8, %%eax\n\t"
		"       wrmsr\n\t"

		"       pop     %%eax\n\t"
		"       add     $4, %%esp\n\t"
		"       pop     %%ebx\n\t"
		"       add     $4, %%esp\n\t"
		"       pop     %%ecx\n\t"

		"       add     $4, %%esp\n\t"
		"       pop     %%edx\n\t"
		"       add     $4, %%esp\n\t"
		"       pop     %%esi\n\t"
		"       add     $4, %%esp\n\t"
#endif

		/* Save the callee save registers... */
		"	pushl	%%esi\n\t"
		"	pushl	%%edi\n\t"
		"	pushl	%%ebx\n\t"
		/* Save the parameters I was passed */
#ifdef __x86_64__
		"	pushl	$0\n\t"    /* 20 adjust */
		"	pushl	%%eax\n\t" /* 16 lb_start */
		"	pushl	%%ebx\n\t" /* 12 buffer */
		"	pushl	%%ecx\n\t" /*  8 lb_size */
		"	pushl	%%edx\n\t" /*  4 entry */
		"	pushl	%%esi\n\t" /*  0 elf_boot_notes */
#else
		"	pushl	$0\n\t" /* 20 adjust */
		"	pushl	%0\n\t" /* 16 lb_start */
		"	pushl	%1\n\t" /* 12 buffer */
		"	pushl	%2\n\t" /*  8 lb_size */
		"	pushl	%3\n\t" /*  4 entry */
		"	pushl	%4\n\t" /*  0 elf_boot_notes */

#endif
		/* Compute the adjustment */
		"	xorl	%%eax, %%eax\n\t"
		"	subl	16(%%esp), %%eax\n\t"
		"	addl	12(%%esp), %%eax\n\t"
		"	addl	 8(%%esp), %%eax\n\t"
		"	movl	%%eax, 20(%%esp)\n\t"
		/* Place a copy of coreboot in its new location */
		/* Move ``longs'' the coreboot size is 4 byte aligned */
		"	movl	12(%%esp), %%edi\n\t"
		"	addl	 8(%%esp), %%edi\n\t"
		"	movl	16(%%esp), %%esi\n\t"
		"	movl	 8(%%esp), %%ecx\n\n"
		"	shrl	$2, %%ecx\n\t"
		"	rep	movsl\n\t"

		/* Adjust the stack pointer to point into the new coreboot image */
		"	addl	20(%%esp), %%esp\n\t"
		/* Adjust the instruction pointer to point into the new coreboot image */
		"	movl	$1f, %%eax\n\t"
		"	addl	20(%%esp), %%eax\n\t"
		"	jmp	*%%eax\n\t"
		"1:	\n\t"

		/* Copy the coreboot bounce buffer over coreboot */
		/* Move ``longs'' the coreboot size is 4 byte aligned */
		"	movl	16(%%esp), %%edi\n\t"
		"	movl	12(%%esp), %%esi\n\t"
		"	movl	 8(%%esp), %%ecx\n\t"
		"	shrl	$2, %%ecx\n\t"
		"	rep	movsl\n\t"

		/* Now jump to the loaded image */
		"	movl	%5, %%eax\n\t"
		"	movl	 0(%%esp), %%ebx\n\t"
		"	call	*4(%%esp)\n\t"

		/* The loaded image returned? */
		"	cli	\n\t"
		"	cld	\n\t"

		/* Copy the saved copy of coreboot where coreboot runs */
		/* Move ``longs'' the coreboot size is 4 byte aligned */
		"	movl	16(%%esp), %%edi\n\t"
		"	movl	12(%%esp), %%esi\n\t"
		"	addl	 8(%%esp), %%esi\n\t"
		"	movl	 8(%%esp), %%ecx\n\t"
		"	shrl	$2, %%ecx\n\t"
		"	rep	movsl\n\t"

		/* Adjust the stack pointer to point into the old coreboot image */
		"	subl	20(%%esp), %%esp\n\t"

		/* Adjust the instruction pointer to point into the old coreboot image */
		"	movl	$1f, %%eax\n\t"
		"	subl	20(%%esp), %%eax\n\t"
		"	jmp	*%%eax\n\t"
		"1:	\n\t"

		/* Drop the parameters I was passed */
		"	addl	$24, %%esp\n\t"

		/* Restore the callee save registers */
		"	popl	%%ebx\n\t"
		"	popl	%%edi\n\t"
		"	popl	%%esi\n\t"
#ifdef __x86_64__
		".code64\n\t"
#endif
		::
		"ri" (lb_start), "ri" (buffer), "ri" (lb_size),
		"ri" (entry),
		"ri"(0), "ri" (0)
		);
}

int arch_supports_bounce_buffer(void)
{
	return 1;
}

static void try_payload(struct prog *prog)
{
	if (prog_type(prog) == PROG_PAYLOAD) {
		if (IS_ENABLED(CONFIG_RELOCATABLE_RAMSTAGE))
			jmp_payload_no_bounce_buffer(prog_entry(prog));
		else
			jmp_payload(prog_entry(prog),
					(uintptr_t)prog_start(prog),
					prog_size(prog));
	}
}

void arch_prog_run(struct prog *prog)
{
	if (ENV_RAMSTAGE)
		try_payload(prog);
	__asm__ volatile (
#ifdef __x86_64__
		"jmp  *%%rdi\n"
#else
		"jmp  *%%edi\n"
#endif

		:: "D"(prog_entry(prog))
	);
}