18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * ACPI wakeup real mode startup stub
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci#include <linux/linkage.h>
68c2ecf20Sopenharmony_ci#include <asm/segment.h>
78c2ecf20Sopenharmony_ci#include <asm/msr-index.h>
88c2ecf20Sopenharmony_ci#include <asm/page_types.h>
98c2ecf20Sopenharmony_ci#include <asm/pgtable_types.h>
108c2ecf20Sopenharmony_ci#include <asm/processor-flags.h>
118c2ecf20Sopenharmony_ci#include "realmode.h"
128c2ecf20Sopenharmony_ci#include "wakeup.h"
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci	.code16
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci/* This should match the structure in wakeup.h */
178c2ecf20Sopenharmony_ci	.section ".data", "aw"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci	.balign	16
208c2ecf20Sopenharmony_ciSYM_DATA_START(wakeup_header)
218c2ecf20Sopenharmony_ci	video_mode:	.short	0	/* Video mode number */
228c2ecf20Sopenharmony_ci	pmode_entry:	.long	0
238c2ecf20Sopenharmony_ci	pmode_cs:	.short	__KERNEL_CS
248c2ecf20Sopenharmony_ci	pmode_cr0:	.long	0	/* Saved %cr0 */
258c2ecf20Sopenharmony_ci	pmode_cr3:	.long	0	/* Saved %cr3 */
268c2ecf20Sopenharmony_ci	pmode_cr4:	.long	0	/* Saved %cr4 */
278c2ecf20Sopenharmony_ci	pmode_efer:	.quad	0	/* Saved EFER */
288c2ecf20Sopenharmony_ci	pmode_gdt:	.quad	0
298c2ecf20Sopenharmony_ci	pmode_misc_en:	.quad	0	/* Saved MISC_ENABLE MSR */
308c2ecf20Sopenharmony_ci	pmode_behavior:	.long	0	/* Wakeup behavior flags */
318c2ecf20Sopenharmony_ci	realmode_flags:	.long	0
328c2ecf20Sopenharmony_ci	real_magic:	.long	0
338c2ecf20Sopenharmony_ci	signature:	.long	WAKEUP_HEADER_SIGNATURE
348c2ecf20Sopenharmony_ciSYM_DATA_END(wakeup_header)
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	.text
378c2ecf20Sopenharmony_ci	.code16
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	.balign	16
408c2ecf20Sopenharmony_ciSYM_CODE_START(wakeup_start)
418c2ecf20Sopenharmony_ci	cli
428c2ecf20Sopenharmony_ci	cld
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	LJMPW_RM(3f)
458c2ecf20Sopenharmony_ci3:
468c2ecf20Sopenharmony_ci	/* Apparently some dimwit BIOS programmers don't know how to
478c2ecf20Sopenharmony_ci	   program a PM to RM transition, and we might end up here with
488c2ecf20Sopenharmony_ci	   junk in the data segment descriptor registers.  The only way
498c2ecf20Sopenharmony_ci	   to repair that is to go into PM and fix it ourselves... */
508c2ecf20Sopenharmony_ci	movw	$16, %cx
518c2ecf20Sopenharmony_ci	lgdtl	%cs:wakeup_gdt
528c2ecf20Sopenharmony_ci	movl	%cr0, %eax
538c2ecf20Sopenharmony_ci	orb	$X86_CR0_PE, %al
548c2ecf20Sopenharmony_ci	movl	%eax, %cr0
558c2ecf20Sopenharmony_ci	ljmpw	$8, $2f
568c2ecf20Sopenharmony_ci2:
578c2ecf20Sopenharmony_ci	movw	%cx, %ds
588c2ecf20Sopenharmony_ci	movw	%cx, %es
598c2ecf20Sopenharmony_ci	movw	%cx, %ss
608c2ecf20Sopenharmony_ci	movw	%cx, %fs
618c2ecf20Sopenharmony_ci	movw	%cx, %gs
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	andb	$~X86_CR0_PE, %al
648c2ecf20Sopenharmony_ci	movl	%eax, %cr0
658c2ecf20Sopenharmony_ci	LJMPW_RM(3f)
668c2ecf20Sopenharmony_ci3:
678c2ecf20Sopenharmony_ci	/* Set up segments */
688c2ecf20Sopenharmony_ci	movw	%cs, %ax
698c2ecf20Sopenharmony_ci	movw	%ax, %ss
708c2ecf20Sopenharmony_ci	movl	$rm_stack_end, %esp
718c2ecf20Sopenharmony_ci	movw	%ax, %ds
728c2ecf20Sopenharmony_ci	movw	%ax, %es
738c2ecf20Sopenharmony_ci	movw	%ax, %fs
748c2ecf20Sopenharmony_ci	movw	%ax, %gs
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	lidtl	.Lwakeup_idt
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	/* Clear the EFLAGS */
798c2ecf20Sopenharmony_ci	pushl $0
808c2ecf20Sopenharmony_ci	popfl
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	/* Check header signature... */
838c2ecf20Sopenharmony_ci	movl	signature, %eax
848c2ecf20Sopenharmony_ci	cmpl	$WAKEUP_HEADER_SIGNATURE, %eax
858c2ecf20Sopenharmony_ci	jne	bogus_real_magic
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	/* Check we really have everything... */
888c2ecf20Sopenharmony_ci	movl	end_signature, %eax
898c2ecf20Sopenharmony_ci	cmpl	$REALMODE_END_SIGNATURE, %eax
908c2ecf20Sopenharmony_ci	jne	bogus_real_magic
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	/* Call the C code */
938c2ecf20Sopenharmony_ci	calll	main
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	/* Restore MISC_ENABLE before entering protected mode, in case
968c2ecf20Sopenharmony_ci	   BIOS decided to clear XD_DISABLE during S3. */
978c2ecf20Sopenharmony_ci	movl	pmode_behavior, %edi
988c2ecf20Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi
998c2ecf20Sopenharmony_ci	jnc	1f
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	movl	pmode_misc_en, %eax
1028c2ecf20Sopenharmony_ci	movl	pmode_misc_en + 4, %edx
1038c2ecf20Sopenharmony_ci	movl	$MSR_IA32_MISC_ENABLE, %ecx
1048c2ecf20Sopenharmony_ci	wrmsr
1058c2ecf20Sopenharmony_ci1:
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	/* Do any other stuff... */
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci#ifndef CONFIG_64BIT
1108c2ecf20Sopenharmony_ci	/* This could also be done in C code... */
1118c2ecf20Sopenharmony_ci	movl	pmode_cr3, %eax
1128c2ecf20Sopenharmony_ci	movl	%eax, %cr3
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_CR4, %edi
1158c2ecf20Sopenharmony_ci	jnc	1f
1168c2ecf20Sopenharmony_ci	movl	pmode_cr4, %eax
1178c2ecf20Sopenharmony_ci	movl	%eax, %cr4
1188c2ecf20Sopenharmony_ci1:
1198c2ecf20Sopenharmony_ci	btl	$WAKEUP_BEHAVIOR_RESTORE_EFER, %edi
1208c2ecf20Sopenharmony_ci	jnc	1f
1218c2ecf20Sopenharmony_ci	movl	pmode_efer, %eax
1228c2ecf20Sopenharmony_ci	movl	pmode_efer + 4, %edx
1238c2ecf20Sopenharmony_ci	movl	$MSR_EFER, %ecx
1248c2ecf20Sopenharmony_ci	wrmsr
1258c2ecf20Sopenharmony_ci1:
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	lgdtl	pmode_gdt
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	/* This really couldn't... */
1308c2ecf20Sopenharmony_ci	movl	pmode_entry, %eax
1318c2ecf20Sopenharmony_ci	movl	pmode_cr0, %ecx
1328c2ecf20Sopenharmony_ci	movl	%ecx, %cr0
1338c2ecf20Sopenharmony_ci	ljmpl	$__KERNEL_CS, $pa_startup_32
1348c2ecf20Sopenharmony_ci	/* -> jmp *%eax in trampoline_32.S */
1358c2ecf20Sopenharmony_ci#else
1368c2ecf20Sopenharmony_ci	jmp	trampoline_start
1378c2ecf20Sopenharmony_ci#endif
1388c2ecf20Sopenharmony_ciSYM_CODE_END(wakeup_start)
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cibogus_real_magic:
1418c2ecf20Sopenharmony_ci1:
1428c2ecf20Sopenharmony_ci	hlt
1438c2ecf20Sopenharmony_ci	jmp	1b
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	.section ".rodata","a"
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	/*
1488c2ecf20Sopenharmony_ci	 * Set up the wakeup GDT.  We set these up as Big Real Mode,
1498c2ecf20Sopenharmony_ci	 * that is, with limits set to 4 GB.  At least the Lenovo
1508c2ecf20Sopenharmony_ci	 * Thinkpad X61 is known to need this for the video BIOS
1518c2ecf20Sopenharmony_ci	 * initialization quirk to work; this is likely to also
1528c2ecf20Sopenharmony_ci	 * be the case for other laptops or integrated video devices.
1538c2ecf20Sopenharmony_ci	 */
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	.balign	16
1568c2ecf20Sopenharmony_ciSYM_DATA_START(wakeup_gdt)
1578c2ecf20Sopenharmony_ci	.word	3*8-1		/* Self-descriptor */
1588c2ecf20Sopenharmony_ci	.long	pa_wakeup_gdt
1598c2ecf20Sopenharmony_ci	.word	0
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	.word	0xffff		/* 16-bit code segment @ real_mode_base */
1628c2ecf20Sopenharmony_ci	.long	0x9b000000 + pa_real_mode_base
1638c2ecf20Sopenharmony_ci	.word	0x008f		/* big real mode */
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	.word	0xffff		/* 16-bit data segment @ real_mode_base */
1668c2ecf20Sopenharmony_ci	.long	0x93000000 + pa_real_mode_base
1678c2ecf20Sopenharmony_ci	.word	0x008f		/* big real mode */
1688c2ecf20Sopenharmony_ciSYM_DATA_END(wakeup_gdt)
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	.section ".rodata","a"
1718c2ecf20Sopenharmony_ci	.balign	8
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	/* This is the standard real-mode IDT */
1748c2ecf20Sopenharmony_ci	.balign	16
1758c2ecf20Sopenharmony_ciSYM_DATA_START_LOCAL(.Lwakeup_idt)
1768c2ecf20Sopenharmony_ci	.word	0xffff		/* limit */
1778c2ecf20Sopenharmony_ci	.long	0		/* address */
1788c2ecf20Sopenharmony_ci	.word	0
1798c2ecf20Sopenharmony_ciSYM_DATA_END(.Lwakeup_idt)
180