* [PATCH 1/2] drm/tyr: add Wait type for GPU events
2026-07-21 15:14 [PATCH 0/2] drm/tyr: add Job IRQ handling and GPU event wait support Laura Nao
@ 2026-07-21 15:14 ` Laura Nao
2026-07-21 15:32 ` Danilo Krummrich
2026-07-21 15:14 ` [PATCH 2/2] drm/tyr: add Job IRQ handling Laura Nao
1 sibling, 1 reply; 5+ messages in thread
From: Laura Nao @ 2026-07-21 15:14 UTC (permalink / raw)
To: daniel.almeida, aliceryhl, dakr, airlied, simona, ojeda
Cc: boqun, gary, bjorn3_gh, a.hindborg, tmgross, tamird, acourbot,
work, deborah.brouwer, linux-kernel, dri-devel, rust-for-linux,
kernel, Beata Michalska
From: Deborah Brouwer <deborah.brouwer@collabora.com>
Add a Wait convenience type wrapping a CondVar and Mutex for sleeping
until a condition is met or a timeout expires.
The helper centralizes a common wait pattern: check the completion
predicate before sleeping, wait interruptibly with a timeout, retry on
spurious or unrelated wakeups, and perform a final predicate check before
returning ETIMEDOUT.
This will be used for CSF firmware responses and other GPU-driven events.
Also add a new_wait! macro so each Wait instance gets a call-site-specific
lockdep class key for its internal mutex.
Co-developed-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
Co-developed-by: Beata Michalska <beata.michalska@arm.com>
Signed-off-by: Beata Michalska <beata.michalska@arm.com>
Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
---
drivers/gpu/drm/tyr/tyr.rs | 1 +
drivers/gpu/drm/tyr/wait.rs | 126 ++++++++++++++++++++++++++++++++++++
2 files changed, 127 insertions(+)
create mode 100644 drivers/gpu/drm/tyr/wait.rs
diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs
index e7ec450bdc9c..3f6fe5fbeb0f 100644
--- a/drivers/gpu/drm/tyr/tyr.rs
+++ b/drivers/gpu/drm/tyr/tyr.rs
@@ -16,6 +16,7 @@
mod regs;
mod slot;
mod vm;
+mod wait;
kernel::module_platform_driver! {
type: TyrPlatformDriver,
diff --git a/drivers/gpu/drm/tyr/wait.rs b/drivers/gpu/drm/tyr/wait.rs
new file mode 100644
index 000000000000..2a4d691c443c
--- /dev/null
+++ b/drivers/gpu/drm/tyr/wait.rs
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+
+//! Code to wait on GPU events.
+#![allow(dead_code)]
+
+use kernel::{
+ new_condvar,
+ prelude::*,
+ sync::{
+ lock::{
+ mutex::MutexBackend,
+ Lock, //
+ },
+ Arc,
+ CondVar,
+ CondVarTimeoutResult,
+ Mutex, //
+ },
+ time::msecs_to_jiffies, //
+};
+
+/// Creates a new [`Wait`] instance with a call-site-specific lockdep class key.
+///
+/// Always prefer this macro over [`Wait::new_with_lock`] when the [`Wait`] instance has
+/// unique locking behaviour that could otherwise trigger false-positive lockdep
+/// warnings.
+#[macro_export]
+macro_rules! new_wait {
+ () => {{
+ let lock = new_mutex!(());
+ $crate::wait::Wait::new_with_lock(lock)
+ }};
+}
+
+/// A convenience type to wait for GPU events.
+///
+/// Wraps a [`CondVar`] and [`Mutex`] pair. The mutex synchronizes predicate checks
+/// with wait/wake operations; the condvar provides the sleep/wake mechanism.
+#[pin_data]
+pub(crate) struct Wait {
+ /// The actual wait/signal mechanism.
+ #[pin]
+ cond: CondVar,
+ /// Synchronizes waiters with notifications.
+ #[pin]
+ lock: Mutex<()>,
+}
+
+impl Wait {
+ /// Creates a new [`Wait`] with a caller-supplied lock instance.
+ ///
+ /// Use [`new_wait!`] instead of calling this directly; the macro ensures a
+ /// per-call-site lockdep class key is registered.
+ pub(crate) fn new_with_lock(lock: impl PinInit<Lock<(), MutexBackend>>) -> Result<Arc<Self>> {
+ Arc::pin_init(
+ pin_init!(Self {
+ cond <- new_condvar!(),
+ lock <- lock,
+ }),
+ GFP_KERNEL,
+ )
+ }
+
+ /// Waits until a GPU event condition is met or the timeout elapses.
+ ///
+ /// Calls `on_woken` before sleeping and after each wakeup. If `on_woken`
+ /// returns [`WaitResult::Retry`], the wait continues; [`WaitResult::Done`]
+ /// returns success.
+ ///
+ /// `on_woken` is called while the internal wait lock is held, so it must be
+ /// cheap and must not call back into code that can notify this wait object.
+ ///
+ /// Returns [`ETIMEDOUT`] if the deadline is reached without the condition
+ /// becoming true, or [`ERESTARTSYS`] if interrupted by a signal.
+ pub(crate) fn wait_interruptible_timeout<F>(&self, timeout_ms: u32, mut on_woken: F) -> Result
+ where
+ F: FnMut() -> Result<WaitResult>,
+ {
+ let mut guard = self.lock.lock();
+ let mut remaining_time = msecs_to_jiffies(timeout_ms);
+
+ loop {
+ // Check the condition before sleeping to avoid missing a wakeup
+ // that arrived between the caller's last check and acquiring the
+ // lock here.
+ if let WaitResult::Done = on_woken()? {
+ return Ok(());
+ }
+
+ match self
+ .cond
+ .wait_interruptible_timeout(&mut guard, remaining_time)
+ {
+ CondVarTimeoutResult::Woken { jiffies } => match on_woken()? {
+ WaitResult::Done => return Ok(()),
+ WaitResult::Retry => remaining_time = jiffies,
+ },
+ CondVarTimeoutResult::Timeout => {
+ // One final check before giving up.
+ if let WaitResult::Done = on_woken()? {
+ return Ok(());
+ }
+ return Err(ETIMEDOUT);
+ }
+ CondVarTimeoutResult::Signal { .. } => return Err(ERESTARTSYS),
+ }
+ }
+ }
+
+ /// Wakes all waiters.
+ ///
+ /// Takes the internal lock so notifications are serialized against waiters
+ /// checking the condition and entering the sleep state.
+ pub(crate) fn notify_all(&self) {
+ let _guard = self.lock.lock();
+ self.cond.notify_all();
+ }
+}
+
+/// The result of a wait operation.
+pub(crate) enum WaitResult {
+ /// The condition was met.
+ Done,
+ /// The wakeup was spurious or for an unrelated event; retry.
+ Retry,
+}
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH 2/2] drm/tyr: add Job IRQ handling
2026-07-21 15:14 [PATCH 0/2] drm/tyr: add Job IRQ handling and GPU event wait support Laura Nao
2026-07-21 15:14 ` [PATCH 1/2] drm/tyr: add Wait type for GPU events Laura Nao
@ 2026-07-21 15:14 ` Laura Nao
1 sibling, 0 replies; 5+ messages in thread
From: Laura Nao @ 2026-07-21 15:14 UTC (permalink / raw)
To: daniel.almeida, aliceryhl, dakr, airlied, simona, ojeda
Cc: boqun, gary, bjorn3_gh, a.hindborg, tmgross, tamird, acourbot,
work, deborah.brouwer, linux-kernel, dri-devel, rust-for-linux,
kernel, Laura Nao
Add a threaded IRQ wrapper for Tyr interrupt sources and use it to handle
the firmware Job IRQ.
The Job IRQ reports requests from the CSF firmware, including global
interface requests and CSG attention bits. Add a Job IRQ handler that
masks the interrupt in the primary IRQ handler, processes pending raw
status in the threaded handler, clears handled bits, and reenables the
mask before returning.
Co-developed-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
Co-developed-by: Deborah Brouwer <deborah.brouwer@collabora.com>
Signed-off-by: Deborah Brouwer <deborah.brouwer@collabora.com>
Signed-off-by: Laura Nao <laura.nao@collabora.com>
---
drivers/gpu/drm/tyr/driver.rs | 75 ++++++++++++++++++++++++
drivers/gpu/drm/tyr/fw.rs | 4 ++
drivers/gpu/drm/tyr/fw/irq.rs | 104 ++++++++++++++++++++++++++++++++++
3 files changed, 183 insertions(+)
create mode 100644 drivers/gpu/drm/tyr/fw/irq.rs
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 46ba91af9aca..6ee6f5e578b7 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 or MIT
+use core::marker::PhantomPinned;
+
use kernel::{
clk::{
Clk,
@@ -21,6 +23,13 @@
poll,
Io, //
},
+ irq::{
+ Flags,
+ IrqReturn,
+ ThreadedHandler,
+ ThreadedIrqReturn,
+ ThreadedRegistration, //
+ },
new_mutex,
of,
platform,
@@ -238,3 +247,69 @@ struct Regulators {
_mali: Regulator<regulator::Enabled>,
_sram: Regulator<regulator::Enabled>,
}
+
+pub(crate) trait TyrIrqTrait: Sync {
+ fn read_status(&self) -> u32;
+ fn clear_mask(&self);
+ fn reenable_mask(&self);
+ fn read_raw_status(&self) -> u32;
+ fn clear_status(&self, status: u32);
+ fn mask(&self) -> u32;
+ fn handle(&self, status: u32);
+}
+
+#[pin_data]
+pub(crate) struct TyrIrq<T: TyrIrqTrait> {
+ irq: T,
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+impl<T: TyrIrqTrait> TyrIrq<T> {
+ pub(crate) fn request<'a>(
+ pdev: &'a platform::Device<Bound>,
+ name: &'static CStr,
+ irq: T,
+ ) -> Result<impl PinInit<ThreadedRegistration<'a, Self>, Error> + 'a>
+ where
+ T: 'a,
+ {
+ let handler = try_pin_init!(Self {
+ irq,
+ _pin: PhantomPinned,
+ });
+
+ // SAFETY: The resulting `PinInit` is not leaked, it is consumed by the caller to
+ // initialize a pinned `ThreadedRegistration`.
+ Ok(unsafe { pdev.request_threaded_irq_by_name(Flags::SHARED, name, name, handler) })
+ }
+}
+
+impl<T: TyrIrqTrait> ThreadedHandler for TyrIrq<T> {
+ fn handle(&self) -> ThreadedIrqReturn {
+ let masked_status = self.irq.read_status();
+
+ if masked_status == 0 {
+ return ThreadedIrqReturn::None;
+ }
+ self.irq.clear_mask();
+ ThreadedIrqReturn::WakeThread
+ }
+
+ fn handle_threaded(&self) -> IrqReturn {
+ let mut ret = IrqReturn::None;
+
+ loop {
+ let raw_status = self.irq.read_raw_status() & self.irq.mask();
+ if raw_status == 0 {
+ break;
+ }
+ self.irq.handle(raw_status);
+ self.irq.clear_status(raw_status);
+ ret = IrqReturn::Handled;
+ }
+
+ self.irq.reenable_mask();
+ ret
+ }
+}
diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
index 554808a792aa..4188dc4232f5 100644
--- a/drivers/gpu/drm/tyr/fw.rs
+++ b/drivers/gpu/drm/tyr/fw.rs
@@ -66,8 +66,12 @@
vm::Vm, //
};
+pub(crate) mod irq;
mod parser;
+/// Maximum number of CSG interfaces supported by hardware.
+const MAX_CSG: usize = 16;
+
impl_flags!(
#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)]
pub(super) struct SectionFlags(u32);
diff --git a/drivers/gpu/drm/tyr/fw/irq.rs b/drivers/gpu/drm/tyr/fw/irq.rs
new file mode 100644
index 000000000000..4084904d53f6
--- /dev/null
+++ b/drivers/gpu/drm/tyr/fw/irq.rs
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+
+//! IRQ handling for the Job IRQ.
+//!
+//! The Job IRQ signals events from the MCU, including global interface acknowledgements.
+#![allow(dead_code)]
+
+use core::sync::atomic::{
+ AtomicBool,
+ Ordering, //
+};
+
+use kernel::{
+ c_str,
+ device::Bound, //
+ io::Io,
+ irq::ThreadedRegistration,
+ platform,
+ prelude::*,
+ sync::Arc, //
+};
+
+use crate::{
+ driver::{
+ IoMem,
+ TyrIrq,
+ TyrIrqTrait, //
+ },
+ regs::job_control::{
+ JOB_IRQ_CLEAR,
+ JOB_IRQ_MASK,
+ JOB_IRQ_RAWSTAT,
+ JOB_IRQ_STATUS, //
+ },
+ wait::Wait, //
+};
+
+const CSG_IRQ_MASK: u32 = (1u32 << super::MAX_CSG) - 1;
+
+pub(crate) struct JobIrq<'bound> {
+ iomem: Arc<IoMem<'bound>>,
+ fw_ready: Arc<AtomicBool>,
+ ready_wait: Arc<Wait>,
+}
+
+pub(crate) fn job_irq_init<'bound>(
+ pdev: &'bound platform::Device<Bound>,
+ iomem: Arc<IoMem<'bound>>,
+ fw_ready: Arc<AtomicBool>,
+ ready_wait: Arc<Wait>,
+) -> Result<impl PinInit<ThreadedRegistration<'bound, TyrIrq<JobIrq<'bound>>>, Error> + 'bound> {
+ iomem.write_reg(
+ JOB_IRQ_MASK::zeroed()
+ .with_const_csg::<CSG_IRQ_MASK>()
+ .with_glb(true),
+ );
+ let job_irq = JobIrq {
+ iomem: iomem.clone(),
+ fw_ready,
+ ready_wait,
+ };
+
+ TyrIrq::request(pdev, c_str!("job"), job_irq)
+}
+
+impl TyrIrqTrait for JobIrq<'_> {
+ fn read_status(&self) -> u32 {
+ self.iomem.read(JOB_IRQ_STATUS).into_raw()
+ }
+
+ fn clear_mask(&self) {
+ self.iomem.write_reg(JOB_IRQ_MASK::zeroed());
+ }
+
+ fn reenable_mask(&self) {
+ self.iomem.write_reg(
+ JOB_IRQ_MASK::zeroed()
+ .with_const_csg::<CSG_IRQ_MASK>()
+ .with_glb(true),
+ );
+ }
+
+ fn read_raw_status(&self) -> u32 {
+ self.iomem.read(JOB_IRQ_RAWSTAT).into_raw()
+ }
+
+ fn clear_status(&self, status: u32) {
+ self.iomem.write_reg(JOB_IRQ_CLEAR::from_raw(status));
+ }
+
+ fn mask(&self) -> u32 {
+ JOB_IRQ_MASK::zeroed()
+ .with_const_csg::<CSG_IRQ_MASK>()
+ .with_glb(true)
+ .into_raw()
+ }
+
+ fn handle(&self, status: u32) {
+ if JOB_IRQ_RAWSTAT::from_raw(status).glb() {
+ self.fw_ready.store(true, Ordering::Release);
+ self.ready_wait.notify_all();
+ }
+ }
+}
--
2.39.5
^ permalink raw reply [flat|nested] 5+ messages in thread