Jetson thor如何准确获取tsc时间戳?

“using new interfaces for TSC HW timestamp”

根据以上帖子,l4t会提供新的接口获取tsc hw时间戳,请问新的获取tsc的时间戳的接口是什么?

hello BossOfNvidia,

please check Thor TRM for [6.3.2.2 Timestamp System Counter (TSC)].

Please check the TRM manual, which only describes the hardware composition of TSC and does not provide software interfaces.

如果贵司没有提供tsc hw的时间戳获取接口,那么我打算自己添加,我在6.3.2.7 TSC Controlled Signals 中看到tsc可以触发中断,能否提供tsc的中断号,和使能tsc中断的方法?

please refer to Argus::Ext::ISensorTimestampTsc() for more details.

回到我最开始的问题,[topic:78821]中反映的 getSensorTimestamp 获取到的时间不准确,这个bug现在解决了么?

it’s very old topic, and it’s for TX2 series. since we’ve lots of changes, please give it a try on the latest release version to have confirmation.

These might be helpful.

source/kernel/kernel-noble/drivers/clocksource/timer-tegra186.c
static u64 tegra186_timer_tsc_read(struct clocksource *cs)
{
        struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer,
                                                    tsc);
        u32 hi, lo, ss;

        hi = readl_relaxed(tegra->regs + TKETSC1);

        /*
         * The 56-bit value of the TSC is spread across two registers that are
         * not synchronized. In order to read them atomically, ensure that the
         * high 24 bits match before and after reading the low 32 bits.
         */
        do {
                /* snapshot the high 24 bits */
./source/kernel/kernel-noble/drivers/clocksource/timer-tegra186.c:static u64 tegra186_timer_tsc_read(struct clocksource *cs)
./source/kernel/kernel-noble/drivers/clocksource/timer-tegra186.c:      tegra->tsc.read = tegra186_timer_tsc_read;
tr -d '\0' </sys/firmware/devicetree/base/tsc_sig_gen@c230000/compatible; echo
xxd -g4 -l 64 /sys/firmware/devicetree/base/tsc_sig_gen@c230000/reg
nvidia,tegra264-cam-cdi-tsc
00000000: 00000000 0c230000 00000000 00000018  .....#..........
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:15:        tsc_controls {
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:16:            tsc_locking_config = <0x119>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:17:            tsc_locking_diff_configuration = <0x26c>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:18:            tsc_locking_ref_frequency_configuration = <0x0>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:19:            tsc_locking_control = <0x1>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:20:            tsc_locking_adjust_configuration = <0x0>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:21:            tsc_locking_fast_adjust_configuration = <0x57011>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:22:            tsc_locking_adjust_delta_control = <0x67>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:23:            tsc_capture_control_ptx = <0x0>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:24:            tsc_capture_config_ptx = <0x313>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:25:            tsc_stscrsr = <0x1>;
bootloader/tegra264-mb1-bct-tsc-defaults.dtsi:26:            tsc_locking_adjust_num_control = <0x0>;
./source/nvidia-oot/drivers/media/platform/tegra/cdi/cam_cdi_tsc.c:651: { .compatible = "nvidia,tegra264-cam-cdi-tsc", .data = &tegra264_cam_tsc_features },
./source/nvidia-oot/Documentation/devicetree/bindings/media/platform/tegra/cam_fsync/nvidia,tegra264-cdi-tsc.yaml:83:            compatible = "nvidia,tegra264-cam-cdi-tsc";
./source/hardware/nvidia/t264/nv-public/tegra264.dtsi:10347:            compatible = "nvidia,tegra264-cam-cdi-tsc";
sudo dmesg | grep -E "clocksource: tsc"
[    3.616792] clocksource: tsc: mask: 0xffffffffffffff max_cycles: 0xe6a171046, max_idle_ns: 881590405314 ns

Thank you, but this method can only export logs from dmesg and search for tsc timestamps. I need to read the timestamp of tsc from the user mode at any time, so I cannot use dmesg. I saw in the development documentation that the latest jetpack no longer uses GTE and instead uses HTE drivers, but I am not sure if HTE supports obtaining timestamps for tsc edge out pin signals.

If nvidia-l4t-jetson-multimedia-api not installed:

apt download
dpkg-deb -x nvidia-l4t-jetson-multimedia-api_38.4.0-20251230160601_arm64.deb

usr/src/jetson_multimedia_api/argus/include/Argus/Ext/SensorTimestampTsc.h

/**
 * @file
 * <b>Libargus Extension: TSC HW SensorTimestamp API</b>
 *
 * @b Description: This file defines the SensorTimestampTsc extension.
 */

#ifndef _ARGUS_SENSOR_TIMESTAMP_TSC_H
#define _ARGUS_SENSOR_TIMESTAMP_TSC_H

namespace Argus
{

/**
 * Adds a timestamp interface to get TSC HW timestamp.
 * It introduces one new interface:
 *   - Ext::ISensorTimestampTsc: gets TSC HW timestamp.
 * @defgroup ArgusExtSensorTimestampTsc Ext::SensorTimestampTsc
 * @ingroup ArgusExtensions
 */
DEFINE_UUID(ExtensionName, EXT_SENSOR_TIMESTAMP_TSC, e6cc1360,06ea,11eb,8b6e,08,00,20,0c,9a,66);


namespace Ext
{

/**
 * @class ISensorTimestampTsc
 *
 * Interface used to get TSC HW timestamp
 *
 * @ingroup ArgusCaptureMetadata ArgusExtSensorTimestampTsc
 */

cat /sys/firmware/devicetree/base/bus@0/hardware-timestamp@c2b0000/*
nvidia,tegra264-gte-aon&hardware-timestamp2�
cat /sys/firmware/devicetree/base/bus@0/hardware-timestamp@8380000/*
nvidia,tegra264-gte-lichhardware-timestamp8okay

If I understand this, following might point one way.

  1. Devicetree: enable provider and add a small consumer node.
    In source/hardware/nvidia/t264/nv-public/tegra264.dtsi they’re status=“disabled”. You need them okay plus a consumer node that references the line(s):
&hte_aon { status = "okay"; };
&hte_lic { status = "okay"; };

hte_forward@0 {
    compatible = "youname,hte-forward";
    /* One cell because #timestamp-cells = <1> */
    timestamps = <&hte_aon 42>;   /* example line id */
    timestamp-names = "aon42";
    status = "okay";
};
  1. Kernel module (platform driver + miscdevice + FIFO)
    This uses the exact API names from source/kernel/kernel-noble/include/linux/hte.h
    (of_hte_req_count, hte_ts_get, devm_hte_request_ts_ns, hte_enable_ts, etc.).
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/kfifo.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/timekeeping.h>
#include <linux/atomic.h>

#include "hte.h"   // or #include <linux/hte.h> in-tree

struct hte_evt_uapi {
    u64 hte_ns;            // edge time from HTE (ns)
    u64 cntvct_mid_ns;     // CNTVCT midpoint near callback (ns)
    s64 hte_minus_cntvct;  // hte_ns - cntvct_mid_ns (ns)
    u64 edge_realtime_ns;  // estimated CLOCK_REALTIME for the edge (ns)
    u64 realtime_now_ns;   // CLOCK_REALTIME sampled in callback (ns)
    u64 seq;
    s32 raw_level;         // -1 if unsupported
    u32 line_id;           // logical id (from desc->attr.line_id)
};

#define FIFO_N 2048
static DECLARE_KFIFO(evt_fifo, struct hte_evt_uapi, FIFO_N);
static spinlock_t fifo_lock;
static wait_queue_head_t wq;

static __always_inline u64 read_cntvct_el0(void)
{
    u64 v;
    asm volatile("mrs %0, cntvct_el0" : "=r"(v));
    return v; // on Thor, CNTFRQ=1e9 => ticks==ns
}

static __always_inline u64 read_cntvct_mid_ns(void)
{
    u64 b = read_cntvct_el0();
    u64 a = read_cntvct_el0();
    return b + ((a - b) >> 1);
}

struct hte_forward_ctx {
    struct device *dev;
    struct miscdevice misc;
    s64 real_minus_cntvct_ns;     // (CLOCK_REALTIME ns) - (CNTVCT ns) at probe
    atomic64_t last_real_ns;      // optional: for detecting time steps
    int ndesc;
    struct hte_ts_desc *descs;    // array
};

static void fifo_push(const struct hte_evt_uapi *e)
{
    unsigned long flags;
    spin_lock_irqsave(&fifo_lock, flags);
    kfifo_put(&evt_fifo, *e);
    spin_unlock_irqrestore(&fifo_lock, flags);
    wake_up_interruptible(&wq);
}

static enum hte_return hte_cb(struct hte_ts_data *ts, void *data)
{
    struct hte_ts_desc *desc = data;
    struct hte_forward_ctx *ctx =
        container_of(desc, struct hte_forward_ctx, descs[desc - ctx->descs]); // not valid C
    // The above is awkward; easiest: store ctx in desc->attr.line_data instead.

    return HTE_CB_HANDLED;
}

The callback needs a clean way to get ctx. Do it like this instead: set desc->attr.line_data = ctx after hte_ts_get(), and use that.
Here is the working callback + probe skeleton:

static enum hte_return hte_cb(struct hte_ts_data *ts, void *data)
{
    struct hte_ts_desc *desc = data;
    struct hte_forward_ctx *ctx = desc->attr.line_data;
    struct hte_evt_uapi e;

    u64 cntvct_mid = read_cntvct_mid_ns();
    u64 real_now   = ktime_get_real_ns(); // CLOCK_REALTIME in ns

    e.hte_ns            = ts->tsc;
    e.cntvct_mid_ns     = cntvct_mid;
    e.hte_minus_cntvct  = (s64)ts->tsc - (s64)cntvct_mid;
    e.edge_realtime_ns  = (u64)((s64)ts->tsc + ctx->real_minus_cntvct_ns);
    e.realtime_now_ns   = real_now;
    e.seq               = ts->seq;
    e.raw_level         = ts->raw_level;
    e.line_id           = desc->attr.line_id;

    atomic64_set(&ctx->last_real_ns, real_now);
    fifo_push(&e);

    return HTE_CB_HANDLED;
}

static ssize_t hte_read(struct file *f, char __user *ubuf, size_t len, loff_t *ppos)
{
    size_t want = len / sizeof(struct hte_evt_uapi);
    struct hte_evt_uapi tmp[32];
    unsigned long flags;
    size_t copied = 0;

    if (!want) return -EINVAL;

    while (copied < want) {
        int batch = min_t(int, want - copied, (size_t)ARRAY_SIZE(tmp));

        spin_lock_irqsave(&fifo_lock, flags);
        if (kfifo_is_empty(&evt_fifo)) {
            spin_unlock_irqrestore(&fifo_lock, flags);
            break;
        }
        batch = kfifo_out(&evt_fifo, tmp, batch);
        spin_unlock_irqrestore(&fifo_lock, flags);

        if (copy_to_user(ubuf + copied * sizeof(tmp[0]), tmp, batch * sizeof(tmp[0])))
            return -EFAULT;

        copied += batch;
    }

    return copied * sizeof(struct hte_evt_uapi);
}

static __poll_t hte_poll(struct file *f, poll_table *wait)
{
    __poll_t mask = 0;
    poll_wait(f, &wq, wait);
    if (!kfifo_is_empty(&evt_fifo))
        mask |= POLLIN | POLLRDNORM;
    return mask;
}

static const struct file_operations hte_fops = {
    .owner  = THIS_MODULE,
    .read   = hte_read,
    .poll   = hte_poll,
    .llseek = no_llseek,
};

static int hte_forward_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct hte_forward_ctx *ctx;
    int i, n, ret;

    ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
    if (!ctx) return -ENOMEM;

    ctx->dev = dev;

    // One-time bracketing correlation: CLOCK_REALTIME - CNTVCT
    {
        u64 c_before = read_cntvct_el0();
        u64 real     = ktime_get_real_ns();
        u64 c_after  = read_cntvct_el0();
        u64 c_mid    = c_before + ((c_after - c_before) >> 1);
        ctx->real_minus_cntvct_ns = (s64)real - (s64)c_mid;
    }
    atomic64_set(&ctx->last_real_ns, ktime_get_real_ns());

    n = of_hte_req_count(dev);
    if (n < 0) return n;
    if (n == 0) return -ENODEV;

    ctx->ndesc = n;
    ctx->descs = devm_kcalloc(dev, n, sizeof(*ctx->descs), GFP_KERNEL);
    if (!ctx->descs) return -ENOMEM;

    for (i = 0; i < n; i++) {
        struct hte_ts_desc *d = &ctx->descs[i];

        ret = hte_ts_get(dev, d, i);
        if (ret) return ret;

        // stash ctx pointer so callback can find it
        d->attr.line_data = ctx;

        ret = devm_hte_request_ts_ns(dev, d, hte_cb, NULL, d);
        if (ret) return ret;

        ret = hte_enable_ts(d);
        if (ret) return ret;
    }

    ctx->misc.minor = MISC_DYNAMIC_MINOR;
    ctx->misc.name  = "hte_forward0";
    ctx->misc.fops  = &hte_fops;
    ctx->misc.mode  = 0444;

    ret = misc_register(&ctx->misc);
    if (ret) return ret;

    platform_set_drvdata(pdev, ctx);
    dev_info(dev, "hte-forward: %d lines, real_minus_cntvct_ns=%lld\n",
             n, (long long)ctx->real_minus_cntvct_ns);
    return 0;
}

static int hte_forward_remove(struct platform_device *pdev)
{
    struct hte_forward_ctx *ctx = platform_get_drvdata(pdev);
    int i;

    misc_deregister(&ctx->misc);
    for (i = 0; i < ctx->ndesc; i++) {
        hte_disable_ts(&ctx->descs[i]);
        hte_ts_put(&ctx->descs[i]);
    }
    return 0;
}

static const struct of_device_id hte_forward_of_match[] = {
    { .compatible = "yourname,hte-forward" },
    { }
};
MODULE_DEVICE_TABLE(of, hte_forward_of_match);

static struct platform_driver hte_forward_driver = {
    .probe  = hte_forward_probe,
    .remove = hte_forward_remove,
    .driver = {
        .name = "hte-forward",
        .of_match_table = hte_forward_of_match,
    },
};

static int __init hte_forward_init(void)
{
    spin_lock_init(&fifo_lock);
    init_waitqueue_head(&wq);
    INIT_KFIFO(evt_fifo);
    return platform_driver_register(&hte_forward_driver);
}
static void __exit hte_forward_exit(void)
{
    platform_driver_unregister(&hte_forward_driver);
}
module_init(hte_forward_init);
module_exit(hte_forward_exit);

MODULE_LICENSE("GPL");

Userspace reader (prints wall clock too)
Read /dev/hte_forward0, each record has edge_realtime_ns (wall clock estimate for the edge)
hte_ns, cntvct_mid_ns, and hte_minus_cntvct to validate the mapping
You can render wall-clock as time_t sec = ns/1e9; nsec = ns%1e9.

// read_cntvct.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <time.h>

static inline uint64_t read_cntvct_el0(void)
{
    uint64_t v;
    asm volatile("mrs %0, cntvct_el0" : "=r"(v));
    return v;
}

static inline uint64_t read_cntfrq_el0(void)
{
    uint64_t f;
    asm volatile("mrs %0, cntfrq_el0" : "=r"(f));
    return f;
}

static inline uint64_t timespec_to_ns(const struct timespec *ts)
{
    return (uint64_t)ts->tv_sec * 1000000000ull + (uint64_t)ts->tv_nsec;
}

static inline void ns_to_timespec(uint64_t ns, struct timespec *ts)
{
    ts->tv_sec  = (time_t)(ns / 1000000000ull);
    ts->tv_nsec = (long)(ns % 1000000000ull);
}

static inline uint64_t cntvct_to_ns(uint64_t c, uint64_t freq_hz)
{
    __uint128_t t = (__uint128_t)c * 1000000000ull;
    return (uint64_t)(t / freq_hz);
}

int main(void)
{
    uint64_t freq = read_cntfrq_el0();

    /*
     * Bracketed correlation:
     *   CNTVCT(before) -> CLOCK_REALTIME -> CNTVCT(after)
     * Use midpoint counter value to reduce skew.
     */
    uint64_t c_before = read_cntvct_el0();

    struct timespec rt0_ts;
    if (clock_gettime(CLOCK_REALTIME, &rt0_ts) != 0) {
        perror("clock_gettime(CLOCK_REALTIME)");
        return 1;
    }
    uint64_t rt0_ns = timespec_to_ns(&rt0_ts);

    uint64_t c_after = read_cntvct_el0();
    uint64_t c_mid = c_before + ((c_after - c_before) / 2);

    uint64_t c0_ns = cntvct_to_ns(c_mid, freq);

    // wallclock_ns ~= counter_ns + offset_ns
    int64_t offset_ns = (int64_t)rt0_ns - (int64_t)c0_ns;

    // Read a fresh counter value and estimate realtime from it
    uint64_t c1 = read_cntvct_el0();
    uint64_t c1_ns = cntvct_to_ns(c1, freq);
    int64_t est_rt1_ns = (int64_t)c1_ns + offset_ns;

    struct timespec est_rt1_ts;
    ns_to_timespec((uint64_t)est_rt1_ns, &est_rt1_ts);

    // Actual realtime now (for comparison)
    struct timespec rt1_ts;
    if (clock_gettime(CLOCK_REALTIME, &rt1_ts) != 0) {
        perror("clock_gettime(CLOCK_REALTIME)");
        return 1;
    }

    int64_t actual_rt1_ns = (int64_t)timespec_to_ns(&rt1_ts);
    int64_t err_ns = actual_rt1_ns - est_rt1_ns;

    printf("CNTFRQ=%llu Hz\n", (unsigned long long)freq);
    printf("CNTVCT=%llu ticks\n", (unsigned long long)c1);
    printf("counter_ns_since_boot=%llu ns\n", (unsigned long long)c1_ns);

    printf("snapshot_realtime=%lld.%09ld (CLOCK_REALTIME at correlation)\n",
           (long long)rt0_ts.tv_sec, rt0_ts.tv_nsec);
    printf("offset_ns=%lld (realtime_ns - counter_ns)\n",
           (long long)offset_ns);

    printf("estimated_realtime_from_counter=%lld.%09ld\n",
           (long long)est_rt1_ts.tv_sec, est_rt1_ts.tv_nsec);
    printf("actual_realtime_now=%lld.%09ld\n",
           (long long)rt1_ts.tv_sec, rt1_ts.tv_nsec);

    printf("estimation_error_ns=%lld\n", (long long)err_ns);

    return 0;
}