Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F168786786
D58535.id183036.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
33 KB
Referenced Files
None
Subscribers
None
D58535.id183036.diff
View Options
diff --git a/sys/arm64/brbe/arm_brbe.h b/sys/arm64/brbe/arm_brbe.h
new file mode 100644
--- /dev/null
+++ b/sys/arm64/brbe/arm_brbe.h
@@ -0,0 +1,100 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Arm Ltd
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef _ARM64_ARM_BRBE_H_
+#define _ARM64_ARM_BRBE_H_
+
+/* kqueue events */
+#define ARM_BRBE_KQ_BUF 138
+#define ARM_BRBE_KQ_SHUTDOWN 139
+#define ARM_BRBE_KQ_SIGNAL 140
+
+/* spe_backend_read() u64 data encoding */
+#define KQ_BUF_POS_SHIFT 0
+#define KQ_BUF_POS (1 << KQ_BUF_POS_SHIFT)
+#define KQ_PARTREC_SHIFT 1
+#define KQ_PARTREC (1 << KQ_PARTREC_SHIFT)
+#define KQ_FINAL_BUF_SHIFT 2
+#define KQ_FINAL_BUF (1 << KQ_FINAL_BUF_SHIFT)
+
+enum arm_brbe_ctx_field {
+ ARM_BRBE_CTX_NONE,
+ ARM_BRBE_CTX_PID,
+ ARM_BRBE_CTX_CPU_ID
+};
+enum arm_brbe_profiling_level {
+ ARM_BRBE_KERNEL_AND_USER,
+ ARM_BRBE_KERNEL_ONLY,
+ ARM_BRBE_USER_ONLY
+};
+struct arm_brbe_config {
+ uint32_t enable_direct_branch:1;
+ uint32_t enable_indirect_branch:1;
+ uint32_t enable_function_return:1;
+ uint32_t enable_direct_branch_link:1;
+ uint32_t enable_indirect_branch_link:1;
+ uint32_t enable_conditional_branch:1;
+ uint32_t enable_exception_to_el1:1;
+ uint32_t enable_return_from_el1:1;
+ uint32_t enable_mispredict_info:1;
+ uint32_t enable_cycle_count_info:1;
+
+ /* Profile kernel (EL1), userspace (EL0) or both */
+ enum arm_brbe_profiling_level level;
+ /*
+ * Configure context field in SPE records to store either the
+ * current PID, the CPU ID or neither
+ *
+ * In PID mode, kernel threads without a process context are
+ * logged as PID 0
+ */
+ enum arm_brbe_ctx_field ctx_field;
+};
+
+#define BRBE_MAX_RECORDS 64
+
+struct arm_brbe_record {
+ uint64_t inf;
+ uint64_t src;
+ uint64_t tgt;
+};
+
+struct arm_brbe_dump {
+ uint64_t ts;
+ uint32_t context_id;
+ int count;
+ struct arm_brbe_record records[BRBE_MAX_RECORDS];
+};
+
+struct arm_brbe_svc_buf {
+ uint32_t ident;
+ uint8_t buf_idx:1;
+};
+
+int brbe_get_dump(void);
+
+#endif /* _ARM64_ARM_BRBE_H_ */
diff --git a/sys/arm64/brbe/arm_brbe_backend.c b/sys/arm64/brbe/arm_brbe_backend.c
new file mode 100644
--- /dev/null
+++ b/sys/arm64/brbe/arm_brbe_backend.c
@@ -0,0 +1,565 @@
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/hwt.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mman.h>
+#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>
+
+#include <arm64/brbe/arm_brbe_dev.h>
+#include <arm64/brbe/arm_brbe_reg.h>
+
+#include <dev/hwt/hwt_vm.h>
+#include <dev/hwt/hwt_backend.h>
+#include <dev/hwt/hwt_config.h>
+#include <dev/hwt/hwt_context.h>
+#include <dev/hwt/hwt_cpu.h>
+#include <dev/hwt/hwt_thread.h>
+
+MALLOC_DECLARE(M_ARM_BRBE);
+
+static struct hwt_backend_ops brbe_ops;
+static struct hwt_backend backend = {
+ .ops = &brbe_ops,
+ .name = "brbe",
+ .kva_req = 1,
+};
+
+static int brbe_backend_disable_smp(struct hwt_context *ctx);
+
+/*
+ * 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).
+ */
+struct arm_brbe_info **brbe_info = NULL;
+static struct arm_brbe_info *brbe_info_cpu;
+
+static struct arm_brbe_softc brbe_sc;
+
+static void
+brbe_invalidate(void)
+{
+ /* Ensure all branches before this point are recorded */
+ isb();
+ SYS(BRB_IALL);
+
+ /* Ensure all branch records are invalidated after this point */
+ isb();
+}
+
+static void
+brbe_backend_init_cpu(struct hwt_context *ctx)
+{
+ struct arm_brbe_info *info;
+ struct arm_brbe_softc *sc = &brbe_sc;
+ char lock_name[32];
+ char *tmp = "Arm BRBE lock/cpu/";
+ int cpu_id;
+
+ brbe_info_cpu = malloc(sizeof(struct arm_brbe_info) * mp_ncpus,
+ M_ARM_BRBE, M_WAITOK | M_ZERO);
+
+ CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) {
+ info = &brbe_info_cpu[cpu_id];
+ info->sc = sc;
+ info->ident = cpu_id;
+ info->buf_info[0].buf_idx = 0;
+ info->buf_info[0].info = info;
+ info->buf_info[1].buf_idx = 1;
+ info->buf_info[1].info = info;
+ snprintf(lock_name, sizeof(lock_name), "%s%d", tmp, cpu_id);
+ mtx_init(&info->lock, lock_name, NULL, MTX_SPIN);
+
+ brbe_info[cpu_id] = info;
+ }
+}
+
+static int
+brbe_backend_init(struct hwt_context *ctx)
+{
+ struct arm_brbe_softc *sc = &brbe_sc;
+ int error = 0;
+
+ brbe_info = malloc(sizeof(struct arm_brbpe_info *) * mp_ncpus,
+ M_ARM_BRBE, M_WAITOK | M_ZERO);
+ sc->brbe_info = brbe_info;
+
+ sc->kqueue_fd = ctx->kqueue_fd;
+ sc->hwt_td = ctx->hwt_td;
+ sc->ctx = ctx;
+
+ if (ctx->mode == HWT_MODE_CPU)
+ brbe_backend_init_cpu(ctx);
+
+ return (error);
+}
+
+static int
+brbe_backend_deinit(struct hwt_context *ctx)
+{
+ brbe_backend_disable_smp(ctx);
+
+ if (ctx->mode == HWT_MODE_CPU)
+ free(brbe_info_cpu, M_ARM_BRBE);
+
+ free(brbe_info, M_ARM_BRBE);
+ brbe_info = NULL;
+
+ return (0);
+}
+
+static int
+brbe_backend_configure(struct hwt_context *ctx, int cpu_id, int thread_id)
+{
+ struct arm_brbe_info *info = NULL;
+ struct hwt_thread *hwt_td = NULL;
+ struct thread *td = NULL;
+
+ struct arm_brbe_config *cfg;
+ int error = 0;
+
+ if (ctx->mode == HWT_MODE_CPU) {
+ info = &brbe_info_cpu[cpu_id];
+ td = PCPU_GET(curthread);
+ } else {
+ TAILQ_FOREACH(hwt_td, &ctx->threads, next) {
+ if (hwt_td->thread_id != thread_id)
+ continue;
+ info = (struct arm_brbe_info *)hwt_td->private;
+ td = hwt_td->td;
+ break;
+ }
+ if (info == NULL)
+ return (ENOENT);
+ }
+
+ mtx_lock_spin(&info->lock);
+
+ if (ctx->mode == HWT_MODE_CPU)
+ info->ident = cpu_id;
+ else
+ info->ident = thread_id;
+
+ info->cr = BRBCR_TS_PHYSICAL | BRBCR_FZP_MASK;
+ info->fcr = BRBFCR_BANK_0_31;
+ info->ctx_field = ARM_BRBE_CTX_NONE;
+
+ if (ctx->config != NULL &&
+ ctx->config_size == sizeof(struct arm_brbe_config) &&
+ ctx->config_version == 1) {
+ cfg = (struct arm_brbe_config *)ctx->config;
+
+ if (cfg->level == ARM_BRBE_KERNEL_AND_USER ||
+ cfg->level == ARM_BRBE_USER_ONLY)
+ info->cr |= BRBCR_E0BRE_MASK;
+ if (cfg->level == ARM_BRBE_KERNEL_AND_USER ||
+ cfg->level == ARM_BRBE_KERNEL_ONLY)
+ info->cr |= BRBCR_E1BRE_MASK;
+
+ if (cfg->enable_exception_to_el1 != 0)
+ info->cr |= BRBCR_EXCEPTION_MASK;
+ if (cfg->enable_return_from_el1 != 0)
+ info->cr |= BRBCR_ERTN_MASK;
+ if (cfg->enable_mispredict_info != 0)
+ info->cr |= BRBCR_MPRED_MASK;
+ if (cfg->enable_cycle_count_info != 0)
+ info->cr |= BRBCR_CC_MASK;
+
+ if (cfg->enable_direct_branch != 0)
+ info->fcr |= BRBFCR_DIRECT_MASK;
+ if (cfg->enable_indirect_branch != 0)
+ info->fcr |= BRBFCR_INDIRECT_MASK;
+ if (cfg->enable_function_return != 0)
+ info->fcr |= BRBFCR_RTN_MASK;
+ if (cfg->enable_direct_branch_link != 0)
+ info->fcr |= BRBFCR_DIRCALL_MASK;
+ if (cfg->enable_indirect_branch_link != 0)
+ info->fcr |= BRBFCR_INDCALL_MASK;
+ if (cfg->enable_conditional_branch != 0)
+ info->fcr |= BRBFCR_CONDDIR_MASK;
+
+ info->ctx_field = cfg->ctx_field;
+ } else
+ error = EINVAL;
+
+ if (ctx->mode == HWT_MODE_THREAD) {
+ info->buf_info[0].kvaddr = hwt_td->vm->kvaddr;
+ info->buf_info[1].kvaddr =
+ (struct arm_brbe_dump *)((uintptr_t)hwt_td->vm->kvaddr +
+ (ctx->bufsize / 2));
+ info->buf_size = ctx->bufsize;
+ info->record_count = (ctx->bufsize / 2) /
+ sizeof(struct arm_brbe_dump);
+ }
+
+ if (info->ctx_field == ARM_BRBE_CTX_PID) {
+ if (td && td->td_proc)
+ info->context_id = td->td_proc->p_pid;
+ else
+ info->context_id = 0;
+ } else if (info->ctx_field == ARM_BRBE_CTX_CPU_ID)
+ info->context_id = cpu_id;
+ else
+ info->context_id = 0;
+
+ brbe_info[cpu_id] = info;
+
+ mtx_unlock_spin(&info->lock);
+
+ return (error);
+}
+
+static int
+brbe_backend_svc_buf(struct hwt_context *ctx, void *data, size_t data_size,
+ int data_version)
+{
+ struct arm_brbe_info *info = NULL;
+ struct arm_brbe_svc_buf *s;
+ struct hwt_thread *thr;
+
+ if (data_size != sizeof(struct arm_brbe_svc_buf))
+ return (E2BIG);
+
+ if (data_version != 1)
+ return (EINVAL);
+
+ s = (struct arm_brbe_svc_buf *)data;
+ if (s->buf_idx > 1)
+ return (ENODEV);
+
+ if (ctx->mode == HWT_MODE_CPU) {
+ if (s->ident >= mp_ncpus)
+ return (EINVAL);
+ info = brbe_info[s->ident];
+ } else {
+ TAILQ_FOREACH(thr, &ctx->threads, next) {
+ if (thr->thread_id != s->ident)
+ continue;
+ info = (struct arm_brbe_info *)thr->private;
+ break;
+ }
+ if (info == NULL)
+ return (ENOENT);
+ }
+
+ mtx_lock_spin(&info->lock);
+
+ if (!info->buf_info[s->buf_idx].pending) {
+ mtx_unlock_spin(&info->lock);
+ return (EINVAL);
+ }
+
+ info->buf_info[s->buf_idx].pending = 0;
+ mtx_unlock_spin(&info->lock);
+
+ return (0);
+}
+
+
+static void
+arm_brbe_flush(void *arg, int pending __unused)
+{
+ struct arm_brbe_info *info = arg;
+ struct arm_brbe_buf_info *buf = &info->buf_info[info->buffer_wr_idx];
+
+ arm_brbe_send_buffer(buf, 0);
+}
+
+static void
+brbe_backend_stop(struct hwt_context *ctx)
+{
+ struct arm_brbe_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_brbe_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_brbe_flush, info);
+ taskqueue_enqueue(taskqueue_arm_brbe,
+ &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_brbe);
+
+ brbe_backend_disable_smp(ctx);
+}
+
+static void
+arm_brbe_enable(void *arg __unused)
+{
+ struct arm_brbe_info *info = brbe_info[PCPU_GET(cpuid)];
+ struct hwt_context *ctx;
+
+ ctx = info->sc->ctx;
+
+ mtx_lock_spin(&info->lock);
+
+ brbe_invalidate();
+
+ WRITE_SPECIALREG(BRBCR_EL1_REG, info->cr);
+
+ isb();
+
+ WRITE_SPECIALREG(BRBFCR_EL1_REG, info->fcr);
+
+ info->enabled = true;
+
+ if (ctx->mode == HWT_MODE_THREAD)
+ CPU_SET(PCPU_GET(cpuid), &ctx->cpu_map);
+
+ mtx_unlock_spin(&info->lock);
+}
+
+static void
+arm_brbe_disable(void *arg __unused)
+{
+ struct arm_brbe_info *info = brbe_info[PCPU_GET(cpuid)];
+ struct hwt_context *ctx = info->sc->ctx;
+
+ mtx_lock_spin(&info->lock);
+
+ WRITE_SPECIALREG(BRBFCR_EL1_REG, BRBFCR_PAUSED_MASK);
+ WRITE_SPECIALREG(BRBCR_EL1_REG, 0);
+ isb();
+
+ info->enabled = false;
+
+ if (ctx->mode == HWT_MODE_THREAD)
+ CPU_CLR(PCPU_GET(cpuid), &ctx->cpu_map);
+
+ mtx_unlock_spin(&info->lock);
+}
+
+static void
+brbe_backend_enable(struct hwt_context *ctx, int cpu_id)
+{
+ struct arm_brbe_info *info;
+
+ if (ctx->mode == HWT_MODE_CPU)
+ return;
+
+ KASSERT(curcpu == cpu_id,
+ ("%s: attempting to enable BRBE on another cpu", __func__));
+
+ info = brbe_info[cpu_id];
+ KASSERT(info != NULL, ("%s: info=NULL", __func__));
+
+ arm_brbe_enable(NULL);
+}
+
+static void
+brbe_backend_disable(struct hwt_context *ctx, int cpu_id)
+{
+ if (ctx->mode == HWT_MODE_CPU)
+ return;
+
+ KASSERT(curcpu == cpu_id,
+ ("%s: attempting to disable BRBE on another cpu", __func__));
+
+ arm_brbe_disable(NULL);
+}
+
+static int
+brbe_backend_enable_smp(struct hwt_context *ctx)
+{
+ struct arm_brbe_info *info;
+ 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;
+ KASSERT(brbe_info[cpu_id] == &brbe_info_cpu[cpu_id],
+ ("%s: brbe_info mismatch for cpu_id=%u", __func__, cpu_id));
+ info = &brbe_info_cpu[cpu_id];
+
+ mtx_lock_spin(&info->lock);
+ info->buf_info[0].kvaddr = vm->kvaddr;
+ info->buf_info[1].kvaddr =
+ (struct arm_brbe_dump *)((uintptr_t)vm->kvaddr +
+ (ctx->bufsize / 2));
+ info->buf_size = ctx->bufsize;
+ info->record_count = (ctx->bufsize / 2) /
+ sizeof(struct arm_brbe_dump);
+ mtx_unlock_spin(&info->lock);
+ }
+ HWT_CTX_UNLOCK(ctx);
+
+ smp_rendezvous_cpus(ctx->cpu_map, smp_no_rendezvous_barrier,
+ arm_brbe_enable, smp_no_rendezvous_barrier, NULL);
+
+ return (0);
+}
+
+static int
+brbe_backend_disable_smp(struct hwt_context *ctx)
+{
+ struct kevent kev;
+ struct arm_brbe_info *info;
+ struct arm_brbe_buf_info *buf;
+ int cpu_id;
+ int ret;
+
+ 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_brbe_disable, smp_no_rendezvous_barrier, NULL);
+
+ CPU_FOREACH_ISSET(cpu_id, &ctx->cpu_map) {
+ info = brbe_info[cpu_id];
+ info->stopped = 1;
+ buf = &info->buf_info[info->buffer_wr_idx];
+ arm_brbe_send_buffer(buf, 0);
+ }
+ }
+
+ /*
+ * Tracing on all CPUs has been disabled, and we've sent write ptr
+ * offsets for all bufs - let userspace know it can shutdown
+ */
+ EV_SET(&kev, ARM_BRBE_KQ_SHUTDOWN, EVFILT_USER, 0, NOTE_TRIGGER, 0,
+ NULL);
+ ret = kqfd_register(ctx->kqueue_fd, &kev, ctx->hwt_td, M_WAITOK);
+ if (ret)
+ dprintf("%s kqfd_register ret:%d\n", __func__, ret);
+
+ return (0);
+}
+
+static int
+brbe_backend_read(struct hwt_vm *vm, int *ident, vm_offset_t *offset,
+ uint64_t *data)
+{
+ struct arm_brbe_softc *sc = &brbe_sc;
+ struct arm_brbe_queue *q;
+ int error = 0;
+
+ mtx_lock_spin(&sc->sc_lock);
+
+ /* Return the first pending buffer that needs servicing */
+ q = STAILQ_FIRST(&sc->pending);
+ if (q == NULL) {
+ error = ENOENT;
+ goto error;
+ }
+
+ *ident = q->ident;
+ *offset = q->offset;
+ *data = (q->buf_idx << KQ_BUF_POS_SHIFT) |
+ (q->final_buf << KQ_FINAL_BUF_SHIFT);
+
+ STAILQ_REMOVE_HEAD(&sc->pending, next);
+ sc->npending--;
+
+error:
+ mtx_unlock_spin(&sc->sc_lock);
+ if (error)
+ return (error);
+
+ free(q, M_ARM_BRBE);
+ return (0);
+}
+
+static int
+brbe_backend_thread_alloc(struct hwt_thread *thr)
+{
+ struct arm_brbe_info *info;
+ char lock_name[32];
+
+ info = malloc(sizeof(*info), M_ARM_BRBE, M_WAITOK | M_ZERO);
+
+ snprintf(lock_name, sizeof(lock_name), "Arm BRBE lock/thr/%d",
+ thr->thread_id);
+ mtx_init(&info->lock, lock_name, NULL, MTX_SPIN);
+
+ info->sc = &brbe_sc;
+
+ info->buf_info[0].buf_idx = 0;
+ info->buf_info[0].info = info;
+ info->buf_info[1].buf_idx = 1;
+ info->buf_info[1].info = info;
+
+ thr->private = info;
+
+ return (0);
+}
+
+static void
+brbe_backend_thread_free(struct hwt_thread *thr)
+{
+ struct arm_brbe_info *info;
+
+ info = (struct arm_brbe_info *)thr->private;
+
+ free(info, M_ARM_BRBE);
+}
+
+static struct hwt_backend_ops brbe_ops = {
+ .hwt_backend_init = brbe_backend_init,
+ .hwt_backend_deinit = brbe_backend_deinit,
+ .hwt_backend_configure = brbe_backend_configure,
+
+ .hwt_backend_svc_buf = brbe_backend_svc_buf,
+ .hwt_backend_stop = brbe_backend_stop,
+ .hwt_backend_enable = brbe_backend_enable,
+ .hwt_backend_disable = brbe_backend_disable,
+ .hwt_backend_enable_smp = brbe_backend_enable_smp,
+ .hwt_backend_disable_smp = brbe_backend_disable_smp,
+ .hwt_backend_read = brbe_backend_read,
+ .hwt_backend_thread_alloc = brbe_backend_thread_alloc,
+ .hwt_backend_thread_free = brbe_backend_thread_free,
+};
+
+int
+brbe_register(void)
+{
+ mtx_init(&brbe_sc.sc_lock, "Arm BRBE lock", NULL, MTX_SPIN);
+
+ STAILQ_INIT(&brbe_sc.pending);
+ brbe_sc.npending = 0;
+
+ return (hwt_backend_register(&backend));
+}
+
diff --git a/sys/arm64/brbe/arm_brbe_dev.h b/sys/arm64/brbe/arm_brbe_dev.h
new file mode 100644
--- /dev/null
+++ b/sys/arm64/brbe/arm_brbe_dev.h
@@ -0,0 +1,130 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Arm Ltd
+ * Copyright (c) 2022 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Andrew Turner under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef _ARM64_ARM_BRBE_DEV_H_
+#define _ARM64_ARM_BRBE_DEV_H_
+
+#include <sys/mutex.h>
+#include <sys/taskqueue.h>
+
+#include <vm/vm.h>
+
+#include <arm64/brbe/arm_brbe.h>
+
+#include <dev/hwt/hwt_context.h>
+
+#define ARM_BRBE_DEBUG
+
+#ifdef ARM_BRBE_DEBUG
+#define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#else
+#define dprintf(fmt, ...)
+#endif
+
+DECLARE_CLASS(arm_brbe_driver);
+
+struct cdev;
+struct resource;
+
+struct arm_brbe_info;
+struct arm_brbe_softc;
+struct arm_brbe_queue;
+
+struct arm_brbe_buf_info {
+ int buf_idx;
+ struct arm_brbe_dump *kvaddr;
+ bool pending;
+
+ struct arm_brbe_info *info;
+};
+
+struct arm_brbe_info {
+ int ident; /* tid or cpu_id */
+
+ struct mtx lock;
+ struct arm_brbe_softc *sc;
+
+ uint64_t fcr;
+ uint64_t cr;
+
+ int buf_size;
+ int record_count;
+
+ struct arm_brbe_buf_info buf_info[2];
+
+ int wr_idx;
+ int buffer_wr_idx;
+
+ bool enabled;
+ bool stopped;
+
+ struct task task[2];
+ struct task flush_task;
+
+ enum arm_brbe_ctx_field ctx_field;
+ uint32_t context_id;
+};
+
+struct arm_brbe_softc {
+ device_t dev;
+
+ struct mtx sc_lock;
+
+ struct hwt_context *ctx;
+
+ struct arm_brbe_info **brbe_info;
+
+ STAILQ_HEAD(, arm_brbe_queue) pending;
+ uint64_t npending;
+
+ int kqueue_fd;
+ struct thread *hwt_td;
+};
+
+struct arm_brbe_queue {
+ int ident;
+ u_int buf_idx:1;
+ bool final_buf:1;
+ vm_offset_t offset;
+ STAILQ_ENTRY(arm_brbe_queue) next;
+};
+
+TASKQUEUE_DECLARE(arm_brbe);
+
+int
+brbe_register(void);
+
+void
+arm_brbe_send_buffer(void *arg, int pending __unused);
+
+extern struct arm_brbe_info **brbe_info;
+
+#endif /* _ARM64_ARM_BRBE_DEV_H_ */
diff --git a/sys/arm64/brbe/arm_brbe_dev.c b/sys/arm64/brbe/arm_brbe_dev.c
new file mode 100644
--- /dev/null
+++ b/sys/arm64/brbe/arm_brbe_dev.c
@@ -0,0 +1,292 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Arm Ltd
+ * Copyright (c) 2022 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Andrew Turner under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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 AUTHOR 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 AUTHOR 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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/event.h>
+#include <sys/hwt.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <sys/smp.h>
+#include <sys/systm.h>
+#include <sys/taskqueue.h>
+#include <sys/pmc.h>
+
+#include <machine/armreg.h>
+#include <machine/bus.h>
+#include <machine/pmc_mdep.h>
+
+#include <arm64/brbe/arm_brbe.h>
+#include <arm64/brbe/arm_brbe_dev.h>
+#include <arm64/brbe/arm_brbe_reg.h>
+
+MALLOC_DEFINE(M_ARM_BRBE, "armbrbe", "Arm BRBE tracing");
+
+#define BRBE_MODULE_NAME "brbe"
+
+/*
+ * taskqueue(9) used for sleepable routines called from interrupt handlers
+ */
+TASKQUEUE_FAST_DEFINE_THREAD(arm_brbe);
+
+static int brbe_record_count;
+
+static void
+brbe_invalidate(void)
+{
+ /* Ensure all branches before this point are recorded */
+ isb();
+ SYS(BRB_IALL);
+
+ /* Ensure all branch records are invalidated after this point */
+ isb();
+}
+
+/* note: Scheduled and run via taskqueue, so can run on any CPU at any time */
+void
+arm_brbe_send_buffer(void *arg, int pending __unused)
+{
+ struct arm_brbe_buf_info *buf = (struct arm_brbe_buf_info *)arg;
+ struct arm_brbe_info *info = buf->info;
+ struct arm_brbe_queue *queue;
+ struct kevent kev;
+ int ret;
+
+ queue = malloc(sizeof(struct arm_brbe_queue), M_ARM_BRBE,
+ M_WAITOK | M_ZERO);
+
+ mtx_lock_spin(&info->lock);
+
+ /* Add to queue for userspace to pickup */
+ queue->ident = info->ident;
+ if (buf->pending)
+ queue->offset = info->record_count *
+ sizeof(struct arm_brbe_dump);
+ else
+ queue->offset = info->wr_idx * sizeof(struct arm_brbe_dump);
+ queue->buf_idx = buf->buf_idx;
+ queue->final_buf = info->stopped;
+ mtx_unlock_spin(&info->lock);
+
+ mtx_lock_spin(&info->sc->sc_lock);
+ STAILQ_INSERT_TAIL(&info->sc->pending, queue, next);
+ info->sc->npending++;
+ EV_SET(&kev, ARM_BRBE_KQ_BUF, EVFILT_USER, 0, NOTE_TRIGGER,
+ info->sc->npending, NULL);
+ mtx_unlock_spin(&info->sc->sc_lock);
+
+ /* Notify userspace */
+ ret = kqfd_register(info->sc->kqueue_fd, &kev, info->sc->hwt_td,
+ M_WAITOK);
+ if (ret)
+ dprintf("%s kqfd_register ret:%d\n", __func__, ret);
+}
+
+int
+brbe_get_dump(void)
+{
+ struct arm_brbe_info *info = brbe_info[PCPU_GET(cpuid)];
+ struct arm_brbe_buf_info *buf;
+ struct arm_brbe_dump *dump;
+ uint64_t fcr;
+ int bank;
+ int i;
+
+ if (!brbe_info)
+ return (ENODEV);
+
+ info = brbe_info[PCPU_GET(cpuid)];
+
+ if (!info)
+ return (ENODEV);
+
+ mtx_lock_spin(&info->lock);
+
+ if (!info->enabled) {
+ mtx_unlock_spin(&info->lock);
+ return (0);
+ }
+
+ if (info->buf_info[info->buffer_wr_idx].pending) {
+ mtx_unlock_spin(&info->lock);
+ return (ENOMEM);
+ }
+
+ dump = &info->buf_info[info->buffer_wr_idx].kvaddr[info->wr_idx];
+
+ fcr = READ_SPECIALREG(BRBFCR_EL1_REG);
+
+ dump->ts = READ_SPECIALREG(BRBTS_EL1_REG);
+ dump->context_id = info->context_id;
+ dump->count = brbe_record_count;
+
+ for (bank = 0; bank < brbe_record_count; bank += 32) {
+ if (bank != 0)
+ WRITE_SPECIALREG(BRBFCR_EL1_REG,
+ (fcr & ~BRBFCR_BANK_MASK) | BRBFCR_BANK_32_63);
+ else
+ WRITE_SPECIALREG(BRBFCR_EL1_REG,
+ (fcr & ~BRBFCR_BANK_MASK) | BRBFCR_BANK_0_31);
+
+ for (i = 0; i < ((brbe_record_count > 32) ? 32 :
+ brbe_record_count); i++) {
+ dump->records[bank + i].inf = read_brbe_inf(i);
+ dump->records[bank + i].src = read_brbe_src(i);
+ dump->records[bank + i].tgt = read_brbe_tgt(i);
+ }
+ }
+
+ info->wr_idx++;
+
+ if (info->wr_idx >= info->record_count) {
+ buf = &info->buf_info[info->buffer_wr_idx];
+
+ buf->pending = 1;
+
+ /*
+ * Notify userspace via kqueue that buffer is full and needs
+ * copying out - since kqueue can sleep, don't do this in the
+ * interrupt handler, add to a taskqueue to be scheduled later
+ * instead.
+ */
+ TASK_INIT(&info->task[info->buffer_wr_idx], 0,
+ (task_fn_t *)arm_brbe_send_buffer, buf);
+ taskqueue_enqueue(taskqueue_arm_brbe,
+ &info->task[info->buffer_wr_idx]);
+
+ info->buffer_wr_idx = (info->buffer_wr_idx + 1) & 1;
+ info->wr_idx = 0;
+ }
+
+ mtx_unlock_spin(&info->lock);
+
+ return (0);
+}
+
+static void
+brbe_start(void)
+{
+ struct arm_brbe_info *info = NULL;
+ uint64_t fcr;
+ uint64_t cr;
+
+ if (brbe_info)
+ info = brbe_info[PCPU_GET(cpuid)];
+
+ if (info) {
+ cr = info->cr;
+ fcr = info->fcr;
+ } else {
+ cr = 0;
+ fcr = 0;
+ }
+
+ brbe_invalidate();
+
+ WRITE_SPECIALREG(BRBCR_EL1_REG, cr);
+
+ isb();
+
+ WRITE_SPECIALREG(BRBFCR_EL1_REG, fcr);
+}
+
+static void
+brbe_stop(void)
+{
+ WRITE_SPECIALREG(BRBFCR_EL1_REG, BRBFCR_PAUSED_MASK);
+
+ WRITE_SPECIALREG(BRBCR_EL1_REG, 0);
+}
+
+static void
+brbe_intr_callback(int ri)
+{
+ brbe_get_dump();
+
+ brbe_stop();
+ brbe_start();
+}
+
+static int
+brbe_init(void)
+{
+ uint64_t idr0;
+ uint64_t dfr;
+
+ get_kernel_reg(ID_AA64DFR0_EL1, &dfr);
+
+ if (ID_AA64DFR0_BRBE_VAL(dfr) < ID_AA64DFR0_BRBE_IMPL) {
+ printf("brbe_init: BRBE not present\n");
+ return (ENODEV);
+ }
+
+ idr0 = READ_SPECIALREG(BRBIDR0_EL1_REG);
+
+ brbe_record_count = (idr0 & BRBIDR0_NUMREC_MASK)
+ >> BRBIDR0_NUMREC_SHIFT;
+
+ pmc_intr_callback = brbe_intr_callback;
+
+ brbe_register();
+
+ return (0);
+}
+
+static int
+brbe_handler(module_t mod, int what, void *arg)
+{
+ int error;
+
+ error = 0;
+
+ switch (what) {
+ case MOD_LOAD:
+ error = brbe_init();
+ break;
+ }
+
+ return (error);
+}
+
+static moduledata_t brbe_mod = {
+ .name = BRBE_MODULE_NAME,
+ .evhand = brbe_handler,
+};
+
+DECLARE_MODULE(brbe, brbe_mod, SI_SUB_SMP, SI_ORDER_ANY);
+MODULE_DEPEND(brbe, hwt, 1, 1, 1);
+MODULE_DEPEND(brbe, pmc, PMC_VERSION, PMC_VERSION, PMC_VERSION);
+MODULE_VERSION(brbe, 1);
diff --git a/sys/arm64/brbe/arm_brbe_reg.h b/sys/arm64/brbe/arm_brbe_reg.h
new file mode 100644
--- /dev/null
+++ b/sys/arm64/brbe/arm_brbe_reg.h
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Arm Ltd
+ *
+ * Portions of this software were developed by Andrew Turner under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#ifndef _ARM64_ARM_BRBE_REG_H_
+#define _ARM64_ARM_BRBE_REG_H_
+
+/* BRBE SYS instructions */
+#define BRB_IALL_op0 1
+#define BRB_IALL_op1 1
+#define BRB_IALL_CRn 7
+#define BRB_IALL_CRm 2
+#define BRB_IALL_op2 4
+
+#define SYS_BRBINF_EL1_0(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 0)
+#define SYS_BRBSRC_EL1_0(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 1)
+#define SYS_BRBTGT_EL1_0(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 2)
+#define SYS_BRBINF_EL1_16(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 4)
+#define SYS_BRBSRC_EL1_16(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 5)
+#define SYS_BRBTGT_EL1_16(nl) _MRS_REG_ALT_NAME(2, 1, 8, nl, 6)
+
+#define RETURN_READ_BRBSRCN_0(nl) \
+ return (READ_SPECIALREG(SYS_BRBSRC_EL1_0(nl)))
+
+#define RETURN_READ_BRBTGTN_0(nl) \
+ return (READ_SPECIALREG(SYS_BRBTGT_EL1_0(nl)))
+
+#define RETURN_READ_BRBINFN_0(nl) \
+ return (READ_SPECIALREG(SYS_BRBINF_EL1_0(nl)))
+
+#define RETURN_READ_BRBSRCN_16(nl) \
+ return (READ_SPECIALREG(SYS_BRBSRC_EL1_16(nl)))
+
+#define RETURN_READ_BRBTGTN_16(nl) \
+ return (READ_SPECIALREG(SYS_BRBTGT_EL1_16(nl)))
+
+#define RETURN_READ_BRBINFN_16(nl) \
+ return (READ_SPECIALREG(SYS_BRBINF_EL1_16(nl)))
+
+#define BRBE_REGN_CASE(n, nl, case_macro) \
+ case n: \
+ case_macro(nl); \
+ break
+
+#define BRBE_REGN_SWITCH(x, case_macro) \
+ do { \
+ switch (x) { \
+ BRBE_REGN_CASE(0, 0, case_macro ## _0); \
+ BRBE_REGN_CASE(1, 1, case_macro ## _0); \
+ BRBE_REGN_CASE(2, 2, case_macro ## _0); \
+ BRBE_REGN_CASE(3, 3, case_macro ## _0); \
+ BRBE_REGN_CASE(4, 4, case_macro ## _0); \
+ BRBE_REGN_CASE(5, 5, case_macro ## _0); \
+ BRBE_REGN_CASE(6, 6, case_macro ## _0); \
+ BRBE_REGN_CASE(7, 7, case_macro ## _0); \
+ BRBE_REGN_CASE(8, 8, case_macro ## _0); \
+ BRBE_REGN_CASE(9, 9, case_macro ## _0); \
+ BRBE_REGN_CASE(10, 10, case_macro ## _0); \
+ BRBE_REGN_CASE(11, 11, case_macro ## _0); \
+ BRBE_REGN_CASE(12, 12, case_macro ## _0); \
+ BRBE_REGN_CASE(13, 13, case_macro ## _0); \
+ BRBE_REGN_CASE(14, 14, case_macro ## _0); \
+ BRBE_REGN_CASE(15, 15, case_macro ## _0); \
+ BRBE_REGN_CASE(16, 0, case_macro ## _16); \
+ BRBE_REGN_CASE(17, 1, case_macro ## _16); \
+ BRBE_REGN_CASE(18, 2, case_macro ## _16); \
+ BRBE_REGN_CASE(19, 3, case_macro ## _16); \
+ BRBE_REGN_CASE(20, 4, case_macro ## _16); \
+ BRBE_REGN_CASE(21, 5, case_macro ## _16); \
+ BRBE_REGN_CASE(22, 6, case_macro ## _16); \
+ BRBE_REGN_CASE(23, 7, case_macro ## _16); \
+ BRBE_REGN_CASE(24, 8, case_macro ## _16); \
+ BRBE_REGN_CASE(25, 9, case_macro ## _16); \
+ BRBE_REGN_CASE(26, 10, case_macro ## _16); \
+ BRBE_REGN_CASE(27, 11, case_macro ## _16); \
+ BRBE_REGN_CASE(28, 12, case_macro ## _16); \
+ BRBE_REGN_CASE(29, 13, case_macro ## _16); \
+ BRBE_REGN_CASE(30, 14, case_macro ## _16); \
+ BRBE_REGN_CASE(31, 15, case_macro ## _16); \
+ default: \
+ panic("Invalid BRB* index %d\n", x); \
+ } \
+ } while (0)
+
+static uint64_t read_brbe_inf(int i)
+{
+ BRBE_REGN_SWITCH(i, RETURN_READ_BRBINFN);
+ return (0);
+}
+
+static uint64_t read_brbe_src(int i)
+{
+ BRBE_REGN_SWITCH(i, RETURN_READ_BRBSRCN);
+ return (0);
+}
+
+static uint64_t read_brbe_tgt(int i)
+{
+ BRBE_REGN_SWITCH(i, RETURN_READ_BRBTGTN);
+ return (0);
+}
+
+#endif /* _ARM64_ARM_BRBE_REG_H_ */
diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64
--- a/sys/conf/files.arm64
+++ b/sys/conf/files.arm64
@@ -27,6 +27,8 @@
arm64/acpica/acpi_wakeup.c optional acpi
arm64/acpica/pci_cfgreg.c optional acpi pci
arm64/arm64/autoconf.c standard
+arm64/brbe/arm_brbe_backend.c optional hwt brbe
+arm64/brbe/arm_brbe_dev.c optional hwt brbe
arm64/spe/arm_spe_backend.c optional hwt spe
arm64/spe/arm_spe_dev.c optional hwt spe
arm64/spe/arm_spe_acpi.c optional hwt spe acpi
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -70,6 +70,7 @@
${_bios} \
${_blake2} \
${_bnxt} \
+ ${_brbe} \
bridgestp \
bwi \
bwn \
@@ -691,6 +692,7 @@
.endif
.if ${MACHINE_CPUARCH} == "aarch64"
+_brbe= brbe
_spe= spe
.endif
diff --git a/sys/modules/brbe/Makefile b/sys/modules/brbe/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/brbe/Makefile
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/arm64/brbe
+
+KMOD = brbe
+SRCS = \
+ arm_brbe_backend.c \
+ arm_brbe_dev.c \
+ arm_brbe.h
+
+SRCS+= opt_platform.h
+
+EXPORT_SYMS= YES
+
+.include <bsd.kmod.mk>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Aug 31, 3:54 AM (1 h, 40 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
37628053
Default Alt Text
D58535.id183036.diff (33 KB)
Attached To
Mode
D58535: arm64: Add BRBE support
Attached
Detach File
Event Timeline
Log In to Comment