“using new interfaces for TSC HW timestamp”
根据以上帖子,l4t会提供新的接口获取tsc hw时间戳,请问新的获取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.
&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";
};
// 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;
}