summaryrefslogtreecommitdiff
path: root/rt/+x86_64/realcall.S
blob: ffbc0a9a49cb2b852f8e363634dbd35672463662 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
.code64

.globl bios.regs
bios.regs:
reax:
	.int 0x0
rebx:
	.int 0x0
recx:
	.int 0x0
redx:
	.int 0x0
redi:
	.int 0x0
resi:
	.int 0x0
res:
	.short 0x0

prev_idt:
	.quad 0x0
prev_gdt:
	.quad 0x0

# :real_call
# This function is intended to be called from long mode
# and calls another function in real mode. Note that precautions have
# to be made. Recall that real mode can only access below 0xFFFFF.
# So the target function, and objects of arguments passed to it cannot
# be located above that. A reasonable scheme is to put the result
# in a buffer and then, once back in long mode, copy its content to higher
# above.
# The procedure to go from Long Mode back to Real Mode is explained in much details
# in the AMD64 Programmer's Manual Vol. 2, in particular, the figure 1-6. in section 1.3.
.globl bios.call
bios.call:
	xchg %bx, %bx
	push %rbx
	push %r12
	push %r13
	push %r14
	push %r15
	pushf

	# :Save code segment (and second push to prepare for far return)
	mov %cs, %ax
	push %ax
	push %ax

	# :Save data segment
	mov %ds, %ax
	push %ax
	# :Save the interrupt number
	push %di

	cli

	sidt (prev_idt)
	sgdt (prev_gdt)

	lgdt (gdtr32)
	pushq $(gdt32_code - gdt32)
	pushq $real_call_to_pmode_down
	retfq
.code32
real_call_to_pmode_down:
	# :Here, we are in compatibility mode (32 bits)

	# :Disable paging
	mov %cr0, %eax
	and $~(1 << 31), %eax
	mov %eax, %cr0

	# :Disabling long mode in msr
	mov $0xc0000080, %ecx
	rdmsr
	and $~(1 << 8), %eax
	wrmsr

	# :Here we are in true protected mode (32 bits)
	# Let's continue our descent to real mode
	# by switching our gdt to 16 bits
	lgdt gdtr16
	ljmp $(gdt16_code - gdt16), $real_call_to_16bits_pmode_down
.code16
real_call_to_16bits_pmode_down:

	# :Disable protected mode
	mov %cr0, %eax
	and $~1, %eax
	mov %eax, %cr0

	ljmp $0, $real_call_to_16bits_rmode_down
real_call_to_16bits_rmode_down:
	mov $0, %ax
	mov %ax, %ds
	mov %ax, %es
	mov %ax, %ss
	mov %ax, %gs
	mov %ax, %fs

	lidt bios_idtr

	# :Self modifying code to call arbitrary interrupts
	pop %ax
	mov $real_call_int, %bx
	mov %al, 1(%bx)

	# :Load registers
	mov (reax), %eax
	mov (rebx), %ebx
	mov (recx), %ecx
	mov (redx), %edx
	mov (redi), %edi
	mov (resi), %esi
	mov (res), %es

	sti

real_call_int:
	int $0x0

	cli

	mov %es, (res)
	mov %eax, (reax)
	mov %ebx, (rebx)
	mov %ecx, (recx)
	mov %edx, (redx)
	mov %edi, (redi)
	mov %esi, (resi)
	
	# :Restore protected mode
	mov %cr0, %eax
	or $1, %eax
	mov %eax, %cr0

	lgdt gdtr32
	ljmp $(gdt32_code - gdt32), $real_call_to_pmode_up
.code32
real_call_to_pmode_up:
	# :Restore PAE (probably unneeded)
	mov %cr4, %eax
	or $(1 << 5), %eax
	mov %eax, %cr4

	# :Restore long mode
	mov $0xc0000080, %ecx
	rdmsr
	or $(1 << 8), %eax
	wrmsr

	# :Restore paging
	mov %cr0, %eax
	or $1 << 31, %eax
	mov %eax, %cr0

	lgdt (prev_gdt)

	# :At this point %ds is latest in stack
	pop %ax

	# :At this point %cs is latest in stack
	# Do a long jump
	push $real_call_to_longmode_up
	retf
.code64
real_call_to_longmode_up:
	mov %ax, %fs
	mov %ax, %gs
	mov %ax, %ss
	mov %ax, %es
	mov %ax, %ds

	# :Avoid doing this until the bootloader loads a 64 bits IDT
	# XXX
	#idt (prev_idt)

real_call_end:
	popf
	pop %r15
	pop %r14
	pop %r13
	pop %r12
	pop %rbx
	ret