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