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