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