xref: /aosp_15_r20/external/coreboot/src/device/oprom/realmode/x86_asm.S (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1/* SPDX-License-Identifier: GPL-2.0-only */
2
3#define REALMODE_BASE		0x600
4#define RELOCATED(x)	(x - __realmode_code + REALMODE_BASE)
5
6#include <arch/ram_segs.h>
7
8/* CR0 bits */
9#define PE		(1 << 0)
10
11/* This is the intXX interrupt handler stub code. It gets copied
12 * to the IDT and to some fixed addresses in the F segment. Before
13 * the code can used, it gets patched up by the C function copying
14 * it: byte 3 (the $0 in movb $0, %al) is overwritten with the int#.
15 */
16
17	.code16
18	.globl __idt_handler
19__idt_handler:
20	pushal
21	movb 	$0, %al /* This instruction gets modified */
22	ljmp 	$0, $__interrupt_handler_16bit
23	.globl __idt_handler_size
24__idt_handler_size:
25	.long  . - __idt_handler
26
27
28/* In order to be independent of coreboot's position in RAM
29 * we relocate a part of the code to the low megabyte, so the
30 * CPU can use it in real-mode. This code lives at __realmode_code.
31 */
32	.globl __realmode_code
33__realmode_code:
34
35/* Realmode function return. */
36__realmode_ret = RELOCATED(.)
37	.long 0
38
39/* Realmode IDT pointer structure. */
40__realmode_idt = RELOCATED(.)
41	.word 1023	/* 16 bit limit */
42	.long 0		/* 24 bit base */
43	.word 0
44
45/* Preserve old stack */
46__stack = RELOCATED(.)
47	.long 0
48
49/* Register store for realmode_call and realmode_interrupt */
50__registers = RELOCATED(.)
51	.long 0 /*  0 - EAX */
52	.long 0 /*  4 - EBX */
53	.long 0 /*  8 - ECX */
54	.long 0 /* 12 - EDX */
55	.long 0 /* 16 - ESI */
56	.long 0 /* 20 - EDI */
57
58/* 256 byte buffer, used by int10 */
59	.globl __realmode_buffer
60__realmode_buffer:
61	.skip 256
62
63	.code32
64	.globl __realmode_call
65__realmode_call:
66	/* save all registers to the stack */
67	pusha
68	pushf
69
70	/* Move the protected mode stack pointer to a safe place */
71	movl	%esp, __stack
72	movl	%esp, %ebp
73
74	/* This function is called with regparm=0 and we have to
75	 * skip the 36 byte from pushf+pusha. Hence start at 40.
76	 */
77
78	/* entry point */
79	movl	40(%ebp), %eax
80	mov	%ax, __lcall_instr + 1
81	andl	$0xffff0000, %eax
82	shrl	$4, %eax
83	mov	%ax, __lcall_instr + 3
84
85	/* initial register values */
86	movl	44(%ebp), %eax
87	movl	%eax, __registers +  0 /* eax */
88	movl	48(%ebp), %eax
89	movl	%eax, __registers +  4 /* ebx */
90	movl	52(%ebp), %eax
91	movl	%eax, __registers +  8 /* ecx */
92	movl	56(%ebp), %eax
93	movl	%eax, __registers + 12 /* edx */
94	movl	60(%ebp), %eax
95	movl	%eax, __registers + 16 /* esi */
96	movl	64(%ebp), %eax
97	movl	%eax, __registers + 20 /* edi */
98
99	/* Activate the right segment descriptor real mode. */
100	ljmp	$RAM_CODE16_SEG, $RELOCATED(1f)
1011:
102.code16
103	/* 16 bit code from here on... */
104
105	/* Load the segment registers w/ properly configured
106	 * segment descriptors. They will retain these
107	 * configurations (limits, writability, etc.) once
108	 * protected mode is turned off.
109	 */
110	mov	$RAM_DATA16_SEG, %ax
111	mov	%ax, %ds
112	mov	%ax, %es
113	mov	%ax, %fs
114	mov	%ax, %gs
115	mov	%ax, %ss
116
117	/* Turn off protection */
118	movl	%cr0, %eax
119	andl	$~PE, %eax
120	movl	%eax, %cr0
121
122	/* Now really going into real mode */
123	ljmp	$0, $RELOCATED(1f)
1241:
125	/* Setup a stack: Put the stack at the end of page zero.
126	 * That way we can easily share it between real and
127	 * protected, since the 16 bit ESP at segment 0 will
128	 * work for any case. */
129	mov	$0x0, %ax
130	mov	%ax, %ss
131	movl	$0x1000, %eax
132	movl	%eax, %esp
133
134	/* Load 16 bit IDT */
135	xor	%ax, %ax
136	mov	%ax, %ds
137	lidt	__realmode_idt
138
139	/* initialize registers for option ROM lcall */
140	movl	__registers +  0, %eax
141	movl	__registers +  4, %ebx
142	movl	__registers +  8, %ecx
143	movl	__registers + 12, %edx
144	movl	__registers + 16, %esi
145	movl	__registers + 20, %edi
146
147	/* Set all segments to 0x0000, ds to 0x0040 */
148	push	%ax
149	xor	%ax, %ax
150	mov	%ax, %es
151	mov	%ax, %fs
152	mov	%ax, %gs
153	mov	$0x40, %ax
154	mov	%ax, %ds
155	pop	%ax
156
157	/* ************************************ */
158__lcall_instr = RELOCATED(.)
159	.byte 0x9a
160	.word 0x0000, 0x0000
161	/* ************************************ */
162
163	/*
164	 * Here is end of real mode call and time to go back to protected mode.
165	 * Before that its better to store current eax into some memory address
166	 * so that context persist in protected mode too.
167	*/
168	mov	%eax, __realmode_ret
169
170	/* If we got here, we are just about done.
171	 * Need to get back to protected mode.
172	 */
173	movl	%cr0, %eax
174	orl	$PE, %eax
175	movl	%eax, %cr0
176
177	/* Now that we are in protected mode
178	 * jump to a 32 bit code segment.
179	 */
180	ljmpl	$RAM_CODE_SEG, $RELOCATED(1f)
1811:
182	.code32
183	mov	$RAM_DATA_SEG, %ax
184	mov	%ax, %ds
185	mov	%ax, %es
186	mov	%ax, %fs
187	mov	%ax, %gs
188	mov	%ax, %ss
189
190	/* restore proper idt */
191	lidt	idtarg
192
193	/* restore stack pointer, eflags and register values */
194	movl	__stack, %esp
195	popf
196	popa
197
198	/* and exit */
199	/* return AX from OPROM call */
200	mov	__realmode_ret, %eax
201	ret
202
203	.globl __realmode_interrupt
204__realmode_interrupt:
205	/* save all registers to the stack */
206	pusha
207	pushf
208
209	/* save the stack pointer */
210	movl	%esp, __stack
211	movl	%esp, %ebp
212
213	/* This function is called with regparm=0 and we have to
214	 * skip the 36 byte from pushf+pusha. Hence start at 40.
215	 */
216
217	/* prepare interrupt calling code */
218	movl	40(%ebp), %eax
219	movb	%al, __intXX_instr + 1 /* intno */
220
221	/* initial register values */
222	movl	44(%ebp), %eax
223	movl	%eax, __registers +  0 /* eax */
224	movl	48(%ebp), %eax
225	movl	%eax, __registers +  4 /* ebx */
226	movl	52(%ebp), %eax
227	movl	%eax, __registers +  8 /* ecx */
228	movl	56(%ebp), %eax
229	movl	%eax, __registers + 12 /* edx */
230	movl	60(%ebp), %eax
231	movl	%eax, __registers + 16 /* esi */
232	movl	64(%ebp), %eax
233	movl	%eax, __registers + 20 /* edi */
234
235	/*  This configures CS properly for real mode. */
236	ljmp 	$RAM_CODE16_SEG, $RELOCATED(1f)
2371:
238	.code16 /* 16 bit code from here on... */
239
240	/* Load the segment registers w/ properly configured segment
241	 * descriptors.  They will retain these configurations (limits,
242	 * writability, etc.) once protected mode is turned off.
243	 */
244	mov	$RAM_DATA16_SEG, %ax
245	mov	%ax, %ds
246	mov	%ax, %es
247	mov	%ax, %fs
248	mov	%ax, %gs
249	mov	%ax, %ss
250
251	/* Turn off protected mode */
252	movl	%cr0, %eax
253	andl	$~PE, %eax
254	movl	%eax, %cr0
255
256	/* Now really going into real mode */
257	ljmpl	$0, $RELOCATED(1f)
2581:
259
260	/* put the stack at the end of page zero.  That way we can easily
261	 * share it between real mode and protected mode, because %esp and
262	 * %ss:%sp point to the same memory.
263	 */
264	/* setup a stack */
265	mov	$0x0, %ax
266	mov	%ax, %ss
267	movl	$0x1000, %eax
268	movl	%eax, %esp
269
270	/* Load 16-bit intXX IDT */
271	xor	%ax, %ax
272	mov	%ax, %ds
273	lidt	__realmode_idt
274
275	/* initialize registers for intXX call */
276	movl	__registers +  0, %eax
277	movl	__registers +  4, %ebx
278	movl	__registers +  8, %ecx
279	movl	__registers + 12, %edx
280	movl	__registers + 16, %esi
281	movl	__registers + 20, %edi
282
283	/* Set all segments to 0x0000 */
284	push	%ax
285	xor	%ax, %ax
286	mov	%ax, %ds
287	mov	%ax, %es
288	mov	%ax, %fs
289	mov	%ax, %gs
290	pop	%ax
291
292__intXX_instr = RELOCATED(.)
293	.byte 0xcd, 0x00 /* This becomes intXX */
294
295	/*
296	 * Here is end of real mode call and time to go back to protected mode.
297	 * Before that its better to store current eax into some memory address
298	 * so that context persist in protected mode too.
299	*/
300	mov	%eax, __realmode_ret
301
302	/* Ok, the job is done, now go back to protected mode coreboot */
303	movl	%cr0, %eax
304	orl	$PE, %eax
305	movl	%eax, %cr0
306
307	/* Now that we are in protected mode jump to a 32-bit code segment. */
308	ljmpl	$RAM_CODE_SEG, $RELOCATED(1f)
3091:
310	.code32
311	mov	$RAM_DATA_SEG, %ax
312	mov	%ax, %ds
313	mov	%ax, %es
314	mov	%ax, %fs
315	mov	%ax, %gs
316	mov	%ax, %ss
317
318	/* restore coreboot's 32-bit IDT */
319	lidt	idtarg
320
321	/* restore stack pointer, eflags and register values and exit */
322	movl	__stack, %esp
323	popf
324	popa
325	/* return AX from OPROM call */
326	mov	__realmode_ret, %eax
327	ret
328
329/* This is the 16-bit interrupt entry point called by the IDT stub code.
330 *
331 * Before this code is called, %eax is pushed to the stack, and the
332 * interrupt number is loaded into %al. On return this function cleans up
333 * for its caller.
334 */
335	.code16
336__interrupt_handler_16bit = RELOCATED(.)
337	push	%ds
338	push	%es
339	push	%fs
340	push	%gs
341
342	/* Clear DF to not break ABI assumptions */
343	cld
344
345	/* Clean up the interrupt number. We could have done this in the stub,
346	 * but it would have cost 2 more bytes per stub entry.
347	 */
348	andl	$0xff, %eax
349	pushl	%eax		/* ... and make it the first parameter */
350
351	/* Switch to protected mode */
352	movl    %cr0, %eax
353	orl	$PE, %eax
354	movl	%eax, %cr0
355
356	/* ... and jump to a 32 bit code segment. */
357	ljmpl	$RAM_CODE_SEG, $RELOCATED(1f)
3581:
359	.code32
360	mov	$RAM_DATA_SEG, %ax
361	mov	%ax, %ds
362	mov	%ax, %es
363	mov	%ax, %fs
364	mov	%ax, %gs
365	mov	%ax, %ss
366
367	lidt	idtarg
368
369	/* Call the C interrupt handler */
370	movl	$interrupt_handler, %eax
371	call	*%eax
372
373	/* Now return to real mode ... */
374	ljmp	$RAM_CODE16_SEG, $RELOCATED(1f)
3751:
376	.code16
377	/* Load the segment registers with properly configured segment
378	 * descriptors.  They will retain these configurations (limits,
379	 * writability, etc.) once protected mode is turned off.
380	 */
381	mov	$RAM_DATA16_SEG, %ax
382	mov	%ax, %ds
383	mov	%ax, %es
384	mov	%ax, %fs
385	mov	%ax, %gs
386	mov	%ax, %ss
387
388	/* Disable Protected Mode */
389	movl	%cr0, %eax
390	andl	$~PE, %eax
391	movl	%eax, %cr0
392
393	/* Now really going into real mode */
394	ljmp $0,  $RELOCATED(1f)
3951:
396	/* Restore real-mode stack segment */
397	mov	$0x0, %ax
398	mov	%ax, %ss
399
400	/* Restore 16 bit IDT */
401	xor	%ax, %ax
402	mov	%ax, %ds
403	lidt	__realmode_idt
404
405	/* Restore all registers, including those
406	 * manipulated by the C handler
407	 */
408	popl	%eax
409	pop	%gs
410	pop	%fs
411	pop	%es
412	pop	%ds
413	popal
414	iret
415
416	.globl __realmode_code_size
417__realmode_code_size:
418	.long  . - __realmode_code
419
420	.code32
421