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
