mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
342 lines
11 KiB
Rust
342 lines
11 KiB
Rust
use log::{error, info, warn};
|
|
use std::error::Error;
|
|
use std::io::Write;
|
|
use std::iter::FromIterator;
|
|
use std::path::Path;
|
|
use std::process::Command;
|
|
use std::str::FromStr;
|
|
use sysfs_class::{PciDevice, SysClass};
|
|
use zbus::dbus_interface;
|
|
|
|
use crate::vendors::*;
|
|
use crate::*;
|
|
use crate::{error::GfxError, system::*};
|
|
|
|
pub struct CtrlGraphics {
|
|
bus: PciBus,
|
|
amd: Vec<GraphicsDevice>,
|
|
intel: Vec<GraphicsDevice>,
|
|
nvidia: Vec<GraphicsDevice>,
|
|
#[allow(dead_code)]
|
|
other: Vec<GraphicsDevice>,
|
|
initfs_cmd: Option<Command>,
|
|
}
|
|
|
|
trait Dbus {
|
|
fn set_vendor(&mut self, vendor: String);
|
|
fn notify_gfx(&self, vendor: &str) -> zbus::Result<()>;
|
|
fn notify_action(&self, action: &str) -> zbus::Result<()>;
|
|
}
|
|
|
|
#[cfg(feature = "use-zbus")]
|
|
use std::convert::TryInto;
|
|
|
|
#[cfg(feature = "use-zbus")]
|
|
#[dbus_interface(name = "org.asuslinux.Daemon")]
|
|
impl Dbus for CtrlGraphics {
|
|
fn set_vendor(&mut self, vendor: String) {
|
|
if let Ok(tmp) = GfxVendors::from_str(&vendor) {
|
|
let action = self.set(tmp).unwrap_or_else(|err| {
|
|
warn!("{}", err);
|
|
format!("Failed: {}", err.to_string())
|
|
});
|
|
self.notify_gfx(&vendor)
|
|
.unwrap_or_else(|err| warn!("{}", err));
|
|
self.notify_action(&action)
|
|
.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 CtrlGraphics {
|
|
pub fn new() -> std::io::Result<CtrlGraphics> {
|
|
let bus = PciBus::new()?;
|
|
|
|
info!("Rescanning PCI bus");
|
|
bus.rescan()?;
|
|
|
|
let devs = PciDevice::all()?;
|
|
|
|
let functions = |parent: &PciDevice| -> Vec<PciDevice> {
|
|
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)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut initfs_cmd = None;
|
|
|
|
if Path::new(INITRAMFS_PATH).exists() {
|
|
let mut cmd = Command::new("update-initramfs");
|
|
cmd.arg("-u");
|
|
initfs_cmd = Some(cmd);
|
|
info!("Using initramfs update command 'update-initramfs'");
|
|
} else if Path::new(DRACUT_PATH).exists() {
|
|
let mut cmd = Command::new("dracut");
|
|
cmd.arg("-f");
|
|
initfs_cmd = Some(cmd);
|
|
info!("Using initramfs update command 'dracut'");
|
|
}
|
|
|
|
Ok(CtrlGraphics {
|
|
bus,
|
|
amd,
|
|
intel,
|
|
nvidia,
|
|
other,
|
|
initfs_cmd,
|
|
})
|
|
}
|
|
|
|
#[cfg(feature = "use-zbus")]
|
|
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
|
|
server
|
|
.at(&"/org/asuslinux/Gfx".try_into().unwrap(), self)
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn reload(&mut self) -> Result<(), Box<dyn Error>> {
|
|
self.auto_power()?;
|
|
info!("Reloaded gfx mode: {:?}", CtrlGraphics::get_vendor()?);
|
|
Ok(())
|
|
}
|
|
|
|
fn can_switch(&self) -> bool {
|
|
!self.nvidia.is_empty() && (!self.intel.is_empty() || !self.amd.is_empty())
|
|
}
|
|
|
|
fn get_prime_discrete() -> Result<String, GfxError> {
|
|
let s = std::fs::read_to_string(PRIME_DISCRETE_PATH)
|
|
.map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))?
|
|
.trim()
|
|
.to_owned();
|
|
Ok(s)
|
|
}
|
|
|
|
fn set_prime_discrete(mode: &str) -> Result<(), GfxError> {
|
|
std::fs::write(PRIME_DISCRETE_PATH, mode)
|
|
.map_err(|err| GfxError::Read(PRIME_DISCRETE_PATH.into(), err))?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Associated method to get which vendor mode is set
|
|
pub fn get_vendor() -> Result<String, GfxError> {
|
|
let modules = Module::all().map_err(|err| GfxError::Read("get_vendor".into(), err))?;
|
|
let vendor = if modules
|
|
.iter()
|
|
.any(|module| module.name == "nouveau" || module.name == "nvidia")
|
|
{
|
|
let mode = match Self::get_prime_discrete() {
|
|
Ok(m) => m,
|
|
Err(_) => "nvidia".to_string(),
|
|
};
|
|
|
|
if mode == "on-demand" {
|
|
"hybrid".to_string()
|
|
} else if mode == "off" {
|
|
"compute".to_string()
|
|
} else {
|
|
"nvidia".to_string()
|
|
}
|
|
} else {
|
|
"integrated".to_string()
|
|
};
|
|
|
|
Ok(vendor)
|
|
}
|
|
|
|
pub fn is_switching_prime_modes(vendor: &GfxVendors) -> Result<bool, GfxError> {
|
|
let prev_mode = GfxVendors::from_str(&Self::get_vendor()?)?;
|
|
let x = (prev_mode == GfxVendors::Hybrid || prev_mode == GfxVendors::Nvidia)
|
|
&& (*vendor == GfxVendors::Hybrid || *vendor == GfxVendors::Nvidia);
|
|
Ok(x)
|
|
}
|
|
|
|
/// Write out config files if required, enable/disable relevant services, and update the ramdisk
|
|
pub fn set(&mut self, vendor: GfxVendors) -> Result<String, GfxError> {
|
|
//self.switchable_or_fail()?;
|
|
|
|
let mode = if vendor == GfxVendors::Hybrid {
|
|
"on-demand\n"
|
|
} else if vendor == GfxVendors::Nvidia {
|
|
"on\n"
|
|
} else {
|
|
// Integrated or Compute
|
|
"off\n"
|
|
};
|
|
|
|
info!("Setting {} to {}", PRIME_DISCRETE_PATH, mode);
|
|
Self::set_prime_discrete(mode)?;
|
|
|
|
// Switching from hybrid to/from nvidia shouldn't require a ramdisk update
|
|
// or a reboot.
|
|
let switching_prime_modes = Self::is_switching_prime_modes(&vendor)?;
|
|
|
|
{
|
|
info!("Creating {}", MODPROBE_PATH);
|
|
|
|
let mut file = std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.truncate(true)
|
|
.write(true)
|
|
.open(MODPROBE_PATH)
|
|
.map_err(|err| GfxError::Path(MODPROBE_PATH.into(), err))?;
|
|
|
|
let text = if vendor == GfxVendors::Hybrid {
|
|
MODPROBE_HYBRID
|
|
} else if vendor == GfxVendors::Compute {
|
|
MODPROBE_COMPUTE
|
|
} else if vendor == GfxVendors::Nvidia {
|
|
MODPROBE_NVIDIA
|
|
} else {
|
|
MODPROBE_INTEGRATED
|
|
};
|
|
|
|
file.write_all(text)
|
|
.and_then(|_| file.sync_all())
|
|
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
|
|
}
|
|
|
|
info!("Creating {}", PRIMARY_GPU_XORG_PATH);
|
|
|
|
// begin section for non-separated Nvidia xorg modules
|
|
// eg, not put in their own directory
|
|
let mut file = std::fs::OpenOptions::new()
|
|
.create(true)
|
|
.truncate(true)
|
|
.write(true)
|
|
.open(PRIMARY_GPU_XORG_PATH)
|
|
.map_err(|err| GfxError::Write(PRIMARY_GPU_XORG_PATH.into(), err))?;
|
|
|
|
let text = if vendor == GfxVendors::Nvidia {
|
|
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_NVIDIA, PRIMARY_GPU_END].concat()
|
|
} else {
|
|
[PRIMARY_GPU_BEGIN, PRIMARY_GPU_END].concat()
|
|
};
|
|
|
|
file.write_all(&text)
|
|
.and_then(|_| file.sync_all())
|
|
.map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?;
|
|
|
|
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| GfxError::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
|
|
);
|
|
}
|
|
|
|
let mut required_action = GfxCtrlAction::None;
|
|
if !switching_prime_modes {
|
|
info!("Updating initramfs");
|
|
if let Some(cmd) = self.initfs_cmd.as_mut() {
|
|
let status = cmd
|
|
.status()
|
|
.map_err(|err| GfxError::Write(format!("{:?}", cmd), err))?;
|
|
if !status.success() {
|
|
error!("Ram disk update failed");
|
|
} else {
|
|
info!("Successfully updated iniramfs");
|
|
}
|
|
}
|
|
required_action = GfxCtrlAction::Reboot;
|
|
} else if switching_prime_modes {
|
|
required_action = GfxCtrlAction::RestartX;
|
|
}
|
|
|
|
Ok(required_action.into())
|
|
}
|
|
|
|
pub fn get_power(&self) -> Option<bool> {
|
|
if self.can_switch() {
|
|
return Some(self.nvidia.iter().any(GraphicsDevice::exists));
|
|
}
|
|
None
|
|
}
|
|
|
|
fn set_power(&self, power: bool) -> Result<(), GfxError> {
|
|
// self.switchable_or_fail()?;
|
|
|
|
if power {
|
|
info!("Enabling graphics power");
|
|
self.bus
|
|
.rescan()
|
|
.map_err(|err| GfxError::Bus("bus rescan error".into(), err))?;
|
|
} else {
|
|
info!("Disabling graphics power");
|
|
|
|
// 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| GfxError::Command("device unbind error".into(), err))?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn auto_power(&self) -> Result<(), GfxError> {
|
|
let vendor = CtrlGraphics::get_vendor()?;
|
|
self.set_power(vendor != "integrated")
|
|
}
|
|
}
|