/*
 * This file is part of the coreboot project.
 *
 * Copyright (C) 2008 Rudolf Marek <r.marek@assembler.cz>
 * Copyright (C) 2009 One Laptop per Child, Association, 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.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

/* Parts of this code is taken from reboot.c from Linux. */

/*
 * This file mostly copied from Rudolf's S3 patch, some changes in
 * acpi_jump_wake().
 */

#include <stdint.h>
#include <string.h>
#include <arch/io.h>
#include <console/console.h>
#include <delay.h>
#include <part/init_timer.h>	/* for jason_tsc_count_end */
#include "wakeup.h"

int enable_a20(void);

/*
 * The following code and data reboots the machine by switching to real
 * mode and jumping to the BIOS reset entry point, as if the CPU has
 * really been reset. The previous version asked the keyboard
 * controller to pulse the CPU reset line, which is more thorough, but
 * doesn't work with at least one type of 486 motherboard. It is easy
 * to stop this code working; hence the copious comments.
 */

static unsigned long long real_mode_gdt_entries[3] = {
	0x0000000000000000ULL,	/* Null descriptor */
	0x00009a000000ffffULL,	/* 16-bit real-mode 64k code at 0x00000000 */
	0x000092000100ffffULL	/* 16-bit real-mode 64k data at 0x00000100 */
};

struct Xgt_desc_struct {
	unsigned short size;
	unsigned long address __attribute__ ((packed));
	unsigned short pad;
} __attribute__ ((packed));

static struct Xgt_desc_struct real_mode_gdt = {
	sizeof(real_mode_gdt_entries) - 1,
	(long)real_mode_gdt_entries
},
real_mode_idt = {0x3ff, 0},
no_idt = { 0, 0 };

/*
 * This is 16-bit protected mode code to disable paging and the cache,
 * switch to real mode and jump to the BIOS reset code.
 *
 * The instruction that switches to real mode by writing to CR0 must be
 * followed immediately by a far jump instruction, which set CS to a
 * valid value for real mode, and flushes the prefetch queue to avoid
 * running instructions that have already been decoded in protected
 * mode.
 *
 * Clears all the flags except ET, especially PG (paging), PE
 * (protected-mode enable) and TS (task switch for coprocessor state
 * save). Flushes the TLB after paging has been disabled. Sets CD and
 * NW, to disable the cache on a 486, and invalidates the cache. This
 * is more like the state of a 486 after reset. I don't know if
 * something else should be done for other chips.
 *
 * More could be done here to set up the registers as if a CPU reset had
 * occurred; hopefully real BIOSs don't assume much.
 */

//      0x66, 0x0d, 0x00, 0x00, 0x00, 0x60,     /* orl $0x60000000, %eax */

static unsigned char real_mode_switch[] = {
	0x66, 0x0f, 0x20, 0xc0,			/* movl %cr0,%eax */
	0x24, 0xfe,				/* andb $0xfe,al */
	0x66, 0x0f, 0x22, 0xc0			/* movl %eax,%cr0 */
};

static unsigned char jump_to_wakeup[] = {
	0xea, 0x00, 0x00, 0x00, 0xe0		/* ljmp $0xffff, $0x0000 */
};

/*
 * Switch to real mode and then execute the code
 * specified by the code and length parameters.
 * We assume that length will aways be less that 100!
 */
static unsigned char show31[6] = {
	0xb0, 0x31, 0xe6, 0x80, 0xeb, 0xFA	/* ljmp $0xffff, $0x0000 */
};

static unsigned char show32[6] = {
	0xb0, 0x32, 0xe6, 0x80, 0xeb, 0xFA	/* ljmp $0xffff, $0x0000 */
};

