1/*	$OpenBSD: dt_prov_profile.c,v 1.8 2024/04/06 11:18:02 mpi Exp $ */
2
3/*
4 * Copyright (c) 2019 Martin Pieuchot <mpi@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/systm.h>
21#include <sys/param.h>
22#include <sys/atomic.h>
23#include <sys/clockintr.h>
24
25#include <dev/dt/dtvar.h>
26
27struct dt_probe	*dtpp_profile;		/* per-CPU profile probe */
28struct dt_probe	*dtpp_interval;		/* global periodic probe */
29
30/* Flags that make sense for this provider */
31#define DTEVT_PROV_PROFILE	DTEVT_COMMON
32
33int	dt_prov_profile_alloc(struct dt_probe *, struct dt_softc *,
34	    struct dt_pcb_list *, struct dtioc_req *);
35
36struct dt_provider dt_prov_profile = {
37	.dtpv_name	= "profile",
38	.dtpv_alloc	= dt_prov_profile_alloc,
39	.dtpv_enter	= NULL,
40	.dtpv_leave	= NULL,
41	.dtpv_dealloc	= NULL,
42};
43
44struct dt_provider dt_prov_interval = {
45	.dtpv_name	= "interval",
46	.dtpv_alloc	= dt_prov_profile_alloc,
47	.dtpv_enter	= NULL,
48	.dtpv_leave	= NULL,
49	.dtpv_dealloc	= NULL,
50};
51
52int
53dt_prov_profile_init(void)
54{
55	dtpp_profile = dt_dev_alloc_probe("hz", "97", &dt_prov_profile);
56	if (dtpp_profile == NULL)
57		return 0;
58	dt_dev_register_probe(dtpp_profile);
59	dtpp_interval = dt_dev_alloc_probe("hz", "1", &dt_prov_interval);
60	if (dtpp_interval == NULL)
61		return 1;
62	dt_dev_register_probe(dtpp_interval);
63	return 2;
64}
65
66int
67dt_prov_profile_alloc(struct dt_probe *dtp, struct dt_softc *sc,
68    struct dt_pcb_list *plist, struct dtioc_req *dtrq)
69{
70	struct dt_pcb *dp;
71	struct cpu_info *ci;
72	CPU_INFO_ITERATOR cii;
73	extern int hz;
74
75	KASSERT(TAILQ_EMPTY(plist));
76	KASSERT(dtp == dtpp_profile || dtp == dtpp_interval);
77
78	if (dtrq->dtrq_rate <= 0 || dtrq->dtrq_rate > hz)
79		return EOPNOTSUPP;
80
81	CPU_INFO_FOREACH(cii, ci) {
82		if (!CPU_IS_PRIMARY(ci) && (dtp == dtpp_interval))
83			continue;
84
85		dp = dt_pcb_alloc(dtp, sc);
86		if (dp == NULL) {
87			dt_pcb_purge(plist);
88			return ENOMEM;
89		}
90
91		dp->dp_nsecs = SEC_TO_NSEC(1) / dtrq->dtrq_rate;
92		dp->dp_cpu = ci;
93
94		dp->dp_evtflags = dtrq->dtrq_evtflags & DTEVT_PROV_PROFILE;
95		TAILQ_INSERT_HEAD(plist, dp, dp_snext);
96	}
97
98	return 0;
99}
100
101void
102dt_clock(struct clockrequest *cr, void *cf, void *arg)
103{
104	uint64_t count, i;
105	struct dt_evt *dtev;
106	struct dt_pcb *dp = arg;
107
108	count = clockrequest_advance(cr, dp->dp_nsecs);
109	for (i = 0; i < count; i++) {
110		dtev = dt_pcb_ring_get(dp, 1);
111		if (dtev == NULL)
112			return;
113		dt_pcb_ring_consume(dp, dtev);
114	}
115}
116