/*	$OpenBSD: frameasm.h,v 1.27 2023/07/27 00:30:07 guenther Exp $	*/
/*	$NetBSD: frameasm.h,v 1.1 2003/04/26 18:39:40 fvdl Exp $	*/

#ifndef _AMD64_MACHINE_FRAMEASM_H
#define _AMD64_MACHINE_FRAMEASM_H

/*
 * Macros to define pushing/popping frames for interrupts, traps
 * and system calls. Currently all the same; will diverge later.
 */

/*
 * These are used on interrupt or trap entry or exit.
 */
#define INTR_SAVE_GPRS \
	subq	$120,%rsp		; \
	INTR_SAVE_MOST_GPRS_NO_ADJ	; \
	movq	%rcx,TF_RCX(%rsp)
#define INTR_SAVE_MOST_GPRS_NO_ADJ \
	movq	%r15,TF_R15(%rsp)	; \
	movq	%r14,TF_R14(%rsp)	; \
	movq	%r13,TF_R13(%rsp)	; \
	movq	%r12,TF_R12(%rsp)	; \
	movq	%r11,TF_R11(%rsp)	; \
	movq	%r10,TF_R10(%rsp)	; \
	movq	%r9,TF_R9(%rsp)		; \
	movq	%r8,TF_R8(%rsp)		; \
	movq	%rdi,TF_RDI(%rsp)	; \
	movq	%rsi,TF_RSI(%rsp)	; \
	movq	%rbp,TF_RBP(%rsp)	; \
	leaq	TF_RBP(%rsp),%rbp	; \
	movq	%rbx,TF_RBX(%rsp)	; \
	movq	%rdx,TF_RDX(%rsp)	; \
	movq	%rax,TF_RAX(%rsp)

/*
 * We clear registers when coming from userspace to prevent
 * user-controlled values from being available for use in speculative
 * execution in the kernel.  %rsp and %rbp are the kernel values when
 * this is used, so there are only 14 to clear.  32bit operations clear
 * the register upper-halves automatically.
 */
#define INTR_CLEAR_GPRS \
	xorl	%eax,%eax		; \
	xorl	%ebx,%ebx		; \
	xorl	%ecx,%ecx		; \
	xorl	%edx,%edx		; \
	xorl	%esi,%esi		; \
	xorl	%edi,%edi		; \
	xorl	%r8d,%r8d		; \
	xorl	%r9d,%r9d		; \
	xorl	%r10d,%r10d		; \
	xorl	%r11d,%r11d		; \
	xorl	%r12d,%r12d		; \
	xorl	%r13d,%r13d		; \
	xorl	%r14d,%r14d		; \
	xorl	%r15d,%r15d


/*
 * For real interrupt code paths, where we can come from userspace.
 * We only have an iretq_frame on entry.
 */
#define INTRENTRY_LABEL(label)	X##label##_untramp
#define	INTRENTRY(label) \
	endbr64				; \
	testb	$SEL_RPL,IRETQ_CS(%rsp)	; \
	je	INTRENTRY_LABEL(label)	; \
	swapgs				; \
	FENCE_SWAPGS_MIS_TAKEN 		; \
	movq	%rax,CPUVAR(SCRATCH)	; \
	CODEPATCH_START			; \
	movq	CPUVAR(KERN_CR3),%rax	; \
	movq	%rax,%cr3		; \
	CODEPATCH_END(CPTAG_MELTDOWN_NOP);\
	jmp	98f			; \