void acpi_jump_wake(u32 vector)
{
	u32 tmp, dwEip;
	u16 tmpvector;
	u8 Data;
	struct Xgt_desc_struct *wake_thunk16_Xgt_desc;

	printk_debug("IN ACPI JUMP WAKE TO %x\n", vector);
	if (enable_a20())
		die("failed to enable A20\n");
	printk_debug("IN ACPI JUMP WAKE TO 3 %x\n", vector);

	*((u16 *) (jump_to_wakeup + 3)) = (u16) (vector >> 4);
	printk_debug("%x %x %x %x %x\n", jump_to_wakeup[0], jump_to_wakeup[1],
		     jump_to_wakeup[2], jump_to_wakeup[3], jump_to_wakeup[4]);

	memcpy((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100),
	       real_mode_switch, sizeof(real_mode_switch));
	memcpy((void *)(WAKE_THUNK16_ADDR - 100), jump_to_wakeup,
	       sizeof(jump_to_wakeup));

	//jason_tsc_count();
	printk_emerg("file '%s', line %d\n\n", __FILE__, __LINE__);
	//jason_tsc_count_end();

	unsigned long long *real_mode_gdt_entries_at_eseg;
	real_mode_gdt_entries_at_eseg = WAKE_THUNK16_GDT;		/* Copy from real_mode_gdt_entries and change limition to 1M and data base to 0; */
	real_mode_gdt_entries_at_eseg[0] = 0x0000000000000000ULL;	/* Null descriptor */
	real_mode_gdt_entries_at_eseg[1] = 0x000f9a000000ffffULL;	/* 16-bit real-mode 1M code at 0x00000000 */
	real_mode_gdt_entries_at_eseg[2] = 0x000f93000000ffffULL;	/* 16-bit real-mode 1M data at 0x00000000 */

	wake_thunk16_Xgt_desc = WAKE_THUNK16_XDTR;
	wake_thunk16_Xgt_desc[0].size = sizeof(real_mode_gdt_entries) - 1;
	wake_thunk16_Xgt_desc[0].address = (long)real_mode_gdt_entries_at_eseg;
	wake_thunk16_Xgt_desc[1].size = 0x3ff;
	wake_thunk16_Xgt_desc[1].address = 0;
	wake_thunk16_Xgt_desc[2].size = 0;
	wake_thunk16_Xgt_desc[2].address = 0;

	/* Added this code to get current value of EIP. */
	__asm__ volatile (
		"calll geip\n\t"
		"geip: \n\t"
		"popl %0\n\t"
		: "=a" (dwEip)
	);

	unsigned char *dest, *src;
	src = (unsigned char *)dwEip;
	dest = WAKE_RECOVER1M_CODE;
	u32 i;
	for (i = 0; i < 0x200; i++)
		dest[i] = src[i];

	__asm__ __volatile__("ljmp $0x0010,%0"	/* 08 error */
			     ::"i"((void *)(WAKE_RECOVER1M_CODE + 0x20)));

	/* Added 0x20 "nop" to make sure the ljmp will not jump then halt. */
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");

	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");

	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");
	asm volatile ("nop");

	__asm__ volatile (
		/*
		 * Set new esp, maybe ebp should not equal to esp?, due to the
		 * variable in acpi_jump_wake?, anyway, this may be not a big
		 * problem. and I didn't clear the area (ef000+-0x200) to zero.
		 */
		"movl %0, %%ebp\n\t"
		"movl %0, %%esp\n\t"::"a" (WAKE_THUNK16_STACK)
	);

	/*
	 * Only "src" and "dest" use the new stack, and the esp maybe also
	 * used in resumevector.
	 */
#if PAYLOAD_IS_SEABIOS == 1
	/* WAKE_MEM_INFO inited in get_set_top_available_mem in tables.c. */
	src =
	    (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024 - 0x100000);
	dest = 0;

	/*
	 * If recovered 0-e0000, then when resume, before WinXP turn on the
	 * desktop screen, there is gray background which last 1sec.
	 */
	for (i = 0; i < 0xa0000; i++)
		dest[i] = src[i];

#if 0
	__asm__ volatile (
		"movl %0, %%esi\n\t"
		"movl $0, %%edi\n\t"
		"movl $0xa0000, %%ecx\n\t"
		"shrl $2, %%ecx\n\t"
		"rep movsd\n\t"
		::"a"(src)
	);
#endif
	src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
			- 0x100000 + 0xc0000);

#if 0
	dest = 0xc0000;
	for (i = 0; i < 0x20000; i++)
	      dest[i] = src[i];

	__asm__ volatile (
		"movl %0, %%esi\n\t"
		"movl $0xc0000, %%edi\n\t"
		"movl $0x20000, %%ecx\n\t"
		"shrl $2, %%ecx\n\t"
		"rep movsd\n\t"
		::"a"(src)
	);
#endif

	src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
			- 0x100000 + 0xe0000 + WAKE_SPECIAL_SIZE);

	/* dest = 0xf0000; */
	/* for (i = 0; i < 0x10000; i++) */
	/* 	dest[i] = src[i]; */
	__asm__ volatile (
		"movl %0, %%esi\n\t"
		"movl %1, %%edi\n\t"
		"movl %2, %%ecx\n\t"
		"shrl $2, %%ecx\n\t"
		"rep movsd\n\t"::"r" (src),
		"r"(0xe0000 + WAKE_SPECIAL_SIZE),
		"r"(0x10000 - WAKE_SPECIAL_SIZE)
	);

	src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
			- 0x100000 + 0xf0000);
	/* dest = 0xf0000; */
	/* for (i = 0; i < 0x10000; i++) */
	/* 	dest[i] = src[i]; */
	__asm__ volatile (
		"movl %0, %%esi\n\t"
		"movl $0xf0000, %%edi\n\t"
		"movl $0x10000, %%ecx\n\t"
		"shrl $2, %%ecx\n\t" "rep movsd\n\t"::"a" (src)
	);

	asm volatile ("wbinvd");
