/* ptrace.c COPYRIGHT FUJITSU LIMITED 2016-2017 */
#include <errno.h>
#include <debug-monitors.h>
#include <hw_breakpoint.h>
#include <elfcore.h>
#include <fpsimd.h>
#include <kmalloc.h>
#include <memory.h>
#include <uio.h>
#include <lwk/compiler.h>
#include <hwcap.h>
#include <string.h>
#include <thread_info.h>

//#define DEBUG_PRINT_SC

#ifdef DEBUG_PRINT_SC
#define dkprintf kprintf
#define ekprintf(...) do { if (0) kprintf(__VA_ARGS__); } while (0)
#else
#define dkprintf(...) do { if (0) kprintf(__VA_ARGS__); } while (0)
#define ekprintf(...) do { if (0) kprintf(__VA_ARGS__); } while (0)
#endif

#define NOT_IMPLEMENTED()  do { kprintf("%s is not implemented\n", __func__); while(1);} while(0)

#define BUG_ON(condition) do { if (condition) { kprintf("PANIC: %s: %s(line:%d)\n",\
				__FILE__, __FUNCTION__, __LINE__); panic(""); } } while(0)

extern void save_debugreg(unsigned long *debugreg);
extern unsigned long do_kill(struct thread *thread, int pid, int tid, int sig, struct siginfo *info, int ptracecont);
extern int interrupt_from_user(void *);

enum aarch64_regset {
	REGSET_GPR,
	REGSET_FPR,
	REGSET_TLS,
	REGSET_HW_BREAK,
	REGSET_HW_WATCH,
	REGSET_SYSTEM_CALL,
#ifdef CONFIG_ARM64_SVE
	REGSET_SVE,
#endif /* CONFIG_ARM64_SVE */
};

struct user_regset;
typedef long user_regset_get_fn(struct thread *target,
				const struct user_regset *regset,
				unsigned int pos, unsigned int count,
				void *kbuf, void __user *ubuf);

typedef long user_regset_set_fn(struct thread *target,
				const struct user_regset *regset,
				unsigned int pos, unsigned int count,
				const void *kbuf, const void __user *ubuf);

struct user_regset {
	user_regset_get_fn *get;
	user_regset_set_fn *set;
	unsigned int n;
	unsigned int size;
	unsigned int core_note_type;
};

long ptrace_read_user(struct thread *thread, long addr, unsigned long *value)
{
	return -EIO;
}

long ptrace_write_user(struct thread *thread, long addr, unsigned long value)
{
	return -EIO;
}

long ptrace_read_fpregs(struct thread *thread, void *fpregs)
{
	return -EIO;
}

long ptrace_write_fpregs(struct thread *thread, void *fpregs)
{
	return -EIO;
}

/* @ref.impl arch/arm64/kernel/ptrace.c::ptrace_hbp_get_resource_info */
unsigned int ptrace_hbp_get_resource_info(unsigned int note_type)
{
	unsigned char num;
	unsigned int reg = 0;

	switch (note_type) {
	case NT_ARM_HW_BREAK:
		num = hw_breakpoint_slots(TYPE_INST);
		break;
	case NT_ARM_HW_WATCH:
		num = hw_breakpoint_slots(TYPE_DATA);
		break;
	default:
		return -EINVAL;
	}

	reg |= debug_monitors_arch();
	reg <<= 8;
	reg |= num;

	return reg;
}

/* @ref.impl include/linux/regset.h::user_regset_copyout */
static inline long user_regset_copyout(unsigned int *pos, unsigned int *count,
				       void **kbuf,
				       void __user **ubuf, const void *data,
				       const int start_pos, const int end_pos)
{
	if (*count == 0) {
		return 0;
	}

	BUG_ON(*pos < start_pos);
	if (end_pos < 0 || *pos < end_pos) {
		unsigned int copy = 0;

		if ((end_pos < 0) || (*count < (end_pos - *pos))) {
			copy = *count;
		} else {
			copy = (end_pos - *pos);
		}

		data += *pos - start_pos;
		if (*kbuf) {
			memcpy(*kbuf, data, copy);
			*kbuf += copy;
		} else if (copy_to_user(*ubuf, data, copy)) {
			return -EFAULT;
		} else {
			*ubuf += copy;
		}
		*pos += copy;
		*count -= copy;
	}
	return 0;
}

