The atomic helpers were completing every page-flip immediately via
drm_atomic_helper_fake_vblank(), because the CRTC had no vblank support --
so a compositor got no refresh-rate pacing and updates arrived in bursts.

Implement VblankSupport for VinoCrtc backed by a per-CRTC hrtimer that
fires once per frame (from the mode's framedur_ns) and drives
drm_crtc_handle_vblank(). The CRTC now enables/disables vblank around
scanout (drm_crtc_vblank_on/off) and arms the page-flip completion event
to the next vblank in atomic_flush (drm_crtc_arm_vblank_event via the safe
PendingVblankEvent::arm()), so flips are paced to the display's refresh
rate. The timer free-runs once started and is cancelled when the CRTC is
dropped at teardown.

Signed-off-by: Mike Lothian <[email protected]>
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
---
 drivers/gpu/drm/vino/drm_sink.rs | 146 +++++++++++++++++++++++++++++--
 1 file changed, 139 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/vino/drm_sink.rs b/drivers/gpu/drm/vino/drm_sink.rs
index ee04a5af9f7d..ce04c8b65461 100644
--- a/drivers/gpu/drm/vino/drm_sink.rs
+++ b/drivers/gpu/drm/vino/drm_sink.rs
@@ -39,21 +39,32 @@
 //! its content-protection channel for vino (see `docs/BLOCKER.md`), so 
`atomic_update`
 //! never gets past the first `bulk_send`.
 
+use core::sync::atomic::{AtomicBool, AtomicI64, AtomicPtr, Ordering};
 use kernel::{
     bindings, drm,
     drm::kms::{
         self,
         connector::{self, Connector, ConnectorGuard, ModeStatus, Status},
-        crtc::{self, CrtcAtomicCommit, RawCrtc as _, RawCrtcState as _},
+        crtc::{self, AsRawCrtc as _, CrtcAtomicCommit, RawCrtc as _, 
RawCrtcState as _},
         encoder,
         modes::DisplayMode,
         plane::{self, PlaneAtomicCommit, RawPlaneState as _},
+        vblank::{RawVblankCrtcState as _, VblankGuard, VblankSupport, 
VblankTimestamp},
         KmsDriver, ModeConfigGuard, ModeConfigInfo, ModeObject as _, 
NewKmsDevice, Probing,
     },
     i2c,
     error::code::EINVAL,
+    impl_has_hr_timer,
+    interrupt::LocalInterruptDisabled,
     prelude::*,
-    sync::{aref::ARef, new_mutex, Mutex},
+    sync::{aref::ARef, new_mutex, new_spinlock, Arc, ArcBorrow, Mutex, 
SpinLock},
+    time::{
+        hrtimer::{
+            ArcHrTimerHandle, HrTimer, HrTimerCallback, 
HrTimerCallbackContext, HrTimerPointer,
+            HrTimerRestart, RelativeMode,
+        },
+        Delta, Monotonic,
+    },
     types::ForLt,
 };
 
@@ -417,11 +428,69 @@ fn probe(dev: &NewKmsDevice<'_, Self, Probing>) -> Result 
{
 
 // ---- CRTC -----------------------------------------------------------------
 
+/// A software vblank source: an hrtimer that fires once per frame and drives
+/// `drm_crtc_handle_vblank()`, so the atomic helpers pace page-flips against 
a real vblank
+/// (via `drm_crtc_arm_vblank_event()` in [`VinoCrtc::atomic_flush`]) instead 
of completing them
+/// immediately with a fake vblank. The timer free-runs once started; 
`enabled` gates delivery so
+/// DPMS off/on is a flag flip. Cancelled when the owning [`VinoCrtc`] is 
dropped at teardown.
+#[pin_data]
+pub(super) struct VblankTimer {
+    #[pin]
+    timer: HrTimer<Self>,
+    /// The `drm_crtc` to deliver vblanks to (set when vblank is first 
enabled).
+    crtc: AtomicPtr<bindings::drm_crtc>,
+    /// One scanout frame in nanoseconds (from the mode's `framedur_ns`).
+    interval_ns: AtomicI64,
+    /// Whether vblanks should currently be delivered (toggled by 
enable/disable_vblank).
+    enabled: AtomicBool,
+    /// Whether the free-running timer has been started yet.
+    started: AtomicBool,
+}
+
+impl VblankTimer {
+    fn new() -> impl PinInit<Self> {
+        pin_init!(VblankTimer {
+            timer <- HrTimer::new(),
+            crtc: AtomicPtr::new(core::ptr::null_mut()),
+            interval_ns: AtomicI64::new(16_666_666), // ~60 Hz until a mode 
sets it
+            enabled: AtomicBool::new(false),
+            started: AtomicBool::new(false),
+        })
+    }
+}
+
+impl HrTimerCallback for VblankTimer {
+    type Pointer<'a> = Arc<Self>;
+
+    fn run(this: ArcBorrow<'_, Self>, mut ctx: HrTimerCallbackContext<'_, 
Self>) -> HrTimerRestart {
+        let crtc = this.crtc.load(Ordering::Relaxed);
+        if !crtc.is_null() && this.enabled.load(Ordering::Relaxed) {
+            // SAFETY: `crtc` is the `drm_crtc` stored in `enable_vblank` 
while the device is live;
+            // the timer is cancelled (its handle dropped) before the crtc is 
freed at teardown.
+            unsafe { bindings::drm_crtc_handle_vblank(crtc) };
+        }
+        let interval = this.interval_ns.load(Ordering::Relaxed).max(1_000_000);
+        ctx.forward_now(Delta::from_nanos(interval));
+        HrTimerRestart::Restart
+    }
+}
+
+impl_has_hr_timer! {
+    impl HasHrTimer<Self> for VblankTimer {
+        mode: RelativeMode<Monotonic>, field: self.timer
+    }
+}
+
 #[pin_data]
 pub(super) struct VinoCrtc {
     /// Which display head (0-based) this CRTC drives. Used for diagnostics; 
the mode-set/DDC CP
     /// messages this CRTC sends are not yet head-differentiated on the wire 
(see the module doc).
     head: u8,
+    /// The software vblank source for this CRTC.
+    vblank: Arc<VblankTimer>,
+    /// Keeps the timer running; dropping it (at CRTC teardown) cancels the 
timer.
+    #[pin]
+    vblank_handle: SpinLock<Option<ArcHrTimerHandle<VblankTimer>>>,
 }
 
 #[derive(Clone, Default)]
@@ -436,20 +505,25 @@ impl crtc::DriverCrtc for VinoCrtc {
     type Args = u8;
     type Driver = VinoDrmDriver;
     type State = VinoCrtcState;
-    type VblankImpl = core::marker::PhantomData<Self>;
+    type VblankImpl = Self;
 
     fn new(
         _device: &drm::Device<Self::Driver, drm::Uninit>,
         head: &u8,
     ) -> impl PinInit<Self, Error> {
-        try_pin_init!(VinoCrtc { head: *head })
+        try_pin_init!(VinoCrtc {
+            head: *head,
+            vblank: Arc::pin_init(VblankTimer::new(), GFP_KERNEL)?,
+            vblank_handle <- new_spinlock!(None),
+        })
     }
 
-    /// The display is turning on (scanout begins). Pushes a live mode-set CP 
message for the
-    /// negotiated mode and brings the monitor out of DPMS standby -- both 
no-ops until CP
-    /// engages (the wall).
+    /// The display is turning on (scanout begins). Enables vblank pacing, 
pushes a live mode-set CP
+    /// message for the negotiated mode and brings the monitor out of DPMS 
standby -- the latter two
+    /// no-ops until CP engages (the wall).
     fn atomic_enable(commit: CrtcAtomicCommit<'_, Self>) {
         let crtc = commit.crtc();
+        crtc.vblank_on();
         let head = crtc.head;
         let data: &VinoDrmData = crtc.drm_dev();
         let new = commit.take_new_state();
@@ -475,12 +549,70 @@ fn atomic_enable(commit: CrtcAtomicCommit<'_, Self>) {
     /// no-op until CP engages.
     fn atomic_disable(commit: CrtcAtomicCommit<'_, Self>) {
         let crtc = commit.crtc();
+        crtc.vblank_off();
         let head = crtc.head;
         let data: &VinoDrmData = crtc.drm_dev();
         data.update_gamma(None);
         let _ = data.set_vcp(super::cp::VCP_POWER_MODE, super::cp::POWER_OFF);
         pr_info!("vino: KMS CRTC disable -- head {head} display OFF (scanout 
stopped)\n");
     }
+
+    /// Arm the page-flip completion event to be sent by the next vblank tick, 
so userspace is paced
+    /// to the refresh rate rather than signalled immediately.
+    fn atomic_flush(commit: CrtcAtomicCommit<'_, Self>) {
+        let crtc = commit.crtc();
+        let mut new = commit.take_new_state();
+        if let Some(pending) = new.get_pending_vblank_event() {
+            match crtc.vblank_get() {
+                Ok(vbl_ref) => pending.arm(vbl_ref),
+                // Vblank couldn't be enabled (e.g. mid-teardown): fall back 
to sending now.
+                Err(_) => pending.send(),
+            }
+        }
+    }
+}
+
+impl VblankSupport for VinoCrtc {
+    type Crtc = VinoCrtc;
+
+    fn enable_vblank(
+        crtc: &crtc::Crtc<Self::Crtc>,
+        vblank_guard: &VblankGuard<'_, Self::Crtc>,
+        _irq: &LocalInterruptDisabled,
+    ) -> Result {
+        let data: &VinoCrtc = crtc;
+        // Track the mode's real frame duration so the tick matches the 
negotiated refresh rate.
+        let fd = vblank_guard.frame_duration();
+        if fd > 0 {
+            data.vblank.interval_ns.store(fd as i64, Ordering::Relaxed);
+        }
+        data.vblank.crtc.store(crtc.as_raw(), Ordering::Relaxed);
+        data.vblank.enabled.store(true, Ordering::Relaxed);
+        // Start the free-running timer the first time vblank is enabled.
+        if !data.vblank.started.swap(true, Ordering::Relaxed) {
+            let interval = data.vblank.interval_ns.load(Ordering::Relaxed);
+            let handle = 
data.vblank.clone().start(Delta::from_nanos(interval));
+            *data.vblank_handle.lock() = Some(handle);
+        }
+        Ok(())
+    }
+
+    fn disable_vblank(
+        crtc: &crtc::Crtc<Self::Crtc>,
+        _vblank_guard: &VblankGuard<'_, Self::Crtc>,
+        _irq: &LocalInterruptDisabled,
+    ) {
+        let data: &VinoCrtc = crtc;
+        data.vblank.enabled.store(false, Ordering::Relaxed);
+    }
+
+    fn get_vblank_timestamp(
+        _crtc: &crtc::Crtc<Self::Crtc>,
+        _in_vblank_irq: bool,
+    ) -> Option<VblankTimestamp> {
+        // Let DRM estimate the timestamp from the mode timings.
+        None
+    }
 }
 
 // ---- Planes: primary (scanout) + cursor 
-------------------------------------
-- 
2.55.0

Reply via email to