git: aab60068943d - main - arm64: Support SVE in ptrace and core dumps

From: Andrew Turner <andrew_at_FreeBSD.org>
Date: Mon, 30 Sep 2024 12:23:39 UTC
The branch main has been updated by andrew:

URL: https://cgit.FreeBSD.org/src/commit/?id=aab60068943d733b0b4573e5481c543ab3d45a00

commit aab60068943d733b0b4573e5481c543ab3d45a00
Author:     Andrew Turner <andrew@FreeBSD.org>
AuthorDate: 2024-09-27 13:37:17 +0000
Commit:     Andrew Turner <andrew@FreeBSD.org>
CommitDate: 2024-09-30 12:04:23 +0000

    arm64: Support SVE in ptrace and core dumps
    
    Add the NT_ARM_SVE note type and use it to access the SVE registers
    from ptrace. This allows userspace to modify the full SVE register
    values.
    
    Try to follow the Linux semantics to allow debuggers to use this with
    minimal changes.
    
    Sponsored by:   Arm Ltd
    Differential Revision:  https://reviews.freebsd.org/D43309
---
 sys/arm64/arm64/vfp.c   | 141 ++++++++++++++++++++++++++++++++++++++++++++++++
 sys/arm64/include/reg.h |  13 +++++
 sys/sys/elf_common.h    |   1 +
 3 files changed, 155 insertions(+)

diff --git a/sys/arm64/arm64/vfp.c b/sys/arm64/arm64/vfp.c
index d57927991c03..a3aa77ed6180 100644
--- a/sys/arm64/arm64/vfp.c
+++ b/sys/arm64/arm64/vfp.c
@@ -30,12 +30,14 @@
 #ifdef VFP
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/elf.h>
 #include <sys/eventhandler.h>
 #include <sys/limits.h>
 #include <sys/kernel.h>
 #include <sys/malloc.h>
 #include <sys/pcpu.h>
 #include <sys/proc.h>
+#include <sys/reg.h>
 #include <sys/smp.h>
 
 #include <vm/uma.h>
@@ -918,6 +920,145 @@ sve_init(const void *dummy __unused)
 }
 SYSINIT(sve, SI_SUB_SMP, SI_ORDER_ANY, sve_init, NULL);
 
