xref: /openbmc/u-boot/arch/x86/lib/bios_asm.S (revision 23ff8633)
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)
511:
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)
721:
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)
1201:
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
140asm_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
163asm_realmode_buffer:
164	.skip 256
165
166	.code32
167	.globl asm_realmode_call
168asm_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
303asm_realmode_code_size:
304	.long  . - asm_realmode_code
305