Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * EFI call stub for IA32. |
| 3 | * |
| 4 | * This stub allows us to make EFI calls in physical mode with interrupts |
| 5 | * turned off. |
| 6 | */ |
| 7 | |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 8 | #include <linux/linkage.h> |
| 9 | #include <asm/page.h> |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 | |
| 11 | /* |
| 12 | * efi_call_phys(void *, ...) is a function with variable parameters. |
| 13 | * All the callers of this function assure that all the parameters are 4-bytes. |
| 14 | */ |
| 15 | |
| 16 | /* |
| 17 | * In gcc calling convention, EBX, ESP, EBP, ESI and EDI are all callee save. |
| 18 | * So we'd better save all of them at the beginning of this function and restore |
| 19 | * at the end no matter how many we use, because we can not assure EFI runtime |
| 20 | * service functions will comply with gcc calling convention, too. |
| 21 | */ |
| 22 | |
| 23 | .text |
| 24 | ENTRY(efi_call_phys) |
| 25 | /* |
| 26 | * 0. The function can only be called in Linux kernel. So CS has been |
| 27 | * set to 0x0010, DS and SS have been set to 0x0018. In EFI, I found |
| 28 | * the values of these registers are the same. And, the corresponding |
| 29 | * GDT entries are identical. So I will do nothing about segment reg |
| 30 | * and GDT, but change GDT base register in prelog and epilog. |
| 31 | */ |
| 32 | |
| 33 | /* |
| 34 | * 1. Now I am running with EIP = <physical address> + PAGE_OFFSET. |
| 35 | * But to make it smoothly switch from virtual mode to flat mode. |
| 36 | * The mapping of lower virtual memory has been created in prelog and |
| 37 | * epilog. |
| 38 | */ |
| 39 | movl $1f, %edx |
| 40 | subl $__PAGE_OFFSET, %edx |
| 41 | jmp *%edx |
| 42 | 1: |
| 43 | |
| 44 | /* |
| 45 | * 2. Now on the top of stack is the return |
| 46 | * address in the caller of efi_call_phys(), then parameter 1, |
| 47 | * parameter 2, ..., param n. To make things easy, we save the return |
| 48 | * address of efi_call_phys in a global variable. |
| 49 | */ |
| 50 | popl %edx |
| 51 | movl %edx, saved_return_addr |
| 52 | /* get the function pointer into ECX*/ |
| 53 | popl %ecx |
| 54 | movl %ecx, efi_rt_function_ptr |
| 55 | movl $2f, %edx |
| 56 | subl $__PAGE_OFFSET, %edx |
| 57 | pushl %edx |
| 58 | |
| 59 | /* |
| 60 | * 3. Clear PG bit in %CR0. |
| 61 | */ |
| 62 | movl %cr0, %edx |
| 63 | andl $0x7fffffff, %edx |
| 64 | movl %edx, %cr0 |
| 65 | jmp 1f |
| 66 | 1: |
| 67 | |
| 68 | /* |
| 69 | * 4. Adjust stack pointer. |
| 70 | */ |
| 71 | subl $__PAGE_OFFSET, %esp |
| 72 | |
| 73 | /* |
| 74 | * 5. Call the physical function. |
| 75 | */ |
| 76 | jmp *%ecx |
| 77 | |
| 78 | 2: |
| 79 | /* |
| 80 | * 6. After EFI runtime service returns, control will return to |
| 81 | * following instruction. We'd better readjust stack pointer first. |
| 82 | */ |
| 83 | addl $__PAGE_OFFSET, %esp |
| 84 | |
| 85 | /* |
| 86 | * 7. Restore PG bit |
| 87 | */ |
| 88 | movl %cr0, %edx |
| 89 | orl $0x80000000, %edx |
| 90 | movl %edx, %cr0 |
| 91 | jmp 1f |
| 92 | 1: |
| 93 | /* |
| 94 | * 8. Now restore the virtual mode from flat mode by |
| 95 | * adding EIP with PAGE_OFFSET. |
| 96 | */ |
| 97 | movl $1f, %edx |
| 98 | jmp *%edx |
| 99 | 1: |
| 100 | |
| 101 | /* |
| 102 | * 9. Balance the stack. And because EAX contain the return value, |
| 103 | * we'd better not clobber it. |
| 104 | */ |
| 105 | leal efi_rt_function_ptr, %edx |
| 106 | movl (%edx), %ecx |
| 107 | pushl %ecx |
| 108 | |
| 109 | /* |
| 110 | * 10. Push the saved return address onto the stack and return. |
| 111 | */ |
| 112 | leal saved_return_addr, %edx |
| 113 | movl (%edx), %ecx |
| 114 | pushl %ecx |
| 115 | ret |
| 116 | .previous |
| 117 | |
| 118 | .data |
| 119 | saved_return_addr: |
| 120 | .long 0 |
| 121 | efi_rt_function_ptr: |
| 122 | .long 0 |