use ctrl_gfx::error::GfxError; use ctrl_gfx::*; use log::{error, info, warn}; use rog_types::gfx_vendors::GfxVendors; use std::iter::FromIterator; use std::process::Command; use std::str::FromStr; use std::{io::Write, ops::Add, path::Path}; use std::{sync::Arc, sync::Mutex}; use sysfs_class::{PciDevice, SysClass}; use system::{GraphicsDevice, PciBus}; use zbus::dbus_interface; use crate::*; pub struct CtrlGraphics { bus: PciBus, _amd: Vec, _intel: Vec, nvidia: Vec, #[allow(dead_code)] other: Vec, config: Arc>, } trait Dbus { fn vendor(&self) -> String; fn power(&self) -> String; fn set_vendor(&mut self, vendor: String); fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>; fn notify_action(&self, action: &str) -> zbus::Result<()>; } use std::convert::TryInto; #[dbus_interface(name = "org.asuslinux.Daemon")] impl Dbus for CtrlGraphics { fn vendor(&self) -> String { self.get_gfx_mode() .map(|gfx| gfx.into()) .unwrap_or_else(|err| format!("Get vendor failed: {}", err)) } fn power(&self) -> String { Self::get_runtime_status().unwrap_or_else(|err| format!("Get power status failed: {}", err)) } fn set_vendor(&mut self, vendor: String) { if let Ok(tmp) = GfxVendors::from_str(&vendor) { info!("Switching gfx mode to {}", vendor); let msg = self.set_gfx_config(tmp).unwrap_or_else(|err| { error!("{}", err); format!("Failed: {}", err.to_string()) }); self.notify_gfx(&vendor) .unwrap_or_else(|err| warn!("{}", err)); self.notify_action(&msg) .unwrap_or_else(|err| warn!("{}", err)); } } #[dbus_interface(signal)] fn notify_gfx(&self, vendor: &str) -> zbus::Result<()> {} #[dbus_interface(signal)] fn notify_action(&self, action: &str) -> zbus::Result<()> {} } impl ZbusAdd for CtrlGraphics { fn add_to_server(self, server: &mut zbus::ObjectServer) { server .at( &"/org/asuslinux/Gfx" .try_into() .expect("Couldn't add to zbus"), self, ) .map_err(|err| { warn!("CtrlGraphics: add_to_server {}", err); err }) .ok(); } } impl Reloadable for CtrlGraphics { fn reload(&mut self) -> Result<(), RogError> { self.auto_power()?; info!("Reloaded gfx mode: {:?}", self.get_gfx_mode()?); Ok(()) } } impl CtrlGraphics { pub fn new(config: Arc>) -> std::io::Result { let bus = PciBus::new()?; info!("Rescanning PCI bus"); bus.rescan()?; let devs = PciDevice::all()?; let functions = |parent: &PciDevice| -> Vec { let mut functions = Vec::new(); if let Some(parent_slot) = parent.id().split('.').next() { for func in devs.iter() { if let Some(func_slot) = func.id().split('.').next() { if func_slot == parent_slot { info!("{}: Function for {}", func.id(), parent.id()); functions.push(func.clone()); } } } } functions }; let mut amd = Vec::new(); let mut intel = Vec::new(); let mut nvidia = Vec::new(); let mut other = Vec::new(); for dev in devs.iter() { let c = dev.class()?; if 0x03 == (c >> 16) & 0xFF { match dev.vendor()? { 0x1002 => { info!("{}: AMD graphics", dev.id()); amd.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } 0x10DE => { info!("{}: NVIDIA graphics", dev.id()); nvidia.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } 0x8086 => { info!("{}: Intel graphics", dev.id()); intel.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } vendor => { info!("{}: Other({:X}) graphics", dev.id(), vendor); other.push(GraphicsDevice::new(dev.id().to_owned(), functions(&dev))); } } } } Ok(CtrlGraphics { bus, _amd: amd, _intel: intel, nvidia, other, config, }) } fn save_gfx_mode(&self, vendor: GfxVendors) -> Result<(), RogError> { if let Ok(mut config) = self.config.lock() { config.gfx_mode = vendor.clone(); config.write(); return Ok(()); } // TODO: Error here Ok(()) } /// Associated method to get which vendor mode is set pub fn get_gfx_mode(&self) -> Result { if let Ok(config) = self.config.lock() { return Ok(config.gfx_mode.clone()); } // TODO: Error here Ok(GfxVendors::Hybrid) } fn get_runtime_status() -> Result { const PATH: &str = "/sys/bus/pci/devices/0000:01:00.0/power/runtime_status"; let buf = std::fs::read_to_string(PATH).map_err(|err| RogError::Read(PATH.into(), err))?; Ok(buf) } fn toggle_fallback_service(vendor: GfxVendors) -> Result<(), RogError> { let action = if vendor == GfxVendors::Nvidia { info!("Enabling nvidia-fallback.service"); "enable" } else { info!("Disabling nvidia-fallback.service"); "disable" }; let status = Command::new("systemctl") .arg(action) .arg("nvidia-fallback.service") .status() .map_err(|err| RogError::Command("systemctl".into(), err))?; if !status.success() { // Error is ignored in case this service is removed warn!( "systemctl: {} (ignore warning if service does not exist!)", status ); } Ok(()) } fn write_xorg_conf(vendor: GfxVendors) -> Result<(), RogError> { let text = if vendor == GfxVendors::Nvidia { [PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat() } else { [PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat() }; if !Path::new(XORG_PATH).exists() { std::fs::create_dir(XORG_PATH).map_err(|err| RogError::Write(XORG_PATH.into(), err))?; } let file = XORG_PATH.to_string().add(XORG_FILE); info!("Writing {}", file); let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(&file) .map_err(|err| RogError::Write(file, err))?; file.write_all(&text) .and_then(|_| file.sync_all()) .map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?; Ok(()) } fn write_modprobe_conf() -> Result<(), RogError> { info!("Writing {}", MODPROBE_PATH); let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(MODPROBE_PATH) .map_err(|err| RogError::Path(MODPROBE_PATH.into(), err))?; file.write_all(MODPROBE_BASE) .and_then(|_| file.sync_all()) .map_err(|err| RogError::Write(MODPROBE_PATH.into(), err))?; Ok(()) } fn unbind_remove_nvidia(&self) -> Result<(), RogError> { // Unbind NVIDIA graphics devices and their functions let unbinds = self.nvidia.iter().map(|dev| dev.unbind()); // Remove NVIDIA graphics devices and their functions let removes = self.nvidia.iter().map(|dev| dev.remove()); Result::from_iter(unbinds.chain(removes)) .map_err(|err| RogError::Command("device unbind error".into(), err))?; Ok(()) } fn do_driver_action(driver: &str, action: &str) -> Result<(), RogError> { let mut cmd; if Self::kmod_exists() { info!("using kmod"); cmd = Command::new("kmod"); cmd.arg(action); } else { cmd = Command::new(action); } cmd.arg(driver); let output = cmd .output() .map_err(|err| RogError::Command(format!("{:?}", cmd), err))?; if !output.status.success() { if output.stderr.ends_with("is not currently loaded\n".as_bytes()) { return Ok(()) } if output.stderr.ends_with("Permission denied\n".as_bytes()) { let msg = format!("{} {} failed: {:?}", action, driver, String::from_utf8_lossy(&output.stderr)); warn!("{}", msg); warn!("It may be safe to ignore the above error, run `lsmod |grep nvidia` to confirm modules loaded"); return Ok(()) } let msg = format!("{} {} failed: {:?}", action, driver, String::from_utf8_lossy(&output.stderr)); return Err(RogError::Modprobe(msg)); } Ok(()) } fn do_display_manager_action(action: &str) -> Result<(), RogError> { let mut cmd = Command::new("systemctl"); cmd.arg(action); cmd.arg(DISPLAY_MANAGER); let status = cmd .status() .map_err(|err| RogError::Command(format!("{:?}", cmd), err))?; if !status.success() { let msg = format!( "systemctl {} {} failed: {:?}", action, DISPLAY_MANAGER, status ); return Err(GfxError::DisplayManager(msg).into()); } Ok(()) } fn wait_display_manager_inactive() -> Result<(), RogError> { let mut cmd = Command::new("systemctl"); cmd.arg("is-active"); cmd.arg(DISPLAY_MANAGER); let mut count = 0; while count <= 4 { let output = cmd .output() .map_err(|err| RogError::Command(format!("{:?}", cmd), err))?; if output.stdout.starts_with("inactive".as_bytes()) { return Ok(()); } std::thread::sleep(std::time::Duration::from_millis(500)); count += 1; } return Err( GfxError::DisplayManager("display-manager did not completely stop".into()).into(), ); } fn kmod_exists() -> bool { let mut cmd = Command::new("which"); cmd.arg("kmod"); if let Ok(output) = cmd .output() { return output.status.success() && output.stdout.ends_with("kmod".as_bytes()) } //Path::new("/usr/bin/kmod").exists() false } pub fn do_vendor_tasks(&mut self, vendor: GfxVendors) -> Result<(), RogError> { Self::write_xorg_conf(vendor)?; Self::write_modprobe_conf()?; // TODO: Not required here, should put in startup? // Rescan before doing remove or add drivers self.bus .rescan() .map_err(|err| GfxError::Bus("bus rescan error".into(), err))?; match vendor { GfxVendors::Nvidia | GfxVendors::Hybrid | GfxVendors::Compute => { for driver in NVIDIA_DRIVERS.iter() { Self::do_driver_action(driver, "modprobe")?; } } // TODO: compute mode, needs different setup // GfxVendors::Compute => {} GfxVendors::Integrated => { for driver in NVIDIA_DRIVERS.iter() { Self::do_driver_action(driver, "rmmod")?; } self.unbind_remove_nvidia()?; } } self.save_gfx_mode(vendor)?; Ok(()) } /// For manually calling (not on boot/startup) /// /// Will stop and start display manager without warning pub fn set_gfx_config(&mut self, vendor: GfxVendors) -> Result { Self::do_display_manager_action("stop")?; Self::wait_display_manager_inactive()?; self.do_vendor_tasks(vendor)?; Self::do_display_manager_action("start")?; // TODO: undo if failed? Save last mode, catch errors... let v: &str = vendor.into(); Ok(format!("Graphics mode changed to {} successfully", v)) } // if CtrlRogBios::has_dedicated_gfx_toggle() { // if let Ok(config) = self.config.clone().try_lock() { // // Switch to dedicated if config says to do so // if config.gfx_nv_mode_is_dedicated && vendor == GfxVendors::Nvidia { // CtrlRogBios::set_gfx_mode(true) // .unwrap_or_else(|err| warn!("Gfx controller: {}", err)); // } else if let Ok(ded) = CtrlRogBios::get_gfx_mode() { // // otherwise if switching to non-Nvidia mode turn off dedicated mode // if ded == 1 && vendor != GfxVendors::Nvidia { // CtrlRogBios::set_gfx_mode(false) // .unwrap_or_else(|err| warn!("Gfx controller: {}", err)); // } // } // } // } fn auto_power(&mut self) -> Result<(), RogError> { let vendor = self.get_gfx_mode()?; self.do_vendor_tasks(vendor)?; Self::toggle_fallback_service(vendor)?; Ok(()) } }