/* @ref.impl include/linux/regset.h::user_regset_copyin */
static inline long user_regset_copyin(unsigned int *pos, unsigned int *count,
				      const void **kbuf,
				      const void __user **ubuf, void *data,
				      const int start_pos, const int end_pos)
{
	if (*count == 0) {
		return 0;
	}

	BUG_ON(*pos < start_pos);
	if (end_pos < 0 || *pos < end_pos) {
		unsigned int copy = 0;

		if ((end_pos < 0) || (*count < (end_pos - *pos))) {
			copy = *count;
		} else {
			copy = (end_pos - *pos);
		}

		data += *pos - start_pos;
		if (*kbuf) {
			memcpy(data, *kbuf, copy);
			*kbuf += copy;
		} else if (copy_from_user(data, *ubuf, copy)) {
			return -EFAULT;
		} else {
			*ubuf += copy;
		}
		*pos += copy;
		*count -= copy;
	}
	return 0;
}

/* @ref.impl include/linux/regset.h::user_regset_copyout_zero */
static inline long user_regset_copyout_zero(unsigned int *pos,
					    unsigned int *count,
					    void **kbuf, void __user **ubuf,
					    const int start_pos,
					    const int end_pos)
{
	if (*count == 0) {
		return 0;
	}

	BUG_ON(*pos < start_pos);
	if (end_pos < 0 || *pos < end_pos) {
		unsigned int copy = 0;
		char *tmp = NULL;

		if ((end_pos < 0) || (*count < (end_pos - *pos))) {
			copy = *count;
		} else {
			copy = (end_pos - *pos);
		}

		if (*kbuf) {
			memset(*kbuf, 0, copy);
			*kbuf += copy;
		} else {
			tmp = kmalloc(copy, IHK_MC_AP_NOWAIT);
			if (tmp == NULL) {
				return -ENOMEM;
			}
			memset(tmp, 0, copy);

			if (copy_to_user(*ubuf, tmp, copy)) {
				kfree(tmp);
				return -EFAULT;
			} else {
				*ubuf += copy;
			}
			kfree(tmp);
		}
		*pos += copy;
		*count -= copy;
	}
	return 0;
}

/* @ref.impl include/linux/regset.h::user_regset_copyin_ignore */
static inline int user_regset_copyin_ignore(unsigned int *pos,
					    unsigned int *count,
					    const void **kbuf,
					    const void __user **ubuf,
					    const int start_pos,
					    const int end_pos)
{
	if (*count == 0) {
		return 0;
	}

	BUG_ON(*pos < start_pos);
	if (end_pos < 0 || *pos < end_pos) {
		unsigned int copy = 0;

		if ((end_pos < 0) || (*count < (end_pos - *pos))) {
			copy = *count;
		} else {
			copy = (end_pos - *pos);
		}

		if (*kbuf) {
			*kbuf += copy;
		} else {
			*ubuf += copy;
		}
		*pos += copy;
		*count -= copy;
	}
	return 0;
}

/* @ref.impl include/linux/regset.h::copy_regset_to_user */
static inline long copy_regset_to_user(struct thread *target,
				       const struct user_regset *regset,
				       unsigned int offset, unsigned int size,
				       void __user *data)
{
	if (!regset->get) {
		return -EOPNOTSUPP;
	}
	return regset->get(target, regset, offset, size, NULL, data);
}

/* @ref.impl include/linux/regset.h::copy_regset_from_user */
static inline long copy_regset_from_user(struct thread *target,
					 const struct user_regset *regset,
					 unsigned int offset, unsigned int size,
					 const void __user *data)
{
	if (!regset->set) {
		return -EOPNOTSUPP;
	}
	return regset->set(target, regset, offset, size, NULL, data);
}

/*
 * Bits which are always architecturally RES0 per ARM DDI 0487A.h
 * Userspace cannot use these until they have an architectural meaning.
 * We also reserve IL for the kernel; SS is handled dynamically.
 */
#define SPSR_EL1_AARCH64_RES0_BITS	0xffffffff0fdffc20UL

