* [PATCH 0/2] nova-core: add initial support for gpu timer
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
0 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
The GPU has a timer, one of the pieces of information
we need to pass into GSP init later is the timer offset.
This series just adds support for setting up the timer
on Turing+, with hals for Turing and Hopper.
Right now it calculates the value to send to GSP but
the code to send it will be added later.
For some hardware variants sending a proper value
is required for GSP init to succeed.
Dave.
---
Dave Airlie (2):
rust/timer: expose as_nanos for clock sources.
gpu: nova-core: add support for GPU timer block.
drivers/gpu/nova-core/gpu.rs | 5 +++
drivers/gpu/nova-core/nova_core.rs | 1 +
drivers/gpu/nova-core/regs.rs | 40 +++++++++++++++++++++
drivers/gpu/nova-core/timer.rs | 37 +++++++++++++++++++
drivers/gpu/nova-core/timer/hal.rs | 32 +++++++++++++++++
drivers/gpu/nova-core/timer/hal/gh100.rs | 61 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/timer/hal/tu102.rs | 55 ++++++++++++++++++++++++++++
rust/kernel/time.rs | 3 +-
8 files changed, 233 insertions(+), 1 deletion(-)
---
base-commit: 3497a403264b094ebd61b8b755165d56e7d9dc37
change-id: 20261026-drm-rust-next-d81ce0f4ecdf
Best regards,
--
Dave Airlie <airlied@redhat.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 0/2] nova-core: add initial support for gpu timer
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
0 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie via B4 Relay @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
The GPU has a timer, one of the pieces of information
we need to pass into GSP init later is the timer offset.
This series just adds support for setting up the timer
on Turing+, with hals for Turing and Hopper.
Right now it calculates the value to send to GSP but
the code to send it will be added later.
For some hardware variants sending a proper value
is required for GSP init to succeed.
Dave.
---
Dave Airlie (2):
rust/timer: expose as_nanos for clock sources.
gpu: nova-core: add support for GPU timer block.
drivers/gpu/nova-core/gpu.rs | 5 +++
drivers/gpu/nova-core/nova_core.rs | 1 +
drivers/gpu/nova-core/regs.rs | 40 +++++++++++++++++++++
drivers/gpu/nova-core/timer.rs | 37 +++++++++++++++++++
drivers/gpu/nova-core/timer/hal.rs | 32 +++++++++++++++++
drivers/gpu/nova-core/timer/hal/gh100.rs | 61 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/timer/hal/tu102.rs | 55 ++++++++++++++++++++++++++++
rust/kernel/time.rs | 3 +-
8 files changed, 233 insertions(+), 1 deletion(-)
---
base-commit: 3497a403264b094ebd61b8b755165d56e7d9dc37
change-id: 20261026-drm-rust-next-d81ce0f4ecdf
Best regards,
--
Dave Airlie <airlied@redhat.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/2] rust/timer: expose as_nanos for clock sources.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
-1 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
The nouveau C driver does ktime_to_ns(ktime_get()) to program
the cpu ns into some hw/fw, so allow this method for rust drivers.
Signed-off-by: Dave Airlie <airlied@redhat.com>
---
rust/kernel/time.rs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
index 6c0a5e8090d0..7fc366842901 100644
--- a/rust/kernel/time.rs
+++ b/rust/kernel/time.rs
@@ -211,8 +211,9 @@ pub fn elapsed(&self) -> Delta {
Self::now() - *self
}
+ /// Return the raw nanonseconds values from the clock source.
#[inline]
- pub(crate) fn as_nanos(&self) -> i64 {
+ pub fn as_nanos(&self) -> i64 {
self.inner
}
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 1/2] rust/timer: expose as_nanos for clock sources.
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
0 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie via B4 Relay @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
From: Dave Airlie <airlied@redhat.com>
The nouveau C driver does ktime_to_ns(ktime_get()) to program
the cpu ns into some hw/fw, so allow this method for rust drivers.
Signed-off-by: Dave Airlie <airlied@redhat.com>
---
rust/kernel/time.rs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
index 6c0a5e8090d0..7fc366842901 100644
--- a/rust/kernel/time.rs
+++ b/rust/kernel/time.rs
@@ -211,8 +211,9 @@ pub fn elapsed(&self) -> Delta {
Self::now() - *self
}
+ /// Return the raw nanonseconds values from the clock source.
#[inline]
- pub(crate) fn as_nanos(&self) -> i64 {
+ pub fn as_nanos(&self) -> i64 {
self.inner
}
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/2] gpu: nova-core: add support for GPU timer block.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
-1 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
This is needed to calculate the system timer offset that is passed
to GSP init.
2 HALs exist:
a) tu102 is the gv100 path from open-gpu, the priv level is checked
and the cpu time is written to the gpu timer. no offset is used.
b) gh100 is the gh100 path from open-gpu, where the secure system
timer is read, the offset is calculated and on everything after
GH100 the offset gets written to secure registers, and also
sent to GSP.
Signed-off-by: Dave Airlie <airlied@redhat.com>
---
drivers/gpu/nova-core/gpu.rs | 5 +++
drivers/gpu/nova-core/nova_core.rs | 1 +
drivers/gpu/nova-core/regs.rs | 40 +++++++++++++++++++++
drivers/gpu/nova-core/timer.rs | 37 +++++++++++++++++++
drivers/gpu/nova-core/timer/hal.rs | 32 +++++++++++++++++
drivers/gpu/nova-core/timer/hal/gh100.rs | 61 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/timer/hal/tu102.rs | 55 ++++++++++++++++++++++++++++
7 files changed, 231 insertions(+)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 4e59072cab50..85ff4a98d783 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -53,6 +53,7 @@
GpuMm,
VramAddress, //
},
+ timer::Timer,
vgpu::VgpuManager, //
};
@@ -331,6 +332,8 @@ struct GspResources<'gpu> {
// TODO: use different resource types for each boot method, and make the relevant Gsp methods
// generic against them.
fsp: Option<Fsp<'gpu>>,
+ /// Timer instance - contains offset
+ timer: Timer,
/// vGPU state detected before GSP boot.
vgpu: VgpuManager,
/// GSP runtime data.
@@ -497,6 +500,8 @@ pub(crate) fn new<'a>(
fsp: Fsp::try_new(dev, bar, spec.chipset)?,
+ timer: Timer::new(bar, spec.chipset)?,
+
vgpu: VgpuManager::new(pdev, spec.chipset, fsp.as_mut()),
gsp <- Gsp::new(pdev, bar),
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index a8f6dc22057b..ee59d81e5105 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -27,6 +27,7 @@
mod sbuffer;
#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
mod selftest;
+mod timer;
mod vbios;
mod vgpu;
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 88662da634dc..d5c67625b561 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -419,6 +419,24 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
}
}
+// PTIMER
+
+register! {
+ base: NovaRegisters;
+
+ pub(crate) NV_PTIMER_TIME_PRIV_LEVEL_MASK(u32) @ 0x00009430 {
+ 4:4 level0 => bool;
+ }
+
+ pub(crate) NV_PTIMER_TIME_0(u32) @ 0x00009400 {
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PTIMER_TIME_1(u32) @ 0x00009410 {
+ 28:0 nsec;
+ }
+}
+
// The modules below provide registers that are not identical on all supported chips. They should
// only be used in HAL modules.
@@ -520,6 +538,28 @@ pub(crate) mod gh100 {
31:0 fsp_boot_complete;
}
}
+
+ // PGC6/TIMER
+ register! {
+ base: NovaRegisters;
+
+ pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_0(u32) @ 0x00118f54 {
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_1(u32) @ 0x00118f58 {
+ 28:0 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_0(u32) @ 0x00118df4 {
+ 0:0 update;
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_1(u32) @ 0x00118df8 {
+ 28:0 nsec;
+ }
+ }
}
pub(crate) mod gb202 {
diff --git a/drivers/gpu/nova-core/timer.rs b/drivers/gpu/nova-core/timer.rs
new file mode 100644
index 000000000000..7fcb9cff8648
--- /dev/null
+++ b/drivers/gpu/nova-core/timer.rs
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+mod hal;
+
+use kernel::{
+ prelude::*,
+ time::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset, //
+};
+
+#[pin_data]
+pub(crate) struct Timer {
+ chipset: Chipset,
+ sys_timer_offset_ns: i64,
+}
+
+impl Timer {
+ pub(crate) fn new(bar: Bar0<'_>, chipset: Chipset) -> Result<Self> {
+ Ok(Self {
+ chipset,
+ sys_timer_offset_ns: hal::timer_hal(chipset).init(chipset, bar)?,
+ })
+ }
+
+ #[expect(dead_code)]
+ pub(crate) fn sys_timer_offset_ns(&self) -> i64 {
+ self.sys_timer_offset_ns
+ }
+}
+
+pub(crate) fn get_cpu_time_ns() -> i64 {
+ Instant::<Monotonic>::now().as_nanos()
+}
diff --git a/drivers/gpu/nova-core/timer/hal.rs b/drivers/gpu/nova-core/timer/hal.rs
new file mode 100644
index 000000000000..2a383cf7688c
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::prelude::*;
+
+use crate::{
+ driver::Bar0,
+ gpu::{
+ Architecture,
+ Chipset, //
+ },
+};
+
+mod gh100;
+mod tu102;
+
+pub(crate) trait TimerHal {
+ /// return system timer offset ns
+ fn init(&self, chipset: Chipset, bar: Bar0<'_>) -> Result<i64>;
+
+ /// read the timer
+ #[allow(dead_code)]
+ fn timer_read(&self, bar: Bar0<'_>) -> u64;
+}
+
+pub(super) fn timer_hal(chipset: Chipset) -> &'static dyn TimerHal {
+ match chipset.arch() {
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada => tu102::TU102_HAL,
+ Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+ gh100::GH100_HAL
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/timer/hal/gh100.rs b/drivers/gpu/nova-core/timer/hal/gh100.rs
new file mode 100644
index 000000000000..e917446e7b01
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal/gh100.rs
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::TimerHal;
+
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset,
+ regs,
+ timer,
+ timer::hal::tu102, //
+};
+
+struct Gh100;
+
+pub(crate) fn timer_time(bar: Bar0<'_>, chipset: Chipset, time_ns: u64) -> Result<u64> {
+ let sec_hi2 = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
+ let sec_timer_ns: u64;
+ loop {
+ let sec_lo = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_0));
+ let sec_hi = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
+ if sec_hi == sec_hi2 {
+ sec_timer_ns = u64::from(sec_hi) << 32 | u64::from(sec_lo);
+ break;
+ }
+ }
+
+ let sys_timer_offset_ns = time_ns - sec_timer_ns;
+
+ // kfspRequiresBug3957833WAR_HAL
+ if chipset != Chipset::GH100 {
+ bar.write_reg(
+ regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_1::zeroed()
+ .try_with_nsec((sys_timer_offset_ns >> 32) as u32)?,
+ );
+ bar.write_reg(
+ regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_0::zeroed()
+ .try_with_nsec((time_ns & 0xffffffff) as u32)?,
+ );
+ }
+ Ok(sys_timer_offset_ns)
+}
+
+impl TimerHal for Gh100 {
+ fn init(&self, chipset: Chipset, bar: Bar0<'_>) -> Result<i64> {
+ let cpu: i64 = timer::get_cpu_time_ns();
+ let sys_timer_offset_ns = timer_time(bar, chipset, cpu as u64)?;
+ Ok(sys_timer_offset_ns as i64)
+ }
+
+ fn timer_read(&self, bar: Bar0<'_>) -> u64 {
+ tu102::timer_read(bar)
+ }
+}
+
+const GH100: Gh100 = Gh100;
+pub(super) const GH100_HAL: &dyn TimerHal = &GH100;
diff --git a/drivers/gpu/nova-core/timer/hal/tu102.rs b/drivers/gpu/nova-core/timer/hal/tu102.rs
new file mode 100644
index 000000000000..06ef88171f58
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal/tu102.rs
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::TimerHal;
+
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset,
+ regs,
+ timer, //
+};
+struct Tu102;
+
+pub(crate) fn timer_read(bar: Bar0<'_>) -> u64 {
+ let hi2: u32 = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
+ loop {
+ let lo = u32::from(bar.read(regs::NV_PTIMER_TIME_0));
+ let hi = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
+ if hi == hi2 {
+ return u64::from(hi) << 32 | u64::from(lo);
+ }
+ }
+}
+
+pub(crate) fn timer_time(bar: Bar0<'_>, time_ns: u64) -> Result {
+ if bar.read(regs::NV_PTIMER_TIME_PRIV_LEVEL_MASK).level0() {
+ bar.write_reg(regs::NV_PTIMER_TIME_1::zeroed().try_with_nsec((time_ns >> 32) as u32)?);
+ bar.write_reg(
+ regs::NV_PTIMER_TIME_0::zeroed().try_with_nsec((time_ns & 0xffffffff) as u32)?,
+ );
+ Ok(())
+ } else {
+ Err(EINVAL)
+ }
+}
+
+impl TimerHal for Tu102 {
+ fn init(&self, _chipset: Chipset, bar: Bar0<'_>) -> Result<i64> {
+ let cpu = timer::get_cpu_time_ns();
+
+ timer_time(bar, cpu as u64)?;
+ Ok(0_i64)
+ }
+
+ fn timer_read(&self, bar: Bar0<'_>) -> u64 {
+ timer_read(bar)
+ }
+}
+
+const TU102: Tu102 = Tu102;
+pub(super) const TU102_HAL: &dyn TimerHal = &TU102;
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/2] gpu: nova-core: add support for GPU timer block.
@ 2026-10-26 2:56 ` Dave Airlie via B4 Relay
0 siblings, 0 replies; 10+ messages in thread
From: Dave Airlie via B4 Relay @ 2026-10-26 2:56 UTC (permalink / raw)
To: Andreas Hindborg, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
From: Dave Airlie <airlied@redhat.com>
This is needed to calculate the system timer offset that is passed
to GSP init.
2 HALs exist:
a) tu102 is the gv100 path from open-gpu, the priv level is checked
and the cpu time is written to the gpu timer. no offset is used.
b) gh100 is the gh100 path from open-gpu, where the secure system
timer is read, the offset is calculated and on everything after
GH100 the offset gets written to secure registers, and also
sent to GSP.
Signed-off-by: Dave Airlie <airlied@redhat.com>
---
drivers/gpu/nova-core/gpu.rs | 5 +++
drivers/gpu/nova-core/nova_core.rs | 1 +
drivers/gpu/nova-core/regs.rs | 40 +++++++++++++++++++++
drivers/gpu/nova-core/timer.rs | 37 +++++++++++++++++++
drivers/gpu/nova-core/timer/hal.rs | 32 +++++++++++++++++
drivers/gpu/nova-core/timer/hal/gh100.rs | 61 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/timer/hal/tu102.rs | 55 ++++++++++++++++++++++++++++
7 files changed, 231 insertions(+)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 4e59072cab50..85ff4a98d783 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -53,6 +53,7 @@
GpuMm,
VramAddress, //
},
+ timer::Timer,
vgpu::VgpuManager, //
};
@@ -331,6 +332,8 @@ struct GspResources<'gpu> {
// TODO: use different resource types for each boot method, and make the relevant Gsp methods
// generic against them.
fsp: Option<Fsp<'gpu>>,
+ /// Timer instance - contains offset
+ timer: Timer,
/// vGPU state detected before GSP boot.
vgpu: VgpuManager,
/// GSP runtime data.
@@ -497,6 +500,8 @@ pub(crate) fn new<'a>(
fsp: Fsp::try_new(dev, bar, spec.chipset)?,
+ timer: Timer::new(bar, spec.chipset)?,
+
vgpu: VgpuManager::new(pdev, spec.chipset, fsp.as_mut()),
gsp <- Gsp::new(pdev, bar),
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index a8f6dc22057b..ee59d81e5105 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -27,6 +27,7 @@
mod sbuffer;
#[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
mod selftest;
+mod timer;
mod vbios;
mod vgpu;
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 88662da634dc..d5c67625b561 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -419,6 +419,24 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
}
}
+// PTIMER
+
+register! {
+ base: NovaRegisters;
+
+ pub(crate) NV_PTIMER_TIME_PRIV_LEVEL_MASK(u32) @ 0x00009430 {
+ 4:4 level0 => bool;
+ }
+
+ pub(crate) NV_PTIMER_TIME_0(u32) @ 0x00009400 {
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PTIMER_TIME_1(u32) @ 0x00009410 {
+ 28:0 nsec;
+ }
+}
+
// The modules below provide registers that are not identical on all supported chips. They should
// only be used in HAL modules.
@@ -520,6 +538,28 @@ pub(crate) mod gh100 {
31:0 fsp_boot_complete;
}
}
+
+ // PGC6/TIMER
+ register! {
+ base: NovaRegisters;
+
+ pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_0(u32) @ 0x00118f54 {
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_1(u32) @ 0x00118f58 {
+ 28:0 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_0(u32) @ 0x00118df4 {
+ 0:0 update;
+ 31:5 nsec;
+ }
+
+ pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_1(u32) @ 0x00118df8 {
+ 28:0 nsec;
+ }
+ }
}
pub(crate) mod gb202 {
diff --git a/drivers/gpu/nova-core/timer.rs b/drivers/gpu/nova-core/timer.rs
new file mode 100644
index 000000000000..7fcb9cff8648
--- /dev/null
+++ b/drivers/gpu/nova-core/timer.rs
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+mod hal;
+
+use kernel::{
+ prelude::*,
+ time::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset, //
+};
+
+#[pin_data]
+pub(crate) struct Timer {
+ chipset: Chipset,
+ sys_timer_offset_ns: i64,
+}
+
+impl Timer {
+ pub(crate) fn new(bar: Bar0<'_>, chipset: Chipset) -> Result<Self> {
+ Ok(Self {
+ chipset,
+ sys_timer_offset_ns: hal::timer_hal(chipset).init(chipset, bar)?,
+ })
+ }
+
+ #[expect(dead_code)]
+ pub(crate) fn sys_timer_offset_ns(&self) -> i64 {
+ self.sys_timer_offset_ns
+ }
+}
+
+pub(crate) fn get_cpu_time_ns() -> i64 {
+ Instant::<Monotonic>::now().as_nanos()
+}
diff --git a/drivers/gpu/nova-core/timer/hal.rs b/drivers/gpu/nova-core/timer/hal.rs
new file mode 100644
index 000000000000..2a383cf7688c
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal.rs
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use kernel::prelude::*;
+
+use crate::{
+ driver::Bar0,
+ gpu::{
+ Architecture,
+ Chipset, //
+ },
+};
+
+mod gh100;
+mod tu102;
+
+pub(crate) trait TimerHal {
+ /// return system timer offset ns
+ fn init(&self, chipset: Chipset, bar: Bar0<'_>) -> Result<i64>;
+
+ /// read the timer
+ #[allow(dead_code)]
+ fn timer_read(&self, bar: Bar0<'_>) -> u64;
+}
+
+pub(super) fn timer_hal(chipset: Chipset) -> &'static dyn TimerHal {
+ match chipset.arch() {
+ Architecture::Turing | Architecture::Ampere | Architecture::Ada => tu102::TU102_HAL,
+ Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
+ gh100::GH100_HAL
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/timer/hal/gh100.rs b/drivers/gpu/nova-core/timer/hal/gh100.rs
new file mode 100644
index 000000000000..e917446e7b01
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal/gh100.rs
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::TimerHal;
+
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset,
+ regs,
+ timer,
+ timer::hal::tu102, //
+};
+
+struct Gh100;
+
+pub(crate) fn timer_time(bar: Bar0<'_>, chipset: Chipset, time_ns: u64) -> Result<u64> {
+ let sec_hi2 = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
+ let sec_timer_ns: u64;
+ loop {
+ let sec_lo = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_0));
+ let sec_hi = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
+ if sec_hi == sec_hi2 {
+ sec_timer_ns = u64::from(sec_hi) << 32 | u64::from(sec_lo);
+ break;
+ }
+ }
+
+ let sys_timer_offset_ns = time_ns - sec_timer_ns;
+
+ // kfspRequiresBug3957833WAR_HAL
+ if chipset != Chipset::GH100 {
+ bar.write_reg(
+ regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_1::zeroed()
+ .try_with_nsec((sys_timer_offset_ns >> 32) as u32)?,
+ );
+ bar.write_reg(
+ regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_0::zeroed()
+ .try_with_nsec((time_ns & 0xffffffff) as u32)?,
+ );
+ }
+ Ok(sys_timer_offset_ns)
+}
+
+impl TimerHal for Gh100 {
+ fn init(&self, chipset: Chipset, bar: Bar0<'_>) -> Result<i64> {
+ let cpu: i64 = timer::get_cpu_time_ns();
+ let sys_timer_offset_ns = timer_time(bar, chipset, cpu as u64)?;
+ Ok(sys_timer_offset_ns as i64)
+ }
+
+ fn timer_read(&self, bar: Bar0<'_>) -> u64 {
+ tu102::timer_read(bar)
+ }
+}
+
+const GH100: Gh100 = Gh100;
+pub(super) const GH100_HAL: &dyn TimerHal = &GH100;
diff --git a/drivers/gpu/nova-core/timer/hal/tu102.rs b/drivers/gpu/nova-core/timer/hal/tu102.rs
new file mode 100644
index 000000000000..06ef88171f58
--- /dev/null
+++ b/drivers/gpu/nova-core/timer/hal/tu102.rs
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use super::TimerHal;
+
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
+
+use crate::{
+ driver::Bar0,
+ gpu::Chipset,
+ regs,
+ timer, //
+};
+struct Tu102;
+
+pub(crate) fn timer_read(bar: Bar0<'_>) -> u64 {
+ let hi2: u32 = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
+ loop {
+ let lo = u32::from(bar.read(regs::NV_PTIMER_TIME_0));
+ let hi = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
+ if hi == hi2 {
+ return u64::from(hi) << 32 | u64::from(lo);
+ }
+ }
+}
+
+pub(crate) fn timer_time(bar: Bar0<'_>, time_ns: u64) -> Result {
+ if bar.read(regs::NV_PTIMER_TIME_PRIV_LEVEL_MASK).level0() {
+ bar.write_reg(regs::NV_PTIMER_TIME_1::zeroed().try_with_nsec((time_ns >> 32) as u32)?);
+ bar.write_reg(
+ regs::NV_PTIMER_TIME_0::zeroed().try_with_nsec((time_ns & 0xffffffff) as u32)?,
+ );
+ Ok(())
+ } else {
+ Err(EINVAL)
+ }
+}
+
+impl TimerHal for Tu102 {
+ fn init(&self, _chipset: Chipset, bar: Bar0<'_>) -> Result<i64> {
+ let cpu = timer::get_cpu_time_ns();
+
+ timer_time(bar, cpu as u64)?;
+ Ok(0_i64)
+ }
+
+ fn timer_read(&self, bar: Bar0<'_>) -> u64 {
+ timer_read(bar)
+ }
+}
+
+const TU102: Tu102 = Tu102;
+pub(super) const TU102_HAL: &dyn TimerHal = &TU102;
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] gpu: nova-core: add support for GPU timer block.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
(?)
@ 2026-10-26 9:07 ` sashiko-bot
-1 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-10-26 9:07 UTC (permalink / raw)
To: Dave Airlie, Andreas Hindborg, Boqun Feng, FUJITA Tomonori,
Lyude Paul, Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: nova-gpu, ojeda
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] gpu: nova-core: infinite loop on rollover in timer_read() and timer_time()
- [High] gpu: nova-core: unshifted nsec value in timer_time() causes EOVERFLOW
- [High] gpu: nova-core: wrong value and missing update bit in gh100::timer_time()
- [High] gpu: nova-core: u64 underflow on negative offset in gh100::timer_time()
--
commit 5b1f5a3805c6ab01f8b19ff029b873428252d6b5
Author: Dave Airlie <airlied@redhat.com>
gpu: nova-core: add support for GPU timer block.
This commit adds GPU timer block support to nova-core with HAL implementations
for TU102 and GH100 to initialize the GPU timer or calculate the system timer
offset for GSP initialization.
> diff --git a/drivers/gpu/nova-core/timer/hal/gh100.rs b/drivers/gpu/nova-core/timer/hal/gh100.rs
> new file mode 100644
> index 0000000000000..e917446e7b01e
> --- /dev/null
> +++ b/drivers/gpu/nova-core/timer/hal/gh100.rs
> @@ -0,0 +1,61 @@
[ ... ]
> +pub(crate) fn timer_time(bar: Bar0<'_>, chipset: Chipset, time_ns: u64) -> Result<u64> {
> + let sec_hi2 = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
> + let sec_timer_ns: u64;
> + loop {
> + let sec_lo = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_0));
> + let sec_hi = u32::from(bar.read(regs::gh100::NV_PGC6_SCI_SEC_TIMER_TIME_1));
> + if sec_hi == sec_hi2 {
> + sec_timer_ns = u64::from(sec_hi) << 32 | u64::from(sec_lo);
> + break;
> + }
> + }
[Severity: High]
Can this loop in timer_time() in drivers/gpu/nova-core/timer/hal/gh100.rs
spin forever if NV_PGC6_SCI_SEC_TIMER_TIME_0 rolls over?
During Gpu::new() -> Timer::new() -> Gh100::init() -> timer_time(), sec_hi2
is read from NV_PGC6_SCI_SEC_TIMER_TIME_1 once before the loop and is never
updated if sec_hi != sec_hi2. If the lower 32-bit register wraps around
between the initial read of sec_hi2 and the in-loop read of sec_hi, sec_hi
increments while sec_hi2 remains at its old value, causing the loop to never
terminate. Should sec_hi2 be made mutable and updated to sec_hi at the end
of each iteration?
> +
> + let sys_timer_offset_ns = time_ns - sec_timer_ns;
> +
> + // kfspRequiresBug3957833WAR_HAL
> + if chipset != Chipset::GH100 {
> + bar.write_reg(
> + regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_1::zeroed()
> + .try_with_nsec((sys_timer_offset_ns >> 32) as u32)?,
> + );
[Severity: High]
What happens in timer_time() in drivers/gpu/nova-core/timer/hal/gh100.rs when
time_ns is smaller than sec_timer_ns?
In drivers/gpu/nova-core/timer.rs, get_cpu_time_ns() returns monotonic time
since OS boot via Instant::<Monotonic>::now().as_nanos(), whereas
sec_timer_ns is read from the always-on PGC6 timer which persists across warm
reboots, kexec, and guest VM restarts.
When time_ns < sec_timer_ns, the u64 subtraction time_ns - sec_timer_ns will
underflow and panic the kernel when CONFIG_RUST_OVERFLOW_CHECKS=y. Even if
wrapping subtraction is used, a negative 64-bit two's-complement offset has
its upper bits set to 1, so passing an unmasked (sys_timer_offset_ns >> 32)
as u32 to NV_PGC6_SCI_SYS_TIMER_OFFSET_1::zeroed().try_with_nsec() will
exceed the 29-bit (28:0 nsec) field and fail with EOVERFLOW.
> + bar.write_reg(
> + regs::gh100::NV_PGC6_SCI_SYS_TIMER_OFFSET_0::zeroed()
> + .try_with_nsec((time_ns & 0xffffffff) as u32)?,
> + );
> + }
> + Ok(sys_timer_offset_ns)
> +}
[Severity: High]
Are there a few issues with how NV_PGC6_SCI_SYS_TIMER_OFFSET_0 is written
here in timer_time() in drivers/gpu/nova-core/timer/hal/gh100.rs?
In drivers/gpu/nova-core/regs.rs, NV_PGC6_SCI_SYS_TIMER_OFFSET_0 is defined
as:
pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_0(u32) @ 0x00118df4 {
0:0 update;
31:5 nsec;
}
First, this call passes (time_ns & 0xffffffff) as u32 instead of the lower
bits of sys_timer_offset_ns, and it does not set the update bit (0:0 update)
to trigger hardware latching of the offset.
Second, because nsec is a 27-bit field at bits 31:5, try_with_nsec() checks
that the argument fits in 27 bits (< 1 << 27) and then shifts it left by 5.
Passing an unshifted 32-bit value causes try_with_nsec() to fail with
EOVERFLOW whenever any of bits 31:27 are non-zero, and shifts the value left
by an extra 5 bits when bits 31:27 happen to be zero. Should this pass
((sys_timer_offset_ns as u32) >> 5) and set update?
> diff --git a/drivers/gpu/nova-core/timer/hal/tu102.rs b/drivers/gpu/nova-core/timer/hal/tu102.rs
> new file mode 100644
> index 0000000000000..06ef88171f587
> --- /dev/null
> +++ b/drivers/gpu/nova-core/timer/hal/tu102.rs
> @@ -0,0 +1,55 @@
[ ... ]
> +pub(crate) fn timer_read(bar: Bar0<'_>) -> u64 {
> + let hi2: u32 = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
> + loop {
> + let lo = u32::from(bar.read(regs::NV_PTIMER_TIME_0));
> + let hi = u32::from(bar.read(regs::NV_PTIMER_TIME_1));
> + if hi == hi2 {
> + return u64::from(hi) << 32 | u64::from(lo);
> + }
> + }
> +}
[Severity: High]
Similarly, in timer_read() in drivers/gpu/nova-core/timer/hal/tu102.rs, hi2
is read once before the loop and never updated to hi when hi != hi2. If
NV_PTIMER_TIME_0 wraps around between the initial read of hi2 and the in-loop
read of hi, won't this loop spin forever?
> +
> +pub(crate) fn timer_time(bar: Bar0<'_>, time_ns: u64) -> Result {
> + if bar.read(regs::NV_PTIMER_TIME_PRIV_LEVEL_MASK).level0() {
> + bar.write_reg(regs::NV_PTIMER_TIME_1::zeroed().try_with_nsec((time_ns >> 32) as u32)?);
> + bar.write_reg(
> + regs::NV_PTIMER_TIME_0::zeroed().try_with_nsec((time_ns & 0xffffffff) as u32)?,
> + );
[Severity: High]
In timer_time() in drivers/gpu/nova-core/timer/hal/tu102.rs, NV_PTIMER_TIME_0
also defines nsec as a 27-bit field at bits 31:5:
pub(crate) NV_PTIMER_TIME_0(u32) @ 0x00009400 {
31:5 nsec;
}
When called during probe via Gpu::new() -> Timer::new() -> Tu102::init() ->
timer_time(), won't passing the unshifted (time_ns & 0xffffffff) as u32 to
try_with_nsec() return EOVERFLOW whenever bits 31:27 of the low 32-bit
timestamp are non-zero, and otherwise shift the value left by an extra 5
bits?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-drm-rust-next-v1-0-14a4ae469175@redhat.com?part=2
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] rust/timer: expose as_nanos for clock sources.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
(?)
@ 2026-10-26 10:09 ` Andreas Hindborg
-1 siblings, 0 replies; 10+ messages in thread
From: Andreas Hindborg @ 2026-10-26 10:09 UTC (permalink / raw)
To: Dave Airlie via B4 Relay, Boqun Feng, FUJITA Tomonori, Lyude Paul,
Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: rust-for-linux, nova-gpu, Dave Airlie
"Dave Airlie via B4 Relay" <devnull+airlied.redhat.com@kernel.org>
writes:
> From: Dave Airlie <airlied@redhat.com>
>
> The nouveau C driver does ktime_to_ns(ktime_get()) to program
> the cpu ns into some hw/fw, so allow this method for rust drivers.
>
> Signed-off-by: Dave Airlie <airlied@redhat.com>
Acked-by: Andreas Hindborg <a.hindborg@kernel.org>
Best regards,
Andreas Hindborg
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/2] gpu: nova-core: add support for GPU timer block.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
(?)
(?)
@ 2026-10-26 12:33 ` Danilo Krummrich
-1 siblings, 0 replies; 10+ messages in thread
From: Danilo Krummrich @ 2026-10-26 12:33 UTC (permalink / raw)
To: Dave Airlie via B4 Relay
Cc: airlied, Andreas Hindborg, Boqun Feng, FUJITA Tomonori,
Lyude Paul, Gary Guo, Alexandre Courbot, rust-for-linux, nova-gpu
On Mon Oct 26, 2026 at 3:56 AM CET, Dave Airlie via B4 Relay wrote:
> diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
> index 88662da634dc..d5c67625b561 100644
> --- a/drivers/gpu/nova-core/regs.rs
> +++ b/drivers/gpu/nova-core/regs.rs
> @@ -419,6 +419,24 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
> }
> }
>
> +// PTIMER
> +
> +register! {
> + base: NovaRegisters;
> +
> + pub(crate) NV_PTIMER_TIME_PRIV_LEVEL_MASK(u32) @ 0x00009430 {
> + 4:4 level0 => bool;
> + }
> +
> + pub(crate) NV_PTIMER_TIME_0(u32) @ 0x00009400 {
> + 31:5 nsec;
> + }
> +
> + pub(crate) NV_PTIMER_TIME_1(u32) @ 0x00009410 {
> + 28:0 nsec;
> + }
> +}
> +
> // The modules below provide registers that are not identical on all supported chips. They should
> // only be used in HAL modules.
>
> @@ -520,6 +538,28 @@ pub(crate) mod gh100 {
> 31:0 fsp_boot_complete;
> }
> }
> +
> + // PGC6/TIMER
> + register! {
> + base: NovaRegisters;
> +
> + pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_0(u32) @ 0x00118f54 {
> + 31:5 nsec;
> + }
> +
> + pub(crate) NV_PGC6_SCI_SEC_TIMER_TIME_1(u32) @ 0x00118f58 {
> + 28:0 nsec;
> + }
> +
> + pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_0(u32) @ 0x00118df4 {
> + 0:0 update;
> + 31:5 nsec;
> + }
> +
> + pub(crate) NV_PGC6_SCI_SYS_TIMER_OFFSET_1(u32) @ 0x00118df8 {
> + 28:0 nsec;
> + }
> + }
> }
Timer specific register definitions should go into the timer module.
> +#[pin_data]
Why is this #[pin_data]?
> +pub(crate) struct Timer {
> + chipset: Chipset,
Why does this carry the Chipset?
> + sys_timer_offset_ns: i64,
> +}
More generally, what's the purpose of this struct? What does it represent?
Nothing seems to tie this object to a particular GPU or prevents constructing
multiple instances for the same GPU.
It also doesn't seem to provide any access to the underlying HAL's functions?
> +impl Timer {
> + pub(crate) fn new(bar: Bar0<'_>, chipset: Chipset) -> Result<Self> {
> + Ok(Self {
> + chipset,
> + sys_timer_offset_ns: hal::timer_hal(chipset).init(chipset, bar)?,
> + })
> + }
> +
> + #[expect(dead_code)]
> + pub(crate) fn sys_timer_offset_ns(&self) -> i64 {
> + self.sys_timer_offset_ns
> + }
What do we need this method for?
> +}
> +
> +pub(crate) fn get_cpu_time_ns() -> i64 {
> + Instant::<Monotonic>::now().as_nanos()
> +}
I don't think this helper adds much value, let's just make users call
Instant::<Monotonic>::now().as_nanos() themselves.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/2] rust/timer: expose as_nanos for clock sources.
2026-10-26 2:56 ` Dave Airlie via B4 Relay
(?)
(?)
@ 2026-10-26 19:04 ` Danilo Krummrich
-1 siblings, 0 replies; 10+ messages in thread
From: Danilo Krummrich @ 2026-10-26 19:04 UTC (permalink / raw)
To: Dave Airlie via B4 Relay
Cc: airlied, Andreas Hindborg, Boqun Feng, FUJITA Tomonori,
Lyude Paul, Gary Guo, Alexandre Courbot, rust-for-linux, nova-gpu
On Mon Oct 26, 2026 at 3:56 AM CET, Dave Airlie via B4 Relay wrote:
> The nouveau C driver does ktime_to_ns(ktime_get()) to program
> the cpu ns into some hw/fw, so allow this method for rust drivers.
It's kinda implicit, but I'd rather justify the change through how the HW timer
within some NVIDIA GPUs works.
There's also other HW that works similarly (e.g. a bunch of net devices), which
share a common abstraction through struct cyclecounter / struct timecounter.
Having similar Rust infrastructure seems reasonable for use-cases as the nova
one.
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-26 19:04 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26 2:56 [PATCH 0/2] nova-core: add initial support for gpu timer Dave Airlie
2026-10-26 2:56 ` Dave Airlie via B4 Relay
2026-10-26 2:56 ` [PATCH 1/2] rust/timer: expose as_nanos for clock sources Dave Airlie
2026-10-26 2:56 ` Dave Airlie via B4 Relay
2026-10-26 10:09 ` Andreas Hindborg
2026-10-26 19:04 ` Danilo Krummrich
2026-10-26 2:56 ` [PATCH 2/2] gpu: nova-core: add support for GPU timer block Dave Airlie
2026-10-26 2:56 ` Dave Airlie via B4 Relay
2026-10-26 9:07 ` sashiko-bot
2026-10-26 12:33 ` Danilo Krummrich
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.