| /* SPDX-License-Identifier: GPL-2.0 */ |
| /* |
| * Copyright (C) 2014, 2015 Intel Corporation; author Matt Fleming |
| * |
| * Early support for invoking 32-bit EFI services from a 64-bit kernel. |
| * |
| * Because this thunking occurs before ExitBootServices() we have to |
| * restore the firmware's 32-bit GDT and IDT before we make EFI service |
| * calls. |
| * |
| * On the plus side, we don't have to worry about mangling 64-bit |
| * addresses into 32-bits because we're executing with an identity |
| * mapped pagetable and haven't transitioned to 64-bit virtual addresses |
| * yet. |
| */ |
| |
| #include <linux/linkage.h> |
| #include <asm/msr.h> |
| #include <asm/page_types.h> |
| #include <asm/processor-flags.h> |
| #include <asm/segment.h> |
| |
| .code64 |
| .text |
| SYM_FUNC_START(__efi64_thunk) |
| push %rbp |
| push %rbx |
| |
| leaq 1f(%rip), %rbp |
| |
| movl %ds, %eax |
| push %rax |
| movl %es, %eax |
| push %rax |
| movl %ss, %eax |
| push %rax |
| |
| /* |
| * Convert x86-64 ABI params to i386 ABI |
| */ |
| subq $64, %rsp |
| movl %esi, 0x0(%rsp) |
| movl %edx, 0x4(%rsp) |
| movl %ecx, 0x8(%rsp) |
| movl %r8d, 0xc(%rsp) |
| movl %r9d, 0x10(%rsp) |
| |
| leaq 0x14(%rsp), %rbx |
| sgdt (%rbx) |
| |
| addq $16, %rbx |
| sidt (%rbx) |
| |
| /* |
| * Switch to IDT and GDT with 32-bit segments. This is the firmware GDT |
| * and IDT that was installed when the kernel started executing. The |
| * pointers were saved at the EFI stub entry point in head_64.S. |
| * |
| * Pass the saved DS selector to the 32-bit code, and use far return to |
| * restore the saved CS selector. |
| */ |
| leaq efi32_boot_idt(%rip), %rax |
| lidt (%rax) |
| leaq efi32_boot_gdt(%rip), %rax |
| lgdt (%rax) |
| |
| movzwl efi32_boot_ds(%rip), %edx |
| movzwq efi32_boot_cs(%rip), %rax |
| pushq %rax |
| leaq efi_enter32(%rip), %rax |
| pushq %rax |
| lretq |
| |
| 1: addq $64, %rsp |
| movq %rdi, %rax |
| |
| pop %rbx |
| movl %ebx, %ss |
| pop %rbx |
| movl %ebx, %es |
| pop %rbx |
| movl %ebx, %ds |
| /* Clear out 32-bit selector from FS and GS */ |
| xorl %ebx, %ebx |
| movl %ebx, %fs |
| movl %ebx, %gs |
| |
| /* |
| * Convert 32-bit status code into 64-bit. |
| */ |
| roll $1, %eax |
| rorq $1, %rax |
| |
| pop %rbx |
| pop %rbp |
| ret |
| SYM_FUNC_END(__efi64_thunk) |
| |
| .code32 |
| /* |
| * EFI service pointer must be in %edi. |
| * |
| * The stack should represent the 32-bit calling convention. |
| */ |
| SYM_FUNC_START_LOCAL(efi_enter32) |
| /* Load firmware selector into data and stack segment registers */ |
| movl %edx, %ds |
| movl %edx, %es |
| movl %edx, %fs |
| movl %edx, %gs |
| movl %edx, %ss |
| |
| /* Reload pgtables */ |
| movl %cr3, %eax |
| movl %eax, %cr3 |
| |
| /* Disable paging */ |
| movl %cr0, %eax |
| btrl $X86_CR0_PG_BIT, %eax |
| movl %eax, %cr0 |
| |
| /* Disable long mode via EFER */ |
| movl $MSR_EFER, %ecx |
| rdmsr |
| btrl $_EFER_LME, %eax |
| wrmsr |
| |
| call *%edi |
| |
| /* We must preserve return value */ |
| movl %eax, %edi |
| |
| /* |
| * Some firmware will return with interrupts enabled. Be sure to |
| * disable them before we switch GDTs and IDTs. |
| */ |
| cli |
| |
| lidtl (%ebx) |
| subl $16, %ebx |
| |
| lgdtl (%ebx) |
| |
| movl %cr4, %eax |
| btsl $(X86_CR4_PAE_BIT), %eax |
| movl %eax, %cr4 |
| |
| movl %cr3, %eax |
| movl %eax, %cr3 |
| |
| movl $MSR_EFER, %ecx |
| rdmsr |
| btsl $_EFER_LME, %eax |
| wrmsr |
| |
| xorl %eax, %eax |
| lldt %ax |
| |
| pushl $__KERNEL_CS |
| pushl %ebp |
| |
| /* Enable paging */ |
| movl %cr0, %eax |
| btsl $X86_CR0_PG_BIT, %eax |
| movl %eax, %cr0 |
| lret |
| SYM_FUNC_END(efi_enter32) |
| |
| .data |
| .balign 8 |
| SYM_DATA_START(efi32_boot_gdt) |
| .word 0 |
| .quad 0 |
| SYM_DATA_END(efi32_boot_gdt) |
| |
| SYM_DATA_START(efi32_boot_idt) |
| .word 0 |
| .quad 0 |
| SYM_DATA_END(efi32_boot_idt) |
| |
| SYM_DATA_START(efi32_boot_cs) |
| .word 0 |
| SYM_DATA_END(efi32_boot_cs) |
| |
| SYM_DATA_START(efi32_boot_ds) |
| .word 0 |
| SYM_DATA_END(efi32_boot_ds) |