| /* |
| * 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 "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; |
| } |