xref: /netbsd/sys/arch/i386/i386/spl.S (revision de64ca6f)
1/*	$NetBSD: spl.S,v 1.58 2023/03/01 08:38:50 riastradh Exp $	*/
2
3/*
4 * Copyright (c) 1998, 2007, 2008, 2020 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Charles M. Hannum and Andrew Doran.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <machine/asm.h>
33__KERNEL_RCSID(0, "$NetBSD: spl.S,v 1.58 2023/03/01 08:38:50 riastradh Exp $");
34
35#include "opt_ddb.h"
36#include "opt_spldebug.h"
37#include "opt_xen.h"
38
39#include <machine/trap.h>
40#include <machine/segments.h>
41#include <machine/frameasm.h>
42
43#include "assym.h"
44
45	.text
46
47/*
48 * int splraise(int s);
49 */
50ENTRY(splraise)
51	movl	4(%esp),%edx
52	movzbl	CPUVAR(ILEVEL),%eax
53	cmpl	%edx,%eax
54	ja	1f
55	movb	%dl,CPUVAR(ILEVEL)
561:
57#ifdef SPLDEBUG
58	pushl	%ebp
59	movl	%esp,%ebp
60	pushl	%eax
61	pushl	%edx
62	call	_C_LABEL(spldebug_raise)
63	addl	$4,%esp
64	popl	%eax
65	popl	%ebp
66#endif /* SPLDEBUG */
67	ret
68END(splraise)
69
70#ifndef XENPV
71/*
72 * void spllower(int s);
73 *
74 * spllower() for i486 and Pentium. Must be the same size as cx8_spllower(),
75 * that is, 96 bytes. This must use pushf/cli/popf as it is used early in boot
76 * where interrupts are disabled via eflags/IE.
77 */
78ENTRY(spllower)
79	HOTPATCH(HP_NAME_SPLLOWER, 96)
80#ifdef SPLDEBUG
81	movl	4(%esp),%ecx
82	pushl	%ebp
83	movl	%esp,%ebp
84	pushl	%ecx
85	call	_C_LABEL(spldebug_lower)
86	addl	$4,%esp
87	popl	%ebp
88#endif /* SPLDEBUG */
89	movl	4(%esp),%ecx
90	cmpb	CPUVAR(ILEVEL),%cl
91	jae	1f
92	movl	CPUVAR(IUNMASK)(,%ecx,8),%edx
93	movl	CPUVAR(IUNMASK)+4(,%ecx,8),%eax
94	PUSHF(%eax)
95	CLI(%eax)
96	testl	CPUVAR(IPENDING),%edx
97	jnz	2f
98	testl	CPUVAR(IPENDING)+4,%eax
99	jnz	2f
100	movb	%cl,CPUVAR(ILEVEL)
101	POPF(%eax)
1021:
103	ret
1042:
105	popf
106	jmp	_C_LABEL(Xspllower)
107	.align	32
108END(spllower)
109#else  /* XENPV */
110STRONG_ALIAS(spllower, cx8_spllower)
111#endif /* !XENPV */
112
113/*
114 * void	cx8_spllower(int s);
115 *
116 * spllower() optimized for Pentium Pro and later, which have long pipelines
117 * that will be stalled by pushf/cli/popf.  Must be the same size as
118 * spllower(), ie 96 bytes.  Does not need to restore eflags/IE as is patched
119 * in once autoconf is underway.
120 *
121 * For cmpxchg8b, edx/ecx are the high words and eax/ebx the low.
122 *
123 * edx : eax = old level + high 24 bit old ipending / low 32 bit old ipending
124 * ecx : ebx = new level + high 24 bit old ipending / low 32 bit old ipending
125 */
126ENTRY(cx8_spllower)
127	movl	4(%esp),%ecx
128	movzbl	CPUVAR(ILEVEL),%edx
129	cmpl	%edx,%ecx			/* new level is lower? */
130	jae	1f
131	pushl	%ebx
132	pushl	%esi
133	pushl	%edi
134	movl	%ecx,%esi
135	movl	%ecx,%edi
136	shll	$24,%edi
1370:
138	movl	CPUVAR(IPENDING),%eax
139	testl	%eax,CPUVAR(IUNMASK)(,%esi,8)	/* deferred interrupts? */
140	jnz	2f
141	movl	CPUVAR(IPENDING)+4,%edx
142	testl	%edx,CPUVAR(IUNMASK)+4(,%esi,8)
143	jnz	2f
144	movl	%eax,%ebx
145	movl	%edx,%ecx
146	andl	$0x00ffffff,%ecx
147	orl	%edi,%ecx
148	cmpxchg8b CPUVAR(ISTATE)		/* swap in new ilevel */
149	jnz	0b
150	popl	%edi
151	popl	%esi
152	popl	%ebx
1531:
154	ret
1552:
156	popl	%edi
157	popl	%esi
158	popl	%ebx
159
160	/* The reference must be absolute, hence the indirect jump. */
161	movl	$Xspllower,%eax
162	jmp	*%eax
163
164	.align	32, 0xCC
165LABEL(cx8_spllower_end)
166END(cx8_spllower)
167
168/*
169 * void Xspllower(int s);
170 *
171 * Process pending interrupts.
172 *
173 * Important registers:
174 *   ebx - cpl
175 *   esi - address to resume loop at
176 *   edi - scratch for Xsoftnet
177 *
178 * It is important that the bit scan instruction is bsr, it will get
179 * the highest 2 bits (currently the IPI and clock handlers) first,
180 * to avoid deadlocks where one CPU sends an IPI, another one is at
181 * splhigh() and defers it, lands in here via splx(), and handles
182 * a lower-prio one first, which needs to take the kernel lock -->
183 * the sending CPU will never see the that CPU accept the IPI
184 * (see pmap_tlb_shootnow).
185 */
186	nop	/* Don't get confused with cx8_spllower_end */
187
188IDTVEC(spllower)
189	pushl	%ebp
190	movl	%esp,%ebp
191	MCOUNT_ASM
192	pushl	%ebx
193	pushl	%esi
194	pushl	%edi
195	movl	8(%ebp),%ebx
196	movl	$.Lspllower_resume,%esi		/* address to resume loop at */
1971:
198	/*
199	 * Because of the way Xen interrupts work *%esi will in fact be called
200	 * from Xdoreti via iret. So we have to always disable interrupts here
201	 * for Xen.
202	 */
203#ifndef XENPV
204	CLI(%eax)
205#endif
206.Lspllower_resume:
207#ifdef XENPV
208	CLI(%eax)
209#endif
210#if defined(DEBUG)
211#ifndef XENPV
212	pushf
213	popl	%eax
214	testl	$PSL_I,%eax
215	jnz	.Lspllower_panic
216#else
217	movl    CPUVAR(VCPU),%eax
218	movb	EVTCHN_UPCALL_MASK(%eax),%al
219	andb	%al,%al
220	jz	.Lspllower_panic
221#endif /* XENPV */
222#endif /* defined(DEBUG) */
223	movl	%ebx,%eax			/* get cpl */
224	movl	CPUVAR(IUNMASK)+4(,%eax,8),%eax
225	andl	CPUVAR(IPENDING)+4,%eax		/* any non-masked bits left? */
226	jz	10f
227	bsrl	%eax,%eax
228	btrl	%eax,CPUVAR(IPENDING)+4
229	addl	$32,%eax
230	movl	CPUVAR(ISOURCES)(,%eax,4),%eax
231	jmp	*IS_RECURSE(%eax)
23210:
233	movl	%ebx,%eax			/* get cpl */
234	movl	CPUVAR(IUNMASK)(,%eax,8),%eax
235	andl	CPUVAR(IPENDING),%eax		/* any non-masked bits left? */
236	jz	2f
237	bsrl	%eax,%eax
238	btrl	%eax,CPUVAR(IPENDING)
239	movl	CPUVAR(ISOURCES)(,%eax,4),%eax
240	jmp	*IS_RECURSE(%eax)
2412:
242	movb	%bl,CPUVAR(ILEVEL)
243#ifdef XENPV
244	STIC(%eax)
245	jz 4f
246	call	_C_LABEL(stipending)
247	testl	%eax,%eax
248	jnz	1b
2494:
250#else
251	STI(%eax)
252#endif
253	popl	%edi
254	popl	%esi
255	popl	%ebx
256	leave
257	ret
258#if defined(DEBUG)
259.Lspllower_panic:
260	pushl	$1f
261	call	_C_LABEL(panic)
2621:	.asciz	"SPLLOWER: INTERRUPT ENABLED"
263#endif
264IDTVEC_END(spllower)
265
266/*
267 * Xdoreti: Handle return from interrupt after device handler finishes.
268 *
269 * Important registers:
270 *   ebx - cpl to restore
271 *   esi - address to resume loop at
272 *   edi - scratch for Xsoftnet
273 *
274 * called with interrupt disabled.
275 */
276IDTVEC(doreti)
277	IDEPTH_DECR
278	popl	%ebx			/* get previous priority */
279.Ldoreti_resume_stic:
280	movl	$.Ldoreti_resume,%esi	/* address to resume loop at */
281.Ldoreti_resume:
282
283#if defined(DEBUG)
284#ifndef XENPV
285	pushf
286	popl	%eax
287	testl	$PSL_I,%eax
288	jnz	.Ldoreti_panic
289#else
290	movl	CPUVAR(VCPU),%eax
291	movb	EVTCHN_UPCALL_MASK(%eax),%al
292	andb	%al,%al
293	jz	.Ldoreti_panic
294#endif /* XENPV */
295#endif /* defined(DEBUG) */
296
297	movl	%ebx,%eax
298	movl	CPUVAR(IUNMASK)+4(,%eax,8),%eax
299	andl	CPUVAR(IPENDING)+4,%eax
300	jz	10f
301	bsrl	%eax,%eax		/* slow, but not worth optimizing */
302	btrl	%eax,CPUVAR(IPENDING)+4
303	addl	$32,%eax
304	movl	CPUVAR(ISOURCES)(,%eax, 4),%eax
305	jmp	*IS_RESUME(%eax)
30610:
307	movl	%ebx,%eax
308	movl	CPUVAR(IUNMASK)(,%eax,8),%eax
309	andl	CPUVAR(IPENDING),%eax
310	jz	2f
311	bsrl	%eax,%eax		/* slow, but not worth optimizing */
312	btrl	%eax,CPUVAR(IPENDING)
313	movl	CPUVAR(ISOURCES)(,%eax, 4),%eax
314	jmp	*IS_RESUME(%eax)
3152:	/* Check for ASTs on exit to user mode. */
316	movb	%bl,CPUVAR(ILEVEL)
3175:
318	testb	$CHK_UPL,TF_CS(%esp)
319	jnz	doreti_checkast
320	jmp	6f
321
322	.type	_C_LABEL(doreti_checkast), @function
323LABEL(doreti_checkast)
324	CHECK_ASTPENDING(%eax)
325	jz	3f
326	CLEAR_ASTPENDING(%eax)
327	STI(%eax)
328	movl	$T_ASTFLT,TF_TRAPNO(%esp)	/* XXX undo later.. */
329	/* Pushed T_ASTFLT into tf_trapno on entry. */
330	pushl	%esp
331	call	_C_LABEL(trap)
332	addl	$4,%esp
333	CLI(%eax)
334	jmp	5b
335END(doreti_checkast)
336
3373:
338	CHECK_DEFERRED_SWITCH
339	jnz	9f
340	HANDLE_DEFERRED_FPU
3416:
342#ifdef XENPV
343	STIC(%eax)
344	jz	4f
345	call	_C_LABEL(stipending)
346	testl	%eax,%eax
347	jz	4f
348	CLI(%eax)
349	jmp	.Ldoreti_resume_stic
3504:
351#endif
352	INTRFASTEXIT
3539:
354	STI(%eax)
355	call	_C_LABEL(pmap_load)
356	CLI(%eax)
357	jmp	doreti_checkast	/* recheck ASTs */
358
359#if defined(DEBUG)
360.Ldoreti_panic:
361	pushl	$1f
362	call	_C_LABEL(panic)
3631:	.asciz	"DORETI: INTERRUPT ENABLED"
364#endif
365IDTVEC_END(doreti)
366
367/*
368 * Xsoftintr()
369 *
370 * Switch to the LWP assigned to handle interrupts from the given
371 * source.  We borrow the VM context from the interrupted LWP.
372 *
373 * On entry:
374 *
375 *	%eax		intrsource
376 *	%esi		address to return to
377 */
378IDTVEC(softintr)
379	pushl	$_C_LABEL(softintr_ret)	/* set up struct switchframe */
380	pushl	%ebx
381	pushl	%esi
382	pushl	%edi
383	movb	$IPL_HIGH,CPUVAR(ILEVEL)
384	STI(%esi)
385	movl	CPUVAR(CURLWP),%esi
386	movl	IS_LWP(%eax),%edi	/* switch to handler LWP */
387	/*
388	 * Simple MOV to set curlwp to softlwp.  See below on ordering
389	 * required to restore softlwp like cpu_switchto.
390	 *
391	 * 1. Don't need store-before-store barrier because x86 is TSO.
392	 *
393	 * 2. Don't need store-before-load barrier because when we
394	 *    enter a softint lwp, it can't be holding any mutexes, so
395	 *    it can't release any until after it has acquired them, so
396	 *    we need not participate in the protocol with
397	 *    mutex_vector_enter barriers here.
398	 *
399	 * Hence no need for XCHG or barriers around MOV.
400	 */
401	movl	%edi,CPUVAR(CURLWP)
402	movl	L_PCB(%edi),%edx
403	movl	L_PCB(%esi),%ecx
404	movl	%esp,PCB_ESP(%ecx)
405	movl	%ebp,PCB_EBP(%ecx)
406	movl	PCB_ESP0(%edx),%esp	/* onto new stack */
407	pushl	IS_MAXLEVEL(%eax)	/* ipl to run at */
408	pushl	%esi
409	call	_C_LABEL(softint_dispatch)/* run handlers */
410	addl	$8,%esp
411	CLI(%ecx)
412	movl	L_PCB(%esi),%ecx
413	movl	PCB_ESP(%ecx),%esp
414
415	/*
416	 * Use XCHG, not MOV, to coordinate mutex_exit on this CPU with
417	 * mutex_vector_enter on another CPU.
418	 *
419	 * 1. Any prior mutex_exit by the softint must be visible to
420	 *    other CPUs before we restore curlwp on this one,
421	 *    requiring store-before-store ordering.
422	 *
423	 *    (This is always guaranteed by the x86 memory model, TSO,
424	 *    but other architectures require a explicit barrier before
425	 *    the store to ci->ci_curlwp.)
426	 *
427	 * 2. Restoring curlwp must be visible on all other CPUs before
428	 *    any subsequent mutex_exit on this one can even test
429	 *    whether there might be waiters, requiring
430	 *    store-before-load ordering.
431	 *
432	 *    (This is the only ordering x86 TSO ever requires any kind
433	 *    of barrier for -- in this case, we take advantage of the
434	 *    sequential consistency implied by XCHG to obviate the
435	 *    need for MFENCE or something.)
436	 *
437	 * See kern_mutex.c for details -- this is necessary for
438	 * adaptive mutexes to detect whether the lwp is on the CPU in
439	 * order to safely block without requiring atomic r/m/w in
440	 * mutex_exit.  See also cpu_switchto.
441	 */
442	xchgl	%esi,CPUVAR(CURLWP)	/* restore ci_curlwp */
443	popl	%edi			/* unwind switchframe */
444	popl	%esi
445	addl	$8,%esp
446	jmp	*%esi			/* back to splx/doreti */
447IDTVEC_END(softintr)
448
449/*
450 * softintr_ret()
451 *
452 * Trampoline function that gets returned to by cpu_switchto() when
453 * an interrupt handler blocks.  On entry:
454 *
455 *	%eax		prevlwp from cpu_switchto()
456 */
457ENTRY(softintr_ret)
458	incl	CPUVAR(MTX_COUNT)	/* re-adjust after mi_switch */
459	CLI(%eax)
460	jmp	*%esi			/* back to splx/doreti */
461END(softintr_ret)
462
463/*
464 * void softint_trigger(uintptr_t machdep);
465 *
466 * Software interrupt registration.
467 */
468ENTRY(softint_trigger)
469	movl	4(%esp),%eax
470	orl	%eax,CPUVAR(IPENDING)	/* atomic on local cpu */
471	ret
472END(softint_trigger)
473
474/*
475 * Xrecurse_preempt()
476 *
477 * Handles preemption interrupts via Xspllower().
478 */
479IDTVEC(recurse_preempt)
480	movb	$IPL_PREEMPT,CPUVAR(ILEVEL)
481	STI(%eax)
482	pushl	$0
483	call	_C_LABEL(kpreempt)
484	addl	$4,%esp
485	CLI(%eax)
486	jmp	*%esi
487IDTVEC_END(recurse_preempt)
488
489/*
490 * Xresume_preempt()
491 *
492 * Handles preemption interrupts via Xdoreti().
493 */
494IDTVEC(resume_preempt)
495	movb	$IPL_PREEMPT,CPUVAR(ILEVEL)
496	STI(%eax)
497	testb	$CHK_UPL,TF_CS(%esp)
498	jnz	1f
499	movl	TF_EIP(%esp),%eax
500	pushl	%eax
501	call	_C_LABEL(kpreempt)		/* from kernel */
502	addl	$4,%esp
503	CLI(%eax)
504	jmp	*%esi
5051:
506	call	_C_LABEL(preempt)		/* from user */
507	CLI(%eax)
508	jmp	*%esi
509IDTVEC_END(resume_preempt)
510