static int valid_native_regs(struct user_pt_regs *regs)
{
	regs->pstate &= ~SPSR_EL1_AARCH64_RES0_BITS;

	if (interrupt_from_user(regs) && !(regs->pstate & PSR_MODE32_BIT) &&
	    (regs->pstate & PSR_D_BIT) == 0 &&
	    (regs->pstate & PSR_A_BIT) == 0 &&
	    (regs->pstate & PSR_I_BIT) == 0 &&
	    (regs->pstate & PSR_F_BIT) == 0) {
		return 1;
	}

	/* Force PSR to a valid 64-bit EL0t */
	regs->pstate &= PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT;

	return 0;
}

static int valid_user_regs(struct user_pt_regs *regs, struct thread *thread)
{
	if (!(thread->ctx.thread->flags & TIF_SINGLESTEP)) {
		regs->pstate &= ~DBG_SPSR_SS;
	}
	return valid_native_regs(regs);
}

/* read NT_PRSTATUS */
static long gpr_get(struct thread *target, const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    void *kbuf, void __user *ubuf)
{
	struct user_pt_regs *uregs = &target->uctx->user_regs;
	return user_regset_copyout(&pos, &count, &kbuf, &ubuf, uregs, 0, -1);
}

/* write NT_PRSTATUS */
static long gpr_set(struct thread *target, const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    const void *kbuf, const void __user *ubuf)
{
	long ret;
	struct user_pt_regs newregs = target->uctx->user_regs;

	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &newregs, 0, -1);
	if (ret) {
		goto out;
	}

	if (!valid_user_regs(&newregs, target)) {
		ret = -EINVAL;
		goto out;
	}
	target->uctx->user_regs = newregs;
out:
	return ret;
}

/* read NT_PRFPREG */
static long fpr_get(struct thread *target,
		    const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    void *kbuf, void __user *ubuf)
{
	long ret = -EINVAL;

	if (target->fp_regs == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	if (likely(elf_hwcap & (HWCAP_FP | HWCAP_ASIMD))) {
		struct user_fpsimd_state *uregs;

		if (likely(elf_hwcap & HWCAP_SVE)) {
			/* sync to sve --> fpsimd */
			thread_sve_to_fpsimd(target, target->fp_regs);
		}

		uregs = &target->fp_regs->user_fpsimd;
		ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, uregs, 0, -1);
	}
out:
	return ret;
}

/* write NT_PRFPREG */
static long __fpr_set(struct thread *target,
		      const struct user_regset *regset,
		      unsigned int pos, unsigned int count,
		      const void *kbuf, const void __user *ubuf,
		      unsigned int start_pos)
{
	long ret = -EINVAL;

	if (target->fp_regs == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	if (likely(elf_hwcap & (HWCAP_FP | HWCAP_ASIMD))) {
		struct user_fpsimd_state newstate;

		if (likely(elf_hwcap & HWCAP_SVE)) {
			/* sync to sve --> fpsimd */
			thread_sve_to_fpsimd(target, target->fp_regs);
		}

		newstate = target->fp_regs->user_fpsimd;
		ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &newstate,
					 start_pos, start_pos + sizeof(newstate));
		if (ret) {
			goto out;
		}

		target->fp_regs->user_fpsimd = newstate;

		if (likely(elf_hwcap & HWCAP_SVE)) {
			/* sync to fpsimd --> sve */
			thread_fpsimd_to_sve(target, target->fp_regs);
		}
	}
out:
	return ret;
}

/* write NT_PRFPREG */
static long fpr_set(struct thread *target, const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    const void *kbuf, const void __user *ubuf)
{
	return __fpr_set(target, regset, pos, count, kbuf, ubuf, 0);
}

/* read NT_ARM_TLS */
static long tls_get(struct thread *target, const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    void *kbuf, void __user *ubuf)
{
	unsigned long *tls = &target->tlsblock_base;
	return user_regset_copyout(&pos, &count, &kbuf, &ubuf, tls, 0, -1);
}

/* write NT_ARM_TLS */
static long tls_set(struct thread *target, const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    const void *kbuf, const void __user *ubuf)
{
	long ret;
	unsigned long tls = target->tlsblock_base;

	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
	if (ret) {
		goto out;
	}

	target->tlsblock_base = tls;
out:
	return ret;
}

/* read NT_ARM_SYSTEM_CALL */
static long system_call_get(struct thread *target,
			    const struct user_regset *regset,
			    unsigned int pos, unsigned int count,
			    void *kbuf, void __user *ubuf)
{
	int syscallno = target->uctx->syscallno;

	return user_regset_copyout(&pos, &count, &kbuf, &ubuf,
				   &syscallno, 0, -1);
}

