aboutsummaryrefslogtreecommitdiff
path: root/arch/x86/realmode
diff options
context:
space:
mode:
authorJarkko Sakkinen <jarkko.sakkinen@intel.com>2012-05-08 21:22:29 +0300
committerH. Peter Anvin <hpa@linux.intel.com>2012-05-08 11:46:05 -0700
commitc9b77ccb52a5c77233b0e557b7d4417b00ef4012 (patch)
tree152d1c9b60796db21458583a76b57f995c4cd3bf /arch/x86/realmode
parent48927bbb97c7d4cf343c05827ab9ac30c60678cb (diff)
x86, realmode: Move ACPI wakeup to unified realmode code
Migrated ACPI wakeup code to the real-mode blob. Code existing in .x86_trampoline can be completely removed. Static descriptor table in wakeup_asm.S is courtesy of H. Peter Anvin. Signed-off-by: Jarkko Sakkinen <jarkko.sakkinen@intel.com> Link: http://lkml.kernel.org/r/1336501366-28617-7-git-send-email-jarkko.sakkinen@intel.com Cc: Rafael J. Wysocki <rjw@sisk.pl> Cc: Len Brown <len.brown@intel.com> Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
Diffstat (limited to 'arch/x86/realmode')
-rw-r--r--arch/x86/realmode/rm/Makefile4
-rw-r--r--arch/x86/realmode/rm/header.S5
-rw-r--r--arch/x86/realmode/rm/realmode.lds.S4
-rw-r--r--arch/x86/realmode/rm/wakeup/.gitignore3
-rw-r--r--arch/x86/realmode/rm/wakeup/Makefile33
-rw-r--r--arch/x86/realmode/rm/wakeup/bioscall.S1
-rw-r--r--arch/x86/realmode/rm/wakeup/copy.S1
-rw-r--r--arch/x86/realmode/rm/wakeup/regs.c1
-rw-r--r--arch/x86/realmode/rm/wakeup/video-bios.c1
-rw-r--r--arch/x86/realmode/rm/wakeup/video-mode.c1
-rw-r--r--arch/x86/realmode/rm/wakeup/video-vesa.c1
-rw-r--r--arch/x86/realmode/rm/wakeup/video-vga.c1
-rw-r--r--arch/x86/realmode/rm/wakeup/wakemain.c82
-rw-r--r--arch/x86/realmode/rm/wakeup/wakeup.h41
-rw-r--r--arch/x86/realmode/rm/wakeup/wakeup_asm.S189
15 files changed, 368 insertions, 0 deletions
diff --git a/arch/x86/realmode/rm/Makefile b/arch/x86/realmode/rm/Makefile
index 56ec64f94e6..2432acb6b04 100644
--- a/arch/x86/realmode/rm/Makefile
+++ b/arch/x86/realmode/rm/Makefile
@@ -14,9 +14,13 @@ always := realmode.bin
realmode-y += header.o
realmode-$(CONFIG_X86_32) += reboot_32.o
realmode-y += trampoline_$(BITS).o
+realmode-$(CONFIG_ACPI_SLEEP) += wakeup/wakeup.o
targets += $(realmode-y)
+$(obj)/wakeup/wakeup.o: FORCE
+ $(Q)$(MAKE) $(build)=$(obj)/wakeup $@
+
REALMODE_OBJS = $(addprefix $(obj)/,$(realmode-y))
sed-pasyms := -n -r -e 's/^([0-9a-fA-F]+) [ABCDGRSTVW] (.+)$$/pa_\2 = \2;/p'
diff --git a/arch/x86/realmode/rm/header.S b/arch/x86/realmode/rm/header.S
index a97900409c6..730b1316c09 100644
--- a/arch/x86/realmode/rm/header.S
+++ b/arch/x86/realmode/rm/header.S
@@ -27,4 +27,9 @@ ENTRY(real_mode_header)
.long pa_level3_ident_pgt
.long pa_level3_kernel_pgt
#endif
+ /* ACPI sleep */
+#ifdef CONFIG_ACPI_SLEEP
+ .long pa_wakeup_start
+ .long pa_wakeup_header
+#endif
END(real_mode_header)
diff --git a/arch/x86/realmode/rm/realmode.lds.S b/arch/x86/realmode/rm/realmode.lds.S
index c5b8a4f31ba..91b83ea55c3 100644
--- a/arch/x86/realmode/rm/realmode.lds.S
+++ b/arch/x86/realmode/rm/realmode.lds.S
@@ -25,6 +25,10 @@ SECTIONS
.rodata : {
*(.rodata)
*(.rodata.*)
+ . = ALIGN(16);
+ video_cards = .;
+ *(.videocards)
+ video_cards_end = .;
}
. = ALIGN(PAGE_SIZE);
diff --git a/arch/x86/realmode/rm/wakeup/.gitignore b/arch/x86/realmode/rm/wakeup/.gitignore
new file mode 100644
index 00000000000..58f1f48a58f
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/.gitignore
@@ -0,0 +1,3 @@
+wakeup.bin
+wakeup.elf
+wakeup.lds
diff --git a/arch/x86/realmode/rm/wakeup/Makefile b/arch/x86/realmode/rm/wakeup/Makefile
new file mode 100644
index 00000000000..4c8533240cd
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/Makefile
@@ -0,0 +1,33 @@
+#
+# arch/x86/kernel/acpi/realmode/Makefile
+#
+# This file is subject to the terms and conditions of the GNU General Public
+# License. See the file "COPYING" in the main directory of this archive
+# for more details.
+#
+
+always := wakeup.o
+
+wakeup-y += wakeup_asm.o wakemain.o video-mode.o
+wakeup-y += copy.o bioscall.o regs.o
+
+# The link order of the video-*.o modules can matter. In particular,
+# video-vga.o *must* be listed first, followed by video-vesa.o.
+# Hardware-specific drivers should follow in the order they should be
+# probed, and video-bios.o should typically be last.
+wakeup-y += video-vga.o
+wakeup-y += video-vesa.o
+wakeup-y += video-bios.o
+
+targets += $(wakeup-y)
+
+WAKEUP_OBJS = $(addprefix $(obj)/,$(wakeup-y))
+
+LDFLAGS_wakeup.o := -m elf_i386 -r
+$(obj)/wakeup.o: $(WAKEUP_OBJS) FORCE
+ $(call if_changed,ld)
+
+bootsrc := $(src)/../../../boot
+
+ccflags-y += -D_WAKEUP -I$(srctree)/$(bootsrc)
+asflags-y += -D_WAKEUP -I$(srctree)/$(bootsrc)
diff --git a/arch/x86/realmode/rm/wakeup/bioscall.S b/arch/x86/realmode/rm/wakeup/bioscall.S
new file mode 100644
index 00000000000..f51eb0bb56c
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/bioscall.S
@@ -0,0 +1 @@
+#include "../../../boot/bioscall.S"
diff --git a/arch/x86/realmode/rm/wakeup/copy.S b/arch/x86/realmode/rm/wakeup/copy.S
new file mode 100644
index 00000000000..dc59ebee69d
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/copy.S
@@ -0,0 +1 @@
+#include "../../../boot/copy.S"
diff --git a/arch/x86/realmode/rm/wakeup/regs.c b/arch/x86/realmode/rm/wakeup/regs.c
new file mode 100644
index 00000000000..6206033ba20
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/regs.c
@@ -0,0 +1 @@
+#include "../../../boot/regs.c"
diff --git a/arch/x86/realmode/rm/wakeup/video-bios.c b/arch/x86/realmode/rm/wakeup/video-bios.c
new file mode 100644
index 00000000000..7deabc144a2
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/video-bios.c
@@ -0,0 +1 @@
+#include "../../../boot/video-bios.c"
diff --git a/arch/x86/realmode/rm/wakeup/video-mode.c b/arch/x86/realmode/rm/wakeup/video-mode.c
new file mode 100644
index 00000000000..328ad209f11
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/video-mode.c
@@ -0,0 +1 @@
+#include "../../../boot/video-mode.c"
diff --git a/arch/x86/realmode/rm/wakeup/video-vesa.c b/arch/x86/realmode/rm/wakeup/video-vesa.c
new file mode 100644
index 00000000000..9dbb9672226
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/video-vesa.c
@@ -0,0 +1 @@
+#include "../../../boot/video-vesa.c"
diff --git a/arch/x86/realmode/rm/wakeup/video-vga.c b/arch/x86/realmode/rm/wakeup/video-vga.c
new file mode 100644
index 00000000000..bcc81255f37
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/video-vga.c
@@ -0,0 +1 @@
+#include "../../../boot/video-vga.c"
diff --git a/arch/x86/realmode/rm/wakeup/wakemain.c b/arch/x86/realmode/rm/wakeup/wakemain.c
new file mode 100644
index 00000000000..91405d515ec
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/wakemain.c
@@ -0,0 +1,82 @@
+#include "wakeup.h"
+#include "boot.h"
+
+static void udelay(int loops)
+{
+ while (loops--)
+ io_delay(); /* Approximately 1 us */
+}
+
+static void beep(unsigned int hz)
+{
+ u8 enable;
+
+ if (!hz) {
+ enable = 0x00; /* Turn off speaker */
+ } else {
+ u16 div = 1193181/hz;
+
+ outb(0xb6, 0x43); /* Ctr 2, squarewave, load, binary */
+ io_delay();
+ outb(div, 0x42); /* LSB of counter */
+ io_delay();
+ outb(div >> 8, 0x42); /* MSB of counter */
+ io_delay();
+
+ enable = 0x03; /* Turn on speaker */
+ }
+ inb(0x61); /* Dummy read of System Control Port B */
+ io_delay();
+ outb(enable, 0x61); /* Enable timer 2 output to speaker */
+ io_delay();
+}
+
+#define DOT_HZ 880
+#define DASH_HZ 587
+#define US_PER_DOT 125000
+
+/* Okay, this is totally silly, but it's kind of fun. */
+static void send_morse(const char *pattern)
+{
+ char s;
+
+ while ((s = *pattern++)) {
+ switch (s) {
+ case '.':
+ beep(DOT_HZ);
+ udelay(US_PER_DOT);
+ beep(0);
+ udelay(US_PER_DOT);
+ break;
+ case '-':
+ beep(DASH_HZ);
+ udelay(US_PER_DOT * 3);
+ beep(0);
+ udelay(US_PER_DOT);
+ break;
+ default: /* Assume it's a space */
+ udelay(US_PER_DOT * 3);
+ break;
+ }
+ }
+}
+
+void main(void)
+{
+ /* Kill machine if structures are wrong */
+ if (wakeup_header.real_magic != 0x12345678)
+ while (1)
+ ;
+
+ if (wakeup_header.realmode_flags & 4)
+ send_morse("...-");
+
+ if (wakeup_header.realmode_flags & 1)
+ asm volatile("lcallw $0xc000,$3");
+
+ if (wakeup_header.realmode_flags & 2) {
+ /* Need to call BIOS */
+ probe_cards(0);
+ set_mode(wakeup_header.video_mode);
+ }
+}
diff --git a/arch/x86/realmode/rm/wakeup/wakeup.h b/arch/x86/realmode/rm/wakeup/wakeup.h
new file mode 100644
index 00000000000..2dfaf06b8af
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/wakeup.h
@@ -0,0 +1,41 @@
+/*
+ * Definitions for the wakeup data structure at the head of the
+ * wakeup code.
+ */
+
+#ifndef ARCH_X86_KERNEL_ACPI_RM_WAKEUP_H
+#define ARCH_X86_KERNEL_ACPI_RM_WAKEUP_H
+
+#ifndef __ASSEMBLY__
+#include <linux/types.h>
+
+/* This must match data at wakeup.S */
+struct wakeup_header {
+ u16 video_mode; /* Video mode number */
+ u32 pmode_entry; /* Protected mode resume point, 32-bit only */
+ u16 pmode_cs;
+ u32 pmode_cr0; /* Protected mode cr0 */
+ u32 pmode_cr3; /* Protected mode cr3 */
+ u32 pmode_cr4; /* Protected mode cr4 */
+ u32 pmode_efer_low; /* Protected mode EFER */
+ u32 pmode_efer_high;
+ u64 pmode_gdt;
+ u32 pmode_misc_en_low; /* Protected mode MISC_ENABLE */
+ u32 pmode_misc_en_high;
+ u32 pmode_behavior; /* Wakeup routine behavior flags */
+ u32 realmode_flags;
+ u32 real_magic;
+ u32 signature; /* To check we have correct structure */
+} __attribute__((__packed__));
+
+extern struct wakeup_header wakeup_header;
+#endif
+
+#define WAKEUP_HEADER_OFFSET 8
+#define WAKEUP_HEADER_SIGNATURE 0x51ee1111
+#define WAKEUP_END_SIGNATURE 0x65a22c82
+
+/* Wakeup behavior bits */
+#define WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE 0
+
+#endif /* ARCH_X86_KERNEL_ACPI_RM_WAKEUP_H */
diff --git a/arch/x86/realmode/rm/wakeup/wakeup_asm.S b/arch/x86/realmode/rm/wakeup/wakeup_asm.S
new file mode 100644
index 00000000000..b61126cb599
--- /dev/null
+++ b/arch/x86/realmode/rm/wakeup/wakeup_asm.S
@@ -0,0 +1,189 @@
+/*
+ * ACPI wakeup real mode startup stub
+ */
+#include <asm/segment.h>
+#include <asm/msr-index.h>
+#include <asm/page_types.h>
+#include <asm/pgtable_types.h>
+#include <asm/processor-flags.h>
+#include "wakeup.h"
+
+ .code16
+
+/* This should match the structure in wakeup.h */
+ .section ".data", "aw"
+ .globl wakeup_header
+wakeup_header:
+video_mode: .short 0 /* Video mode number */
+pmode_entry: .long 0
+pmode_cs: .short __KERNEL_CS
+pmode_cr0: .long 0 /* Saved %cr0 */
+pmode_cr3: .long 0 /* Saved %cr3 */
+pmode_cr4: .long 0 /* Saved %cr4 */
+pmode_efer: .quad 0 /* Saved EFER */
+pmode_gdt: .quad 0
+pmode_misc_en: .quad 0 /* Saved MISC_ENABLE MSR */
+pmode_behavior: .long 0 /* Wakeup behavior flags */
+realmode_flags: .long 0
+real_magic: .long 0
+signature: .long WAKEUP_HEADER_SIGNATURE
+ .size wakeup_header, .-wakeup_header
+
+ .text
+ .code16
+ .globl wakeup_start
+wakeup_start:
+ cli
+ cld
+
+ .byte 0xea /* ljmpw */
+ .word 3f
+ .word real_mode_seg
+3:
+ /* Apparently some dimwit BIOS programmers don't know how to
+ program a PM to RM transition, and we might end up here with
+ junk in the data segment descriptor registers. The only way
+ to repair that is to go into PM and fix it ourselves... */
+ movw $16, %cx
+ lgdtl %cs:wakeup_gdt
+ movl %cr0, %eax
+ orb $X86_CR0_PE, %al
+ movl %eax, %cr0
+ ljmpw $8, $2f
+2:
+ movw %cx, %ds
+ movw %cx, %es
+ movw %cx, %ss
+ movw %cx, %fs
+ movw %cx, %gs
+
+ andb $~X86_CR0_PE, %al
+ movl %eax, %cr0
+ .byte 0xea /* ljmpw */
+ .word 3f
+ .word real_mode_seg
+3:
+ /* Set up segments */
+ movw %cs, %ax
+ movw %ax, %ds
+ movw %ax, %es
+ movw %ax, %ss
+ lidtl wakeup_idt
+
+ movl $wakeup_stack_end, %esp
+
+ /* Clear the EFLAGS */
+ pushl $0
+ popfl
+
+ /* Check header signature... */
+ movl signature, %eax
+ cmpl $WAKEUP_HEADER_SIGNATURE, %eax
+ jne bogus_real_magic
+
+ /* Check we really have everything... */
+ movl end_signature, %eax
+ cmpl $WAKEUP_END_SIGNATURE, %eax
+ jne bogus_real_magic
+
+ /* Call the C code */
+ calll main
+
+ /* Restore MISC_ENABLE before entering protected mode, in case
+ BIOS decided to clear XD_DISABLE during S3. */
+ movl pmode_behavior, %eax
+ btl $WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %eax
+ jnc 1f
+
+ movl pmode_misc_en, %eax
+ movl pmode_misc_en + 4, %edx
+ movl $MSR_IA32_MISC_ENABLE, %ecx
+ wrmsr
+1:
+
+ /* Do any other stuff... */
+
+#ifndef CONFIG_64BIT
+ /* This could also be done in C code... */
+ movl pmode_cr3, %eax
+ movl %eax, %cr3
+
+ movl pmode_cr4, %ecx
+ jecxz 1f
+ movl %ecx, %cr4
+1:
+ movl pmode_efer, %eax
+ movl pmode_efer + 4, %edx
+ movl %eax, %ecx
+ orl %edx, %ecx
+ jz 1f
+ movl $MSR_EFER, %ecx
+ wrmsr
+1:
+
+ lgdtl pmode_gdt
+
+ /* This really couldn't... */
+ movl pmode_cr0, %eax
+ movl %eax, %cr0
+ ljmpl *pmode_entry
+#else
+ jmp trampoline_data
+#endif
+
+bogus_real_magic:
+1:
+ hlt
+ jmp 1b
+
+ .section ".rodata","a"
+
+ /*
+ * Set up the wakeup GDT. We set these up as Big Real Mode,
+ * that is, with limits set to 4 GB. At least the Lenovo
+ * Thinkpad X61 is known to need this for the video BIOS
+ * initialization quirk to work; this is likely to also
+ * be the case for other laptops or integrated video devices.
+ */
+
+ .globl wakeup_gdt
+ .balign 16
+wakeup_gdt:
+ .word 3*8-1 /* Self-descriptor */
+ .long pa_wakeup_gdt
+ .word 0
+
+ .word 0xffff /* 16-bit code segment @ real_mode_base */
+ .long 0x9b000000 + pa_real_mode_base
+ .word 0x008f /* big real mode */
+
+ .word 0xffff /* 16-bit data segment @ real_mode_base */
+ .long 0x93000000 + pa_real_mode_base
+ .word 0x008f /* big real mode */
+ .size wakeup_gdt, .-wakeup_gdt
+
+ .data
+ .balign 8
+
+ /* This is the standard real-mode IDT */
+wakeup_idt:
+ .word 0xffff /* limit */
+ .long 0 /* address */
+ .word 0
+
+ .globl HEAP, heap_end
+HEAP:
+ .long wakeup_heap
+heap_end:
+ .long wakeup_stack
+
+ .bss
+wakeup_heap:
+ .space 2048
+wakeup_stack:
+ .space 2048
+wakeup_stack_end:
+
+ .section ".signature","a"
+end_signature:
+ .long WAKEUP_END_SIGNATURE