END(X##label)				; \
_ENTRY(INTRENTRY_LABEL(label)) /* from kernel */ \
	FENCE_NO_SAFE_SMAP		; \
	subq	$TF_RIP,%rsp		; \
	movq	%rcx,TF_RCX(%rsp)	; \
	jmp	99f			; \
	_ALIGN_TRAPS			; \
98:	/* from userspace */		  \
	movq	CPUVAR(KERN_RSP),%rax	; \
	xchgq	%rax,%rsp		; \
	movq	%rcx,TF_RCX(%rsp)	; \
	RET_STACK_REFILL_WITH_RCX	; \
	/* copy iretq frame to the trap frame */ \
	movq	IRETQ_RIP(%rax),%rcx	; \
	movq	%rcx,TF_RIP(%rsp)	; \
	movq	IRETQ_CS(%rax),%rcx	; \
	movq	%rcx,TF_CS(%rsp)	; \
	movq	IRETQ_RFLAGS(%rax),%rcx	; \
	movq	%rcx,TF_RFLAGS(%rsp)	; \
	movq	IRETQ_RSP(%rax),%rcx	; \
	movq	%rcx,TF_RSP(%rsp)	; \
	movq	IRETQ_SS(%rax),%rcx	; \
	movq	%rcx,TF_SS(%rsp)	; \
	movq	CPUVAR(SCRATCH),%rax	; \
99:	INTR_SAVE_MOST_GPRS_NO_ADJ	; \
	INTR_CLEAR_GPRS			; \
	movq	%rax,TF_ERR(%rsp)

#define INTRFASTEXIT \
	jmp	intr_fast_exit

/*
 * Entry for faking up an interrupt frame after spllower() unblocks
 * a previously received interrupt.  On entry, %r13 has the %rip
 * to return to.  %r10 and %r11 are scratch.
 */
#define	INTR_RECURSE \
	endbr64				; \
	/* fake the iretq_frame */	; \
	movq	%rsp,%r10		; \
	movl	%ss,%r11d		; \
	pushq	%r11			; \
	pushq	%r10			; \
	pushfq				; \
	movl	%cs,%r11d		; \
	pushq	%r11			; \
	pushq	%r13			; \
	/* now do the rest of the intrframe */ \
	subq	$16,%rsp		; \
	INTR_SAVE_GPRS


/* 
 * Entry for traps from kernel, where there's a trapno + err already
 * on the stack.  We have to move the err from its hardware location
 * to the location we want it.
 */
#define	TRAP_ENTRY_KERN \
	subq	$120,%rsp		; \
	movq	%rcx,TF_RCX(%rsp)	; \
	movq	(TF_RIP - 8)(%rsp),%rcx	; \
	movq	%rcx,TF_ERR(%rsp)	; \
	INTR_SAVE_MOST_GPRS_NO_ADJ

/*
 * Entry for traps from userland, where there's a trapno + err on
 * the iretq stack.
 * Assumes that %rax has been saved in CPUVAR(SCRATCH).
 */
#define	TRAP_ENTRY_USER \
	movq	CPUVAR(KERN_RSP),%rax		; \
	xchgq	%rax,%rsp			; \
	movq	%rcx,TF_RCX(%rsp)		; \
	RET_STACK_REFILL_WITH_RCX		; \
	/* copy trapno+err to the trap frame */ \
	movq	0(%rax),%rcx			; \
	movq	%rcx,TF_TRAPNO(%rsp)		; \
	movq	8(%rax),%rcx			; \
	movq	%rcx,TF_ERR(%rsp)		; \
	/* copy iretq frame to the trap frame */ \
	movq	(IRETQ_RIP+16)(%rax),%rcx	; \
	movq	%rcx,TF_RIP(%rsp)		; \
	movq	(IRETQ_CS+16)(%rax),%rcx	; \
	movq	%rcx,TF_CS(%rsp)		; \
	movq	(IRETQ_RFLAGS+16)(%rax),%rcx	; \
	movq	%rcx,TF_RFLAGS(%rsp)		; \
	movq	(IRETQ_RSP+16)(%rax),%rcx	; \
	movq	%rcx,TF_RSP(%rsp)		; \
	movq	(IRETQ_SS+16)(%rax),%rcx	; \
	movq	%rcx,TF_SS(%rsp)		; \
	movq	CPUVAR(SCRATCH),%rax		; \
	INTR_SAVE_MOST_GPRS_NO_ADJ		; \
	INTR_CLEAR_GPRS

/*
 * Entry from syscall instruction, where RIP is in %rcx and RFLAGS is in %r11.
 * We stash the syscall # in tf_err for SPL check.
 * Assumes that %rax has been saved in CPUVAR(SCRATCH).
 */
#define	SYSCALL_ENTRY \
	movq	CPUVAR(KERN_RSP),%rax				; \
	xchgq	%rax,%rsp					; \
	movq	%rcx,TF_RCX(%rsp)				; \
	movq	%rcx,TF_RIP(%rsp)				; \
	RET_STACK_REFILL_WITH_RCX				; \
	movq	$(GSEL(GUDATA_SEL, SEL_UPL)),TF_SS(%rsp)	; \
	movq	%rax,TF_RSP(%rsp)				; \
	movq	CPUVAR(SCRATCH),%rax				; \
	INTR_SAVE_MOST_GPRS_NO_ADJ				; \
	movq	%r11, TF_RFLAGS(%rsp)				; \
	movq	$(GSEL(GUCODE_SEL, SEL_UPL)), TF_CS(%rsp)	; \
	movq	%rax,TF_ERR(%rsp)				; \
	INTR_CLEAR_GPRS

#define CHECK_ASTPENDING(reg)	movq	CPUVAR(CURPROC),reg		; \
				cmpq	$0, reg				; \
				je	99f				; \
				cmpl	$0, P_MD_ASTPENDING(reg)	; \
				99:

#define CLEAR_ASTPENDING(reg)	movl	$0, P_MD_ASTPENDING(reg)

#endif /* _AMD64_MACHINE_FRAMEASM_H */