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