Page MenuHomeFreeBSD

D58535.id183036.diff
No OneTemporary

D58535.id183036.diff

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

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)

Event Timeline