To improve the utility of dt(4)'s interval and profile probes we need
to move the probe entry points from the fixed-frequency hardclock() to
a dedicated clock interrupt callback so that the probes can fire at
arbitrary frequencies.
- Remove entry points for interval/profile probes from hardclock().
- Merge dt_prov_profile_enter(), dt_prov_interval_enter(), and
dt_prov_profile_fire() into one function, dt_clock(). This is
the now-unified callback for interval/profile probes. dt_clock()
will consume multiple events during a single execution if it is
delayed, but on platforms with high quality interrupt clocks this
should be rare.
- Each struct dt_pcb gets its own clockintr handle, dp_clockintr.
- In struct dt_pcb, replace dp_maxtick/dp_nticks with dp_nsecs,
the PCB's sampling period. Aynchronous probes must initialize
dp_nsecs to a non-zero value during dtpv_alloc().
- In struct dt_pcb, replace dp_cpuid with dp_cpu so that
dt_ioctl_record_start() knows where to bind the PCB's
dp_clockintr.
- dt_ioctl_record_start() binds, staggers, and starts all
interval/profile PCBs on the given dt_softc. Each dp_clockintr
is given a reference to its enclosing PCB so that dt_clock()
doesn't need to search for it. The staggering sort-of simulates
the current behavior under hardclock().
- dt_ioctl_record_stop() unbinds all interval/profile PCBs. The
CL_BARRIER ensures that dp_clockintr's PCB reference is not in
use by dt_clock() so that the PCB may be safely freed upon
return from dt_ioctl_record_stop(). Blocking while holding
dt_lock is not ideal, but in practice blocking in this spot is
rare and dt_clock() completes quickly on all but the oldest
hardware. An extremely unlucky thread could block for every
interval/profile PCB on the softc, but this is implausible.
DT_FA_PROFILE values are up-to-date for amd64, i386, and macppc.
Somebody with the right hardware needs to check-and-maybe-fix the
values on octeon, powerpc64, and sparc64.
Joint effort with mpi@.
Thread: https://marc.info/?l=openbsd-tech&m=
170629371821879&w=2
ok mpi@
-/* $OpenBSD: dt_dev.c,v 1.29 2024/01/02 16:32:48 bluhm Exp $ */
+/* $OpenBSD: dt_dev.c,v 1.30 2024/02/09 17:42:18 cheloha Exp $ */
/*
* Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/param.h>
+#include <sys/clockintr.h>
#include <sys/device.h>
#include <sys/exec_elf.h>
#include <sys/malloc.h>
* proc_trampoline+0x1c
*/
#if defined(__amd64__)
-#define DT_FA_PROFILE 7
+#define DT_FA_PROFILE 5
#define DT_FA_STATIC 2
#elif defined(__i386__)
-#define DT_FA_PROFILE 8
+#define DT_FA_PROFILE 5
#define DT_FA_STATIC 2
#elif defined(__macppc__)
-#define DT_FA_PROFILE 7
+#define DT_FA_PROFILE 5
#define DT_FA_STATIC 2
#elif defined(__octeon__)
#define DT_FA_PROFILE 6
SMR_SLIST_INSERT_HEAD_LOCKED(&dtp->dtp_pcbs, dp, dp_pnext);
dtp->dtp_recording++;
dtp->dtp_prov->dtpv_recording++;
+
+ if (dp->dp_nsecs != 0) {
+ clockintr_bind(&dp->dp_clockintr, dp->dp_cpu, dt_clock,
+ dp);
+ clockintr_stagger(&dp->dp_clockintr, dp->dp_nsecs,
+ CPU_INFO_UNIT(dp->dp_cpu), MAXCPUS);
+ clockintr_advance(&dp->dp_clockintr, dp->dp_nsecs);
+ }
}
rw_exit_write(&dt_lock);
TAILQ_FOREACH(dp, &sc->ds_pcbs, dp_snext) {
struct dt_probe *dtp = dp->dp_dtp;
+ /*
+ * Set an execution barrier to ensure the shared
+ * reference to dp is inactive.
+ */
+ if (dp->dp_nsecs != 0)
+ clockintr_unbind(&dp->dp_clockintr, CL_BARRIER);
+
dtp->dtp_recording--;
dtp->dtp_prov->dtpv_recording--;
SMR_SLIST_REMOVE_LOCKED(&dtp->dtp_pcbs, dp, dt_pcb, dp_pnext);
-/* $OpenBSD: dt_prov_profile.c,v 1.5 2023/04/26 16:53:59 claudio Exp $ */
+/* $OpenBSD: dt_prov_profile.c,v 1.6 2024/02/09 17:42:18 cheloha Exp $ */
/*
* Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
#include <sys/systm.h>
#include <sys/param.h>
#include <sys/atomic.h>
+#include <sys/clockintr.h>
#include <dev/dt/dtvar.h>
int dt_prov_profile_alloc(struct dt_probe *, struct dt_softc *,
struct dt_pcb_list *, struct dtioc_req *);
-int dt_prov_profile_enter(struct dt_provider *, ...);
-int dt_prov_interval_enter(struct dt_provider *, ...);
struct dt_provider dt_prov_profile = {
.dtpv_name = "profile",
.dtpv_alloc = dt_prov_profile_alloc,
- .dtpv_enter = dt_prov_profile_enter,
+ .dtpv_enter = NULL,
.dtpv_leave = NULL,
.dtpv_dealloc = NULL,
};
struct dt_provider dt_prov_interval = {
.dtpv_name = "interval",
.dtpv_alloc = dt_prov_profile_alloc,
- .dtpv_enter = dt_prov_interval_enter,
+ .dtpv_enter = NULL,
.dtpv_leave = NULL,
.dtpv_dealloc = NULL,
};
return ENOMEM;
}
- dp->dp_maxtick = hz / dtrq->dtrq_rate;
- dp->dp_cpuid = ci->ci_cpuid;
+ dp->dp_nsecs = SEC_TO_NSEC(1) / dtrq->dtrq_rate;
+ dp->dp_cpu = ci;
dp->dp_filter = dtrq->dtrq_filter;
dp->dp_evtflags = dtrq->dtrq_evtflags & DTEVT_PROV_PROFILE;
return 0;
}
-static inline void
-dt_prov_profile_fire(struct dt_pcb *dp)
+void
+dt_clock(struct clockrequest *cr, void *cf, void *arg)
{
+ uint64_t count, i;
struct dt_evt *dtev;
-
- if (++dp->dp_nticks < dp->dp_maxtick)
- return;
-
- dtev = dt_pcb_ring_get(dp, 1);
- if (dtev == NULL)
- return;
- dt_pcb_ring_consume(dp, dtev);
- dp->dp_nticks = 0;
-}
-
-int
-dt_prov_profile_enter(struct dt_provider *dtpv, ...)
-{
- struct cpu_info *ci = curcpu();
- struct dt_pcb *dp;
-
- KASSERT(dtpv == &dt_prov_profile);
-
- smr_read_enter();
- SMR_SLIST_FOREACH(dp, &dtpp_profile->dtp_pcbs, dp_pnext) {
- if (dp->dp_cpuid != ci->ci_cpuid)
- continue;
-
- dt_prov_profile_fire(dp);
+ struct dt_pcb *dp = arg;
+
+ count = clockrequest_advance(cr, dp->dp_nsecs);
+ for (i = 0; i < count; i++) {
+ dtev = dt_pcb_ring_get(dp, 1);
+ if (dtev == NULL)
+ return;
+ dt_pcb_ring_consume(dp, dtev);
}
- smr_read_leave();
- return 0;
-}
-
-int
-dt_prov_interval_enter(struct dt_provider *dtpv, ...)
-{
- struct dt_pcb *dp;
-
- KASSERT(dtpv == &dt_prov_interval);
-
- smr_read_enter();
- SMR_SLIST_FOREACH(dp, &dtpp_interval->dtp_pcbs, dp_pnext) {
- dt_prov_profile_fire(dp);
- }
- smr_read_leave();
- return 0;
}
-/* $OpenBSD: dtvar.h,v 1.17 2023/04/26 16:53:59 claudio Exp $ */
+/* $OpenBSD: dtvar.h,v 1.18 2024/02/09 17:42:18 cheloha Exp $ */
/*
* Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
* userland read(2)s them.
*
* Locks used to protect struct members in this file:
+ * D dt_lock
* I immutable after creation
* K kernel lock
* K,S kernel lock for writing and SMR for reading
struct dt_filter dp_filter; /* [I] filter to match */
/* Provider specific fields. */
- unsigned int dp_cpuid; /* [I] on which CPU */
- unsigned int dp_maxtick; /* [I] freq. of profiling */
- unsigned int dp_nticks; /* [c] current tick count */
+ struct clockintr dp_clockintr; /* [D] profiling handle */
+ uint64_t dp_nsecs; /* [I] profiling period */
+ struct cpu_info *dp_cpu; /* [I] on which CPU */
/* Counters */
uint64_t dp_dropevt; /* [m] # dropped event */
struct dt_provider *);
void dt_dev_register_probe(struct dt_probe *);
+void dt_clock(struct clockrequest *, void *, void *);
extern volatile uint32_t dt_tracing; /* currently tracing? */
-/* $OpenBSD: kern_clock.c,v 1.121 2023/10/17 00:04:02 cheloha Exp $ */
+/* $OpenBSD: kern_clock.c,v 1.122 2024/02/09 17:42:18 cheloha Exp $ */
/* $NetBSD: kern_clock.c,v 1.34 1996/06/09 04:51:03 briggs Exp $ */
/*-
#include <sys/sched.h>
#include <sys/timetc.h>
-#include "dt.h"
-#if NDT > 0
-#include <dev/dt/dtvar.h>
-#endif
-
/*
* Clock handling routines.
*
void
hardclock(struct clockframe *frame)
{
-#if NDT > 0
- DT_ENTER(profile, NULL);
- if (CPU_IS_PRIMARY(curcpu()))
- DT_ENTER(interval, NULL);
-#endif
-
/*
* If we are not the primary CPU, we're not allowed to do
* any more work.