🔒 Repository is read-only – file editing is disabled.

PaganDE/smithay-0.7.0/src/backend/drm/surface/legacy.rs main

515 linii Raw ← Powrót
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
use drm::control::{connector, crtc, encoder, framebuffer, Device as ControlDevice, Mode, PageFlipFlags};

use std::collections::HashSet;
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc, Mutex, RwLock,
};

use crate::backend::drm::error::AccessError;
use crate::{
    backend::drm::{
        device::legacy::set_connector_state, device::DrmDeviceInternal, error::Error, DrmDeviceFd,
    },
    utils::DevPath,
};

use tracing::{debug, info, info_span, instrument, trace};

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct State {
    pub mode: Mode,
    pub connectors: HashSet<connector::Handle>,
}

impl State {
    fn current_state<A: DevPath + ControlDevice>(fd: &A, crtc: crtc::Handle) -> Result<Self, Error> {
        // Try to enumarate the current state to set the initial state variable correctly.
        // We need an accurate state to handle `commit_pending`.
        let crtc_info = fd.get_crtc(crtc).map_err(|source| {
            Error::Access(AccessError {
                errmsg: "Error loading crtc info",
                dev: fd.dev_path(),
                source,
            })
        })?;

        let current_mode = crtc_info.mode();

        let mut current_connectors = HashSet::new();
        let res_handles = fd.resource_handles().map_err(|source| {
            Error::Access(AccessError {
                errmsg: "Error loading drm resources",
                dev: fd.dev_path(),
                source,
            })
        })?;
        for &con in res_handles.connectors() {
            let con_info = fd.get_connector(con, false).map_err(|source| {
                Error::Access(AccessError {
                    errmsg: "Error loading connector info",
                    dev: fd.dev_path(),
                    source,
                })
            })?;
            if let Some(enc) = con_info.current_encoder() {
                let enc_info = fd.get_encoder(enc).map_err(|source| {
                    Error::Access(AccessError {
                        errmsg: "Error loading encoder info",
                        dev: fd.dev_path(),
                        source,
                    })
                })?;
                if let Some(current_crtc) = enc_info.crtc() {
                    if crtc == current_crtc {
                        current_connectors.insert(con);
                    }
                }
            }
        }

        // If we have no current mode, we create a fake one, which will not match (and thus gets overriden on the commit below).
        // A better fix would probably be making mode an `Option`, but that would mean
        // we need to be sure, we require a mode to always be set without relying on the compiler.
        // So we cheat, because it works and is easier to handle later.
        Ok(State {
            mode: current_mode.unwrap_or_else(|| unsafe { std::mem::zeroed() }),
            connectors: current_connectors,
        })
    }
}

#[derive(Debug)]
pub struct LegacyDrmSurface {
    pub(super) fd: Arc<DrmDeviceInternal>,
    pub(super) active: Arc<AtomicBool>,
    crtc: crtc::Handle,
    state: RwLock<State>,
    pending: RwLock<State>,
    dpms: Mutex<bool>,
    pub(super) span: tracing::Span,
}

impl LegacyDrmSurface {
    pub fn new(
        fd: Arc<DrmDeviceInternal>,
        active: Arc<AtomicBool>,
        crtc: crtc::Handle,
        mode: Mode,
        connectors: &[connector::Handle],
    ) -> Result<Self, Error> {
        let span = info_span!("drm_legacy", crtc = ?crtc);
        let _guard = span.enter();
        info!(?mode, ?connectors, ?crtc, "Initializing drm surface",);

        let state = State::current_state(&*fd, crtc)?;
        let pending = State {
            mode,
            connectors: connectors.iter().copied().collect(),
        };

        drop(_guard);
        let surface = LegacyDrmSurface {
            fd,
            active,
            crtc,
            state: RwLock::new(state),
            pending: RwLock::new(pending),
            dpms: Mutex::new(true),
            span,
        };

        Ok(surface)
    }

    pub fn current_connectors(&self) -> HashSet<connector::Handle> {
        self.state.read().unwrap().connectors.clone()
    }

    pub fn pending_connectors(&self) -> HashSet<connector::Handle> {
        self.pending.read().unwrap().connectors.clone()
    }

    pub fn current_mode(&self) -> Mode {
        self.state.read().unwrap().mode
    }

    pub fn pending_mode(&self) -> Mode {
        self.pending.read().unwrap().mode
    }

    #[instrument(parent = &self.span, skip(self))]
    pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        if self.check_connector(conn, &pending.mode)? {
            pending.connectors.insert(conn);
        }

