The branch main has been updated by andrew: URL: https://cgit.FreeBSD.org/src/commit/?id=6748ac4ee7ce1e6e6888293cf855fed4eb734126
commit 6748ac4ee7ce1e6e6888293cf855fed4eb734126 Author: Sarah Walker <[email protected]> AuthorDate: 2026-01-02 16:27:43 +0000 Commit: Andrew Turner <[email protected]> CommitDate: 2026-01-06 10:36:36 +0000 arm64: Add Arm SPE thread mode support Add support for the using the Arm Statistical Profiling Extension (SPE) in the hardware tracing framework, hwt(4), thread mode. SPE is an optional extension added in Armv8.1 to provide profiling of software using randomised instruction sampling. Thread mode allows tracing of a single thread, regardless of the CPU it is scheduled on. Reviewed by: andrew Sponsored by: Arm Ltd Differential Revision: https://reviews.freebsd.org/D53738 --- sys/arm64/spe/arm_spe_backend.c | 302 +++++++++++++++++++++++++++++++++------- sys/arm64/spe/arm_spe_dev.c | 7 +- sys/arm64/spe/arm_spe_dev.h | 4 +- 3 files changed, 261 insertions(+), 52 deletions(-) diff --git a/sys/arm64/spe/arm_spe_backend.c b/sys/arm64/spe/arm_spe_backend.c index b4e1132f9cbc..c8d7de8f0c8c 100644 --- a/sys/arm64/spe/arm_spe_backend.c +++ b/sys/arm64/spe/arm_spe_backend.c @@ -91,11 +91,13 @@ #include <sys/module.h> #include <sys/mutex.h> #include <sys/proc.h> +#include <sys/queue.h> #include <sys/rman.h> #include <sys/rwlock.h> #include <sys/smp.h> #include <sys/sysctl.h> #include <sys/systm.h> +#include <sys/taskqueue.h> #include <machine/bus.h> @@ -123,13 +125,12 @@ static struct hwt_backend backend = { .kva_req = 1, }; -static struct arm_spe_info *spe_info; +/* Pointers to current info structure per CPU. This points to either a per-CPU + * structure (for CPU mode) or a per-thread structure (for thread mode). + */ +static struct arm_spe_info **spe_info; -static int -spe_backend_init_thread(struct hwt_context *ctx) -{ - return (ENOTSUP); -} +static struct arm_spe_info *spe_info_cpu; static void spe_backend_init_cpu(struct hwt_context *ctx) @@ -140,13 +141,12 @@ spe_backend_init_cpu(struct hwt_context *ctx) char *tmp = "Arm SPE lock/cpu/"; int cpu_id; - spe_info = malloc(sizeof(struct arm_spe_info) * mp_ncpus, + spe_info_cpu = malloc(sizeof(struct arm_spe_info) * mp_ncpus, M_ARM_SPE, M_WAITOK | M_ZERO); - sc->spe_info = spe_info; CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { - info = &spe_info[cpu_id]; + info = &spe_info_cpu[cpu_id]; info->sc = sc; info->ident = cpu_id; info->buf_info[0].info = info; @@ -155,6 +155,8 @@ spe_backend_init_cpu(struct hwt_context *ctx) info->buf_info[1].buf_idx = 1; snprintf(lock_name, sizeof(lock_name), "%s%d", tmp, cpu_id); mtx_init(&info->lock, lock_name, NULL, MTX_SPIN); + + spe_info[cpu_id] = info; } } @@ -183,9 +185,11 @@ spe_backend_init(struct hwt_context *ctx) sc->kqueue_fd = ctx->kqueue_fd; sc->hwt_td = ctx->hwt_td; - if (ctx->mode == HWT_MODE_THREAD) - error = spe_backend_init_thread(ctx); - else + spe_info = malloc(sizeof(struct arm_spe_info *) * mp_ncpus, + M_ARM_SPE, M_WAITOK | M_ZERO); + sc->spe_info = spe_info; + + if (ctx->mode == HWT_MODE_CPU) spe_backend_init_cpu(ctx); return (error); @@ -218,19 +222,30 @@ spe_backend_deinit(struct hwt_context *ctx) { #ifdef ARM_SPE_DEBUG struct arm_spe_info *info; + struct hwt_thread *thr; int cpu_id; - CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { - info = &spe_info[cpu_id]; - hex_dump((void *)info->kvaddr, 128); - hex_dump((void *)(info->kvaddr + (info->buf_size/2)), 128); + if (ctx->mode == HWT_MODE_CPU) { + CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { + info = &spe_info_cpu[cpu_id]; + printf("CPU %u:\n", cpu_id); + hex_dump((void *)info->kvaddr, 128); + hex_dump((void *)(info->kvaddr + (info->buf_size/2)), 128); + } + } else { + TAILQ_FOREACH(thr, &ctx->threads, next) { + info = (struct arm_spe_info *)thr->private; + printf("TID %u:\n", thr->thread_id); + hex_dump((void *)info->kvaddr, 128); + hex_dump((void *)(info->kvaddr + (info->buf_size/2)), 128); + } } #endif - if (ctx->state == CTX_STATE_RUNNING) { - spe_backend_disable_smp(ctx); - ctx->state = CTX_STATE_STOPPED; - } + spe_backend_disable_smp(ctx); + + if (ctx->mode == HWT_MODE_CPU) + free(spe_info_cpu, M_ARM_SPE); free(spe_info, M_ARM_SPE); @@ -279,14 +294,31 @@ arm_spe_set_interval(struct arm_spe_info *info, uint64_t interval) } static int -spe_backend_configure(struct hwt_context *ctx, int cpu_id, int session_id) +spe_backend_configure(struct hwt_context *ctx, int cpu_id, int thread_id) { - struct arm_spe_info *info = &spe_info[cpu_id]; + struct arm_spe_info *info = NULL; struct arm_spe_config *cfg; + struct hwt_thread *thr = NULL; int err = 0; + if (ctx->mode == HWT_MODE_CPU) + info = &spe_info_cpu[cpu_id]; + else { + TAILQ_FOREACH(thr, &ctx->threads, next) { + if (thr->thread_id != thread_id) + continue; + info = (struct arm_spe_info *)thr->private; + break; + } + if (info == NULL) + return (ENOENT); + } + mtx_lock_spin(&info->lock); - info->ident = cpu_id; + if (ctx->mode == HWT_MODE_CPU) + info->ident = cpu_id; + else + info->ident = thread_id; /* Set defaults */ info->pmsfcr = 0; info->pmsevfr = 0xFFFFFFFFFFFFFFFFUL; @@ -311,6 +343,13 @@ spe_backend_configure(struct hwt_context *ctx, int cpu_id, int session_id) info->ctx_field = cfg->ctx_field; } else err = (EINVAL); + + if (ctx->mode == HWT_MODE_THREAD) { + info->kvaddr = thr->vm->kvaddr; + info->buf_size = ctx->bufsize; + } + + spe_info[cpu_id] = info; mtx_unlock_spin(&info->lock); return (err); @@ -320,13 +359,20 @@ spe_backend_configure(struct hwt_context *ctx, int cpu_id, int session_id) static void arm_spe_enable(void *arg __unused) { - struct arm_spe_info *info = &spe_info[PCPU_GET(cpuid)]; + struct arm_spe_info *info = spe_info[PCPU_GET(cpuid)]; + struct arm_spe_buf_info *buf = &info->buf_info[info->buf_idx]; + struct hwt_context *ctx = info->sc->ctx; uint64_t base, limit; dprintf("%s on cpu:%d\n", __func__, PCPU_GET(cpuid)); mtx_lock_spin(&info->lock); + if (info->stopped) { + mtx_unlock_spin(&info->lock); + return; + } + if (info->ctx_field == ARM_SPE_CTX_CPU_ID) WRITE_SPECIALREG(CONTEXTIDR_EL1_REG, PCPU_GET(cpuid)); @@ -342,13 +388,19 @@ arm_spe_enable(void *arg __unused) WRITE_SPECIALREG(PMSICR_EL1_REG, info->pmsicr); isb(); - base = info->kvaddr; + base = buf_start_addr(info->buf_idx, info); limit = base + (info->buf_size/2); /* Enable the buffer */ limit &= PMBLIMITR_LIMIT_MASK; /* Zero lower 12 bits */ limit |= PMBLIMITR_E; - /* Set the base and limit */ - WRITE_SPECIALREG(PMBPTR_EL1_REG, base); + /* Set the base and limit. Restore base pointer if sampling has previously + * been enabled for this thread. + */ + if (buf->pmbptr == 0) { + WRITE_SPECIALREG(PMBPTR_EL1_REG, base); + } else { + WRITE_SPECIALREG(PMBPTR_EL1_REG, buf->pmbptr); + } WRITE_SPECIALREG(PMBLIMITR_EL1_REG, limit); isb(); @@ -358,6 +410,9 @@ arm_spe_enable(void *arg __unused) info->enabled = true; + if (ctx->mode == HWT_MODE_THREAD) + CPU_SET(PCPU_GET(cpuid), &ctx->cpu_map); + mtx_unlock_spin(&info->lock); } @@ -368,11 +423,13 @@ spe_backend_enable_smp(struct hwt_context *ctx) struct hwt_vm *vm; int cpu_id; + KASSERT(ctx->mode == HWT_MODE_CPU, ("%s: should only be called for CPU mode", __func__)); + HWT_CTX_LOCK(ctx); CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { vm = hwt_cpu_get(ctx, cpu_id)->vm; - - info = &spe_info[cpu_id]; + KASSERT(spe_info[cpu_id] == &spe_info_cpu[cpu_id], ("%s: spe_info mismatch for cpu_id=%u", __func__, cpu_id)); + info = &spe_info_cpu[cpu_id]; mtx_lock_spin(&info->lock); info->kvaddr = vm->kvaddr; @@ -382,7 +439,8 @@ spe_backend_enable_smp(struct hwt_context *ctx) HWT_CTX_UNLOCK(ctx); cpu_id = CPU_FFS(&ctx->cpu_map) - 1; - info = &spe_info[cpu_id]; + KASSERT(spe_info[cpu_id] == &spe_info_cpu[cpu_id], ("%s: spe_info mismatch for cpu_id=%u", __func__, cpu_id)); + info = spe_info[cpu_id]; if (info->ctx_field == ARM_SPE_CTX_PID) arm64_pid_in_contextidr = true; else @@ -394,11 +452,12 @@ spe_backend_enable_smp(struct hwt_context *ctx) return (0); } -void -arm_spe_disable(void *arg __unused) +static void +arm_spe_disable_nolock(void) { - struct arm_spe_info *info = &spe_info[PCPU_GET(cpuid)]; + struct arm_spe_info *info = spe_info[PCPU_GET(cpuid)]; struct arm_spe_buf_info *buf = &info->buf_info[info->buf_idx]; + struct hwt_context *ctx = info->sc->ctx; if (!info->enabled) return; @@ -423,9 +482,20 @@ arm_spe_disable(void *arg __unused) /* Clear PID/CPU_ID from context ID reg */ WRITE_SPECIALREG(CONTEXTIDR_EL1_REG, 0); - mtx_lock_spin(&info->lock); buf->pmbptr = READ_SPECIALREG(PMBPTR_EL1_REG); info->enabled = false; + + if (ctx->mode == HWT_MODE_THREAD) + CPU_CLR(PCPU_GET(cpuid), &ctx->cpu_map); +} + +void +arm_spe_disable(void *arg __unused) +{ + struct arm_spe_info *info = spe_info[PCPU_GET(cpuid)]; + + mtx_lock_spin(&info->lock); + arm_spe_disable_nolock(); mtx_unlock_spin(&info->lock); } @@ -438,14 +508,16 @@ spe_backend_disable_smp(struct hwt_context *ctx) int cpu_id; int ret; - /* Disable and send out remaining data in bufs */ - smp_rendezvous_cpus(ctx->cpu_map, smp_no_rendezvous_barrier, - arm_spe_disable, smp_no_rendezvous_barrier, NULL); + if (!CPU_EMPTY(&ctx->cpu_map)) { + /* Disable and send out remaining data in bufs */ + smp_rendezvous_cpus(ctx->cpu_map, smp_no_rendezvous_barrier, + arm_spe_disable, smp_no_rendezvous_barrier, NULL); - CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { - info = &spe_info[cpu_id]; - buf = &info->buf_info[info->buf_idx]; - arm_spe_send_buffer(buf, 0); + CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) { + info = spe_info[cpu_id]; + buf = &info->buf_info[info->buf_idx]; + arm_spe_send_buffer(buf, 0); + } } arm64_pid_in_contextidr = false; @@ -462,16 +534,97 @@ spe_backend_disable_smp(struct hwt_context *ctx) return (0); } +static void +spe_backend_enable(struct hwt_context *ctx, int cpu_id) +{ + struct arm_spe_info *info; + + if (ctx->mode == HWT_MODE_CPU) + return; + KASSERT(curcpu == cpu_id, ("%s: attempting to enable SPE on another cpu", __func__)); + + info = spe_info[cpu_id]; + + KASSERT(info != NULL, ("%s: info=NULL", __func__)); + + if (info->ctx_field == ARM_SPE_CTX_PID) + arm64_pid_in_contextidr = true; + else + arm64_pid_in_contextidr = false; + + arm_spe_enable(NULL); +} + +static void +spe_backend_disable(struct hwt_context *ctx, int cpu_id) +{ + struct arm_spe_info *info = spe_info[PCPU_GET(cpuid)]; + + if (ctx->mode == HWT_MODE_CPU) + return; + + KASSERT(curcpu == cpu_id, ("%s: attempting to disable SPE on another cpu", __func__)); + + mtx_lock_spin(&info->lock); + + if (!info->stopped) + arm_spe_disable_nolock(); + + mtx_unlock_spin(&info->lock); +} + +static void +arm_spe_flush(void *arg, int pending __unused) +{ + struct arm_spe_info *info = arg; + struct arm_spe_buf_info *buf = &info->buf_info[info->buf_idx]; + + arm_spe_send_buffer(buf, 0); +} + static void spe_backend_stop(struct hwt_context *ctx) { + struct arm_spe_info *info; + struct hwt_thread *thr; + + HWT_CTX_LOCK(ctx); + + if (ctx->mode == HWT_MODE_THREAD) { + ctx->state = CTX_STATE_STOPPED; + + TAILQ_FOREACH(thr, &ctx->threads, next) { + info = (struct arm_spe_info *)thr->private; + + mtx_lock_spin(&info->lock); + + info->stopped = true; + + if (!info->enabled) { + /* Not currently tracing. Enqueue buffer for sending */ + TASK_INIT(&info->flush_task, 0, (task_fn_t *)arm_spe_flush, info); + taskqueue_enqueue(taskqueue_arm_spe, &info->flush_task); + } + /* Otherwise tracing currently active. As this thread has been + * marked as stopped, buffer will be sent on next disable + */ + + mtx_unlock_spin(&info->lock); + } + + } + + HWT_CTX_UNLOCK(ctx); + + taskqueue_drain_all(taskqueue_arm_spe); + spe_backend_disable_smp(ctx); } static void arm_spe_reenable(void *arg __unused) { - struct arm_spe_info *info = &spe_info[PCPU_GET(cpuid)];; + struct arm_spe_info *info = spe_info[PCPU_GET(cpuid)]; WRITE_SPECIALREG(PMSCR_EL1_REG, info->pmscr); isb(); @@ -481,9 +634,10 @@ static int spe_backend_svc_buf(struct hwt_context *ctx, void *data, size_t data_size, int data_version) { - struct arm_spe_info *info; + struct arm_spe_info *info = NULL; struct arm_spe_buf_info *buf; struct arm_spe_svc_buf *s; + struct hwt_thread *thr; int err = 0; cpuset_t cpu_set; @@ -496,15 +650,29 @@ spe_backend_svc_buf(struct hwt_context *ctx, void *data, size_t data_size, s = (struct arm_spe_svc_buf *)data; if (s->buf_idx > 1) return (ENODEV); - if (s->ident >= mp_ncpus) - return (EINVAL); - info = &spe_info[s->ident]; + if (ctx->mode == HWT_MODE_CPU) { + if (s->ident >= mp_ncpus) + return (EINVAL); + + info = spe_info[s->ident]; + } else { + TAILQ_FOREACH(thr, &ctx->threads, next) { + if (thr->thread_id != s->ident) + continue; + info = (struct arm_spe_info *)thr->private; + break; + } + + if (info == NULL) + return (ENOENT); + } + mtx_lock_spin(&info->lock); buf = &info->buf_info[s->buf_idx]; - if (!info->enabled) { + if (!info->enabled && ctx->mode == HWT_MODE_CPU) { err = ENXIO; goto end; } @@ -513,7 +681,7 @@ spe_backend_svc_buf(struct hwt_context *ctx, void *data, size_t data_size, buf->buf_svc = false; /* Re-enable profiling if we've been waiting for this notification */ - if (buf->buf_wait) { + if (buf->buf_wait && !info->stopped) { CPU_SETOF(s->ident, &cpu_set); mtx_unlock_spin(&info->lock); @@ -563,6 +731,38 @@ error: return (0); } +static int +spe_backend_thread_alloc(struct hwt_thread *thr) +{ + struct arm_spe_softc *sc = device_get_softc(spe_dev); + char lock_name[32]; + struct arm_spe_info *info; + + info = malloc(sizeof(*info), M_ARM_SPE, M_WAITOK | M_ZERO); + + info->sc = sc; + info->buf_info[0].info = info; + info->buf_info[0].buf_idx = 0; + info->buf_info[1].info = info; + info->buf_info[1].buf_idx = 1; + snprintf(lock_name, sizeof(lock_name), "Arm SPE lock/thr/%d", thr->thread_id); + mtx_init(&info->lock, lock_name, NULL, MTX_SPIN); + + thr->private = info; + + return (0); +} + +static void +spe_backend_thread_free(struct hwt_thread *thr) +{ + struct arm_spe_info *info; + + info = (struct arm_spe_info *)thr->private; + + free(info, M_ARM_SPE); +} + static struct hwt_backend_ops spe_ops = { .hwt_backend_init = spe_backend_init, .hwt_backend_deinit = spe_backend_deinit, @@ -571,10 +771,16 @@ static struct hwt_backend_ops spe_ops = { .hwt_backend_svc_buf = spe_backend_svc_buf, .hwt_backend_stop = spe_backend_stop, + .hwt_backend_enable = spe_backend_enable, + .hwt_backend_disable = spe_backend_disable, + .hwt_backend_enable_smp = spe_backend_enable_smp, .hwt_backend_disable_smp = spe_backend_disable_smp, .hwt_backend_read = spe_backend_read, + + .hwt_backend_thread_alloc = spe_backend_thread_alloc, + .hwt_backend_thread_free = spe_backend_thread_free, }; int diff --git a/sys/arm64/spe/arm_spe_dev.c b/sys/arm64/spe/arm_spe_dev.c index 8a834197eeef..61c0b5cc16c4 100644 --- a/sys/arm64/spe/arm_spe_dev.c +++ b/sys/arm64/spe/arm_spe_dev.c @@ -131,7 +131,7 @@ arm_spe_intr(void *arg) uint64_t pmbsr; uint64_t base, limit; uint8_t ec; - struct arm_spe_info *info = &sc->spe_info[cpu_id]; + struct arm_spe_info *info = sc->spe_info[cpu_id]; uint8_t i = info->buf_idx; struct arm_spe_buf_info *buf = &info->buf_info[i]; struct arm_spe_buf_info *prev_buf = &info->buf_info[!i]; @@ -311,8 +311,9 @@ arm_spe_error(void *arg, int pending __unused) struct kevent kev; int ret; - smp_rendezvous_cpus(ctx->cpu_map, smp_no_rendezvous_barrier, - arm_spe_disable, smp_no_rendezvous_barrier, NULL); + if (!CPU_EMPTY(&ctx->cpu_map)) + smp_rendezvous_cpus(ctx->cpu_map, smp_no_rendezvous_barrier, + arm_spe_disable, smp_no_rendezvous_barrier, NULL); EV_SET(&kev, ARM_SPE_KQ_SHUTDOWN, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); ret = kqfd_register(ctx->kqueue_fd, &kev, ctx->hwt_td, M_WAITOK); diff --git a/sys/arm64/spe/arm_spe_dev.h b/sys/arm64/spe/arm_spe_dev.h index df88d98ef1c0..ed1727b5b090 100644 --- a/sys/arm64/spe/arm_spe_dev.h +++ b/sys/arm64/spe/arm_spe_dev.h @@ -80,7 +80,7 @@ struct arm_spe_softc { int64_t sc_pmsidr; int kqueue_fd; struct thread *hwt_td; - struct arm_spe_info *spe_info; + struct arm_spe_info **spe_info; struct hwt_context *ctx; STAILQ_HEAD(, arm_spe_queue) pending; uint64_t npending; @@ -105,7 +105,9 @@ struct arm_spe_info { struct mtx lock; struct arm_spe_softc *sc; struct task task[2]; + struct task flush_task; bool enabled : 1; + bool stopped : 1; /* buffer is split in half as a ping-pong buffer */ vm_object_t bufobj;
