xref: /netbsd-src/sys/kern/kern_cctr.c (revision c6e0728e3610bc755cbea6cfb1d55ff18f707fab)
1*c6e0728eSriastradh /*	$NetBSD: kern_cctr.c,v 1.14 2023/10/05 12:05:59 riastradh Exp $	*/
2ccee2438Stsutsui 
3ccee2438Stsutsui /*-
4b6262570Sthorpej  * Copyright (c) 2020 Jason R. Thorpe
5b6262570Sthorpej  * Copyright (c) 2018 Naruaki Etomi
6ccee2438Stsutsui  * All rights reserved.
7ccee2438Stsutsui  *
8ccee2438Stsutsui  * Redistribution and use in source and binary forms, with or without
9ccee2438Stsutsui  * modification, are permitted provided that the following conditions
10ccee2438Stsutsui  * are met:
11ccee2438Stsutsui  * 1. Redistributions of source code must retain the above copyright
12ccee2438Stsutsui  *    notice, this list of conditions and the following disclaimer.
13ccee2438Stsutsui  * 2. Redistributions in binary form must reproduce the above copyright
14ccee2438Stsutsui  *    notice, this list of conditions and the following disclaimer in the
15ccee2438Stsutsui  *    documentation and/or other materials provided with the distribution.
16ccee2438Stsutsui  *
17b6262570Sthorpej  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18b6262570Sthorpej  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19b6262570Sthorpej  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20b6262570Sthorpej  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21b6262570Sthorpej  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22b6262570Sthorpej  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23b6262570Sthorpej  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24b6262570Sthorpej  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25b6262570Sthorpej  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26b6262570Sthorpej  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27ccee2438Stsutsui  */
28ccee2438Stsutsui 
29b6262570Sthorpej /*
30b6262570Sthorpej  * Most of the following was adapted from the Linux/ia64 cycle counter
31b6262570Sthorpej  * synchronization algorithm:
32ccee2438Stsutsui  *
33b6262570Sthorpej  *	IA-64 Linux Kernel: Design and Implementation p356-p361
34b6262570Sthorpej  *	(Hewlett-Packard Professional Books)
35ccee2438Stsutsui  *
36b6262570Sthorpej  * Here's a rough description of how it works.
37b6262570Sthorpej  *
38b6262570Sthorpej  * The primary CPU is the reference monotonic counter.  Each secondary
39b6262570Sthorpej  * CPU is responsible for knowing the offset of its own cycle counter
40b6262570Sthorpej  * relative to the primary's.  When the time counter is read, the CC
41b6262570Sthorpej  * value is adjusted by this delta.
42b6262570Sthorpej  *
43b6262570Sthorpej  * Calibration happens periodically, and works like this:
44b6262570Sthorpej  *
45b6262570Sthorpej  * Secondary CPU                               Primary CPU
46b6262570Sthorpej  *   Send IPI to publish reference CC
47b6262570Sthorpej  *                                   --------->
48d17473c8Sthorpej  *                                             Indicate Primary Ready
49d17473c8Sthorpej  *                <----------------------------
50d17473c8Sthorpej  *   T0 = local CC
51d17473c8Sthorpej  *   Indicate Secondary Ready
52d17473c8Sthorpej  *                           ----------------->
53b6262570Sthorpej  *     (assume this happens at Tavg)           Publish reference CC
54d17473c8Sthorpej  *                                             Indicate completion
55d17473c8Sthorpej  *                    <------------------------
56d17473c8Sthorpej  *   Notice completion
57b6262570Sthorpej  *   T1 = local CC
58b6262570Sthorpej  *
59b6262570Sthorpej  *   Tavg = (T0 + T1) / 2
60b6262570Sthorpej  *
61b6262570Sthorpej  *   Delta = Tavg - Published primary CC value
62b6262570Sthorpej  *
63d17473c8Sthorpej  * "Notice completion" is performed by waiting for the primary to set
64d17473c8Sthorpej  * the calibration state to FINISHED.  This is a little unfortunate,
65d17473c8Sthorpej  * because T0->Tavg involves a single store-release on the secondary, and
66d17473c8Sthorpej  * Tavg->T1 involves a store-relaxed and a store-release.  It would be
67d17473c8Sthorpej  * better to simply wait for the reference CC to transition from 0 to
68d17473c8Sthorpej  * non-0 (i.e. just wait for a single store-release from Tavg->T1), but
69d17473c8Sthorpej  * if the cycle counter just happened to read back as 0 at that instant,
70d17473c8Sthorpej  * we would never break out of the loop.
71d17473c8Sthorpej  *
72b6262570Sthorpej  * We trigger calibration roughly once a second; the period is actually
73b6262570Sthorpej  * skewed based on the CPU index in order to avoid lock contention.  The
74b6262570Sthorpej  * calibration interval does not need to be precise, and so this is fine.
75ccee2438Stsutsui  */
76ccee2438Stsutsui 
77ccee2438Stsutsui #include <sys/cdefs.h>
78*c6e0728eSriastradh __KERNEL_RCSID(0, "$NetBSD: kern_cctr.c,v 1.14 2023/10/05 12:05:59 riastradh Exp $");
79ccee2438Stsutsui 
80ccee2438Stsutsui #include <sys/param.h>
81b6262570Sthorpej #include <sys/atomic.h>
82ccee2438Stsutsui #include <sys/systm.h>
83ccee2438Stsutsui #include <sys/sysctl.h>
84b6262570Sthorpej #include <sys/timepps.h>
85ccee2438Stsutsui #include <sys/time.h>
86ccee2438Stsutsui #include <sys/timetc.h>
87ccee2438Stsutsui #include <sys/kernel.h>
88ccee2438Stsutsui #include <sys/power.h>
89a2a38285Sad #include <sys/cpu.h>
90ccee2438Stsutsui #include <machine/cpu_counter.h>
91ccee2438Stsutsui 
92ccee2438Stsutsui /* XXX make cc_timecounter.tc_frequency settable by sysctl() */
93ccee2438Stsutsui 
94b6262570Sthorpej #if defined(MULTIPROCESSOR)
95b6262570Sthorpej static uint32_t cc_primary __cacheline_aligned;
96b6262570Sthorpej static uint32_t cc_calibration_state __cacheline_aligned;
97b6262570Sthorpej static kmutex_t cc_calibration_lock __cacheline_aligned;
98ccee2438Stsutsui 
99b6262570Sthorpej #define	CC_CAL_START		0	/* initial state */
100b6262570Sthorpej #define	CC_CAL_PRIMARY_READY	1	/* primary CPU ready to respond */
101b6262570Sthorpej #define	CC_CAL_SECONDARY_READY	2	/* secondary CPU ready to receive */
102b6262570Sthorpej #define	CC_CAL_FINISHED		3	/* calibration attempt complete */
103b6262570Sthorpej #endif /* MULTIPROCESSOR */
104ccee2438Stsutsui 
105ccee2438Stsutsui static struct timecounter cc_timecounter = {
106ccee2438Stsutsui 	.tc_get_timecount	= cc_get_timecount,
107b6262570Sthorpej 	.tc_poll_pps		= NULL,
108ccee2438Stsutsui 	.tc_counter_mask	= ~0u,
109ccee2438Stsutsui 	.tc_frequency		= 0,
110f89a668aSskrll 	.tc_name		= "unknown cycle counter",
111ccee2438Stsutsui 	/*
112ccee2438Stsutsui 	 * don't pick cycle counter automatically
113ccee2438Stsutsui 	 * if frequency changes might affect cycle counter
114ccee2438Stsutsui 	 */
115ccee2438Stsutsui 	.tc_quality		= -100000,
116ccee2438Stsutsui 
117ccee2438Stsutsui 	.tc_priv		= NULL,
118ccee2438Stsutsui 	.tc_next		= NULL
119ccee2438Stsutsui };
120ccee2438Stsutsui 
121ccee2438Stsutsui /*
122b6262570Sthorpej  * Initialize cycle counter based timecounter.  This must be done on the
123b6262570Sthorpej  * primary CPU.
124ccee2438Stsutsui  */
125ccee2438Stsutsui struct timecounter *
cc_init(timecounter_get_t getcc,uint64_t freq,const char * name,int quality)126cde344d3Stsutsui cc_init(timecounter_get_t getcc, uint64_t freq, const char *name, int quality)
127ccee2438Stsutsui {
128b6262570Sthorpej 	static bool cc_init_done __diagused;
129b6262570Sthorpej 	struct cpu_info * const ci = curcpu();
130b6262570Sthorpej 
131b6262570Sthorpej 	KASSERT(!cc_init_done);
132b6262570Sthorpej 	KASSERT(cold);
133b6262570Sthorpej 	KASSERT(CPU_IS_PRIMARY(ci));
134b6262570Sthorpej 
135b6262570Sthorpej #if defined(MULTIPROCESSOR)
136b6262570Sthorpej 	mutex_init(&cc_calibration_lock, MUTEX_DEFAULT, IPL_HIGH);
137b6262570Sthorpej #endif
138b6262570Sthorpej 
139b6262570Sthorpej 	cc_init_done = true;
140b6262570Sthorpej 
141b6262570Sthorpej 	ci->ci_cc.cc_delta = 0;
142b6262570Sthorpej 	ci->ci_cc.cc_ticks = 0;
143b6262570Sthorpej 	ci->ci_cc.cc_cal_ticks = 0;
144ccee2438Stsutsui 
145cde344d3Stsutsui 	if (getcc != NULL)
146cde344d3Stsutsui 		cc_timecounter.tc_get_timecount = getcc;
147cde344d3Stsutsui 
148ccee2438Stsutsui 	cc_timecounter.tc_frequency = freq;
149ccee2438Stsutsui 	cc_timecounter.tc_name = name;
150ccee2438Stsutsui 	cc_timecounter.tc_quality = quality;
151ccee2438Stsutsui 	tc_init(&cc_timecounter);
152ccee2438Stsutsui 
153ccee2438Stsutsui 	return &cc_timecounter;
154ccee2438Stsutsui }
155ccee2438Stsutsui 
156ccee2438Stsutsui /*
157b6262570Sthorpej  * Initialize cycle counter timecounter calibration data on a secondary
158b6262570Sthorpej  * CPU.  Must be called on that secondary CPU.
159b6262570Sthorpej  */
160b6262570Sthorpej void
cc_init_secondary(struct cpu_info * const ci)161b6262570Sthorpej cc_init_secondary(struct cpu_info * const ci)
162b6262570Sthorpej {
163b6262570Sthorpej 	KASSERT(!CPU_IS_PRIMARY(curcpu()));
164b6262570Sthorpej 	KASSERT(ci == curcpu());
165b6262570Sthorpej 
166b6262570Sthorpej 	ci->ci_cc.cc_ticks = 0;
167b6262570Sthorpej 
168b6262570Sthorpej 	/*
169b6262570Sthorpej 	 * It's not critical that calibration be performed in
170b6262570Sthorpej 	 * precise intervals, so skew when calibration is done
171b6262570Sthorpej 	 * on each secondary CPU based on it's CPU index to
172b6262570Sthorpej 	 * avoid contending on the calibration lock.
173b6262570Sthorpej 	 */
174b6262570Sthorpej 	ci->ci_cc.cc_cal_ticks = hz - cpu_index(ci);
175b6262570Sthorpej 	KASSERT(ci->ci_cc.cc_cal_ticks);
176b6262570Sthorpej 
177b6262570Sthorpej 	cc_calibrate_cpu(ci);
178b6262570Sthorpej }
179b6262570Sthorpej 
180b6262570Sthorpej /*
181ccee2438Stsutsui  * pick up tick count scaled to reference tick count
182ccee2438Stsutsui  */
183cde344d3Stsutsui u_int
cc_get_timecount(struct timecounter * tc)184ccee2438Stsutsui cc_get_timecount(struct timecounter *tc)
185ccee2438Stsutsui {
186b6262570Sthorpej #if defined(MULTIPROCESSOR)
187a355028fSad 	int64_t rcc;
188a355028fSad 	long pctr;
189ccee2438Stsutsui 
190a355028fSad 	do {
191a355028fSad 		pctr = lwp_pctr();
192b6262570Sthorpej 		/* N.B. the delta is always 0 on the primary. */
193b6262570Sthorpej 		rcc = cpu_counter32() - curcpu()->ci_cc.cc_delta;
194a355028fSad 	} while (pctr != lwp_pctr());
195ccee2438Stsutsui 
196ccee2438Stsutsui 	return rcc;
197b6262570Sthorpej #else
198b6262570Sthorpej 	return cpu_counter32();
199b6262570Sthorpej #endif /* MULTIPROCESSOR */
200ccee2438Stsutsui }
201ccee2438Stsutsui 
202ccee2438Stsutsui #if defined(MULTIPROCESSOR)
203b6262570Sthorpej static inline bool
cc_get_delta(struct cpu_info * const ci)204b6262570Sthorpej cc_get_delta(struct cpu_info * const ci)
205b6262570Sthorpej {
206b6262570Sthorpej 	int64_t t0, t1, tcenter = 0;
207b6262570Sthorpej 
208b6262570Sthorpej 	t0 = cpu_counter32();
209b6262570Sthorpej 
210b6262570Sthorpej 	atomic_store_release(&cc_calibration_state, CC_CAL_SECONDARY_READY);
211b6262570Sthorpej 
212b6262570Sthorpej 	for (;;) {
213b6262570Sthorpej 		if (atomic_load_acquire(&cc_calibration_state) ==
214b6262570Sthorpej 		    CC_CAL_FINISHED) {
215b6262570Sthorpej 			break;
216b6262570Sthorpej 		}
217ccee2438Stsutsui 	}
218ccee2438Stsutsui 
219b6262570Sthorpej 	t1 = cpu_counter32();
220b6262570Sthorpej 
221b6262570Sthorpej 	if (t1 < t0) {
222b6262570Sthorpej 		/* Overflow! */
223b6262570Sthorpej 		return false;
224b6262570Sthorpej 	}
225b6262570Sthorpej 
226b6262570Sthorpej 	/* average t0 and t1 without overflow: */
227b6262570Sthorpej 	tcenter = (t0 >> 1) + (t1 >> 1);
228b6262570Sthorpej 	if ((t0 & 1) + (t1 & 1) == 2)
229b6262570Sthorpej 		tcenter++;
230b6262570Sthorpej 
231b6262570Sthorpej 	ci->ci_cc.cc_delta = tcenter - cc_primary;
232b6262570Sthorpej 
233b6262570Sthorpej 	return true;
234b6262570Sthorpej }
235b6262570Sthorpej #endif /* MULTIPROCESSOR */
236b6262570Sthorpej 
237ccee2438Stsutsui /*
238b6262570Sthorpej  * Called on secondary CPUs to calibrate their cycle counter offset
239b6262570Sthorpej  * relative to the primary CPU.
240ccee2438Stsutsui  */
241ccee2438Stsutsui void
cc_calibrate_cpu(struct cpu_info * const ci)242b6262570Sthorpej cc_calibrate_cpu(struct cpu_info * const ci)
243ccee2438Stsutsui {
244b6262570Sthorpej #if defined(MULTIPROCESSOR)
245b6262570Sthorpej 	KASSERT(!CPU_IS_PRIMARY(ci));
246ccee2438Stsutsui 
247b6262570Sthorpej 	mutex_spin_enter(&cc_calibration_lock);
248ccee2438Stsutsui 
249b6262570Sthorpej  retry:
250b6262570Sthorpej 	atomic_store_release(&cc_calibration_state, CC_CAL_START);
251ccee2438Stsutsui 
252b6262570Sthorpej 	/* Trigger primary CPU. */
253b6262570Sthorpej 	cc_get_primary_cc();
254ccee2438Stsutsui 
255b6262570Sthorpej 	for (;;) {
256b6262570Sthorpej 		if (atomic_load_acquire(&cc_calibration_state) ==
257b6262570Sthorpej 		    CC_CAL_PRIMARY_READY) {
258b6262570Sthorpej 			break;
259b6262570Sthorpej 		}
260ccee2438Stsutsui 	}
261ccee2438Stsutsui 
262b6262570Sthorpej 	if (! cc_get_delta(ci)) {
263b6262570Sthorpej 		goto retry;
264b6262570Sthorpej 	}
265ccee2438Stsutsui 
266b6262570Sthorpej 	mutex_exit(&cc_calibration_lock);
267b6262570Sthorpej #endif /* MULTIPROCESSOR */
268b6262570Sthorpej }
269ccee2438Stsutsui 
270b6262570Sthorpej void
cc_primary_cc(void)271b6262570Sthorpej cc_primary_cc(void)
272b6262570Sthorpej {
273b6262570Sthorpej #if defined(MULTIPROCESSOR)
274b6262570Sthorpej 	/* N.B. We expect all interrupts to be blocked. */
275ccee2438Stsutsui 
276b6262570Sthorpej 	atomic_store_release(&cc_calibration_state, CC_CAL_PRIMARY_READY);
277ccee2438Stsutsui 
278b6262570Sthorpej 	for (;;) {
279b6262570Sthorpej 		if (atomic_load_acquire(&cc_calibration_state) ==
280b6262570Sthorpej 		    CC_CAL_SECONDARY_READY) {
281b6262570Sthorpej 			break;
282b6262570Sthorpej 		}
283b6262570Sthorpej 	}
284ccee2438Stsutsui 
285b6262570Sthorpej 	cc_primary = cpu_counter32();
286b6262570Sthorpej 	atomic_store_release(&cc_calibration_state, CC_CAL_FINISHED);
287b6262570Sthorpej #endif /* MULTIPROCESSOR */
288ccee2438Stsutsui }
289