furix
cute os
arch/x86_64/asm.S
6287 bytes raw
1/*
2 * low-level x86_64 primitives for furix.
3 *
4 * these live in a real assembly file rather than inline asm so that the exact
5 * instruction encodings are visible, reviewable and assembled by `as` instead of
6 * the compiler. all functions follow the Microsoft x64 ABI (the kernel is built
7 * for x86_64-unknown-windows): first argument in RCX, return value in RAX.
8 */
9
10.text
11
12/* ports */
13
14.globl inb
15
16
17inb:
18
19 movl %ecx, %edx
20 xorb %al, %al
21 inb %dx, %al
22 ret
23
24.globl outb
25
26
27outb:
28
29 movl %edx, %eax /* value -> AL */
30 movl %ecx, %edx /* port -> DX (8-bit OUT needs DX) */
31 outb %al, %dx
32 ret
33
34/* interrupts */
35
36.globl cpu_cli
37
38
39cpu_cli:
40
41 cli
42 ret
43
44.globl cpu_sti
45
46
47cpu_sti:
48
49 sti
50 ret
51
52.globl cpu_hlt
53
54
55cpu_hlt:
56
57 hlt
58 ret
59
60/*
61 * atomic_fetch_add(ptr, val): returns the OLD value, so the result is usable
62 * as a completion index.
63 *
64 * `lock xadd` is a single bus transaction: no other CPU can interleave a read
65 * and a write of the same location. without the lock prefix the two halves race
66 * and two CPUs adding to the same counter can both read the same old value and
67 * one increment is lost.
68 *
69 * arg0 = ptr (RCX), arg1 = val (RDX), per the Microsoft x64 ABI. the result
70 * lands in EAX, which is only the low half ; callers counting events want the
71 * 32-bit form, so keep the operand and result 32-bit throughout.
72 */
73.globl atomic_fetch_add_u32
74
75
76atomic_fetch_add_u32:
77
78 movl %edx, %eax /* EAX = value to add */
79 lock xaddl %eax, (%rcx) /* EAX = old, memory += value */
80 ret
81
82/*
83 * test_and_set(ptr): atomically read *ptr, and write 1 to it. returns the
84 * value it read, so nonzero means "somebody else got here first".
85 *
86 * this is the acquire primitive for the serial lock. `xchg` with a memory
87 * operand is implicitly locked on x86, so no separate lock prefix is needed.
88 * the write is a full barrier, which is what makes the caller's own flag check
89 * after a successful claim unnecessary.
90 */
91.globl atomic_test_and_set_u8
92
93
94atomic_test_and_set_u8:
95
96 movzbl (%rcx), %eax
97 movl $1, %edx
98 xchgb %dl, (%rcx)
99 ret
100
101/*
102 * atomic_load_u32(ptr): a plain aligned 32-bit load, which x86 already makes
103 * atomic against concurrent stores. spelled as a function so the volatile
104 * read cannot be reordered or elided by the compiler at a call site.
105 */
106.globl atomic_load_u32
107
108
109atomic_load_u32:
110
111 movl (%rcx), %eax
112 ret
113
114/*
115 * cpu_irq_save(): disable interrupts and return the previous EFLAGS word, so
116 * the caller can restore the prior IF state exactly rather than assuming it.
117 * the bit is forced clear in the returned value so that a caller who ignores
118 * the result still leaves interrupts off.
119 */
120.globl cpu_irq_save
121
122
123cpu_irq_save:
124
125 pushfq
126 popq %rax
127 pushfq
128 popq %rcx
129 andl $0xfffffdff, %ecx /* clear IF in the copy we return */
130 pushq %rcx
131 popfq /* interrupts now off */
132 ret
133
134/* cpu_irq_restore(flags = ECX): re-enable interrupts iff the saved word had IF. */
135
136.globl cpu_irq_restore
137
138
139cpu_irq_restore:
140
141 pushq %rcx
142 orl $0x200, %ecx /* normalise IF to 1 */
143 testb $1, %cl /* CL holds the saved IF */
144 jne 1f
145 andl $0xfffffdff, %ecx
146
147
1481:
149
150 popfq
151 ret
152
153/* read the IF flag on its own: 1 if interrupts are enabled, else 0. */
154
155.globl cpu_irqs_enabled
156
157
158cpu_irqs_enabled:
159
160 pushfq
161 popq %rax
162 andl $0x200, %eax
163 xorl %eax, %eax /* normalise to 0 or 1 */
164 ret
165
166/* control registers */
167
168.globl read_cr2
169
170
171read_cr2:
172
173 movq %cr2, %rax
174 ret
175
176.globl read_cr3
177
178
179read_cr3:
180
181 movq %cr3, %rax
182 ret
183
184/*
185 * MOV to/from a control register in long mode uses a 64-bit operand, so these
186 * must be 64-bit moves. (A 32-bit `mov %eax, %cr4` is rejected: in long mode
187 * that form does not encode.)
188 */
189.globl read_cr4
190
191
192read_cr4:
193
194 movq %cr4, %rax
195 ret
196
197.globl write_cr4
198
199
200write_cr4:
201
202 movq %rcx, %cr4
203 ret
204
205/* MSRs */
206
207.globl rdmsr
208
209
210rdmsr:
211
212 rdmsr
213 shlq $32, %rdx
214 orq %rdx, %rax
215 ret
216
217/* wrmsr(msr = ECX, value = RDX) */
218.globl wrmsr
219
220
221wrmsr:
222
223 movq %rdx, %r8
224 movl %r8d, %eax /* EAX = low 32 */
225 shrq $32, %r8 /* 64-bit shift: 32-bit would mask to 0 */
226 movl %r8d, %edx /* EDX = high 32 */
227 wrmsr
228 ret
229
230.data
231.balign 8
232.globl cpuid_out
233
234
235cpuid_out:
236
237 .quad 0
238 .quad 0
239 .quad 0
240 .quad 0
241.text
242
243/* CPUID with the leaf in ECX; the four result registers land in cpuid_out. */
244.globl cpuid
245
246
247cpuid:
248
249 movl %ecx, %eax
250 xorl %ecx, %ecx
251 cpuid
252 movq %rax, cpuid_out(%rip)
253 movq %rbx, cpuid_out+8(%rip)
254 movq %rcx, cpuid_out+16(%rip)
255 movq %rdx, cpuid_out+24(%rip)
256 ret
257
258/* descriptor table registers */
259
260.globl lgdt
261
262
263lgdt:
264
265 lgdt (%rcx)
266 ret
267
268.globl lidt
269
270
271lidt:
272
273 lidt (%rcx)
274 ret
275
276.globl ltr
277
278
279ltr:
280
281 movl %ecx, %eax
282 ltr %ax
283 ret
284
285/*
286 * CS cannot be reloaded with mov. push the new selector and a return address,
287 * then far-return into it.
288 */
289.globl reload_cs
290
291
292reload_cs:
293
294 leaq 1f(%rip), %rax
295 pushq %rcx
296 pushq %rax
297 lretq
298
299
3001:
301
302 ret
303
304.globl load_data_segments
305
306
307load_data_segments:
308
309 movl %ecx, %eax
310 movw %ax, %ds
311 movw %ax, %es
312 movw %ax, %ss
313 ret
314
315/* per-CPU (GS) access */
316
317.globl swapgs
318
319
320swapgs:
321
322 swapgs
323 ret
324
325/*
326 * wrgsbase/rdfsbase require CR4.FSGSBASE, which in turn requires the CPU to
327 * advertise CPUID.(EAX=7,ECX=0):EBX[0]. qemu's default model (qemu64) leaves
328 * that bit clear, and setting CR4.FSGSBASE there does not come back as a
329 * usable #GP, the guest simply stops. prefer IA32_KERNEL_GS_BASE + swapgs
330 * (see percpu_init), which works everywhere.
331 */
332.globl wrgsbase
333
334
335wrgsbase:
336
337 wrgsbaseq %rcx
338 ret
339
340.globl rdfsbase
341
342
343rdfsbase:
344
345 rdfsbaseq %rax
346 ret
347
348/* GS-relative access to the per-CPU area, which starts at GS.base + 0. */
349.globl gs_store
350
351
352gs_store:
353
354 movq %rcx, %gs:0
355 ret
356
357.globl gs_load
358
359
360gs_load:
361
362 movq %gs:0, %rax
363 ret