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