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)
501:
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)
711:
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)
1191:
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
139asm_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
162asm_realmode_buffer:
163	.skip 256
164
165	.code32
166	.globl asm_realmode_call
167asm_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
302asm_realmode_code_size:
303	.long  . - asm_realmode_code
304