Skip to content
Snippets Groups Projects
  • Lyude Paul's avatar
    f121122a
    WIP: drm: Introduce RVKMS! · f121122a
    Lyude Paul authored
    
    Now that we've added all of the bits that we need for the KMS API, it's
    time to introduce rvkms! This is a port of the VKMS driver to rust, with
    the intent of acting as an example usecase of the KMS bindings that we've
    come up with so far in preparation for writing a display driver for nova.
    
    Currently RVKMS is an extremely bear bones driver - it only registers a
    device and emulates vblanking, but it exercises a good portion of the API
    that we've introduced so far! Eventually I hope to introduce CRC generation
    and maybe writeback connectors like.
    
    Signed-off-by: Lyude Paul's avatarLyude Paul <lyude@redhat.com>
    
    ---
    V3:
    * Replace platform device usage with faux device
    f121122a
    History
    WIP: drm: Introduce RVKMS!
    Lyude Paul authored
    
    Now that we've added all of the bits that we need for the KMS API, it's
    time to introduce rvkms! This is a port of the VKMS driver to rust, with
    the intent of acting as an example usecase of the KMS bindings that we've
    come up with so far in preparation for writing a display driver for nova.
    
    Currently RVKMS is an extremely bear bones driver - it only registers a
    device and emulates vblanking, but it exercises a good portion of the API
    that we've introduced so far! Eventually I hope to introduce CRC generation
    and maybe writeback connectors like.
    
    Signed-off-by: Lyude Paul's avatarLyude Paul <lyude@redhat.com>
    
    ---
    V3:
    * Replace platform device usage with faux device
crtc.rs 7.27 KiB
// TODO: License and stuff
// Contain's rvkms's drm_crtc implementation
use core::marker::*;
use super::{RvkmsDriver, plane::*};
use kernel::{
    prelude::*,
    drm::{
        device::Device,
        kms::{
            atomic::*,
            crtc::{self, RawCrtcState, DriverCrtcOps},
            ModeObject,
            KmsRef,
            vblank::*,
        }
    },
    sync::{
        lock::Guard,
        SpinLockIrq,
        LockedBy,
    },
    hrtimer::*,
    time::*,
    local_irq::*,
    sync::{Arc, ArcBorrow},
    new_spinlock_irq,
    impl_has_timer
};

pub(crate) type Crtc = crtc::Crtc<RvkmsCrtc>;
pub(crate) type UnregisteredCrtc = crtc::UnregisteredCrtc<RvkmsCrtc>;
pub(crate) type CrtcState = crtc::CrtcState<RvkmsCrtcState>;

#[derive(Default)]
pub(crate) struct VblankState {
    /// A reference to the current VblankTimer
    timer: Option<Arc<VblankTimer>>,

    /// A reference to a handle for the current VblankTimer
    handle: Option<ArcTimerHandle<VblankTimer>>,

    /// The current frame duration in ns
    ///
    /// Stored separately here so it can be read safely without the vblank lock
    period_ns: i32,
}

#[pin_data]
pub(crate) struct RvkmsCrtc {
    /// The current vblank emulation state
    ///
    /// This is uninitalized when the CRTC is disabled to prevent circular references
    #[pin]
    vblank_state: SpinLockIrq<VblankState>
}

#[vtable]
impl crtc::DriverCrtc for RvkmsCrtc {
    #[unique]
    const OPS: &'static DriverCrtcOps;

    type Args = ();
    type State = RvkmsCrtcState;
    type Driver = RvkmsDriver;
    type VblankImpl = Self;

    fn new(device: &Device<Self::Driver>, args: &Self::Args) -> impl PinInit<Self, Error> {
        try_pin_init!(Self {
            vblank_state <- new_spinlock_irq!(VblankState::default(), "vblank_handle_lock")
        })
    }

    fn atomic_check(
        crtc: &Crtc,
        old_state: &CrtcState,
        mut new_state: crtc::CrtcStateMutator<'_, CrtcState>,
        state: &AtomicStateComposer<Self::Driver>
    ) -> Result {
        state.add_affected_planes(crtc)?;

        // Create a vblank timer when enabling a CRTC, and destroy said timer when disabling to
        // resolve the circular reference to CRTC it creates
        if old_state.active() != new_state.active() {
            new_state.vblank_timer = if new_state.active() {
                Some(VblankTimer::new(crtc)?)
            } else {
                None
            };
        }

        Ok(())
    }

    fn atomic_flush(
        crtc: &Crtc,
        _old_state: &CrtcState,
        mut new_state: crtc::CrtcStateMutator<'_, CrtcState>,
        _state: &AtomicStateMutator<Self::Driver>
    ) {
        if let Some(event) = new_state.get_pending_vblank_event() {
            if let Ok(vbl_ref) = crtc.vblank_get() {
                event.arm(vbl_ref);
            } else {
                event.send();
            }
        }
    }