/* write NT_ARM_SYSTEM_CALL */
static long system_call_set(struct thread *target,
			    const struct user_regset *regset,
			    unsigned int pos, unsigned int count,
			    const void *kbuf, const void __user *ubuf)
{
	int syscallno = target->uctx->syscallno;
	long ret;

	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &syscallno, 0, -1);
	if (ret) {
		goto out;
	}

	target->uctx->syscallno = syscallno;
out:
	return ret;
}

#define PTRACE_HBP_ADDR_SZ	sizeof(uint64_t)
#define PTRACE_HBP_CTRL_SZ	sizeof(uint32_t)
#define PTRACE_HBP_PAD_SZ	sizeof(uint32_t)

/* read NT_ARM_HW_BREAK or NT_ARM_HW_WATCH */
static long hw_break_get(struct thread *target,
			 const struct user_regset *regset,
			 unsigned int pos, unsigned int count,
			 void *kbuf, void __user *ubuf)
{
	unsigned int note_type = regset->core_note_type;
	long ret = -EINVAL;
	int idx = 0, offset, limit, bw;
	uint32_t info, ctrl;
	uint64_t addr;
	struct user_hwdebug_state *hws = NULL;

	if (note_type != NT_ARM_HW_BREAK &&
	    note_type != NT_ARM_HW_WATCH) {
		goto out;
	}

	if (target->ptrace_debugreg == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	bw = (note_type == NT_ARM_HW_BREAK ? HWS_BREAK : HWS_WATCH);
	hws = (struct user_hwdebug_state *)target->ptrace_debugreg + bw;

	/* Resource info */
	info = hws->dbg_info;
	ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &info, 0,
				  sizeof(info));
	if (ret) {
		goto out;
	}

	/* Pad */
	offset = offsetof(struct user_hwdebug_state, pad);
	ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, offset,
				       offset + PTRACE_HBP_PAD_SZ);
	if (ret) {
		goto out;
	}

	/* (address, ctrl) registers */
	offset = offsetof(struct user_hwdebug_state, dbg_regs);
	limit = regset->n * regset->size;
	while (count && offset < limit) {
		addr = hws->dbg_regs[idx].addr;
		ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &addr,
					  offset, offset + PTRACE_HBP_ADDR_SZ);
		if (ret) {
			goto out;
		}
		offset += PTRACE_HBP_ADDR_SZ;

		ctrl = hws->dbg_regs[idx].ctrl;
		ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &ctrl,
					  offset, offset + PTRACE_HBP_CTRL_SZ);
		if (ret) {
			goto out;
		}
		offset += PTRACE_HBP_CTRL_SZ;

		ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
					       offset,
					       offset + PTRACE_HBP_PAD_SZ);
		if (ret) {
			goto out;
		}
		offset += PTRACE_HBP_PAD_SZ;
		idx++;
	}
out:
	return ret;
}

/* write NT_ARM_HW_BREAK or NT_ARM_HW_WATCH */
static long hw_break_set(struct thread *target,
			 const struct user_regset *regset,
			 unsigned int pos, unsigned int count,
			 const void *kbuf, const void __user *ubuf)
{
	unsigned int note_type = regset->core_note_type;
	long ret = -EINVAL;
	int idx = 0, offset, limit, bw;
	uint32_t ctrl;
	uint64_t addr;
	struct user_hwdebug_state *hws = NULL;

	if (note_type != NT_ARM_HW_BREAK &&
	    note_type != NT_ARM_HW_WATCH) {
		goto out;
	}

	if (target->ptrace_debugreg == NULL) {
		ret = -ENOMEM;
		goto out;
	}
	bw = (note_type == NT_ARM_HW_BREAK ? HWS_BREAK : HWS_WATCH);
	hws = (struct user_hwdebug_state *)target->ptrace_debugreg + bw;

	/* Resource info and pad */
	offset = offsetof(struct user_hwdebug_state, dbg_regs);
	ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, offset);
	if (ret) {
		goto out;
	}

	/* (address, ctrl) registers */
	limit = regset->n * regset->size;
	while (count && offset < limit) {
		if (count < PTRACE_HBP_ADDR_SZ) {
			ret = -EINVAL;
			goto out;
		}

		ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr,
					 offset, offset + PTRACE_HBP_ADDR_SZ);
		if (ret) {
			goto out;
		}
		hws->dbg_regs[idx].addr = addr;
		offset += PTRACE_HBP_ADDR_SZ;

		if (!count) {
			break;
		}

		ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl,
					 offset, offset + PTRACE_HBP_CTRL_SZ);
		if (ret) {
			goto out;
		}
		hws->dbg_regs[idx].ctrl = ctrl;
		offset += PTRACE_HBP_CTRL_SZ;

		ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
						offset,
						offset + PTRACE_HBP_PAD_SZ);
		if (ret) {
			goto out;
		}
		offset += PTRACE_HBP_PAD_SZ;
		idx++;
	}
