162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ACPI wakeup real mode startup stub
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#include <linux/linkage.h>
662306a36Sopenharmony_ci#include <asm/segment.h>
762306a36Sopenharmony_ci#include <asm/msr-index.h>
862306a36Sopenharmony_ci#include <asm/page_types.h>
962306a36Sopenharmony_ci#include <asm/pgtable_types.h>
1062306a36Sopenharmony_ci#include <asm/processor-flags.h>
1162306a36Sopenharmony_ci#include "realmode.h"
1262306a36Sopenharmony_ci#include "wakeup.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci	.code16
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* This should match the structure in wakeup.h */
1762306a36Sopenharmony_ci	.section ".data", "aw"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	.balign	16
2062306a36Sopenharmony_ciSYM_DATA_START(wakeup_header)
2162306a36Sopenharmony_ci	video_mode:	.short	0	/* Video mode number */
2262306a36Sopenharmony_ci	pmode_entry:	.long	0
2362306a36Sopenharmony_ci	pmode_cs:	.short	__KERNEL_CS
2462306a36Sopenharmony_ci	pmode_cr0:	.long	0	/* Saved %cr0 */
2562306a36Sopenharmony_ci	pmode_cr3:	.long	0	/* Saved %cr3 */
2662306a36Sopenharmony_ci	pmode_cr4:	.long	0	/* Saved %cr4 */
2762306a36Sopenharmony_ci	pmode_efer:	.quad	0	/* Saved EFER */
2862306a36Sopenharmony_ci	pmode_gdt:	.quad	0
2962306a36Sopenharmony_ci	pmode_misc_en:	.quad	0	/* Saved MISC_ENABLE MSR */
3062306a36Sopenharmony_ci	pmode_behavior:	.long	0	/* Wakeup behavior flags */
3162306a36Sopenharmony_ci	realmode_flags:	.long	0
3262306a36Sopenharmony_ci	real_magic:	.long	0
3362306a36Sopenharmony_ci	signature:	.long	WAKEUP_HEADER_SIGNATURE
3462306a36Sopenharmony_ciSYM_DATA_END(wakeup_header)
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	.text
3762306a36Sopenharmony_ci	.code16
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	.balign	16
4062306a36Sopenharmony_ciSYM_CODE_START(wakeup_start)
4162306a36Sopenharmony_ci	cli
4262306a36Sopenharmony_ci	cld
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	LJMPW_RM(3f)
4562306a36Sopenharmony_ci3:
4662306a36Sopenharmony_ci	/* Apparently some dimwit BIOS programmers don't know how to
4762306a36Sopenharmony_ci	   program a PM to RM transition, and we might end up here with
4862306a36Sopenharmony_ci	   junk in the data segment descriptor registers.  The only way
4962306a36Sopenharmony_ci	   to repair that is to go into PM and fix it ourselves... */
5062306a36Sopenharmony_ci	movw	$16, %cx
5162306a36Sopenharmony_ci	lgdtl	%cs:wakeup_gdt
5262306a36Sopenharmony_ci	movl	%cr0, %eax
5362306a36Sopenharmony_ci	orb	$X86_CR0_PE, %al
5462306a36Sopenharmony_ci	movl	%eax, %cr0
5562306a36Sopenharmony_ci	ljmpw	$8, $2f
5662306a36Sopenharmony_ci2:
5762306a36Sopenharmony_ci	movw	%cx, %ds
5862306a36Sopenharmony_ci	movw	%cx, %es
5962306a36Sopenharmony_ci	movw	%cx, %ss
6062306a36Sopenharmony_ci	movw	%cx, %fs
6162306a36Sopenharmony_ci	movw	%cx, %gs
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	andb	$~X86_CR0_PE, %al
6462306a36Sopenharmony_ci	movl	%eax, %cr0
6562306a36Sopenharmony_ci	LJMPW_RM(3f)
6662306a36Sopenharmony_ci3:
6762306a36Sopenharmony_ci	/* Set up segments */
6862306a36Sopenharmony_ci	movw	%cs, %ax
6962306a36Sopenharmony_ci	movw	%ax, %ss
7062306a36Sopenharmony_ci	movl	$rm_stack_end, %esp
7162306a36Sopenharmony_ci	movw	%ax, %ds
7262306a36Sopenharmony_ci	movw	%ax, %es
7362306a36Sopenharmony_ci	movw	%ax, %fs
7462306a36Sopenharmony_ci	movw	%ax, %gs
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	lidtl	.Lwakeup_idt
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/* Clear the EFLAGS */
7962306a36Sopenharmony_ci	pushl $0
8062306a36Sopenharmony_ci	popfl
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* Check header signature... */
8362306a36Sopenharmony_ci	movl	signature, %eax
8462306a36Sopenharmony_ci	cmpl	$WAKEUP_HEADER_SIGNATURE, %eax
8562306a36Sopenharmony_ci	jne	bogus_real_magic
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* Check we really have everything... */
8862306a36Sopenharmony_ci	movl	end_signature, %eax
8962306a36Sopenharmony_ci	cmpl	$REALMODE_END_SIGNATURE, %eax
9062306a36Sopenharmony_ci	jne	bogus_real_magic
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* Call the C code */
9362306a36Sopenharmony_ci	calll	main
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	/* Restore MISC_ENABLE before entering protected mode, in case
9662306a36Sopenharmony_ci	   BIOS decided to clear XD_DISABLE during S3. */
9762306a36Sopenharmony_ci	movl	pmode_behavior, %edi
9862306a36Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi
9962306a36Sopenharmony_ci	jnc	1f
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	movl	pmode_misc_en, %eax
10262306a36Sopenharmony_ci	movl	pmode_misc_en + 4, %edx
10362306a36Sopenharmony_ci	movl	$MSR_IA32_MISC_ENABLE, %ecx
10462306a36Sopenharmony_ci	wrmsr
10562306a36Sopenharmony_ci1:
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* Do any other stuff... */
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci#ifndef CONFIG_64BIT
11062306a36Sopenharmony_ci	/* This could also be done in C code... */
11162306a36Sopenharmony_ci	movl	pmode_cr3, %eax
11262306a36Sopenharmony_ci	movl	%eax, %cr3
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_CR4, %edi
11562306a36Sopenharmony_ci	jnc	1f
11662306a36Sopenharmony_ci	movl	pmode_cr4, %eax
11762306a36Sopenharmony_ci	movl	%eax, %cr4
11862306a36Sopenharmony_ci1:
11962306a36Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_EFER, %edi
12062306a36Sopenharmony_ci	jnc	1f
12162306a36Sopenharmony_ci	movl	pmode_efer, %eax
12262306a36Sopenharmony_ci	movl	pmode_efer + 4, %edx
12362306a36Sopenharmony_ci	movl	$MSR_EFER, %ecx
12462306a36Sopenharmony_ci	wrmsr
12562306a36Sopenharmony_ci1:
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	lgdtl	pmode_gdt
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/* This really couldn't... */
13062306a36Sopenharmony_ci	movl	pmode_entry, %eax
13162306a36Sopenharmony_ci	movl	pmode_cr0, %ecx
13262306a36Sopenharmony_ci	movl	%ecx, %cr0
13362306a36Sopenharmony_ci	ljmpl	$__KERNEL_CS, $pa_startup_32
13462306a36Sopenharmony_ci	/* -> jmp *%eax in trampoline_32.S */
13562306a36Sopenharmony_ci#else
13662306a36Sopenharmony_ci	jmp	trampoline_start
13762306a36Sopenharmony_ci#endif
13862306a36Sopenharmony_ciSYM_CODE_END(wakeup_start)
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cibogus_real_magic:
14162306a36Sopenharmony_ci1:
14262306a36Sopenharmony_ci	hlt
14362306a36Sopenharmony_ci	jmp	1b
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	.section ".rodata","a"
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/*
14862306a36Sopenharmony_ci	 * Set up the wakeup GDT.  We set these up as Big Real Mode,
14962306a36Sopenharmony_ci	 * that is, with limits set to 4 GB.  At least the Lenovo
15062306a36Sopenharmony_ci	 * Thinkpad X61 is known to need this for the video BIOS
15162306a36Sopenharmony_ci	 * initialization quirk to work; this is likely to also
15262306a36Sopenharmony_ci	 * be the case for other laptops or integrated video devices.
15362306a36Sopenharmony_ci	 */
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	.balign	16
15662306a36Sopenharmony_ciSYM_DATA_START(wakeup_gdt)
15762306a36Sopenharmony_ci	.word	3*8-1		/* Self-descriptor */
15862306a36Sopenharmony_ci	.long	pa_wakeup_gdt
15962306a36Sopenharmony_ci	.word	0
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	.word	0xffff		/* 16-bit code segment @ real_mode_base */
16262306a36Sopenharmony_ci	.long	0x9b000000 + pa_real_mode_base
16362306a36Sopenharmony_ci	.word	0x008f		/* big real mode */
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	.word	0xffff		/* 16-bit data segment @ real_mode_base */
16662306a36Sopenharmony_ci	.long	0x93000000 + pa_real_mode_base
16762306a36Sopenharmony_ci	.word	0x008f		/* big real mode */
16862306a36Sopenharmony_ciSYM_DATA_END(wakeup_gdt)
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	.section ".rodata","a"
17162306a36Sopenharmony_ci	.balign	8
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* This is the standard real-mode IDT */
17462306a36Sopenharmony_ci	.balign	16
17562306a36Sopenharmony_ciSYM_DATA_START_LOCAL(.Lwakeup_idt)
17662306a36Sopenharmony_ci	.word	0xffff		/* limit */
17762306a36Sopenharmony_ci	.long	0		/* address */
17862306a36Sopenharmony_ci	.word	0
17962306a36Sopenharmony_ciSYM_DATA_END(.Lwakeup_idt)
180