blob: bae0fd4b3266004035c9365955c6767a1c359370 [file] [log] [blame]
Uwe Hermann20a98c92009-06-05 23:02:43 +00001/*
2 * This file is part of the coreboot project.
3 *
4 * Copyright (C) 2008 Rudolf Marek <r.marek@assembler.cz>
5 * Copyright (C) 2009 One Laptop per Child, Association, Inc.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
Stefan Reinauer23836e22010-04-15 12:39:29 +000022/* FIXME This code should be dropped and instead the generic resume code
23 * should be used.
24 */
25
Uwe Hermannd64f4032009-06-07 14:38:32 +000026/* Parts of this code is taken from reboot.c from Linux. */
Uwe Hermann0ffff342009-06-07 13:46:50 +000027
28/*
29 * This file mostly copied from Rudolf's S3 patch, some changes in
30 * acpi_jump_wake().
31 */
32
Uwe Hermann20a98c92009-06-05 23:02:43 +000033#include <stdint.h>
34#include <string.h>
35#include <arch/io.h>
36#include <console/console.h>
37#include <delay.h>
Uwe Hermann20a98c92009-06-05 23:02:43 +000038#include "wakeup.h"
39
Uwe Hermann20a98c92009-06-05 23:02:43 +000040int enable_a20(void);
41
Uwe Hermann0ffff342009-06-07 13:46:50 +000042/*
43 * The following code and data reboots the machine by switching to real
44 * mode and jumping to the BIOS reset entry point, as if the CPU has
45 * really been reset. The previous version asked the keyboard
46 * controller to pulse the CPU reset line, which is more thorough, but
47 * doesn't work with at least one type of 486 motherboard. It is easy
48 * to stop this code working; hence the copious comments.
49 */
Uwe Hermann20a98c92009-06-05 23:02:43 +000050
Uwe Hermann0ffff342009-06-07 13:46:50 +000051static unsigned long long real_mode_gdt_entries[3] = {
Uwe Hermann20a98c92009-06-05 23:02:43 +000052 0x0000000000000000ULL, /* Null descriptor */
53 0x00009a000000ffffULL, /* 16-bit real-mode 64k code at 0x00000000 */
54 0x000092000100ffffULL /* 16-bit real-mode 64k data at 0x00000100 */
55};
56
57struct Xgt_desc_struct {
Uwe Hermann0ffff342009-06-07 13:46:50 +000058 unsigned short size;
59 unsigned long address __attribute__ ((packed));
60 unsigned short pad;
61} __attribute__ ((packed));
Uwe Hermann20a98c92009-06-05 23:02:43 +000062
Uwe Hermannd64f4032009-06-07 14:38:32 +000063static struct Xgt_desc_struct real_mode_gdt = {
64 sizeof(real_mode_gdt_entries) - 1,
65 (long)real_mode_gdt_entries
66},
67real_mode_idt = {0x3ff, 0},
68no_idt = { 0, 0 };
Uwe Hermann20a98c92009-06-05 23:02:43 +000069
Uwe Hermann0ffff342009-06-07 13:46:50 +000070/*
71 * This is 16-bit protected mode code to disable paging and the cache,
72 * switch to real mode and jump to the BIOS reset code.
73 *
74 * The instruction that switches to real mode by writing to CR0 must be
75 * followed immediately by a far jump instruction, which set CS to a
76 * valid value for real mode, and flushes the prefetch queue to avoid
77 * running instructions that have already been decoded in protected
78 * mode.
79 *
80 * Clears all the flags except ET, especially PG (paging), PE
81 * (protected-mode enable) and TS (task switch for coprocessor state
82 * save). Flushes the TLB after paging has been disabled. Sets CD and
83 * NW, to disable the cache on a 486, and invalidates the cache. This
84 * is more like the state of a 486 after reset. I don't know if
85 * something else should be done for other chips.
86 *
87 * More could be done here to set up the registers as if a CPU reset had
88 * occurred; hopefully real BIOSs don't assume much.
89 */
Uwe Hermann20a98c92009-06-05 23:02:43 +000090
Uwe Hermannd64f4032009-06-07 14:38:32 +000091// 0x66, 0x0d, 0x00, 0x00, 0x00, 0x60, /* orl $0x60000000, %eax */
Uwe Hermann20a98c92009-06-05 23:02:43 +000092
Uwe Hermann0ffff342009-06-07 13:46:50 +000093static unsigned char real_mode_switch[] = {
Uwe Hermannd64f4032009-06-07 14:38:32 +000094 0x66, 0x0f, 0x20, 0xc0, /* movl %cr0,%eax */
95 0x24, 0xfe, /* andb $0xfe,al */
96 0x66, 0x0f, 0x22, 0xc0 /* movl %eax,%cr0 */
Uwe Hermann20a98c92009-06-05 23:02:43 +000097};
Uwe Hermann0ffff342009-06-07 13:46:50 +000098
99static unsigned char jump_to_wakeup[] = {
Uwe Hermannd64f4032009-06-07 14:38:32 +0000100 0xea, 0x00, 0x00, 0x00, 0xe0 /* ljmp $0xffff, $0x0000 */
Uwe Hermann20a98c92009-06-05 23:02:43 +0000101};
102
Uwe Hermann20a98c92009-06-05 23:02:43 +0000103void acpi_jump_wake(u32 vector)
104{
Stefan Reinauer56a684a2010-04-07 15:40:26 +0000105 u32 dwEip;
Uwe Hermann0ffff342009-06-07 13:46:50 +0000106 struct Xgt_desc_struct *wake_thunk16_Xgt_desc;
Uwe Hermann20a98c92009-06-05 23:02:43 +0000107
Stefan Reinauerc02b4fc2010-03-22 11:42:32 +0000108 printk(BIOS_DEBUG, "IN ACPI JUMP WAKE TO %x\n", vector);
Uwe Hermann20a98c92009-06-05 23:02:43 +0000109 if (enable_a20())
110 die("failed to enable A20\n");
Stefan Reinauerc02b4fc2010-03-22 11:42:32 +0000111 printk(BIOS_DEBUG, "IN ACPI JUMP WAKE TO 3 %x\n", vector);
Uwe Hermann20a98c92009-06-05 23:02:43 +0000112
Uwe Hermann0ffff342009-06-07 13:46:50 +0000113 *((u16 *) (jump_to_wakeup + 3)) = (u16) (vector >> 4);
Stefan Reinauerc02b4fc2010-03-22 11:42:32 +0000114 printk(BIOS_DEBUG, "%x %x %x %x %x\n", jump_to_wakeup[0], jump_to_wakeup[1],
Uwe Hermann0ffff342009-06-07 13:46:50 +0000115 jump_to_wakeup[2], jump_to_wakeup[3], jump_to_wakeup[4]);
116
117 memcpy((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100),
118 real_mode_switch, sizeof(real_mode_switch));
119 memcpy((void *)(WAKE_THUNK16_ADDR - 100), jump_to_wakeup,
120 sizeof(jump_to_wakeup));
Uwe Hermann20a98c92009-06-05 23:02:43 +0000121
Myles Watsonbd4f2f82009-07-02 21:19:33 +0000122 //jason_tsc_count();
Stefan Reinauerc02b4fc2010-03-22 11:42:32 +0000123 printk(BIOS_EMERG, "file '%s', line %d\n\n", __FILE__, __LINE__);
Myles Watsonbd4f2f82009-07-02 21:19:33 +0000124 //jason_tsc_count_end();
Uwe Hermann20a98c92009-06-05 23:02:43 +0000125
Uwe Hermann0ffff342009-06-07 13:46:50 +0000126 unsigned long long *real_mode_gdt_entries_at_eseg;
Myles Watsonf4cc0892010-04-14 16:50:16 +0000127 real_mode_gdt_entries_at_eseg = (void *)WAKE_THUNK16_GDT; /* Copy from real_mode_gdt_entries and change limition to 1M and data base to 0; */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000128 real_mode_gdt_entries_at_eseg[0] = 0x0000000000000000ULL; /* Null descriptor */
129 real_mode_gdt_entries_at_eseg[1] = 0x000f9a000000ffffULL; /* 16-bit real-mode 1M code at 0x00000000 */
130 real_mode_gdt_entries_at_eseg[2] = 0x000f93000000ffffULL; /* 16-bit real-mode 1M data at 0x00000000 */
Uwe Hermann20a98c92009-06-05 23:02:43 +0000131
Myles Watsonf4cc0892010-04-14 16:50:16 +0000132 wake_thunk16_Xgt_desc = (void *)WAKE_THUNK16_XDTR;
Uwe Hermann0ffff342009-06-07 13:46:50 +0000133 wake_thunk16_Xgt_desc[0].size = sizeof(real_mode_gdt_entries) - 1;
134 wake_thunk16_Xgt_desc[0].address = (long)real_mode_gdt_entries_at_eseg;
135 wake_thunk16_Xgt_desc[1].size = 0x3ff;
136 wake_thunk16_Xgt_desc[1].address = 0;
137 wake_thunk16_Xgt_desc[2].size = 0;
138 wake_thunk16_Xgt_desc[2].address = 0;
Uwe Hermann20a98c92009-06-05 23:02:43 +0000139
Uwe Hermannd64f4032009-06-07 14:38:32 +0000140 /* Added this code to get current value of EIP. */
141 __asm__ volatile (
142 "calll geip\n\t"
143 "geip: \n\t"
144 "popl %0\n\t"
145 : "=a" (dwEip)
146 );
Uwe Hermann20a98c92009-06-05 23:02:43 +0000147
Uwe Hermannd64f4032009-06-07 14:38:32 +0000148 unsigned char *dest, *src;
Uwe Hermann0ffff342009-06-07 13:46:50 +0000149 src = (unsigned char *)dwEip;
Myles Watsonf4cc0892010-04-14 16:50:16 +0000150 dest = (void *)WAKE_RECOVER1M_CODE;
Uwe Hermann20a98c92009-06-05 23:02:43 +0000151 u32 i;
Uwe Hermann0ffff342009-06-07 13:46:50 +0000152 for (i = 0; i < 0x200; i++)
153 dest[i] = src[i];
Uwe Hermann20a98c92009-06-05 23:02:43 +0000154
Uwe Hermannd64f4032009-06-07 14:38:32 +0000155 __asm__ __volatile__("ljmp $0x0010,%0" /* 08 error */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000156 ::"i"((void *)(WAKE_RECOVER1M_CODE + 0x20)));
Uwe Hermann20a98c92009-06-05 23:02:43 +0000157
Uwe Hermannd64f4032009-06-07 14:38:32 +0000158 /* Added 0x20 "nop" to make sure the ljmp will not jump then halt. */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000159 asm volatile ("nop");
160 asm volatile ("nop");
161 asm volatile ("nop");
162 asm volatile ("nop");
163 asm volatile ("nop");
164 asm volatile ("nop");
165 asm volatile ("nop");
166 asm volatile ("nop");
167 asm volatile ("nop");
168 asm volatile ("nop");
169
170 asm volatile ("nop");
171 asm volatile ("nop");
172 asm volatile ("nop");
173 asm volatile ("nop");
174 asm volatile ("nop");
175 asm volatile ("nop");
176 asm volatile ("nop");
177 asm volatile ("nop");
178 asm volatile ("nop");
179 asm volatile ("nop");
180
181 asm volatile ("nop");
182 asm volatile ("nop");
183 asm volatile ("nop");
184 asm volatile ("nop");
185 asm volatile ("nop");
186 asm volatile ("nop");
187 asm volatile ("nop");
188 asm volatile ("nop");
189 asm volatile ("nop");
190 asm volatile ("nop");
Uwe Hermann20a98c92009-06-05 23:02:43 +0000191
192 __asm__ volatile (
Uwe Hermannd64f4032009-06-07 14:38:32 +0000193 /*
194 * Set new esp, maybe ebp should not equal to esp?, due to the
195 * variable in acpi_jump_wake?, anyway, this may be not a big
196 * problem. and I didn't clear the area (ef000+-0x200) to zero.
197 */
198 "movl %0, %%ebp\n\t"
199 "movl %0, %%esp\n\t"::"a" (WAKE_THUNK16_STACK)
200 );
Uwe Hermann20a98c92009-06-05 23:02:43 +0000201
Uwe Hermannd64f4032009-06-07 14:38:32 +0000202 /*
203 * Only "src" and "dest" use the new stack, and the esp maybe also
204 * used in resumevector.
Uwe Hermann0ffff342009-06-07 13:46:50 +0000205 */
Uwe Hermannd64f4032009-06-07 14:38:32 +0000206#if PAYLOAD_IS_SEABIOS == 1
207 /* WAKE_MEM_INFO inited in get_set_top_available_mem in tables.c. */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000208 src =
209 (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024 - 0x100000);
210 dest = 0;
Uwe Hermannd64f4032009-06-07 14:38:32 +0000211
212 /*
213 * If recovered 0-e0000, then when resume, before WinXP turn on the
214 * desktop screen, there is gray background which last 1sec.
215 */
216 for (i = 0; i < 0xa0000; i++)
Uwe Hermann20a98c92009-06-05 23:02:43 +0000217 dest[i] = src[i];
Uwe Hermann20a98c92009-06-05 23:02:43 +0000218
Uwe Hermannd64f4032009-06-07 14:38:32 +0000219#if 0
220 __asm__ volatile (
221 "movl %0, %%esi\n\t"
222 "movl $0, %%edi\n\t"
223 "movl $0xa0000, %%ecx\n\t"
224 "shrl $2, %%ecx\n\t"
225 "rep movsd\n\t"
226 ::"a"(src)
227 );
228#endif
229 src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
230 - 0x100000 + 0xc0000);
Uwe Hermann20a98c92009-06-05 23:02:43 +0000231
Uwe Hermannd64f4032009-06-07 14:38:32 +0000232#if 0
233 dest = 0xc0000;
234 for (i = 0; i < 0x20000; i++)
235 dest[i] = src[i];
236
237 __asm__ volatile (
238 "movl %0, %%esi\n\t"
239 "movl $0xc0000, %%edi\n\t"
240 "movl $0x20000, %%ecx\n\t"
241 "shrl $2, %%ecx\n\t"
242 "rep movsd\n\t"
243 ::"a"(src)
244 );
245#endif
246
247 src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
248 - 0x100000 + 0xe0000 + WAKE_SPECIAL_SIZE);
249
250 /* dest = 0xf0000; */
251 /* for (i = 0; i < 0x10000; i++) */
252 /* dest[i] = src[i]; */
253 __asm__ volatile (
254 "movl %0, %%esi\n\t"
255 "movl %1, %%edi\n\t"
256 "movl %2, %%ecx\n\t"
257 "shrl $2, %%ecx\n\t"
258 "rep movsd\n\t"::"r" (src),
259 "r"(0xe0000 + WAKE_SPECIAL_SIZE),
260 "r"(0x10000 - WAKE_SPECIAL_SIZE)
261 );
262
263 src = (unsigned char *)((*(u32 *) WAKE_MEM_INFO) - 64 * 1024
264 - 0x100000 + 0xf0000);
265 /* dest = 0xf0000; */
266 /* for (i = 0; i < 0x10000; i++) */
267 /* dest[i] = src[i]; */
268 __asm__ volatile (
269 "movl %0, %%esi\n\t"
270 "movl $0xf0000, %%edi\n\t"
271 "movl $0x10000, %%ecx\n\t"
272 "shrl $2, %%ecx\n\t" "rep movsd\n\t"::"a" (src)
273 );
Uwe Hermann0ffff342009-06-07 13:46:50 +0000274
275 asm volatile ("wbinvd");
Uwe Hermann20a98c92009-06-05 23:02:43 +0000276#endif
277 /* Set up the IDT for real mode. */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000278 asm volatile ("lidt %0"::"m" (wake_thunk16_Xgt_desc[1]));
279
Uwe Hermannd64f4032009-06-07 14:38:32 +0000280 /*
281 * Set up a GDT from which we can load segment descriptors for real
282 * mode. The GDT is not used in real mode; it is just needed here to
283 * prepare the descriptors.
284 */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000285 asm volatile ("lgdt %0"::"m" (wake_thunk16_Xgt_desc[0]));
Uwe Hermann20a98c92009-06-05 23:02:43 +0000286
Uwe Hermannd64f4032009-06-07 14:38:32 +0000287 /*
288 * Load the data segment registers, and thus the descriptors ready for
289 * real mode. The base address of each segment is 0x100, 16 times the
290 * selector value being loaded here. This is so that the segment
291 * registers don't have to be reloaded after switching to real mode:
292 * the values are consistent for real mode operation already.
293 */
294 __asm__ __volatile__(
295 "movl $0x0010,%%eax\n"
296 "\tmovl %%eax,%%ds\n"
297 "\tmovl %%eax,%%es\n"
298 "\tmovl %%eax,%%fs\n"
299 "\tmovl %%eax,%%gs\n"
300 "\tmovl %%eax,%%ss":::"eax"
301 );
Uwe Hermann20a98c92009-06-05 23:02:43 +0000302
Uwe Hermannd64f4032009-06-07 14:38:32 +0000303 /*
304 * Jump to the 16-bit code that we copied earlier. It disables paging
305 * and the cache, switches to real mode, and jumps to the BIOS reset
306 * entry point.
307 */
Uwe Hermann20a98c92009-06-05 23:02:43 +0000308
Uwe Hermannd64f4032009-06-07 14:38:32 +0000309 __asm__ __volatile__(
310 "ljmp $0x0008,%0"::"i"
311 ((void *)(WAKE_THUNK16_ADDR - sizeof(real_mode_switch) - 100))
312 );
Uwe Hermann0ffff342009-06-07 13:46:50 +0000313}
Uwe Hermann20a98c92009-06-05 23:02:43 +0000314
315/* -*- linux-c -*- ------------------------------------------------------- *
316 *
317 * Copyright (C) 1991, 1992 Linus Torvalds
318 * Copyright 2007 rPath, Inc. - All Rights Reserved
319 *
320 * This file is part of the Linux kernel, and is made available under
321 * the terms of the GNU General Public License version 2.
322 *
323 * ----------------------------------------------------------------------- */
324
325/*
326 * arch/i386/boot/a20.c
327 *
328 * Enable A20 gate (return -1 on failure)
329 */
330
Uwe Hermann20a98c92009-06-05 23:02:43 +0000331#define MAX_8042_LOOPS 100000
332
333static int empty_8042(void)
334{
335 u8 status;
336 int loops = MAX_8042_LOOPS;
337
338 while (loops--) {
339 udelay(1);
340
341 status = inb(0x64);
342 if (status & 1) {
343 /* Read and discard input data */
344 udelay(1);
345 (void)inb(0x60);
346 } else if (!(status & 2)) {
347 /* Buffers empty, finished! */
348 return 0;
349 }
350 }
351
352 return -1;
353}
354
355/* Returns nonzero if the A20 line is enabled. The memory address
356 used as a test is the int $0x80 vector, which should be safe. */
357
358#define A20_TEST_ADDR (4*0x80)
359#define A20_TEST_SHORT 32
360#define A20_TEST_LONG 2097152 /* 2^21 */
361
362static int a20_test(int loops)
363{
364 int ok = 0;
365 int saved, ctr;
366
Uwe Hermann0ffff342009-06-07 13:46:50 +0000367 saved = ctr = *((u32 *) A20_TEST_ADDR);
Uwe Hermann20a98c92009-06-05 23:02:43 +0000368
369 while (loops--) {
Uwe Hermann0ffff342009-06-07 13:46:50 +0000370
371 *((u32 *) A20_TEST_ADDR) = ++ctr;
372
Uwe Hermann20a98c92009-06-05 23:02:43 +0000373 udelay(1); /* Serialize and make delay constant */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000374
375 ok = *((u32 *) A20_TEST_ADDR + 0xffff0 + 0x10) ^ ctr;
Uwe Hermann20a98c92009-06-05 23:02:43 +0000376 if (ok)
377 break;
378 }
379
Uwe Hermann0ffff342009-06-07 13:46:50 +0000380 *((u32 *) A20_TEST_ADDR) = saved;
Uwe Hermann20a98c92009-06-05 23:02:43 +0000381 return ok;
382}
383
384/* Quick test to see if A20 is already enabled */
385static int a20_test_short(void)
386{
387 return a20_test(A20_TEST_SHORT);
388}
389
390/* Longer test that actually waits for A20 to come on line; this
391 is useful when dealing with the KBC or other slow external circuitry. */
392static int a20_test_long(void)
393{
394 return a20_test(A20_TEST_LONG);
395}
396
397static void enable_a20_kbc(void)
398{
399 empty_8042();
400
401 outb(0xd1, 0x64); /* Command write */
402 empty_8042();
403
404 outb(0xdf, 0x60); /* A20 on */
405 empty_8042();
406}
407
408static void enable_a20_fast(void)
409{
410 u8 port_a;
411
412 port_a = inb(0x92); /* Configuration port A */
Uwe Hermann0ffff342009-06-07 13:46:50 +0000413 port_a |= 0x02; /* Enable A20 */
Uwe Hermann20a98c92009-06-05 23:02:43 +0000414 port_a &= ~0x01; /* Do not reset machine */
415 outb(port_a, 0x92);
416}
417
418/*
419 * Actual routine to enable A20; return 0 on ok, -1 on failure
420 */
421
422#define A20_ENABLE_LOOPS 255 /* Number of times to try */
423
424int enable_a20(void)
425{
426 int loops = A20_ENABLE_LOOPS;
427
428 while (loops--) {
429 /* First, check to see if A20 is already enabled
430 (legacy free, etc.) */
431 if (a20_test_short())
432 return 0;
433
434 /* Try enabling A20 through the keyboard controller */
435 empty_8042();
Uwe Hermannd64f4032009-06-07 14:38:32 +0000436
437 // if (a20_test_short())
438 // return 0; /* BIOS worked, but with delayed reaction */
Uwe Hermann20a98c92009-06-05 23:02:43 +0000439
440 enable_a20_kbc();
441 if (a20_test_long())
442 return 0;
443
444 /* Finally, try enabling the "fast A20 gate" */
445 enable_a20_fast();
446 if (a20_test_long())
447 return 0;
448 }
449
450 return -1;
451}