/* $NetBSD: copyinout.S,v 1.16 2021/02/11 08:35:11 ryo Exp $ */

/*-
 * Copyright (c) 2014 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas of 3am Software Foundry.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "opt_gprof.h"
#include <sys/errno.h>
#include <aarch64/asm.h>
#include "assym.h"

RCSID("$NetBSD: copyinout.S,v 1.16 2021/02/11 08:35:11 ryo Exp $");

#ifdef ARMV81_PAN
#define PAN_ENABLE	\
	adrl	x9, _C_LABEL(aarch64_pan_enabled)	; \
	ldr	w9, [x9]				; \
	cbz	w9, 666f				; \
	msr	pan, #1					; \
666:
#define PAN_DISABLE	\
	adrl	x9, _C_LABEL(aarch64_pan_enabled)	; \
	ldr	w9, [x9]				; \
	cbz	w9, 666f				; \
	msr	pan, #0					; \
666:
#else
#define PAN_ENABLE	/* nothing */
#define PAN_DISABLE	/* nothing */
#endif

	ARMV8_DEFINE_OPTIONS

	.macro enter_cpu_onfault
	stp	fp, lr, [sp, #-16]!	/* save fp, lr */
	mov	fp, sp			/* link frame pointer */

	stp	x19, x20, [sp, #-16]!	/* save x19, x20 */
	mov	x19, x0			/* x19 = arg0 */
	mov	x20, x1			/* x20 = arg1 */

	/* if (cpu_set_onfault(fb) != 0) return -1 */
	sub	sp, sp, #FB_T_SIZE	/* allocate struct faultbuf */
	mov	x0, sp			/* x0 = faultbuf */
	stp	x2, x3, [sp, #-16]!	/* save x2, x3 */
	bl	cpu_set_onfault
	ldp	x2, x3, [sp], #16	/* restore x2, x3 */
	mov	x8, x0			/* x8 = cpu_set_onfault() */
	cbnz	x0, 9f			/* return if error */

	mov	x0, x19			/* x0 = x19 = arg0 */
	mov	x1, x20			/* x1 = x20 = arg1 */

	PAN_DISABLE			/* disable PAN */
	.endm

	.macro exit_cpu_onfault
	/* curlwp->l_md.md_onfault = NULL */
	mrs	x0, tpidr_el1			/* x0 = curlwp */
	str	xzr, [x0, #L_MD_ONFAULT]	/* lwp->l_md_onfault = NULL */
9:
	PAN_ENABLE				/* enable PAN */
	add	sp, sp, #FB_T_SIZE		/* pop stack */
	ldp	x19, x20, [sp], #16		/* restore x19, x20 */
	ldp	fp, lr, [sp], #16		/* restore fp, lr */
	mov	x0, x8				/* x0 = return value */
	.endm


/* LINTSTUB: int copyin(const void *uaddr, void *kaddr, size_t len); */

ENTRY(copyin)
	enter_cpu_onfault

	/*
	 * How much to copy?  Less than 32 bytes?  ...
	 */
	lsr	x4, x2, #5			/* x4 = len / 32 */
	cbz	x4, .Lcopyin_last_suboword

	/*
	 * Subtract the amount of data we copied to get a octword aligned
	 * from the length: length -= 32 - offset -> length += offset - 32;
	 */
	and	x5, x0, #31
	cbz	x5, .Lcopyin_loop

	add	x2, x2, x5
	sub	x2, x2, #32
	lsr	x4, x2, #5			/* update x4 = len / 32 */

	/* XXX: not considered alignment of destination */
	tbz	x0, #0, .Lcopyin_hword_aligned
	ldtrb	w5, [x0]
	add	x0, x0, #1
	strb	w5, [x1], #1
.Lcopyin_hword_aligned:
	tbz	x0, #1, .Lcopyin_word_aligned
	ldtrh	w5, [x0]
	add	x0, x0, #2
	strh	w5, [x1], #2
.Lcopyin_word_aligned:
	tbz	x0, #2, .Lcopyin_dword_aligned
	ldtr	w5, [x0]
	add	x0, x0, #4
	str	w5, [x1], #4
.Lcopyin_dword_aligned:
	tbz	x0, #3, .Lcopyin_qword_aligned
	ldtr	x5, [x0]
	add	x0, x0, #8
	str	x5, [x1], #8
.Lcopyin_qword_aligned:
	tbz	x0, #4, .Lcopyin_loop0
	ldtr	x5, [x0, #0]
	ldtr	x6, [x0, #8]
	add	x0, x0, #16
	stp	x5, x6, [x1], #16
.Lcopyin_loop0:
	cbz	x4, .Lcopyin_last_suboword
.Lcopyin_loop:
	ldtr	x5, [x0, #0]
	ldtr	x6, [x0, #8]
	ldtr	x7, [x0, #16]
	ldtr	x8, [x0, #24]
	add	x0, x0, #32
	stp	x5, x6, [x1], #16
	stp	x7, x8, [x1], #16
	sub	x4, x4, #1
	cbnz	x4, .Lcopyin_loop
.Lcopyin_last_suboword:
	tbz	x2, #4, .Lcopyin_last_subqword
	ldtr	x5, [x0, #0]
	ldtr	x6, [x0, #8]
	add	x0, x0, #16
	stp	x5, x6, [x1], #16
.Lcopyin_last_subqword:
	tbz	x2, #3, .Lcopyin_last_subdword
	ldtr	x5, [x0]
	add	x0, x0, #8
	str	x5, [x1], #8
.Lcopyin_last_subdword:
	tbz	x2, #2, .Lcopyin_last_subword
	ldtr	w5, [x0]
	add	x0, x0, #4
	str	w5, [x1], #4
.Lcopyin_last_subword:
	tbz	x2, #1, .Lcopyin_last_subhword
	ldtrh	w5, [x0]
	add	x0, x0, #2
	strh	w5, [x1], #2
.Lcopyin_last_subhword:
	tbz	x2, #0, .Lcopyin_done
	ldtrb	w5, [x0]
	strb	w5, [x1]
.Lcopyin_done:
	mov	x8, #0

	exit_cpu_onfault
	ret
END(copyin)


/* LINTSTUB: int copyout(const void *kaddr, void *uaddr, size_t len); */

ENTRY(copyout)
	enter_cpu_onfault

	/*
	 * How much to copy?  Less than 32 bytes?  ...
	 */
	lsr	x4, x2, #5			/* x4 = len / 32 */
	cbz	x4, .Lcopyout_last_suboword

	/*
	 * Subtract the amount of data we copied to get a octword aligned
	 * from the length: length -= 32 - offset -> length += offset - 32;
	 */
	and	x5, x0, #31
	cbz	x5, .Lcopyout_loop

	add	x2, x2, x5
	sub	x2, x2, #32
	lsr	x4, x2, #5			/* update x4 = len / 32 */

	/* XXX: not considered alignment of destination */
	tbz	x0, #0, .Lcopyout_hword_aligned
	ldrb	w5, [x0], #1
	sttrb	w5, [x1]
	add	x1, x1, #1
.Lcopyout_hword_aligned:
	tbz	x0, #1, .Lcopyout_word_aligned
	ldrh	w5, [x0], #2
	sttrh	w5, [x1]
	add	x1, x1, #2
.Lcopyout_word_aligned:
	tbz	x0, #2, .Lcopyout_dword_aligned
	ldr	w5, [x0], #4
	sttr	w5, [x1]
	add	x1, x1, #4
.Lcopyout_dword_aligned:
	tbz	x0, #3, .Lcopyout_qword_aligned
	ldr	x5, [x0], #8
	sttr	x5, [x1]
	add	x1, x1, #8
.Lcopyout_qword_aligned:
	tbz	x0, #4, .Lcopyout_loop0
	ldp	x5, x6, [x0], #16
	sttr	x5, [x1, #0]
	sttr	x6, [x1, #8]
	add	x1, x1, #16
.Lcopyout_loop0:
	cbz	x4, .Lcopyout_last_suboword
.Lcopyout_loop:
	ldp	x5, x6, [x0], #16
	ldp	x7, x8, [x0], #16
	sttr	x5, [x1, #0]
	sttr	x6, [x1, #8]
	sttr	x7, [x1, #16]
	sttr	x8, [x1, #24]
	add	x1, x1, #32
	sub	x4, x4, #1
	cbnz	x4, .Lcopyout_loop
.Lcopyout_last_suboword:
	tbz	x2, #4, .Lcopyout_last_subqword
	ldp	x5, x6, [x0], #16
	sttr	x5, [x1, #0]
	sttr	x6, [x1, #8]
	add	x1, x1, #16
.Lcopyout_last_subqword:
	tbz	x2, #3, .Lcopyout_last_subdword
	ldr	x5, [x0], #8
	sttr	x5, [x1]
	add	x1, x1, #8
.Lcopyout_last_subdword:
	tbz	x2, #2, .Lcopyout_last_subword
	ldr	w5, [x0], #4
	sttr	w5, [x1]
	add	x1, x1, #4
.Lcopyout_last_subword:
	tbz	x2, #1, .Lcopyout_last_subhword
	ldrh	w5, [x0], #2
	sttrh	w5, [x1]
	add	x1, x1, #2
.Lcopyout_last_subhword:
	tbz	x2, #0, .Lcopyout_done
	ldrb	w5, [x0]
	sttrb	w5, [x1]
.Lcopyout_done:
	mov	x8, #0

	exit_cpu_onfault
	ret
END(copyout)


/* LINTSTUB: int copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done); */

ENTRY(copyinstr)
	enter_cpu_onfault

	mov	x8, #0			/* error = 0 */

	mov	x4, xzr			/* i = 0 */
	cbz	x2, copyinstr_done	/* if (len == 0) goto done */
copyinstr_loop:
	ldtrb	w5, [x0]		/* ch = *uaddr++ */
	add	x0, x0, #1
	strb	w5, [x1], #1		/* *kaddr++ = ch */
	add	x4, x4, #1		/* i++ */
	cbz	w5, copyinstr_done	/* if (ch == '\0') goto done */

	cmp	x4, x2			/* if (i < len) goto loop */
	bcc	copyinstr_loop
	mov	x8, #ENAMETOOLONG	/* error = ENAMETOOLONG */

copyinstr_done:
	cbz	x3, 1f			/* if (done != NULL) *done = i */
	str	x4, [x3]
1:
	exit_cpu_onfault
	ret
END(copyinstr)


/* LINTSTUB: int copyoutstr(const void *kaddr, void *uaddr, size_t len, size_t *done); */

ENTRY(copyoutstr)
	enter_cpu_onfault

	mov	x8, #0			/* error = 0 */

	mov	x4, xzr			/* i = 0 */
	cbz	x2, copyoutstr_done	/* if (len == 0) goto done */
copyoutstr_loop:
	ldrb	w5, [x0], #1		/* ch = *kaddr++ */
	sttrb	w5, [x1]		/* *uaddr++ = ch */
	add	x1, x1, #1
	add	x4, x4, #1		/* i++ */
	cbz	w5, copyoutstr_done	/* if (ch == '\0') goto done */

	cmp	x4, x2			/* if (i < len) goto loop */
	bcc	copyoutstr_loop
	mov	x8, #ENAMETOOLONG	/* error = ENAMETOOLONG */

copyoutstr_done:
	cbz	x3, 1f			/* if (done != NULL) *done = i */
	str	x4, [x3]
1:
	exit_cpu_onfault
	ret
END(copyoutstr)


/* LINTSTUB: int kcopy(const void *src, void *dst, size_t len); */

ENTRY(kcopy)
	enter_cpu_onfault

	mov	x3, x0			/* swap src and dst for memcpy */
	mov	x0, x1
	mov	x1, x3
	bl	_C_LABEL(memcpy)
	mov	x8, #0			/* error = 0 */

	exit_cpu_onfault
	ret
END(kcopy)


/* LINTSTUB: int _ucas_32(volatile uint32_t *uptr, uint32_t old, uint32_t new, uint32_t *retp); */
ENTRY(_ucas_32)
	tbnz	x0, AARCH64_ADDRTOP_TAG_BIT, 3f	/* not userspace */
	ands	x8, x0, #3
	cbnz	x8, 3f				/* not aligned */

	enter_cpu_onfault

1:	ldxr	w4, [x0]		/* load old value */
	cmp	w4, w1			/*   compare? */
	b.ne	2f			/*     return if different */
	stxr	w5, w2, [x0]		/* store new value */
	cbnz	w5, 1b			/*   succeed? nope, try again. */
2:	str	w4, [x3]
	mov	x8, #0			/* error = 0 */

	exit_cpu_onfault
	ret
3:
	mov	x0, #EFAULT
	ret
END(_ucas_32)

/* LINTSTUB: int _ucas_64(volatile uint64_t *uptr, uint64_t old, uint64_t new, uint64_t *retp); */
ENTRY(_ucas_64)
	tbnz	x0, AARCH64_ADDRTOP_TAG_BIT, 3f	/* not userspace */
	ands	x8, x0, #7
	cbnz	x8, 3f				/* not aligned */

	enter_cpu_onfault

1:	ldxr	x4, [x0]		/* load old value */
	cmp	x4, x1			/*   compare? */
	b.ne	2f			/*     return if different */
	stxr	w5, x2, [x0]		/* store new value */
	cbnz	w5, 1b			/*   succeed? nope, try again. */
2:	str	x4, [x3]
	mov	x8, #0			/* error = 0 */

	exit_cpu_onfault
	ret
3:
	mov	x0, #EFAULT
	ret
END(_ucas_64)