out:
	return ret;
}

#ifdef CONFIG_ARM64_SVE

/* read NT_ARM_SVE */
static long sve_get(struct thread *target,
		    const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    void *kbuf, void __user *ubuf)
{
	long ret = -EINVAL;
	struct user_sve_header header;
	unsigned int vq;
	unsigned long start, end;

	if (target->fp_regs == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	/* Instead of system_supports_sve() */
	if (unlikely(!(elf_hwcap & HWCAP_SVE))) {
		goto out;
	}

	/* Header */
	memset(&header, 0, sizeof(header));

	header.vl = target->ctx.thread->sve_vl;

	BUG_ON(!sve_vl_valid(header.vl));
	vq = sve_vq_from_vl(header.vl);

	BUG_ON(!sve_vl_valid(sve_max_vl));
	header.max_vl = sve_max_vl;

	/* McKernel processes always enable SVE. */
	header.flags = SVE_PT_REGS_SVE;

	header.size = SVE_PT_SIZE(vq, header.flags);
	header.max_size = SVE_PT_SIZE(sve_vq_from_vl(header.max_vl),
				      SVE_PT_REGS_SVE);

	ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &header,
				  0, sizeof(header));
	if (ret) {
		goto out;
	}

	/* Registers: FPSIMD-only case */
	/*
	 * If McKernel, Nothing to do.
	 * Because McKernel processes always enable SVE.
	 */

	/* Otherwise: full SVE case */
	start = SVE_PT_SVE_OFFSET;
	end = SVE_PT_SVE_FFR_OFFSET(vq) + SVE_PT_SVE_FFR_SIZE(vq);

	BUG_ON(end < start);
	BUG_ON(end - start > sve_state_size(target));
	ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
				  target->ctx.thread->sve_state,
				  start, end);
	if (ret) {
		goto out;
	}

	start = end;
	end = SVE_PT_SVE_FPSR_OFFSET(vq);

	BUG_ON(end < start);
	ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
				       start, end);
	if (ret) {
		goto out;
	}

	start = end;
	end = SVE_PT_SVE_FPCR_OFFSET(vq) + SVE_PT_SVE_FPCR_SIZE;

	BUG_ON((char *)(&target->fp_regs->fpcr + 1) <
	       (char *)&target->fp_regs->fpsr);
	BUG_ON(end < start);
	BUG_ON((char *)(&target->fp_regs->fpcr + 1) -
	       (char *)&target->fp_regs->fpsr !=
	        end - start);

	ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
				  &target->fp_regs->fpsr,
				  start, end);
	if (ret) {
		goto out;
	}

	start = end;
	end = (SVE_PT_SIZE(SVE_VQ_MAX, SVE_PT_REGS_SVE) + 15) / 16 * 16;

	BUG_ON(end < start);
	ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
				       start, end);
out:
	return ret;
}

