root/arch/arm64/include/asm/gcs.h
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2023 ARM Ltd.
 */
#ifndef __ASM_GCS_H
#define __ASM_GCS_H

#include <asm/types.h>
#include <asm/uaccess.h>

struct kernel_clone_args;
struct ksignal;

static inline void gcsb_dsync(void)
{
        asm volatile(".inst 0xd503227f" : : : "memory");
}

static inline void gcsstr(u64 *addr, u64 val)
{
        register u64 *_addr __asm__ ("x0") = addr;
        register long _val __asm__ ("x1") = val;

        /* GCSSTTR x1, [x0] */
        asm volatile(
                ".inst 0xd91f1c01\n"
                :
                : "rZ" (_val), "r" (_addr)
                : "memory");
}

static inline void gcsss1(u64 Xt)
{
        asm volatile (
                "sys #3, C7, C7, #2, %0\n"
                :
                : "rZ" (Xt)
                : "memory");
}

static inline u64 gcsss2(void)
{
        u64 Xt;

        asm volatile(
                "SYSL %0, #3, C7, C7, #3\n"
                : "=r" (Xt)
                :
                : "memory");

        return Xt;
}

#define PR_SHADOW_STACK_SUPPORTED_STATUS_MASK \
        (PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE | PR_SHADOW_STACK_PUSH)

#ifdef CONFIG_ARM64_GCS

static inline bool task_gcs_el0_enabled(struct task_struct *task)
{
        return task->thread.gcs_el0_mode & PR_SHADOW_STACK_ENABLE;
}

void gcs_set_el0_mode(struct task_struct *task);
void gcs_free(struct task_struct *task);
void gcs_preserve_current_state(void);
unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
                                     const struct kernel_clone_args *args);

static inline int gcs_check_locked(struct task_struct *task,
                                   unsigned long new_val)
{
        unsigned long cur_val = task->thread.gcs_el0_mode;

        cur_val &= task->thread.gcs_el0_locked;
        new_val &= task->thread.gcs_el0_locked;

        if (cur_val != new_val)
                return -EBUSY;

        return 0;
}

static inline int gcssttr(unsigned long __user *addr, unsigned long val)
{
        register unsigned long __user *_addr __asm__ ("x0") = addr;
        register unsigned long _val __asm__ ("x1") = val;
        int err = 0;

        /* GCSSTTR x1, [x0] */
        asm volatile(
                "1: .inst 0xd91f1c01\n"
                "2: \n"
                _ASM_EXTABLE_UACCESS_ERR(1b, 2b, %w0)
                : "+r" (err)
                : "rZ" (_val), "r" (_addr)
                : "memory");

        return err;
}

static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
                                int *err)
{
        int ret;

        if (!access_ok((char __user *)addr, sizeof(u64))) {
                *err = -EFAULT;
                return;
        }

        uaccess_ttbr0_enable();
        ret = gcssttr(addr, val);
        if (ret != 0)
                *err = ret;
        uaccess_ttbr0_disable();
}

static inline void push_user_gcs(unsigned long val, int *err)
{
        u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);

        gcspr -= sizeof(u64);
        put_user_gcs(val, (unsigned long __user *)gcspr, err);
        if (!*err)
                write_sysreg_s(gcspr, SYS_GCSPR_EL0);
}

/*
 * Unlike put/push_user_gcs() above, get/pop_user_gsc() doesn't
 * validate the GCS permission is set on the page being read.  This
 * differs from how the hardware works when it consumes data stored at
 * GCSPR. Callers should ensure this is acceptable.
 */
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
{
        unsigned long ret;
        u64 load = 0;

        /* Ensure previous GCS operation are visible before we read the page */
        gcsb_dsync();
        ret = copy_from_user(&load, addr, sizeof(load));
        if (ret != 0)
                *err = ret;
        return load;
}

static inline u64 pop_user_gcs(int *err)
{
        u64 gcspr = read_sysreg_s(SYS_GCSPR_EL0);
        u64 read_val;

        read_val = get_user_gcs((__force unsigned long __user *)gcspr, err);
        if (!*err)
                write_sysreg_s(gcspr + sizeof(u64), SYS_GCSPR_EL0);

        return read_val;
}

#else

static inline bool task_gcs_el0_enabled(struct task_struct *task)
{
        return false;
}

static inline void gcs_set_el0_mode(struct task_struct *task) { }
static inline void gcs_free(struct task_struct *task) { }
static inline void gcs_preserve_current_state(void) { }
static inline void put_user_gcs(unsigned long val, unsigned long __user *addr,
                                int *err) { }
static inline void push_user_gcs(unsigned long val, int *err) { }

static inline unsigned long gcs_alloc_thread_stack(struct task_struct *tsk,
                                                   const struct kernel_clone_args *args)
{
        return -ENOTSUPP;
}
static inline int gcs_check_locked(struct task_struct *task,
                                   unsigned long new_val)
{
        return 0;
}
static inline u64 get_user_gcs(unsigned long __user *addr, int *err)
{
        *err = -EFAULT;
        return 0;
}
static inline u64 pop_user_gcs(int *err)
{
        return 0;
}

#endif

#endif