#endif
	/* Set up the IDT for real mode. */
	asm volatile ("lidt %0"::"m" (wake_thunk16_Xgt_desc[1]));

	/*
	 * Set up a GDT from which we can load segment descriptors for real
	 * mode. The GDT is not used in real mode; it is just needed here to
	 * prepare the descriptors.
	 */
	asm volatile ("lgdt %0"::"m" (wake_thunk16_Xgt_desc[0]));

	/*
	 * Load the data segment registers, and thus the descriptors ready for
	 * real mode.  The base address of each segment is 0x100, 16 times the
	 * selector value being loaded here.  This is so that the segment
	 * registers don't have to be reloaded after switching to real mode:
	 * the values are consistent for real mode operation already.
	 */
	__asm__ __volatile__(
		"movl $0x0010,%%eax\n"
		"\tmovl %%eax,%%ds\n"
		"\tmovl %%eax,%%es\n"
		"\tmovl %%eax,%%fs\n"
		"\tmovl %%eax,%%gs\n"
		"\tmovl %%eax,%%ss":::"eax"
	);

	/*
	 * Jump to the 16-bit code that we copied earlier. It disables paging
	 * and the cache, switches to real mode, and jumps to the BIOS reset
	 * entry point.
	 */

	__asm__ __volatile__(
		"ljmp $0x0008,%0"::"i"
		((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100))
	);
}

/* -*- linux-c -*- ------------------------------------------------------- *
 *
 *   Copyright (C) 1991, 1992 Linus Torvalds
 *   Copyright 2007 rPath, Inc. - All Rights Reserved
 *
 *   This file is part of the Linux kernel, and is made available under
 *   the terms of the GNU General Public License version 2.
 *
 * ----------------------------------------------------------------------- */

/*
 * arch/i386/boot/a20.c
 *
 * Enable A20 gate (return -1 on failure)
 */

// #include "boot.h"

#define MAX_8042_LOOPS	100000

static int empty_8042(void)
{
	u8 status;
	int loops = MAX_8042_LOOPS;

	while (loops--) {
		udelay(1);

		status = inb(0x64);
		if (status & 1) {
			/* Read and discard input data */
			udelay(1);
			(void)inb(0x60);
		} else if (!(status & 2)) {
			/* Buffers empty, finished! */
			return 0;
		}
	}

	return -1;
}

/* Returns nonzero if the A20 line is enabled.  The memory address
   used as a test is the int $0x80 vector, which should be safe. */

#define A20_TEST_ADDR	(4*0x80)
#define A20_TEST_SHORT  32
#define A20_TEST_LONG	2097152	/* 2^21 */

static int a20_test(int loops)
{
	int ok = 0;
	int saved, ctr;

//      set_fs(0x0000);
//      set_gs(0xffff);

	saved = ctr = *((u32 *) A20_TEST_ADDR);

	while (loops--) {
		//wrfs32(++ctr, A20_TEST_ADDR);

		*((u32 *) A20_TEST_ADDR) = ++ctr;

		udelay(1);	/* Serialize and make delay constant */

		ok = *((u32 *) A20_TEST_ADDR + 0xffff0 + 0x10) ^ ctr;
		if (ok)
			break;
	}

	*((u32 *) A20_TEST_ADDR) = saved;
	return ok;
}

/* Quick test to see if A20 is already enabled */
static int a20_test_short(void)
{
	return a20_test(A20_TEST_SHORT);
}

/* Longer test that actually waits for A20 to come on line; this
   is useful when dealing with the KBC or other slow external circuitry. */
static int a20_test_long(void)
{
	return a20_test(A20_TEST_LONG);
}

static void enable_a20_kbc(void)
{
	empty_8042();

	outb(0xd1, 0x64);	/* Command write */
	empty_8042();

	outb(0xdf, 0x60);	/* A20 on */
	empty_8042();
}

static void enable_a20_fast(void)
{
	u8 port_a;

	port_a = inb(0x92);	/* Configuration port A */
	port_a |= 0x02;		/* Enable A20 */
	port_a &= ~0x01;	/* Do not reset machine */
	outb(port_a, 0x92);
}

/*
 * Actual routine to enable A20; return 0 on ok, -1 on failure
 */

#define A20_ENABLE_LOOPS 255	/* Number of times to try */

int enable_a20(void)
{
	int loops = A20_ENABLE_LOOPS;

	while (loops--) {
		/* First, check to see if A20 is already enabled
		   (legacy free, etc.) */
		if (a20_test_short())
			return 0;

		/* Try enabling A20 through the keyboard controller */
		empty_8042();

		// if (a20_test_short())
		// 	return 0; /* BIOS worked, but with delayed reaction */

		enable_a20_kbc();
		if (a20_test_long())
			return 0;

		/* Finally, try enabling the "fast A20 gate" */
		enable_a20_fast();
		if (a20_test_long())
			return 0;
	}

	return -1;
}
