180 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			ArmAsm
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			ArmAsm
		
	
	
	
	
	
| /* 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)
 |