/* write NT_ARM_SVE case */
static long sve_set(struct thread *target,
		    const struct user_regset *regset,
		    unsigned int pos, unsigned int count,
		    const void *kbuf, const void __user *ubuf)
{
	long ret = -EINVAL;
	struct user_sve_header header;
	unsigned int vq;
	unsigned long start, end;

	if (target->fp_regs == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	/* Instead of system_supports_sve() */
	if (unlikely(!(elf_hwcap & HWCAP_SVE))) {
		goto out;
	}

	/* Header */
	if (count < sizeof(header)) {
		goto out;
	}

	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &header,
				 0, sizeof(header));
	if (ret) {
		goto out;
	}

	/*
	 * Apart from PT_SVE_REGS_MASK, all PT_SVE_* flags are consumed by
	 * sve_set_vector_length(), which will also validate them for us:
	 */
	ret = sve_set_vector_length(target, header.vl,
				    header.flags & ~SVE_PT_REGS_MASK);
	if (ret) {
		goto out;
	}

	/* Actual VL set may be less than the user asked for: */
	BUG_ON(!sve_vl_valid(target->ctx.thread->sve_vl));
	vq = sve_vq_from_vl(target->ctx.thread->sve_vl);

	/* Registers: FPSIMD-only case */
	if ((header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD) {
		ret = __fpr_set(target, regset, pos, count, kbuf, ubuf,
				SVE_PT_FPSIMD_OFFSET);
		goto out;
	}

	/* Otherwise: full SVE case */
	start = SVE_PT_SVE_OFFSET;
	end = SVE_PT_SVE_FFR_OFFSET(vq) + SVE_PT_SVE_FFR_SIZE(vq);

	BUG_ON(end < start);
	BUG_ON(end - start > sve_state_size(target));
	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
				 target->ctx.thread->sve_state,
				 start, end);
	if (ret) {
		goto out;
	}

	start = end;
	end = SVE_PT_SVE_FPSR_OFFSET(vq);

	BUG_ON(end < start);
	ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
					start, end);
	if (ret) {
		goto out;
	}

	start = end;
	end = SVE_PT_SVE_FPCR_OFFSET(vq) + SVE_PT_SVE_FPCR_SIZE;

	BUG_ON((char *)(&target->fp_regs->fpcr + 1) <
		(char *)&target->fp_regs->fpsr);
	BUG_ON(end < start);
	BUG_ON((char *)(&target->fp_regs->fpcr + 1) -
		(char *)&target->fp_regs->fpsr !=
		 end - start);

	user_regset_copyin(&pos, &count, &kbuf, &ubuf,
			   &target->fp_regs->fpsr,
			   start, end);
out:
	return ret;
}

#endif /* CONFIG_ARM64_SVE */

static const struct user_regset aarch64_regsets[] = {
	[REGSET_GPR] = {
		.core_note_type = NT_PRSTATUS,
		.n = sizeof(struct user_pt_regs) / sizeof(uint64_t),
		.size = sizeof(uint64_t),
		.get = gpr_get,
		.set = gpr_set
	},
	[REGSET_FPR] = {
		.core_note_type = NT_PRFPREG,
		.n = sizeof(struct user_fpsimd_state) / sizeof(uint32_t),
		/*
		 * We pretend we have 32-bit registers because the fpsr and
		 * fpcr are 32-bits wide.
		 */
		.size = sizeof(uint32_t),
		.get = fpr_get,
		.set = fpr_set
	},
	[REGSET_TLS] = {
		.core_note_type = NT_ARM_TLS,
		.n = 1,
		.size = sizeof(void *),
		.get = tls_get,
		.set = tls_set
	},
	[REGSET_HW_BREAK] = {
		.core_note_type = NT_ARM_HW_BREAK,
		.n = sizeof(struct user_hwdebug_state) / sizeof(uint32_t),
		.size = sizeof(uint32_t),
		.get = hw_break_get,
		.set = hw_break_set
	},
	[REGSET_HW_WATCH] = {
		.core_note_type = NT_ARM_HW_WATCH,
		.n = sizeof(struct user_hwdebug_state) / sizeof(uint32_t),
		.size = sizeof(uint32_t),
		.get = hw_break_get,
		.set = hw_break_set
	},
	[REGSET_SYSTEM_CALL] = {
		.core_note_type = NT_ARM_SYSTEM_CALL,
		.n = 1,
		.size = sizeof(int),
		.get = system_call_get,
		.set = system_call_set
	},
#ifdef CONFIG_ARM64_SVE
	[REGSET_SVE] = { /* Scalable Vector Extension */
		.core_note_type = NT_ARM_SVE,
		.n = (SVE_PT_SIZE(SVE_VQ_MAX, SVE_PT_REGS_SVE) + 15) / 16,
		.size = 16,
		.get = sve_get,
		.set = sve_set
	},
#endif /* CONFIG_ARM64_SVE */
};

static const struct user_regset *
find_regset(const struct user_regset *regset, unsigned int type, int n)
{
	int i = 0;

	for (i = 0; i < n; i++) {
		if (regset[i].core_note_type == type) {
			return &regset[i];
		}
	}
	return NULL;
}

