1/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * Copyright (C) 2014, 2015 Intel Corporation; author Matt Fleming
4 *
5 * Early support for invoking 32-bit EFI services from a 64-bit kernel.
6 *
7 * Because this thunking occurs before ExitBootServices() we have to
8 * restore the firmware's 32-bit GDT and IDT before we make EFI service
9 * calls.
10 *
11 * On the plus side, we don't have to worry about mangling 64-bit
12 * addresses into 32-bits because we're executing with an identity
13 * mapped pagetable and haven't transitioned to 64-bit virtual addresses
14 * yet.
15 */
16
17#include <linux/linkage.h>
18#include <asm/msr.h>
19#include <asm/page_types.h>
20#include <asm/processor-flags.h>
21#include <asm/segment.h>
22
23	.code64
24	.text
25SYM_FUNC_START(__efi64_thunk)
26	push	%rbp
27	push	%rbx
28
29	leaq	1f(%rip), %rbp
30
31	movl	%ds, %eax
32	push	%rax
33	movl	%es, %eax
34	push	%rax
35	movl	%ss, %eax
36	push	%rax
37
38	/*
39	 * Convert x86-64 ABI params to i386 ABI
40	 */
41	subq	$64, %rsp
42	movl	%esi, 0x0(%rsp)
43	movl	%edx, 0x4(%rsp)
44	movl	%ecx, 0x8(%rsp)
45	movl	%r8d, 0xc(%rsp)
46	movl	%r9d, 0x10(%rsp)
47
48	leaq	0x14(%rsp), %rbx
49	sgdt	(%rbx)
50
51	addq	$16, %rbx
52	sidt	(%rbx)
53
54	/*
55	 * Switch to IDT and GDT with 32-bit segments. This is the firmware GDT
56	 * and IDT that was installed when the kernel started executing. The
57	 * pointers were saved at the EFI stub entry point in head_64.S.
58	 *
59	 * Pass the saved DS selector to the 32-bit code, and use far return to
60	 * restore the saved CS selector.
61	 */
62	leaq	efi32_boot_idt(%rip), %rax
63	lidt	(%rax)
64	leaq	efi32_boot_gdt(%rip), %rax
65	lgdt	(%rax)
66
67	movzwl	efi32_boot_ds(%rip), %edx
68	movzwq	efi32_boot_cs(%rip), %rax
69	pushq	%rax
70	leaq	efi_enter32(%rip), %rax
71	pushq	%rax
72	lretq
73
741:	addq	$64, %rsp
75	movq	%rdi, %rax
76
77	pop	%rbx
78	movl	%ebx, %ss
79	pop	%rbx
80	movl	%ebx, %es
81	pop	%rbx
82	movl	%ebx, %ds
83	/* Clear out 32-bit selector from FS and GS */
84	xorl	%ebx, %ebx
85	movl	%ebx, %fs
86	movl	%ebx, %gs
87
88	/*
89	 * Convert 32-bit status code into 64-bit.
90	 */
91	roll	$1, %eax
92	rorq	$1, %rax
93
94	pop	%rbx
95	pop	%rbp
96	ret
97SYM_FUNC_END(__efi64_thunk)
98
99	.code32
100/*
101 * EFI service pointer must be in %edi.
102 *
103 * The stack should represent the 32-bit calling convention.
104 */
105SYM_FUNC_START_LOCAL(efi_enter32)
106	/* Load firmware selector into data and stack segment registers */
107	movl	%edx, %ds
108	movl	%edx, %es
109	movl	%edx, %fs
110	movl	%edx, %gs
111	movl	%edx, %ss
112
113	/* Reload pgtables */
114	movl	%cr3, %eax
115	movl	%eax, %cr3
116
117	/* Disable paging */
118	movl	%cr0, %eax
119	btrl	$X86_CR0_PG_BIT, %eax
120	movl	%eax, %cr0
121
122	/* Disable long mode via EFER */
123	movl	$MSR_EFER, %ecx
124	rdmsr
125	btrl	$_EFER_LME, %eax
126	wrmsr
127
128	call	*%edi
129
130	/* We must preserve return value */
131	movl	%eax, %edi
132
133	/*
134	 * Some firmware will return with interrupts enabled. Be sure to
135	 * disable them before we switch GDTs and IDTs.
136	 */
137	cli
138
139	lidtl	(%ebx)
140	subl	$16, %ebx
141
142	lgdtl	(%ebx)
143
144	movl	%cr4, %eax
145	btsl	$(X86_CR4_PAE_BIT), %eax
146	movl	%eax, %cr4
147
148	movl	%cr3, %eax
149	movl	%eax, %cr3
150
151	movl	$MSR_EFER, %ecx
152	rdmsr
153	btsl	$_EFER_LME, %eax
154	wrmsr
155
156	xorl	%eax, %eax
157	lldt	%ax
158
159	pushl	$__KERNEL_CS
160	pushl	%ebp
161
162	/* Enable paging */
163	movl	%cr0, %eax
164	btsl	$X86_CR0_PG_BIT, %eax
165	movl	%eax, %cr0
166	lret
167SYM_FUNC_END(efi_enter32)
168
169	.data
170	.balign	8
171SYM_DATA_START(efi32_boot_gdt)
172	.word	0
173	.quad	0
174SYM_DATA_END(efi32_boot_gdt)
175
176SYM_DATA_START(efi32_boot_idt)
177	.word	0
178	.quad	0
179SYM_DATA_END(efi32_boot_idt)
180
181SYM_DATA_START(efi32_boot_cs)
182	.word	0
183SYM_DATA_END(efi32_boot_cs)
184
185SYM_DATA_START(efi32_boot_ds)
186	.word	0
187SYM_DATA_END(efi32_boot_ds)
188