root/sys/arch/aarch64/aarch64/copyinout.S
/* $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)