+static bool
+get_arm64_sve(struct regset *rs, struct thread *td, void *buf,
+    size_t *sizep)
+{
+	struct svereg_header *header;
+	struct pcb *pcb;
+	size_t buf_size;
+	uint16_t sve_flags;
+
+	pcb = td->td_pcb;
+
+	/* If there is no SVE support in HW then we don't support NT_ARM_SVE */
+	if (pcb->pcb_sve_len == 0)
+		return (false);
+
+	sve_flags = 0;
+	if ((pcb->pcb_fpflags & PCB_FP_SVEVALID) == 0) {
+		/* If SVE hasn't been used yet provide the VFP registers */
+		buf_size = sizeof(struct fpreg);
+		sve_flags |= SVEREG_FLAG_FP;
+	} else {
+		/* We have SVE registers */
+		buf_size = sve_buf_size(td);
+		sve_flags |= SVEREG_FLAG_SVE;
+		KASSERT(pcb->pcb_svesaved != NULL, ("%s: no saved sve",
+		    __func__));
+	}
+
+	if (buf != NULL) {
+		KASSERT(*sizep == sizeof(struct svereg_header) + buf_size,
+		    ("%s: invalid size", __func__));
+
+		if (td == curthread && (pcb->pcb_fpflags & PCB_FP_STARTED) != 0)
+			vfp_save_state(td, pcb);
+
+		header = buf;
+		memset(header, 0, sizeof(*header));
+
+		header->sve_size = sizeof(struct svereg_header) + buf_size;
+		header->sve_maxsize = sizeof(struct svereg_header) +
+		    sve_max_buf_size();
+		header->sve_vec_len = pcb->pcb_sve_len;
+		header->sve_max_vec_len = sve_max_vector_len;
+		header->sve_flags = sve_flags;
+
+		if ((sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_FP) {
+			struct fpreg *fpregs;
+
+			fpregs = (void *)(&header[1]);
+			memcpy(fpregs->fp_q, pcb->pcb_fpustate.vfp_regs,
+			    sizeof(fpregs->fp_q));
+			fpregs->fp_cr = pcb->pcb_fpustate.vfp_fpcr;
+			fpregs->fp_sr = pcb->pcb_fpustate.vfp_fpsr;
+		} else {
+			memcpy((void *)(&header[1]), pcb->pcb_svesaved,
+			    buf_size);
+		}
+	}
+	*sizep = sizeof(struct svereg_header) + buf_size;
+
+	return (true);
+}
+
+static bool
+set_arm64_sve(struct regset *rs, struct thread *td, void *buf, size_t size)
+{
+	struct svereg_header *header;
+	struct pcb *pcb;
+	size_t buf_size;
+	uint16_t sve_flags;
+
+	pcb = td->td_pcb;
+
+	/* If there is no SVE support in HW then we don't support NT_ARM_SVE */
+	if (pcb->pcb_sve_len == 0)
+		return (false);
+
+	sve_flags = 0;
+	if ((pcb->pcb_fpflags & PCB_FP_SVEVALID) == 0) {
+		/*
+		 * If the SVE state is invalid it provide the FP registers.
+		 * This may be beause it hasn't been used, or it has but
+		 * was switched out in a system call.
+		 */
+		buf_size = sizeof(struct fpreg);
+		sve_flags |= SVEREG_FLAG_FP;
+	} else {
+		/* We have SVE registers */
+		MPASS(pcb->pcb_svesaved != NULL);
+		buf_size = sve_buf_size(td);
+		sve_flags |= SVEREG_FLAG_SVE;
+		KASSERT(pcb->pcb_svesaved != NULL, ("%s: no saved sve",
+		    __func__));
+	}
+
+	if (size != sizeof(struct svereg_header) + buf_size)
+		return (false);
+
+	header = buf;
+	/* Sanity checks on the header */
+	if (header->sve_size != sizeof(struct svereg_header) + buf_size)
+		return (false);
+
+	if (header->sve_maxsize != sizeof(struct svereg_header) +
+	    sve_max_buf_size())
+		return (false);
+
+	if (header->sve_vec_len != pcb->pcb_sve_len)
+		return (false);
+
+	if (header->sve_max_vec_len != sve_max_vector_len)
+		return (false);
+
+	if (header->sve_flags != sve_flags)
+		return (false);
+
+	if ((sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_FP) {
+		struct fpreg *fpregs;
+
+		fpregs = (void *)(&header[1]);
+		memcpy(pcb->pcb_fpustate.vfp_regs, fpregs->fp_q,
+		    sizeof(fpregs->fp_q));
+		pcb->pcb_fpustate.vfp_fpcr = fpregs->fp_cr;
+		pcb->pcb_fpustate.vfp_fpsr = fpregs->fp_sr;
+	} else {
+		/* Restore the SVE registers */
+		memcpy(pcb->pcb_svesaved, (void *)(&header[1]), buf_size);
+	}
+
+	return (true);
+}
+
+static struct regset regset_arm64_sve = {
+	.note = NT_ARM_SVE,
+	.get = get_arm64_sve,
+	.set = set_arm64_sve,
+};
+ELF_REGSET(regset_arm64_sve);
+
 struct fpu_kern_ctx *
 fpu_kern_alloc_ctx(u_int flags)
 {
diff --git a/sys/arm64/include/reg.h b/sys/arm64/include/reg.h
index c699752197a8..4226385480e8 100644
--- a/sys/arm64/include/reg.h
+++ b/sys/arm64/include/reg.h
@@ -63,6 +63,19 @@ struct fpreg32 {
 	int dummy;
 };
 
+#define	SVEREG_FLAG_REGS_MASK	0x0001
+#define	SVEREG_FLAG_FP		0x0000
+#define	SVEREG_FLAG_SVE		0x0001
+
+struct svereg_header {
+	__uint32_t	sve_size;
+	__uint32_t	sve_maxsize;
+	__uint16_t	sve_vec_len;
+	__uint16_t	sve_max_vec_len;
+	__uint16_t	sve_flags;
+	__uint16_t	sve_reserved;
+};
+
 struct dbreg {
 	__uint8_t	db_debug_ver;
 	__uint8_t	db_nbkpts;
diff --git a/sys/sys/elf_common.h b/sys/sys/elf_common.h
index 6cb92d1011fe..ec5bbbf9f0e6 100644
--- a/sys/sys/elf_common.h
+++ b/sys/sys/elf_common.h
@@ -826,6 +826,7 @@ typedef struct {
 #define	NT_X86_XSTATE	0x202	/* x86 XSAVE extended state. */
 #define	NT_ARM_VFP	0x400	/* ARM VFP registers */
 #define	NT_ARM_TLS	0x401	/* ARM TLS register */
+#define	NT_ARM_SVE	0x405	/* ARM SVE registers */
 #define	NT_ARM_ADDR_MASK	0x406	/* arm64 address mask (e.g. for TBI) */
 
 /* GNU note types. */