static long ptrace_regset(struct thread *thread, int req, long type, struct iovec *iov)
{
	long rc = -EINVAL;
	const struct user_regset *regset = find_regset(aarch64_regsets, type,
					sizeof(aarch64_regsets) / sizeof(aarch64_regsets[0]));

	if (!regset) {
		kprintf("%s: not supported type 0x%x\n", __FUNCTION__, type);
		goto out;
	}

	if ((iov->iov_len % regset->size) != 0) {
		goto out;
	}

	if ((size_t)(regset->n * regset->size) < iov->iov_len) {
		iov->iov_len = (size_t)(regset->n * regset->size);
	}

	if (req == PTRACE_GETREGSET) {
		rc = copy_regset_to_user(thread, regset, 0,
					 iov->iov_len, iov->iov_base);
	} else {
		rc = copy_regset_from_user(thread, regset, 0,
					   iov->iov_len, iov->iov_base);
	}
out:
	return rc;
}

long ptrace_read_regset(struct thread *thread, long type, struct iovec *iov)
{
	return ptrace_regset(thread, PTRACE_GETREGSET, type, iov);
}

long ptrace_write_regset(struct thread *thread, long type, struct iovec *iov)
{
	return ptrace_regset(thread, PTRACE_SETREGSET, type, iov);
}

void ptrace_report_signal(struct thread *thread, int sig)
{
	struct mcs_rwlock_node_irqsave lock;
	struct process *proc = thread->proc;
	int parent_pid;
	siginfo_t info;
	struct thread_info tinfo;

	dkprintf("ptrace_report_signal, tid=%d, pid=%d\n", thread->tid, thread->proc->pid);

	/* save thread_info, if called by ptrace_report_exec() */
	if (sig == ((SIGTRAP | (PTRACE_EVENT_EXEC << 8)))) {
		memcpy(&tinfo, thread->ctx.thread, sizeof(struct thread_info));
	}

	mcs_rwlock_writer_lock(&proc->update_lock, &lock);
	if(!(proc->ptrace & PT_TRACED)){
		mcs_rwlock_writer_unlock(&proc->update_lock, &lock);
		return;
	}
	thread->exit_status = sig;
	/* Transition thread state */
#ifdef POSTK_DEBUG_TEMP_FIX_41 /* early to wait4() wakeup for ptrace, fix. */
	proc->status = PS_DELAY_TRACED;
#else /* POSTK_DEBUG_TEMP_FIX_41 */
	proc->status = PS_TRACED;
#endif /* POSTK_DEBUG_TEMP_FIX_41 */
	thread->status = PS_TRACED;
	proc->ptrace &= ~PT_TRACE_SYSCALL;
	if (sig == SIGSTOP || sig == SIGTSTP ||
			sig == SIGTTIN || sig == SIGTTOU) {
		proc->signal_flags |= SIGNAL_STOP_STOPPED;
	} else {
		proc->signal_flags &= ~SIGNAL_STOP_STOPPED;
	}
	parent_pid = proc->parent->pid;
	save_debugreg(thread->ptrace_debugreg);
	mcs_rwlock_writer_unlock(&proc->update_lock, &lock);

	memset(&info, '\0', sizeof info);
	info.si_signo = SIGCHLD;
	info.si_code = CLD_TRAPPED;
	info._sifields._sigchld.si_pid = thread->tid;
	info._sifields._sigchld.si_status = thread->exit_status;
	do_kill(cpu_local_var(current), parent_pid, -1, SIGCHLD, &info, 0);
#ifndef POSTK_DEBUG_TEMP_FIX_41 /* early to wait4() wakeup for ptrace, fix. */
	/* Wake parent (if sleeping in wait4()) */
	waitq_wakeup(&proc->parent->waitpid_q);
#endif /* !POSTK_DEBUG_TEMP_FIX_41 */

	dkprintf("ptrace_report_signal,sleeping\n");
	/* Sleep */
	schedule();
	dkprintf("ptrace_report_signal,wake up\n");

	/* restore thread_info, if called by ptrace_report_exec() */
	if (sig == ((SIGTRAP | (PTRACE_EVENT_EXEC << 8)))) {
		memcpy(thread->ctx.thread, &tinfo, sizeof(struct thread_info));
	}
}

long
arch_ptrace(long request, int pid, long addr, long data)
{
	return -EIO;
}

