blob: e225a3529006ec0914a34f8901414912a912793d [file] [log] [blame]
/*
* Copyright (c) 2014-2021, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Intel Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "pt_time.h"
#include "intel-pt.h"
#include <string.h>
#include <limits.h>
void pt_time_init(struct pt_time *time)
{
if (!time)
return;
memset(time, 0, sizeof(*time));
}
int pt_time_query_tsc(uint64_t *tsc, uint32_t *lost_mtc,
uint32_t *lost_cyc, const struct pt_time *time)
{
if (!tsc || !time)
return -pte_internal;
*tsc = time->tsc;
if (lost_mtc)
*lost_mtc = time->lost_mtc;
if (lost_cyc)
*lost_cyc = time->lost_cyc;
if (!time->have_tsc)
return -pte_no_time;
return 0;
}
int pt_time_query_cbr(uint32_t *cbr, const struct pt_time *time)
{
if (!cbr || !time)
return -pte_internal;
if (!time->have_cbr)
return -pte_no_cbr;
*cbr = time->cbr;
return 0;
}
/* Compute the distance between two CTC sources.
*
* We adjust a single wrap-around but fail if the distance is bigger than that.
*
* Returns zero on success, a negative error code otherwise.
*/
static int pt_time_ctc_delta(uint32_t *ctc_delta, uint32_t ctc,
uint32_t last_ctc, const struct pt_config *config)
{
if (!config || !ctc_delta)
return -pte_internal;
/* Correct a single wrap-around. If we lost enough MTCs to wrap
* around twice, timing will be wrong until the next TSC.
*/
if (ctc < last_ctc) {
ctc += 1u << (config->mtc_freq + pt_pl_mtc_bit_size);
/* Since we only store the CTC between TMA/MTC or MTC/TMC a
* single correction should suffice.
*/
if (ctc < last_ctc)
return -pte_bad_packet;
}
*ctc_delta = ctc - last_ctc;
return 0;
}
/* Translate CTC into the same unit as the FastCounter by multiplying with P.
*
* Returns zero on success, a negative error code otherwise.
*/
static int pt_time_ctc_fc(uint64_t *fc, uint64_t ctc,
const struct pt_config *config)
{
uint32_t eax, ebx;
if (!fc || !config)
return -pte_internal;
eax = config->cpuid_0x15_eax;
ebx = config->cpuid_0x15_ebx;
/* Neither multiply nor divide by zero. */
if (!eax || !ebx)
return -pte_bad_config;
*fc = (ctc * ebx) / eax;
return 0;
}
int pt_time_update_tsc(struct pt_time *time,
const struct pt_packet_tsc *packet,
const struct pt_config *config)
{
(void) config;
if (!time || !packet)
return -pte_internal;
time->have_tsc = 1;
time->have_tma = 0;
time->have_mtc = 0;
time->tsc = time->base = packet->tsc;
time->ctc = 0;
time->fc = 0ull;
/* We got the full time; we recover from previous losses. */
time->lost_mtc = 0;
time->lost_cyc = 0;
return 0;
}
int pt_time_update_cbr(struct pt_time *time,
const struct pt_packet_cbr *packet,
const struct pt_config *config)
{
uint8_t cbr;
(void) config;
if (!time || !packet)
return -pte_internal;
cbr = packet->ratio;
if (!cbr)
return -pte_bad_packet;
time->have_cbr = 1;
time->cbr = cbr;
return 0;
}
int pt_time_update_tma(struct pt_time *time,
const struct pt_packet_tma *packet,
const struct pt_config *config)
{
uint32_t ctc, mtc_freq, mtc_hi, ctc_mask;
uint64_t fc;
if (!time || !packet || !config)
return -pte_internal;
/* Without a TSC something is seriously wrong. */
if (!time->have_tsc)
return -pte_bad_context;
/* We shouldn't have more than one TMA per TSC. */
if (time->have_tma)
return -pte_bad_context;
/* We're ignoring MTC between TSC and TMA. */
if (time->have_mtc)
return -pte_internal;
ctc = packet->ctc;
fc = packet->fc;
mtc_freq = config->mtc_freq;
mtc_hi = mtc_freq + pt_pl_mtc_bit_size;
/* A mask for the relevant CTC bits ignoring high-order bits that are
* not provided by MTC.
*/
ctc_mask = (1u << mtc_hi) - 1u;
time->have_tma = 1;
time->base -= fc;
time->fc += fc;
/* If the MTC frequency is low enough that TMA provides the full CTC
* value, we can use the TMA as an MTC.
*
* If it isn't, we will estimate the preceding MTC based on the CTC bits
* the TMA provides at the next MTC. We forget about the previous MTC
* in this case.
*
* If no MTC packets are dropped around TMA, we will estimate the
* forgotten value again at the next MTC.
*
* If MTC packets are dropped, we can't really tell where in this
* extended MTC period the TSC occurred. The estimation will place it
* right before the next MTC.
*/
if (mtc_hi <= pt_pl_tma_ctc_bit_size)
time->have_mtc = 1;
/* In both cases, we store the TMA's CTC bits until the next MTC. */
time->ctc = time->ctc_cyc = ctc & ctc_mask;
return 0;
}
int pt_time_update_mtc(struct pt_time *time,
const struct pt_packet_mtc *packet,
const struct pt_config *config)
{
uint32_t last_ctc, ctc, ctc_delta;
uint64_t tsc, base;
uint8_t mtc_freq;
int errcode, have_tsc, have_tma, have_mtc;
if (!time || !packet || !config)
return -pte_internal;
have_tsc = time->have_tsc;
have_tma = time->have_tma;
have_mtc = time->have_mtc;
/* We ignore MTCs between TSC and TMA to avoid apparent CTC overflows.
*
* Later MTCs will ensure that no time is lost - provided TMA provides
* enough bits. If TMA doesn't provide any of the MTC bits we may place
* the TSC into the wrong MTC period.
*/
if (have_tsc && !have_tma)
return 0;
base = time->base;
last_ctc = time->ctc;
mtc_freq = config->mtc_freq;
ctc = packet->ctc << mtc_freq;
/* Store our CTC value if we have or would have reset FC. */
if (time->fc || time->lost_cyc || !have_mtc)
time->ctc_cyc = ctc;
/* Prepare for the next packet in case we error out below. */
time->have_mtc = 1;
time->fc = 0ull;
time->ctc = ctc;
/* We recover from previous CYC losses. */
time->lost_cyc = 0;
/* Avoid a big jump when we see the first MTC with an arbitrary CTC
* payload.
*/
if (!have_mtc) {
uint32_t ctc_lo, ctc_hi;
/* If we have not seen a TMA, we ignore this first MTC.
*
* We have no idea where in this MTC period tracing started.
* We could lose an entire MTC period or just a tiny fraction.
*
* On the other hand, if we assumed a previous MTC value, we
* might make just the same error.
*/
if (!have_tma)
return 0;
/* The TMA's CTC value didn't provide enough bits - otherwise,
* we would have treated the TMA as an MTC.
*/
if (last_ctc & ~pt_pl_tma_ctc_mask)
return -pte_internal;
/* Split this MTC's CTC value into low and high parts with
* respect to the bits provided by TMA.
*/
ctc_lo = ctc & pt_pl_tma_ctc_mask;
ctc_hi = ctc & ~pt_pl_tma_ctc_mask;
/* We estimate the high-order CTC bits that are not provided by
* TMA based on the CTC bits provided by this MTC.
*
* We assume that no MTC packets were dropped around TMA. If
* there are, we might place the TSC into the wrong MTC period
* depending on how many CTC bits TMA provides and how many MTC
* packets were dropped.
*
* Note that we may underflow which results in more bits to be
* set than MTC packets may provide. Drop those extra bits.
*/
if (ctc_lo < last_ctc) {
ctc_hi -= 1u << pt_pl_tma_ctc_bit_size;
ctc_hi &= pt_pl_mtc_mask << mtc_freq;
}
last_ctc |= ctc_hi;
}
errcode = pt_time_ctc_delta(&ctc_delta, ctc, last_ctc, config);
if (errcode < 0) {
time->lost_mtc += 1;
return errcode;
}
errcode = pt_time_ctc_fc(&tsc, ctc_delta, config);
if (errcode < 0)
return errcode;
base += tsc;
time->tsc = time->base = base;
return 0;
}
/* Adjust a CYC packet's payload spanning multiple MTC periods.
*
* CYC packets measure the Fast Counter since the last CYC(-eligible) packet.
* Depending on the CYC threshold, we may not get a CYC for each MTC, so a CYC
* period may overlap with or even span multiple MTC periods.
*
* We can't do much about the overlap case without examining all packets in
* the respective periods. We leave this as expected imprecision.
*
* If we find a CYC packet to span multiple MTC packets, though, we try to
* approximate the portion for the current MTC period by subtracting the
* estimated portion for previous MTC periods using calibration information.
*
* We only consider MTC. For the first CYC after TSC, the corresponding TMA
* will contain the Fast Counter at TSC.
*
* Returns zero on success, a negative error code otherwise.
*/
static int pt_time_adjust_cyc(uint64_t *cyc, const struct pt_time *time,
const struct pt_config *config, uint64_t fcr)
{
uint32_t last_ctc, ctc, ctc_delta;
uint64_t fc, total_cyc, old_cyc;
int errcode;
if (!time || !config || !fcr)
return -pte_internal;
last_ctc = time->ctc_cyc;
ctc = time->ctc;
/* There is nothing to do if this is the current MTC period. */
if (ctc == last_ctc)
return 0;
/* Calibration computes
*
* fc = (ctc_delta * cpuid[0x15].ebx) / cpuid[0x15].eax.
* fcr = (fc << pt_tcal_fcr_shr) / cyc
*
* So cyc = (fc << pt_tcal_fcr_shr) / fcr.
*/
errcode = pt_time_ctc_delta(&ctc_delta, ctc, last_ctc, config);
if (errcode < 0)
return errcode;
errcode = pt_time_ctc_fc(&fc, ctc_delta, config);
if (errcode < 0)
return errcode;
old_cyc = (fc << pt_tcal_fcr_shr) / fcr;
total_cyc = *cyc;
/* Make sure we don't wrap around. If we would, attribute the entire
* CYC payload to any previous MTC period.
*
* We lost an unknown portion of the CYC payload for the current MTC
* period, but it's usually better to run too slow than too fast.
*/
if (total_cyc < old_cyc)
total_cyc = old_cyc;
*cyc = total_cyc - old_cyc;
return 0;
}
int pt_time_update_cyc(struct pt_time *time,
const struct pt_packet_cyc *packet,
const struct pt_config *config, uint64_t fcr)
{
uint64_t cyc, fc;
if (!time || !packet || !config)
return -pte_internal;
if (!fcr) {
time->lost_cyc += 1;
return 0;
}
cyc = packet->value;
fc = time->fc;
if (!fc) {
int errcode;
errcode = pt_time_adjust_cyc(&cyc, time, config, fcr);
if (errcode < 0)
return errcode;
}
fc += (cyc * fcr) >> pt_tcal_fcr_shr;
time->fc = fc;
time->tsc = time->base + fc;
return 0;
}
void pt_tcal_init(struct pt_time_cal *tcal)
{
if (!tcal)
return;
memset(tcal, 0, sizeof(*tcal));
tcal->min_fcr = UINT64_MAX;
}
static int pt_tcal_have_fcr(const struct pt_time_cal *tcal)
{
if (!tcal)
return 0;
return (tcal->min_fcr <= tcal->max_fcr);
}
int pt_tcal_fcr(uint64_t *fcr, const struct pt_time_cal *tcal)
{
if (!fcr || !tcal)
return -pte_internal;
if (!pt_tcal_have_fcr(tcal))
return -pte_no_time;
*fcr = tcal->fcr;
return 0;
}
int pt_tcal_set_fcr(struct pt_time_cal *tcal, uint64_t fcr)
{
if (!tcal)
return -pte_internal;
tcal->fcr = fcr;
if (fcr < tcal->min_fcr)
tcal->min_fcr = fcr;
if (fcr > tcal->max_fcr)
tcal->max_fcr = fcr;
return 0;
}
int pt_tcal_update_psb(struct pt_time_cal *tcal,
const struct pt_config *config)
{
if (!tcal || !config)
return -pte_internal;
if (config->errata.skl168)
tcal->check_skl168 = 1;
return 0;
}
int pt_tcal_update_tsc(struct pt_time_cal *tcal,
const struct pt_packet_tsc *packet,
const struct pt_config *config)
{
(void) config;
if (!tcal || !packet)
return -pte_internal;
/* A TSC outside of PSB+ may indicate loss of time. We do not use it
* for calibration. We store the TSC value for calibration at the next
* TSC in PSB+, though.
*/
tcal->tsc = packet->tsc;
tcal->cyc_tsc = 0ull;
return 0;
}
int pt_tcal_header_tsc(struct pt_time_cal *tcal,
const struct pt_packet_tsc *packet,
const struct pt_config *config)
{
uint64_t tsc, last_tsc, tsc_delta, cyc, fcr;
(void) config;
if (!tcal || !packet)
return -pte_internal;
last_tsc = tcal->tsc;
cyc = tcal->cyc_tsc;
tsc = packet->tsc;
tcal->tsc = tsc;
tcal->cyc_tsc = 0ull;
if (!last_tsc || !cyc)
return 0;
/* Prefer MTC over TSC for calibration. */
if (tcal->have_mtc)
return 0;
/* Correct a single wrap-around. */
if (tsc < last_tsc) {
tsc += 1ull << pt_pl_tsc_bit_size;
if (tsc < last_tsc)
return -pte_bad_packet;
}
tsc_delta = tsc - last_tsc;
/* We shift the nominator to improve rounding precision.
*
* Since we're only collecting the CYCs between two TSC, we shouldn't
* overflow. Let's rather fail than overflow.
*/
if (tsc_delta & ~(~0ull >> pt_tcal_fcr_shr))
return -pte_internal;
fcr = (tsc_delta << pt_tcal_fcr_shr) / cyc;
return pt_tcal_set_fcr(tcal, fcr);
}
int pt_tcal_update_cbr(struct pt_time_cal *tcal,
const struct pt_packet_cbr *packet,
const struct pt_config *config)
{
/* A CBR outside of PSB+ indicates a frequency change. Reset our
* calibration state.
*/
pt_tcal_init(tcal);
return pt_tcal_header_cbr(tcal, packet, config);
}
int pt_tcal_header_cbr(struct pt_time_cal *tcal,
const struct pt_packet_cbr *packet,
const struct pt_config *config)
{
uint64_t cbr, p1, fcr;
if (!tcal || !packet || !config)
return -pte_internal;
p1 = config->nom_freq;
if (!p1)
return 0;
/* If we know the nominal frequency, we can use it for calibration. */
cbr = packet->ratio;
if (!cbr)
return -pte_bad_packet;
fcr = (p1 << pt_tcal_fcr_shr) / cbr;
return pt_tcal_set_fcr(tcal, fcr);
}
int pt_tcal_update_tma(struct pt_time_cal *tcal,
const struct pt_packet_tma *packet,
const struct pt_config *config)
{
(void) tcal;
(void) packet;
(void) config;
/* Nothing to do. */
return 0;
}
int pt_tcal_update_mtc(struct pt_time_cal *tcal,
const struct pt_packet_mtc *packet,
const struct pt_config *config)
{
uint32_t last_ctc, ctc, ctc_delta, have_mtc, check_skl168;
uint64_t cyc, fc, fcr;
int errcode;
if (!tcal || !packet || !config)
return -pte_internal;
last_ctc = tcal->ctc;
have_mtc = tcal->have_mtc;
cyc = tcal->cyc_mtc;
check_skl168 = tcal->check_skl168;
/* This only affects the first MTC after PSB. */
tcal->check_skl168 = 0;
ctc = packet->ctc << config->mtc_freq;
/* We need at least two MTC (including this). */
if (!have_mtc) {
tcal->cyc_mtc = 0ull;
tcal->ctc = ctc;
tcal->have_mtc = 1;
return 0;
}
/* Without any cycles, we can't calibrate. Try again at the next
* MTC and distribute the cycles over the combined MTC period.
*/
if (!cyc)
return 0;
/* Prepare for the next packet in case we error out below. */
tcal->have_mtc = 1;
tcal->cyc_mtc = 0ull;
tcal->ctc = ctc;
/* Let's pretend we will fail. We'll correct it at the end. */
tcal->lost_mtc += 1;
errcode = pt_time_ctc_delta(&ctc_delta, ctc, last_ctc, config);
if (errcode < 0)
return errcode;
errcode = pt_time_ctc_fc(&fc, ctc_delta, config);
if (errcode < 0)
return errcode;
/* We shift the nominator to improve rounding precision.
*
* Since we're only collecting the CYCs between two MTC, we shouldn't
* overflow. Let's rather fail than overflow.
*/
if (fc & ~(~0ull >> pt_tcal_fcr_shr))
return -pte_internal;
fcr = (fc << pt_tcal_fcr_shr) / cyc;
/* SKL168: Intel(R) PT CYC Packets Can be Dropped When Immediately
* Preceding PSB.
*
* We skip this MTC if we lost one or more MTC since the last PSB or if
* it looks like we lost a wrap CYC packet.
*
* This is not an error but we count that MTC as lost.
*/
if (check_skl168) {
/* If we lost one or more MTC, the case is clear. */
if ((1u << config->mtc_freq) < ctc_delta)
return 0;
/* The case is less clear for a lost wrap CYC packet since we do
* have some variation in the number of cycles.
*
* The CYC counter wraps on the affected processors every 4096
* cycles. For low MTC frequencies (high values), losing one
* may not be noticeable.
*
* We restrict the workaround to higher MTC frequencies (lower
* values).
*
* We also need a previous FCR so we know how many cycles to
* expect.
*/
if ((config->mtc_freq < 10) && pt_tcal_have_fcr(tcal)) {
uint64_t dfc;
/* We choose a slightly lower adjustment to account for
* some normal variation.
*/
dfc = (tcal->fcr * (cyc + 0xf00)) >> pt_tcal_fcr_shr;
/* If we didn't drop a wrap CYC, @dfc should be way
* bigger than @fc. If it isn't, we assume that the
* erratum applied.
*/
if (dfc < fc)
return 0;
}
}
errcode = pt_tcal_set_fcr(tcal, fcr);
if (errcode < 0)
return errcode;
/* We updated the FCR. This recovers from previous MTC losses. */
tcal->lost_mtc = 0;
return 0;
}
int pt_tcal_update_cyc(struct pt_time_cal *tcal,
const struct pt_packet_cyc *packet,
const struct pt_config *config)
{
uint64_t cyc;
(void) config;
if (!tcal || !packet)
return -pte_internal;
cyc = packet->value;
tcal->cyc_mtc += cyc;
tcal->cyc_tsc += cyc;
return 0;
}