        Ok(())
    }

    #[instrument(parent = &self.span, skip(self))]
    pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
        let mut pending = self.pending.write().unwrap();

        if pending.connectors.contains(&connector) && pending.connectors.len() == 1 {
            return Err(Error::SurfaceWithoutConnectors(self.crtc));
        }

        pending.connectors.remove(&connector);
        Ok(())
    }

    #[instrument(parent = &self.span, skip(self))]
    pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
        if connectors.is_empty() {
            return Err(Error::SurfaceWithoutConnectors(self.crtc));
        }

        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        if connectors
            .iter()
            .map(|conn| self.check_connector(*conn, &pending.mode))
            .collect::<Result<Vec<bool>, _>>()?
            .iter()
            .all(|v| *v)
        {
            pending.connectors = connectors.iter().cloned().collect();
        }

        Ok(())
    }

    #[instrument(level = "debug", parent = &self.span, skip(self))]
    pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut pending = self.pending.write().unwrap();

        // check the connectors to see if this mode is supported
        for connector in &pending.connectors {
            if !self
                .fd
                .get_connector(*connector, false)
                .map_err(|source| {
                    Error::Access(AccessError {
                        errmsg: "Error loading connector info",
                        dev: self.fd.dev_path(),
                        source,
                    })
                })?
                .modes()
                .contains(&mode)
            {
                return Err(Error::ModeNotSuitable(mode));
            }
        }

        pending.mode = mode;

        Ok(())
    }

    pub fn commit_pending(&self) -> bool {
        *self.pending.read().unwrap() != *self.state.read().unwrap()
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let mut current = self.state.write().unwrap();
        let pending = self.pending.read().unwrap();
        let mut dpms = self.dpms.lock().unwrap();

        if !*dpms {
            let connectors = current.connectors.intersection(&pending.connectors);
            set_connector_state(&*self.fd, connectors.copied(), true)?;
            *dpms = true;
        }

        {
            let removed = current.connectors.difference(&pending.connectors);
            let added = pending.connectors.difference(&current.connectors);

            let mut conn_removed = false;
            for conn in removed.clone() {
                if let Ok(info) = self.fd.get_connector(*conn, false) {
                    info!("Removing connector: {:?}", info.interface());
                } else {
                    info!("Removing unknown connector");
                }
                // if the connector was mapped to our crtc, we need to ack the disconnect.
                // the graphics pipeline will not be freed otherwise
                conn_removed = true;
            }
            set_connector_state(&*self.fd, removed.copied(), false)?;

            if conn_removed {
                // null commit (necessary to trigger removal on the kernel side with the legacy api.)
                self.fd
                    .set_crtc(self.crtc, None, (0, 0), &[], None)
                    .map_err(|source| {
                        Error::Access(AccessError {
                            errmsg: "Error setting crtc",
                            dev: self.fd.dev_path(),
                            source,
                        })
                    })?;
            }

            for conn in added.clone() {
                if let Ok(info) = self.fd.get_connector(*conn, false) {
                    info!("Adding connector: {:?}", info.interface());
                } else {
                    info!("Adding unknown connector");
                }
            }
            set_connector_state(&*self.fd, added.copied(), true)?;

            if current.mode != pending.mode {
                info!("Setting new mode: {:?}", pending.mode.name());
            }
        }

        debug!("Setting screen");
        // do a modeset and attach the given framebuffer
        self.fd
            .set_crtc(
                self.crtc,
                Some(framebuffer),
                (0, 0),
                &pending
                    .connectors
                    .iter()
                    .copied()
                    .collect::<Vec<connector::Handle>>(),
                Some(pending.mode),
            )
            .map_err(|source| {
                Error::Access(AccessError {
                    errmsg: "Error setting crtc",
                    dev: self.fd.dev_path(),
                    source,
                })
            })?;

        *current = pending.clone();

        if event {
            // set crtc does not trigger page_flip events, so we immediately queue a flip
            // with the same framebuffer.
            // this will result in wasting a frame, because this flip will need to wait
            // for `set_crtc`, but is necessary to drive the event loop and thus provide
            // a more consistent api.
            ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err(
                |source| {
                    Error::Access(AccessError {
                        errmsg: "Failed to queue page flip",
                        dev: self.fd.dev_path(),
                        source,
                    })
                },
            )?;
        }

        Ok(())
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
        trace!("Queueing Page flip");

        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let current = self.state.read().unwrap();
        let mut dpms = self.dpms.lock().unwrap();
        if !*dpms {
            set_connector_state(&*self.fd, current.connectors.iter().copied(), true)?;
            *dpms = true;
        }

        ControlDevice::page_flip(
            &*self.fd,
            self.crtc,
            framebuffer,
            if event {
                PageFlipFlags::EVENT
            } else {
                PageFlipFlags::empty()
            },
            None,
        )
        .map_err(|source| {
            Error::Access(AccessError {
                errmsg: "Failed to page flip",
                dev: self.fd.dev_path(),
                source,
            })
        })
    }

    #[instrument(level = "trace", parent = &self.span, skip(self))]
    #[profiling::function]
    pub fn test_buffer(&self, fb: framebuffer::Handle, mode: &Mode) -> Result<(), Error> {
        if !self.active.load(Ordering::SeqCst) {
            return Err(Error::DeviceInactive);
        }

        let pending = self.pending.read().unwrap();

        debug!("Setting screen for buffer *testing*");
        self.fd
            .set_crtc(
                self.crtc,
                Some(fb),
                (0, 0),
                &pending
                    .connectors
                    .iter()
                    .copied()
                    .collect::<Vec<connector::Handle>>(),
                Some(*mode),
            )
            .map_err(|source| {
                Error::Access(AccessError {
                    errmsg: "Failed to test buffer",
                    dev: self.fd.dev_path(),
                    source,
                })
            })
    }

    // we use this function to verify, if a certain connector/mode combination
    // is valid on our crtc. We do this with the most basic information we have:
    // - is there a matching encoder
    // - does the connector support the provided Mode.
    //
    // Better would be some kind of test commit to ask the driver,
    // but that only exists for the atomic api.
    fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
        let info = self.fd.get_connector(conn, false).map_err(|source| {
            Error::Access(AccessError {
                errmsg: "Error loading connector info",
                dev: self.fd.dev_path(),
                source,
            })
        })?;

        // check if the connector can handle the current mode
        if info.modes().contains(mode) {
            // check if there is a valid encoder
            let encoders = info
                .encoders()
                .iter()
                .map(|encoder| {
                    self.fd.get_encoder(*encoder).map_err(|source| {
                        Error::Access(AccessError {
                            errmsg: "Error loading encoder info",
                            dev: self.fd.dev_path(),
                            source,
                        })
                    })
                })
                .collect::<Result<Vec<encoder::Info>, _>>()?;

            // and if any encoder supports the selected crtc
            let resource_handles = self.fd.resource_handles().map_err(|source| {
                Error::Access(AccessError {
                    errmsg: "Error loading resources",
                    dev: self.fd.dev_path(),
                    source,
                })
            })?;
            if !encoders
                .iter()
                .map(|encoder| encoder.possible_crtcs())
                .all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc))
            {
                Ok(false)
            } else {
                Ok(true)
            }
        } else {
            Ok(false)
        }
    }

    pub(crate) fn reset_state<B: DevPath + ControlDevice + 'static>(
        &self,
        fd: Option<&B>,
    ) -> Result<(), Error> {
        *self.state.write().unwrap() = if let Some(fd) = fd {
            State::current_state(fd, self.crtc)?
        } else {
            State::current_state(&*self.fd, self.crtc)?
        };
        Ok(())
    }

    pub(crate) fn device_fd(&self) -> &DrmDeviceFd {
        self.fd.device_fd()
    }

    pub fn clear(&self) -> Result<(), Error> {
        let current = self.state.read().unwrap();
        let mut dpms = self.dpms.lock().unwrap();
        if *dpms {
            set_connector_state(&*self.fd, current.connectors.iter().copied(), false)?;
            *dpms = false;
        }
        Ok(())
    }
}

impl Drop for LegacyDrmSurface {
    fn drop(&mut self) {
        let _guard = self.span.enter();
        // ignore failure at this point

        if !self.active.load(Ordering::SeqCst) {
            // the device is gone or we are on another tty
            // old state has been restored, we shouldn't touch it.
            // if we are on another tty the connectors will get disabled
            // by the device, when switching back
            return;
        }

        // disable connectors again
        let current = self.state.read().unwrap();
        if set_connector_state(&*self.fd, current.connectors.iter().copied(), false).is_ok() {
            // null commit
            let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None);
        }
    }
}

#[cfg(test)]
mod test {
    use super::LegacyDrmSurface;

    fn is_send<S: Send>() {}

    #[test]
    fn surface_is_send() {
        is_send::<LegacyDrmSurface>();
    }
}