| /* SPDX-License-Identifier: GPL-2.0 */ | 
 | /* | 
 |  * ACPI wakeup real mode startup stub | 
 |  */ | 
 | #include <linux/linkage.h> | 
 | #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 "realmode.h" | 
 | #include "wakeup.h" | 
 |  | 
 | 	.code16 | 
 |  | 
 | /* This should match the structure in wakeup.h */ | 
 | 	.section ".data", "aw" | 
 |  | 
 | 	.balign	16 | 
 | SYM_DATA_START(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 | 
 | SYM_DATA_END(wakeup_header) | 
 |  | 
 | 	.text | 
 | 	.code16 | 
 |  | 
 | 	.balign	16 | 
 | SYM_CODE_START(wakeup_start) | 
 | 	cli | 
 | 	cld | 
 |  | 
 | 	LJMPW_RM(3f) | 
 | 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 | 
 | 	LJMPW_RM(3f) | 
 | 3: | 
 | 	/* Set up segments */ | 
 | 	movw	%cs, %ax | 
 | 	movw	%ax, %ss | 
 | 	movl	$rm_stack_end, %esp | 
 | 	movw	%ax, %ds | 
 | 	movw	%ax, %es | 
 | 	movw	%ax, %fs | 
 | 	movw	%ax, %gs | 
 |  | 
 | 	lidtl	.Lwakeup_idt | 
 |  | 
 | 	/* 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	$REALMODE_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, %edi | 
 | 	btl	$WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi | 
 | 	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 | 
 |  | 
 | 	btl	$WAKEUP_BEHAVIOR_RESTORE_CR4, %edi | 
 | 	jnc	1f | 
 | 	movl	pmode_cr4, %eax | 
 | 	movl	%eax, %cr4 | 
 | 1: | 
 | 	btl	$WAKEUP_BEHAVIOR_RESTORE_EFER, %edi | 
 | 	jnc	1f | 
 | 	movl	pmode_efer, %eax | 
 | 	movl	pmode_efer + 4, %edx | 
 | 	movl	$MSR_EFER, %ecx | 
 | 	wrmsr | 
 | 1: | 
 |  | 
 | 	lgdtl	pmode_gdt | 
 |  | 
 | 	/* This really couldn't... */ | 
 | 	movl	pmode_entry, %eax | 
 | 	movl	pmode_cr0, %ecx | 
 | 	movl	%ecx, %cr0 | 
 | 	ljmpl	$__KERNEL_CS, $pa_startup_32 | 
 | 	/* -> jmp *%eax in trampoline_32.S */ | 
 | #else | 
 | 	jmp	trampoline_start | 
 | #endif | 
 | SYM_CODE_END(wakeup_start) | 
 |  | 
 | 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. | 
 | 	 */ | 
 |  | 
 | 	.balign	16 | 
 | SYM_DATA_START(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 */ | 
 | SYM_DATA_END(wakeup_gdt) | 
 |  | 
 | 	.section ".rodata","a" | 
 | 	.balign	8 | 
 |  | 
 | 	/* This is the standard real-mode IDT */ | 
 | 	.balign	16 | 
 | SYM_DATA_START_LOCAL(.Lwakeup_idt) | 
 | 	.word	0xffff		/* limit */ | 
 | 	.long	0		/* address */ | 
 | 	.word	0 | 
 | SYM_DATA_END(.Lwakeup_idt) |