Home | History | Annotate | Download | only in lib
      1 /* SPDX-License-Identifier: GPL-2.0 */
      2 /*
      3  * From coreboot x86_asm.S, cleaned up substantially
      4  *
      5  * Copyright (C) 2009-2010 coresystems GmbH
      6  */
      7 
      8 #include <asm/processor.h>
      9 #include <asm/processor-flags.h>
     10 #include "bios.h"
     11 
     12 #define SEG(segment)	$segment * X86_GDT_ENTRY_SIZE
     13 
     14 /*
     15  * This is the interrupt handler stub code. It gets copied to the IDT and
     16  * to some fixed addresses in the F segment. Before the code can used,
     17  * it gets patched up by the C function copying it: byte 3 (the $0 in
     18  * movb $0, %al) is overwritten with the interrupt numbers.
     19  */
     20 
     21 	.code16
     22 	.globl __idt_handler
     23 __idt_handler:
     24 	pushal
     25 	movb 	$0, %al /* This instruction gets modified */
     26 	ljmp 	$0, $__interrupt_handler_16bit
     27 	.globl __idt_handler_size
     28 __idt_handler_size:
     29 	.long  . - __idt_handler
     30 
     31 .macro setup_registers
     32 	/* initial register values */
     33 	movl	44(%ebp), %eax
     34 	movl	%eax, __registers +  0 /* eax */
     35 	movl	48(%ebp), %eax
     36 	movl	%eax, __registers +  4 /* ebx */
     37 	movl	52(%ebp), %eax
     38 	movl	%eax, __registers +  8 /* ecx */
     39 	movl	56(%ebp), %eax
     40 	movl	%eax, __registers + 12 /* edx */
     41 	movl	60(%ebp), %eax
     42 	movl	%eax, __registers + 16 /* esi */
     43 	movl	64(%ebp), %eax
     44 	movl	%eax, __registers + 20 /* edi */
     45 .endm
     46 
     47 .macro	enter_real_mode
     48 	/* Activate the right segment descriptor real mode. */
     49 	ljmp	SEG(X86_GDT_ENTRY_16BIT_CS), $PTR_TO_REAL_MODE(1f)
     50 1:
     51 .code16
     52 	/*
     53 	 * Load the segment registers with properly configured segment
     54 	 * descriptors. They will retain these configurations (limits,
     55 	 * writability, etc.) once protected mode is turned off.
     56 	 */
     57 	mov	SEG(X86_GDT_ENTRY_16BIT_DS), %ax
     58 	mov	%ax, %ds
     59 	mov	%ax, %es
     60 	mov	%ax, %fs
     61 	mov	%ax, %gs
     62 	mov	%ax, %ss
     63 
     64 	/* Turn off protection */
     65 	movl	%cr0, %eax
     66 	andl	$~X86_CR0_PE, %eax
     67 	movl	%eax, %cr0
     68 
     69 	/* Now really going into real mode */
     70 	ljmp	$0, $PTR_TO_REAL_MODE(1f)
     71 1:
     72 	/*
     73 	 * Set up a stack: Put the stack at the end of page zero. That way
     74 	 * we can easily share it between real and protected, since the
     75 	 * 16-bit ESP at segment 0 will work for any case.
     76 	 */
     77 	mov	$0x0, %ax
     78 	mov	%ax, %ss
     79 
     80 	/* Load 16 bit IDT */
     81 	xor	%ax, %ax
     82 	mov	%ax, %ds
     83 	lidt	__realmode_idt
     84 
     85 .endm
     86 
     87 .macro	prepare_for_irom
     88 	movl	$0x1000, %eax
     89 	movl	%eax, %esp
     90 
     91 	/* Initialise registers for option rom lcall */
     92 	movl	__registers +  0, %eax
     93 	movl	__registers +  4, %ebx
     94 	movl	__registers +  8, %ecx
     95 	movl	__registers + 12, %edx
     96 	movl	__registers + 16, %esi
     97 	movl	__registers + 20, %edi
     98 
     99 	/* Set all segments to 0x0000, ds to 0x0040 */
    100 	push	%ax
    101 	xor	%ax, %ax
    102 	mov	%ax, %es
    103 	mov	%ax, %fs
    104 	mov	%ax, %gs
    105 	mov	SEG(X86_GDT_ENTRY_16BIT_FLAT_DS), %ax
    106 	mov	%ax, %ds
    107 	pop	%ax
    108 
    109 .endm
    110 
    111 .macro	enter_protected_mode
    112 	/* Go back to protected mode */
    113 	movl	%cr0, %eax
    114 	orl	$X86_CR0_PE, %eax
    115 	movl	%eax, %cr0
    116 
    117 	/* Now that we are in protected mode jump to a 32 bit code segment */
    118 	data32	ljmp	SEG(X86_GDT_ENTRY_32BIT_CS), $PTR_TO_REAL_MODE(1f)
    119 1:
    120 	.code32
    121 	mov	SEG(X86_GDT_ENTRY_32BIT_DS), %ax
    122 	mov	%ax, %ds
    123 	mov	%ax, %es
    124 	mov	%ax, %gs
    125 	mov	%ax, %ss
    126 	mov	SEG(X86_GDT_ENTRY_32BIT_FS), %ax
    127 	mov	%ax, %fs
    128 
    129 	/* restore proper idt */
    130 	lidt	idt_ptr
    131 .endm
    132 
    133 /*
    134  * In order to be independent of U-Boot's position in RAM we relocate a part
    135  * of the code to the first megabyte of RAM, so the CPU can use it in
    136  * real-mode. This code lives at asm_realmode_code.
    137  */
    138 	.globl asm_realmode_code
    139 asm_realmode_code:
    140 
    141 /* Realmode IDT pointer structure. */
    142 __realmode_idt = PTR_TO_REAL_MODE(.)
    143 	.word 1023	/* 16 bit limit */
    144 	.long 0		/* 24 bit base */
    145 	.word 0
    146 
    147 /* Preserve old stack */
    148 __stack = PTR_TO_REAL_MODE(.)
    149 	.long 0
    150 
    151 /* Register store for realmode_call and realmode_interrupt */
    152 __registers = PTR_TO_REAL_MODE(.)
    153 	.long 0 /*  0 - EAX */
    154 	.long 0 /*  4 - EBX */
    155 	.long 0 /*  8 - ECX */
    156 	.long 0 /* 12 - EDX */
    157 	.long 0 /* 16 - ESI */
    158 	.long 0 /* 20 - EDI */
    159 
    160 /* 256 byte buffer, used by int10 */
    161 	.globl asm_realmode_buffer
    162 asm_realmode_buffer:
    163 	.skip 256
    164 
    165 	.code32
    166 	.globl asm_realmode_call
    167 asm_realmode_call:
    168 	/* save all registers to the stack */
    169 	pusha
    170 	pushf
    171 	movl	%esp, __stack
    172 	movl	%esp, %ebp
    173 
    174 	/*
    175 	 * This function is called with regparm=0 and we have to skip the
    176 	 * 36 bytes from pushf+pusha. Hence start at 40.
    177 	 * Set up our call instruction.
    178 	 */
    179 	movl	40(%ebp), %eax
    180 	mov	%ax, __lcall_instr + 1
    181 	andl	$0xffff0000, %eax
    182 	shrl	$4, %eax
    183 	mov	%ax, __lcall_instr + 3
    184 
    185 	wbinvd
    186 
    187 	setup_registers
    188 	enter_real_mode
    189 	prepare_for_irom
    190 
    191 __lcall_instr = PTR_TO_REAL_MODE(.)
    192 	.byte 0x9a
    193 	.word 0x0000, 0x0000
    194 
    195 	enter_protected_mode
    196 
    197 	/* restore stack pointer, eflags and register values and exit */
    198 	movl	__stack, %esp
    199 	popf
    200 	popa
    201 	ret
    202 
    203 	.globl __realmode_interrupt
    204 __realmode_interrupt:
    205 	/* save all registers to the stack and store the stack pointer */
    206 	pusha
    207 	pushf
    208 	movl	%esp, __stack
    209 	movl	%esp, %ebp
    210 
    211 	/*
    212 	 * This function is called with regparm=0 and we have to skip the
    213 	 * 36 bytes from pushf+pusha. Hence start at 40.
    214 	 * Prepare interrupt calling code.
    215 	 */
    216 	movl	40(%ebp), %eax
    217 	movb	%al, __intXX_instr + 1 /* intno */
    218 
    219 	setup_registers
    220 	enter_real_mode
    221 	prepare_for_irom
    222 
    223 __intXX_instr = PTR_TO_REAL_MODE(.)
    224 	.byte 0xcd, 0x00 /* This becomes intXX */
    225 
    226 	enter_protected_mode
    227 
    228 	/* restore stack pointer, eflags and register values and exit */
    229 	movl	__stack, %esp
    230 	popf
    231 	popa
    232 	ret
    233 
    234 /*
    235  * This is the 16-bit interrupt entry point called by the IDT stub code.
    236  *
    237  * Before this code code is called, %eax is pushed to the stack, and the
    238  * interrupt number is loaded into %al. On return this function cleans up
    239  * for its caller.
    240  */
    241 	.code16
    242 __interrupt_handler_16bit = PTR_TO_REAL_MODE(.)
    243 	push	%ds
    244 	push	%es
    245 	push	%fs
    246 	push	%gs
    247 
    248 	/* Save real mode SS */
    249 	movw	%ss, %cs:__realmode_ss
    250 
    251 	/* Clear DF to not break ABI assumptions */
    252 	cld
    253 
    254 	/*
    255 	 * Clean up the interrupt number. We could do this in the stub, but
    256 	 * it would cost two more bytes per stub entry.
    257 	 */
    258 	andl	$0xff, %eax
    259 	pushl	%eax		/* ... and make it the first parameter */
    260 
    261 	enter_protected_mode
    262 
    263 	/*
    264 	 * Now we are in protected mode. We need compute the right ESP based
    265 	 * on saved real mode SS otherwise interrupt_handler() won't get
    266 	 * correct parameters from the stack.
    267 	 */
    268 	movzwl	%cs:__realmode_ss, %ecx
    269 	shll	$4, %ecx
    270 	addl	%ecx, %esp
    271 
    272 	/* Call the C interrupt handler */
    273 	movl	$interrupt_handler, %eax
    274 	call	*%eax
    275 
    276 	/* Restore real mode ESP based on saved SS */
    277 	movzwl	%cs:__realmode_ss, %ecx
    278 	shll	$4, %ecx
    279 	subl	%ecx, %esp
    280 
    281 	enter_real_mode
    282 
    283 	/* Restore real mode SS */
    284 	movw	%cs:__realmode_ss, %ss
    285 
    286 	/*
    287 	 * Restore all registers, including those manipulated by the C
    288 	 * handler
    289 	 */
    290 	popl	%eax
    291 	pop	%gs
    292 	pop	%fs
    293 	pop	%es
    294 	pop	%ds
    295 	popal
    296 	iret
    297 
    298 __realmode_ss = PTR_TO_REAL_MODE(.)
    299 	.word	0
    300 
    301 	.globl asm_realmode_code_size
    302 asm_realmode_code_size:
    303 	.long  . - asm_realmode_code
    304