summaryrefslogtreecommitdiff
path: root/libipt/src/pt_time.c
diff options
context:
space:
mode:
Diffstat (limited to 'libipt/src/pt_time.c')
-rw-r--r--libipt/src/pt_time.c760
1 files changed, 760 insertions, 0 deletions
diff --git a/libipt/src/pt_time.c b/libipt/src/pt_time.c
new file mode 100644
index 000000000000..d1698147b3d1
--- /dev/null
+++ b/libipt/src/pt_time.c
@@ -0,0 +1,760 @@
+/*
+ * Copyright (c) 2014-2019, 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 "pt_opcodes.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 = (uint32_t) 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 & ~(uint32_t) 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 & (uint32_t) pt_pl_tma_ctc_mask;
+ ctc_hi = ctc & ~(uint32_t) 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 &= (uint32_t) 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 = (uint32_t) 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;
+}
+
+int pt_tcal_update_ovf(struct pt_time_cal *tcal,
+ const struct pt_config *config)
+{
+ if (!tcal || !config)
+ return -pte_internal;
+
+ tcal->tsc = 0ull;
+ tcal->cyc_tsc = 0ull;
+ tcal->cyc_mtc = 0ull;
+ tcal->ctc = 0;
+ tcal->have_mtc = 0;
+
+ return 0;
+}