    fn atomic_enable(
        crtc: &Crtc,
        old_state: &CrtcState,
        new_state: crtc::CrtcStateMutator<'_, CrtcState>,
        _state: &AtomicStateMutator<Self::Driver>
    ) {
        crtc.vblank_state.lock_with_new(|state, _| {
            // Store a reference to the newly created vblank timer for this CRTC
            state.timer = new_state.vblank_timer.clone()
        });

        crtc.vblank_on();
    }

    fn atomic_disable(
        crtc: &Crtc,
        _old_state: &CrtcState,
        _new_state: crtc::CrtcStateMutator<'_, CrtcState>,
        _state: &AtomicStateMutator<Self::Driver>
    ) {
        crtc.vblank_off();

        // Since we just explicitly disabled vblanks, destroy the vblank state to resolve circular
        // reference to this CRTC that it holds. Note that dropping the handle will cause us to wait
        // for the timer to finish, so we return it from with_irqs_disabled so that it is only
        // dropped once the vblank_state lock has been released
        drop(crtc.vblank_state.lock_with_new(|mut state, _| {
            (state.timer.take(), state.handle.take())
        }));
    }
}
impl VblankSupport for RvkmsCrtc {
    type Crtc = Self;

    fn enable_vblank(
        crtc: &Crtc,
        vblank: &VblankGuard<'_, Self::Crtc>,
        irq: IrqDisabled<'_>,
    ) -> Result {
        let period_ns = vblank.frame_duration();
        let mut vbl_state = crtc.vblank_state.lock_with(irq);

        if let Some(timer) = vbl_state.timer.clone() {
            vbl_state.period_ns = period_ns;
            vbl_state.handle = Some(timer.start(Ktime::from_raw(period_ns as _)));
        }

        Ok(())
    }

    fn disable_vblank(crtc: &Crtc, _vbl_guard: &VblankGuard<'_, Self::Crtc>, irq: IrqDisabled<'_>) {
        let handle = crtc.vblank_state.lock_with(irq).handle.take();

        // Now that we're outside of the vblank lock, we can safely drop the handle
        drop(handle);
    }

    fn get_vblank_timestamp(crtc: &Crtc, _handling_vblank_irq: bool) -> Option<VblankTimestamp> {
        let time = crtc.vblank_state.lock_with_new(|state, _| {
            // Return the expiration of our vblank timer if we have one (if not, vblanks are
            // disabled)
            state.timer.as_ref().map(|t| {
                // To prevent races, we roll the hrtimer forward before we do any interrupt
                // processing - this is how real hw works (the interrupt is only generated after all
                // the vblank registers are updated) and what the vblank core expects. Therefore we
                // need to always correct the timestamps by one frame.
                t.timer.expires() - Ktime::from_ns(state.period_ns)
            })
        });

        Some(VblankTimestamp {
            // …otherwise, just use the current time
            time: time.unwrap_or_else(|| Ktime::ktime_get()),
            max_error: 0
        })
    }
}

#[derive(Clone, Default)]
pub(crate) struct RvkmsCrtcState {
    vblank_timer: Option<Arc<VblankTimer>>
}

impl crtc::DriverCrtcState for RvkmsCrtcState {
    type Crtc = RvkmsCrtc;
}

/// The main hrtimer structure for emulating vblanks.
#[pin_data]
pub(crate) struct VblankTimer {
    /// The actual hrtimer used for sending out vblanks
    #[pin]
    timer: Timer<Self>,

    /// An owned reference to the CRTC that this [`VblankTimer`] belongs to
    crtc: KmsRef<Crtc>,
}

impl_has_timer! {
    impl HasTimer<Self> for VblankTimer { self.timer }
}

impl VblankTimer {
    pub(crate) fn new(crtc: &Crtc) -> Result<Arc<Self>> {
        Arc::pin_init(
            pin_init!(Self {
                timer <- Timer::<Self>::new(TimerMode::Relative, ClockSource::Monotonic),
                crtc: crtc.into(),
            }),
            GFP_KERNEL
        )
    }
}

impl TimerCallback for VblankTimer {
    type CallbackTarget<'a> = Arc<Self>;
    type CallbackTargetParameter<'a> = ArcBorrow<'a, Self>;

    fn run<T>(
        this: Self::CallbackTargetParameter<'_>,
        context: TimerCallbackContext<'_, T>
    ) -> TimerRestart
    where
        Self: Sized
    {
        let period_ns = this.crtc.vblank_state.lock_with_new(|guard, _| guard.period_ns);

        let overrun = context.forward_now(Ktime::from_ns(period_ns));
        if overrun != 1 {
            dev_warn!(
                this.crtc.drm_dev().as_ref(),
                "vblank timer overrun (expected 1, got {overrun})\n"
            );
        }

        this.crtc.handle_vblank();

        TimerRestart::Restart
    }
}