diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 6b2c6b91f96250c7cacda4d6bfdf0df1f33c787a..525e2e1615ca26cf17246d91febf6b1589b2204b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -323,6 +323,8 @@ source "drivers/gpu/drm/amd/amdgpu/Kconfig" source "drivers/gpu/drm/nouveau/Kconfig" +source "drivers/gpu/drm/rvkms/Kconfig" + source "drivers/gpu/drm/i915/Kconfig" source "drivers/gpu/drm/xe/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 68cc9258ffc4b88041efc7f6ade13b827e46fba0..ebedcab4dece0ebf7f4d7a93787c3a2dfcf99972 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -172,6 +172,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/ obj-$(CONFIG_DRM_VGEM) += vgem/ obj-$(CONFIG_DRM_VKMS) += vkms/ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/ +obj-$(CONFIG_DRM_RVKMS) += rvkms/ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/ obj-$(CONFIG_DRM_GMA500) += gma500/ diff --git a/drivers/gpu/drm/rvkms/Kconfig b/drivers/gpu/drm/rvkms/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..551422803b9a6b9cb72664e851cd84bf826e5dfe --- /dev/null +++ b/drivers/gpu/drm/rvkms/Kconfig @@ -0,0 +1,3 @@ +config DRM_RVKMS + tristate "Rust VKMS PoC driver (EXPERIMENTAL)" + depends on RUST && DRM && DRM_GEM_SHMEM_HELPER=y diff --git a/drivers/gpu/drm/rvkms/Makefile b/drivers/gpu/drm/rvkms/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..18e06fc3343c67b9b18c58164b479b2d49f183f2 --- /dev/null +++ b/drivers/gpu/drm/rvkms/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DRM_RVKMS) += rvkms.o diff --git a/drivers/gpu/drm/rvkms/connector.rs b/drivers/gpu/drm/rvkms/connector.rs new file mode 100644 index 0000000000000000000000000000000000000000..97b94054fbe1f62d6c0cd44897672480dca0b139 --- /dev/null +++ b/drivers/gpu/drm/rvkms/connector.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0 +use super::{Rvkms, RvkmsDevice, MAX_RES, DEFAULT_RES}; +use kernel::{ + prelude::*, + drm::{ + device::Device, + kms::{ + connector::{self, ConnectorGuard, DriverConnectorOps}, + ModeConfigGuard + } + }, + prelude::* +}; +use core::marker::PhantomPinned; + +#[pin_data] +pub(crate) struct DriverConnector { + #[pin] + _p: PhantomPinned +} + +pub(crate) type Connector = connector::Connector<DriverConnector>; + +#[vtable] +impl connector::DriverConnector for DriverConnector { + #[unique] + const OPS: &'static DriverConnectorOps; + + type State = ConnectorState; + type Driver = Rvkms; + type Args = (); + + fn new(dev: &Device<Self::Driver>, args: Self::Args) -> impl PinInit<Self, Error> { + try_pin_init!(Self { _p: PhantomPinned }) + } + + fn get_modes( + connector: ConnectorGuard<'_, Self>, + _guard: &ModeConfigGuard<'_, Self::Driver> + ) -> i32 { + let count = connector.add_modes_noedid(MAX_RES); + + connector.set_preferred_mode(DEFAULT_RES); + count + } +} + +#[derive(Clone, Default)] +pub(crate) struct ConnectorState; + +impl connector::DriverConnectorState for ConnectorState { + type Connector = DriverConnector; +} diff --git a/drivers/gpu/drm/rvkms/crtc.rs b/drivers/gpu/drm/rvkms/crtc.rs new file mode 100644 index 0000000000000000000000000000000000000000..c3a90c76e4a5e9ef5a73956b0a7ae31f2c8639db --- /dev/null +++ b/drivers/gpu/drm/rvkms/crtc.rs @@ -0,0 +1,253 @@ +// TODO: License and stuff +// Contain's rvkms's drm_crtc implementation +use core::marker::*; +use super::{Rvkms, plane::*}; +use kernel::{ + prelude::*, + drm::{ + device::Device, + kms::{ + atomic::*, + crtc::{self, RawCrtcState, DriverCrtcOps}, + ModeObject, + KmsRef, + vblank::*, + } + }, + sync::{ + lock::Guard, + SpinLockIrq, + LockedBy, + }, + hrtimer::*, + time::*, + irq::*, + sync::Arc, + new_spinlock_irq, + impl_has_timer +}; + +pub(crate) type Crtc = crtc::Crtc<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 = Rvkms; + 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::BorrowedCrtcState<'_, 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::BorrowedCrtcState<'_, 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::BorrowedCrtcState<'_, CrtcState>, + _state: &AtomicStateMutator<Self::Driver> + ) { + with_irqs_disabled(|irq| { + // Store a reference to the newly created vblank timer for this CRTC + crtc.vblank_state.lock_with(irq).timer = new_state.vblank_timer.clone() + }); + + crtc.vblank_on(); + } + + fn atomic_disable( + crtc: &Crtc, + _old_state: &CrtcState, + _new_state: crtc::BorrowedCrtcState<'_, 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(with_irqs_disabled(|irq| { + let mut state = crtc.vblank_state.lock_with(irq); + + (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.schedule(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 = with_irqs_disabled(|irq| { + let vbl_state = crtc.vblank_state.lock_with(irq); + + // Return the expiration of our vblank timer if we have one (if not, vblanks are + // disabled) + vbl_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(vbl_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::<Arc<Self>>(), + crtc: crtc.into(), + }), + GFP_KERNEL + ) + } +} + +impl TimerCallback for VblankTimer { + type CallbackTarget<'a> = Arc<Self>; + + fn run( + this: Self::CallbackTarget<'_>, + context: TimerCallbackContext<'_, Self> + ) -> TimerRestart + where + Self: Sized + { + with_irqs_disabled(|irq| { + let period_ns = this.crtc.vblank_state.lock_with(irq).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 + } +} diff --git a/drivers/gpu/drm/rvkms/encoder.rs b/drivers/gpu/drm/rvkms/encoder.rs new file mode 100644 index 0000000000000000000000000000000000000000..f426ef10bcd7eaeff979229d6986e106c107b3aa --- /dev/null +++ b/drivers/gpu/drm/rvkms/encoder.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +use core::marker::PhantomPinned; +use kernel::{ + drm::{device::Device, kms::encoder}, + prelude::*, + types::ARef, +}; +use crate::{Rvkms, connector::Connector}; + +#[pin_data] +pub(crate) struct DriverEncoder { + connector: ARef<Connector>, + #[pin] + _p: PhantomPinned, +} + +pub(crate) type Encoder = encoder::Encoder<DriverEncoder>; + +#[vtable] +impl encoder::DriverEncoder for DriverEncoder { + #[unique] + const OPS: &'static encoder::DriverEncoderOps; + + type Driver = Rvkms; + type Args = ARef<Connector>; + + fn new(device: &Device<Self::Driver>, args: Self::Args) -> impl PinInit<Self, Error> { + try_pin_init!(Self { + connector: args, + _p: PhantomPinned + }) + } +} diff --git a/drivers/gpu/drm/rvkms/file.rs b/drivers/gpu/drm/rvkms/file.rs new file mode 100644 index 0000000000000000000000000000000000000000..baa9297673ecceb02b0a20964b32737a6e461c9f --- /dev/null +++ b/drivers/gpu/drm/rvkms/file.rs @@ -0,0 +1,22 @@ +use super::Rvkms; + +use kernel::{ + drm::{ + self, + device::Device as DrmDevice + }, + prelude::*, +}; +use core::option::*; + +pub(crate) struct File; + +impl drm::file::DriverFile for File { + type Driver = Rvkms; + + fn open(device: &DrmDevice<Self::Driver>) -> Result<Pin<Box<Self>>> { + pr_info!("Someone opened a file! But I do not yet know which one...\n"); + + Box::pin_init(init!(File { }), GFP_KERNEL) + } +} diff --git a/drivers/gpu/drm/rvkms/gem.rs b/drivers/gpu/drm/rvkms/gem.rs new file mode 100644 index 0000000000000000000000000000000000000000..950ef33758657de297e1a92517e030ec0ae9d2dd --- /dev/null +++ b/drivers/gpu/drm/rvkms/gem.rs @@ -0,0 +1,30 @@ +use crate::{Rvkms, RvkmsDevice}; +use core::sync::atomic::{AtomicU64, Ordering}; +use kernel::{ + drm::{self, gem}, + prelude::*, +}; + +static GEM_ID: AtomicU64 = AtomicU64::new(0); + +/// GEM Object implementation +#[pin_data] +pub(crate) struct DriverObject { + /// ID for debugging + id: u64, +} + +pub(crate) type Object = gem::shmem::Object<DriverObject>; + +impl gem::BaseDriverObject<Object> for DriverObject { + fn new(dev: &RvkmsDevice, size: usize) -> impl PinInit<Self, Error> { + let id = GEM_ID.fetch_add(1, Ordering::Relaxed); + + pr_debug!("DriverObject::new id={id}\n"); + DriverObject { id } + } +} + +impl gem::shmem::DriverObject for DriverObject { + type Driver = Rvkms; +} diff --git a/drivers/gpu/drm/rvkms/output.rs b/drivers/gpu/drm/rvkms/output.rs new file mode 100644 index 0000000000000000000000000000000000000000..b110e2d5d8a8b64d3fd8c225d3fb1837a237fb27 --- /dev/null +++ b/drivers/gpu/drm/rvkms/output.rs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0 +use crate::{ + crtc::Crtc, + plane::Plane, + connector::Connector, + encoder::Encoder, + RvkmsDevice, + Rvkms +}; +use kernel::{ + drm::{ + fourcc::*, + kms::{ + connector::DRM_MODE_CONNECTOR_VIRTUAL, + encoder::DRM_MODE_ENCODER_VIRTUAL, + plane::{self, PlaneType}, + framebuffer::*, + UnregisteredKmsDevice, + }, + }, + sync::Arc, + prelude::*, + types::ARef, +}; + +const FORMATS: FormatList<1> = FormatList::new([XRGB888]); + +pub(crate) fn create_output(dev: &UnregisteredKmsDevice<'_, Rvkms>, index: u8) -> Result { + let possible_crtcs = 1 << index; + + let primary = Plane::new( + dev, + possible_crtcs, + &FORMATS, + Option::<&ModifierList<0>>::None, + PlaneType::PRIMARY, + None, + () + )?; + + let crtc = Crtc::new(dev, primary, Option::<&Plane>::None, None, ())?; + + let connector = Connector::new(dev, DRM_MODE_CONNECTOR_VIRTUAL, ())?; + + let encoder = Encoder::new( + dev, + DRM_MODE_ENCODER_VIRTUAL, + possible_crtcs, + 0, + None, + connector.clone() + )?; + + connector.attach_encoder(encoder) +} diff --git a/drivers/gpu/drm/rvkms/plane.rs b/drivers/gpu/drm/rvkms/plane.rs new file mode 100644 index 0000000000000000000000000000000000000000..2722845a32e9ab355033fc1408bce0db05937656 --- /dev/null +++ b/drivers/gpu/drm/rvkms/plane.rs @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0 +use core::marker::PhantomPinned; +use super::{Rvkms, crtc::{RvkmsCrtc, Crtc}}; +use kernel::{ + prelude::*, + drm::{ + device::Device, + kms::{ + atomic::*, + plane::{ + self, + AsRawPlaneState, + FromRawPlaneState, + DriverPlaneState, + RawPlane, + RawPlaneState, + BorrowedPlaneState, + DriverPlaneOps, + }, + ModeObject + } + }, +}; + +#[pin_data] +pub(crate) struct RvkmsPlane { + #[pin] + _p: PhantomPinned, +} + +pub(crate) type Plane = plane::Plane<RvkmsPlane>; +pub(crate) type PlaneState = plane::PlaneState<RvkmsPlaneState>; + +#[vtable] +impl plane::DriverPlane for RvkmsPlane { + #[unique] + const OPS: &'static DriverPlaneOps; + + type State = RvkmsPlaneState; + type Driver = Rvkms; + type Args = (); + + fn new(device: &Device<Self::Driver>, args: Self::Args) -> impl PinInit<Self, Error> { + try_pin_init!(Self { _p: PhantomPinned }) + } + + fn atomic_check( + plane: &Plane, + mut new_state: BorrowedPlaneState<'_, PlaneState>, + _old_state: &PlaneState, + state: &AtomicStateComposer<Self::Driver> + ) -> Result { + if new_state.framebuffer().is_none() { + return Ok(()); + } + + if let Some(crtc) = new_state.crtc() { + let crtc_state = state.add_crtc_state(crtc)?; + new_state.atomic_helper_check(&crtc_state, true, true) + } else { + // TODO: We should be printing a warning here if we have no CRTC but do have an fb + return Ok(()); + } + } + + fn atomic_update( + _plane: &Plane, + _new_state: BorrowedPlaneState<'_, PlaneState>, + _old_state: &PlaneState, + _state: &AtomicStateMutator<Self::Driver>, + ) { + // TODO, no-op for now + } +} + +#[derive(Clone, Default)] +pub(crate) struct RvkmsPlaneState; + +impl DriverPlaneState for RvkmsPlaneState { + type Plane = RvkmsPlane; +} diff --git a/drivers/gpu/drm/rvkms/rvkms.rs b/drivers/gpu/drm/rvkms/rvkms.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c72c0ec6989defe9045190b67ca8163ed545f3a --- /dev/null +++ b/drivers/gpu/drm/rvkms/rvkms.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +mod connector; +mod crtc; +mod file; +mod gem; +mod plane; +mod output; +mod encoder; + +use alloc::boxed::Box; + +use core::{option::*, marker::*}; + +use kernel::{ + c_str, + str::CStr, + device, + driver, + drm::{ + self, + drv, + kms::{ + Kms, + ModeConfigInfo, + UnregisteredKmsDevice, + atomic::*, + fbdev::*, + }, + }, + platform, + prelude::*, + sync::Arc, +}; + +/// Convienence type alias for the DRM device type for this driver +pub(crate) type RvkmsDevice = drm::device::Device<Rvkms>; + +/// The name of the driver +const NAME: &'static CStr = c_str!("rvkms"); + +/// Driver metadata +const INFO: drv::DriverInfo = drv::DriverInfo { + major: 0, + minor: 0, + patchlevel: 0, + name: &NAME, + desc: c_str!("Rust VKMS PoC"), + date: c_str!("20240115"), +}; + +/// The minimum supported resolution +const MIN_RES: (i32, i32) = (10, 10); + +/// The maximum supported resolution +const MAX_RES: (i32, i32) = (8192, 8192); + +/// The "preferred" resolution +const DEFAULT_RES: (i32, i32) = (1024, 768); + +pub(crate) struct Data { +} + +/// DRM Driver implementation for `RvkmsDriver` +#[vtable] +impl drv::Driver for Rvkms { + type Data = Arc<Data>; + type Object = gem::Object; + type File = file::File; + type Kms = Self; + + const INFO: drv::DriverInfo = INFO; + const FEATURES:u32 = drv::FEAT_GEM | drv::FEAT_MODESET | drv::FEAT_ATOMIC; + + kernel::declare_drm_ioctls! {} +} + +#[vtable] +impl Kms for Rvkms { + type Driver = Self; + type Fbdev = FbdevShmem<Self>; + + fn mode_config_info( + _dev: &device::Device, + _drm_data: <<Self::Driver as drv::Driver>::Data as kernel::types::ForeignOwnable>::Borrowed<'_>, + ) -> Result<ModeConfigInfo> { + Ok(MODE_CONFIG_INFO) + } + + fn create_objects(drm: &UnregisteredKmsDevice<'_, Self::Driver>) -> Result { + output::create_output(drm, 0) + } + + fn atomic_commit_tail<'a>( + mut state: AtomicCommitTail<'a, Self::Driver>, + modeset_token: ModesetsReadyToken<'_>, + plane_update_token: PlaneUpdatesReadyToken<'_>, + ) -> CommittedAtomicState<'a, Self::Driver> { + let modeset_token = state.commit_modeset_disables(modeset_token); + + let plane_update_token = state.commit_planes(plane_update_token, Default::default()); + + let modeset_token = state.commit_modeset_enables(modeset_token); + + state.fake_vblank(); + + let state = state.commit_hw_done(modeset_token, plane_update_token); + + state.wait_for_flip_done(); + + state + } +} + +impl platform::Driver for Rvkms { + type Data = Arc<Data>; + type IdInfo = (); + + fn probe(pdev: &mut platform::Device, id_info: Option<&Self::IdInfo>) -> Result<Self::Data> { + // XXX: do not fret, the mutable reference here is temporary (poke dakr if it isn't) + let dev: &device::Device = pdev.as_ref(); + dev.pr_info(format_args!("RVKMS probing\n")); + + let data = Arc::new(Data { }, GFP_KERNEL)?; + let drm = drv::Registration::<Rvkms>::new_foreign_owned(dev, data.clone(), 0)?; + + Ok(data) + } +} + +pub(crate) struct Rvkms { + drv_reg: Pin<Box<platform::Registration<Self>>>, + pdev: platform::Device, +} + +const MODE_CONFIG_INFO: ModeConfigInfo = ModeConfigInfo { + min_resolution: MIN_RES, + max_resolution: MAX_RES, + max_cursor: (512, 512), + preferred_depth: 0, +}; + +impl kernel::Module for Rvkms { + fn init(name: &'static CStr, module: &'static ThisModule) -> kernel::error::Result<Self> { + pr_info!("RVKMS module loaded\n"); + + // Register the driver (FIXME: this should be static + let drv_reg = Box::try_pin_init( + platform::Registration::<Self>::new(name, module), + GFP_KERNEL + )?; + + let pdev = platform::Device::create_simple(&NAME, 0)?; + let dev: &device::Device = pdev.as_ref(); + + Ok(Self { + drv_reg, + pdev, + }) + } +} + +module! { + type: Rvkms, + name: "rvkms", + author: "Lyude Paul", + description: "Rust VKMS Proof of Concept driver", + license: "GPL v2", +}