From 0b1bf8d185f99825b89bdfd457b92f654dc4a6c2 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Fri, 29 Aug 2025 17:16:50 +0200 Subject: [PATCH 1/2] Remove switchable graphics --- data/com.system76.PowerDaemon.xml | 24 -- src/args.rs | 32 -- src/client.rs | 49 +-- src/daemon/mod.rs | 58 +--- src/graphics.rs | 491 +++--------------------------- src/lib.rs | 1 - src/module.rs | 25 -- 7 files changed, 42 insertions(+), 638 deletions(-) delete mode 100644 src/module.rs diff --git a/data/com.system76.PowerDaemon.xml b/data/com.system76.PowerDaemon.xml index b691a0c2..557a91ca 100644 --- a/data/com.system76.PowerDaemon.xml +++ b/data/com.system76.PowerDaemon.xml @@ -28,30 +28,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/args.rs b/src/args.rs index d663fa76..ade9c886 100644 --- a/src/args.rs +++ b/src/args.rs @@ -4,34 +4,6 @@ use clap::{builder::PossibleValuesParser, Parser}; -#[derive(Parser)] -#[clap( - about = "Query or set the graphics mode", - long_about = "Query or set the graphics mode.\n\n - If an argument is not provided, the \ - graphics profile will be queried\n - Otherwise, that profile will be set, if it \ - is a valid profile\n\nA reboot is required after switching modes." -)] -pub enum GraphicsArgs { - #[clap(about = "Like integrated, but the dGPU is available for compute")] - Compute, - #[clap(about = "Set the graphics mode to Hybrid (PRIME)")] - Hybrid, - #[clap(about = "Set the graphics mode to integrated")] - Integrated, - #[clap(about = "Set the graphics mode to NVIDIA")] - Nvidia, - #[clap(about = "Determines if the system has switchable graphics")] - Switchable, - #[clap(about = "Query or set the discrete graphics power state")] - Power { - #[clap(help = "Set whether discrete graphics should be on or off")] - #[arg( - value_parser = PossibleValuesParser::new(["auto", "off", "on"]) - )] - state: Option, - }, -} - #[derive(Parser)] #[clap( name = "system76-power", @@ -78,10 +50,6 @@ pub enum Args { )] profile: Option, }, - Graphics { - #[clap(subcommand)] - cmd: Option, - }, #[clap( about = "Set thresholds for battery charging", // Autogenerated usage seemed to have issues diff --git a/src/client.rs b/src/client.rs index 48f393e6..63da5cd1 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use crate::args::{Args, GraphicsArgs}; +use crate::args::Args; use anyhow::Context; use intel_pstate::PState; use std::io; @@ -75,53 +75,6 @@ Battery power profile is not supported on desktop computers. Some("performance") => client.performance().await.map_err(zbus_error), _ => profile(&mut client).await.context("failed to get power profile"), }, - Args::Graphics { cmd } => { - if !client.get_switchable().await? { - return Err(anyhow::anyhow!( - r#" -Graphics switching is not supported on this device, because -this device is either a desktop or doesn't have both an iGPU and dGPU. -"#, - )); - } - - match cmd.as_ref() { - Some(GraphicsArgs::Compute) => { - client.set_graphics("compute").await.map_err(zbus_error) - } - Some(GraphicsArgs::Hybrid) => { - client.set_graphics("hybrid").await.map_err(zbus_error) - } - Some(GraphicsArgs::Integrated) => { - client.set_graphics("integrated").await.map_err(zbus_error) - } - Some(GraphicsArgs::Nvidia) => { - client.set_graphics("nvidia").await.map_err(zbus_error) - } - Some(GraphicsArgs::Switchable) => client - .get_switchable() - .await - .map_err(zbus_error) - .map(|b| println!("{}", if b { "switchable" } else { "not switchable" })), - Some(GraphicsArgs::Power { state }) => match state.as_deref() { - Some("auto") => client.auto_graphics_power().await.map_err(zbus_error), - Some("off") => client.set_graphics_power(false).await.map_err(zbus_error), - Some("on") => client.set_graphics_power(true).await.map_err(zbus_error), - _ => { - if client.get_graphics_power().await.map_err(zbus_error)? { - println!("on (discrete)"); - } else { - println!("off (discrete)"); - } - Ok(()) - } - }, - None => { - println!("{}", client.get_graphics().await.map_err(zbus_error)?); - Ok(()) - } - } - } Args::ChargeThresholds { profile, list_profiles, thresholds } => { if client.get_desktop().await.map_err(zbus_error)? { return Err(anyhow::anyhow!( diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 6cfed39e..2a90f233 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -25,7 +25,7 @@ use crate::{ charge_thresholds::{get_charge_profiles, get_charge_thresholds, set_charge_thresholds}, errors::ProfileError, fan::FanDaemon, - graphics::{Graphics, GraphicsMode}, + graphics::Graphics, hid_backlight, hotplug::{mux, Detect, HotPlugDetect}, kernel_parameters::{KernelParameter, NmiWatchdog}, @@ -238,60 +238,11 @@ impl System76Power { .map_err(zbus_error_from_display) } - #[dbus_interface(out_args("vendor"))] - async fn get_default_graphics(&self) -> zbus::fdo::Result { - self.0 - .lock() - .await - .graphics - .get_default_graphics() - .map_err(zbus_error_from_display) - .map(|mode| <&'static str>::from(mode).to_owned()) - } - - #[dbus_interface(out_args("vendor"))] - async fn get_graphics(&self) -> zbus::fdo::Result { - self.0 - .lock() - .await - .graphics - .get_vendor() - .map_err(zbus_error_from_display) - .map(|mode| <&'static str>::from(mode).to_owned()) - } - - async fn set_graphics(&mut self, vendor: &str) -> zbus::fdo::Result<()> { - self.0 - .lock() - .await - .graphics - .set_vendor(GraphicsMode::from(vendor)) - .map_err(zbus_error_from_display) - } - #[dbus_interface(out_args("desktop"))] async fn get_desktop(&mut self) -> zbus::fdo::Result { Ok(self.0.lock().await.graphics.is_desktop()) } - #[dbus_interface(out_args("switchable"))] - async fn get_switchable(&mut self) -> zbus::fdo::Result { - Ok(self.0.lock().await.graphics.can_switch()) - } - - #[dbus_interface(out_args("power"))] - async fn get_graphics_power(&mut self) -> zbus::fdo::Result { - self.0.lock().await.graphics.get_power().map_err(zbus_error_from_display) - } - - async fn set_graphics_power(&mut self, power: bool) -> zbus::fdo::Result<()> { - self.0.lock().await.graphics.set_power(power).map_err(zbus_error_from_display) - } - - async fn auto_graphics_power(&mut self) -> zbus::fdo::Result<()> { - self.0.lock().await.graphics.auto_power().map_err(zbus_error_from_display) - } - #[dbus_interface(out_args("start", "end"))] async fn get_charge_thresholds(&mut self) -> zbus::fdo::Result<(u8, u8)> { get_charge_thresholds().map_err(zbus_error_from_display) @@ -532,13 +483,6 @@ pub async fn daemon() -> anyhow::Result<()> { let daemon = Arc::new(Mutex::new(daemon)); let mut system76_daemon = System76Power(daemon.clone()); - match system76_daemon.auto_graphics_power().await { - Ok(()) => (), - Err(err) => { - log::warn!("Failed to set automatic graphics power: {}", err); - } - } - let vendor = fs::read_to_string("/sys/class/dmi/id/sys_vendor")?; let model = fs::read_to_string("/sys/class/dmi/id/product_version")?; match runtime_pm_quirks(&vendor, &model) { diff --git a/src/graphics.rs b/src/graphics.rs index b473fcfd..f7577922 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -2,76 +2,20 @@ // // SPDX-License-Identifier: GPL-3.0-only -use crate::{module::Module, pci::PciBus}; +use crate::pci::PciBus; use serde::{Deserialize, Serialize}; use std::{ fs, io::{self, Write}, - path, - process::{self, ExitStatus}, + process::ExitStatus, }; use sysfs_class::{PciDevice, SysClass}; const MODPROBE_PATH: &str = "/etc/modprobe.d/system76-power.conf"; - -static MODPROBE_NVIDIA: &[u8] = br"# Automatically generated by system76-power -options nvidia-drm modeset=1 -"; - -static MODPROBE_HYBRID: &[u8] = br"# Automatically generated by system76-power -blacklist i2c_nvidia_gpu -alias i2c_nvidia_gpu off -options nvidia-drm modeset=1 -"; - -static MODPROBE_COMPUTE: &[u8] = br"# Automatically generated by system76-power -blacklist i2c_nvidia_gpu -blacklist nvidia-drm -blacklist nvidia-modeset -alias i2c_nvidia_gpu off -alias nvidia-drm off -alias nvidia-modeset off -"; - -static MODPROBE_INTEGRATED: &[u8] = br"# Automatically generated by system76-power -blacklist i2c_nvidia_gpu -blacklist nouveau -blacklist nvidia -blacklist nvidia-drm -blacklist nvidia-modeset -alias i2c_nvidia_gpu off -alias nouveau off -alias nvidia off -alias nvidia-drm off -alias nvidia-modeset off -"; - -// Systems that cannot use other sleep options -static SYSTEM_SLEEP_EMPTY: &[u8] = b""; - -// Systems using S0ix must enable S0ix-based power management. -static SYSTEM_SLEEP_S0IX: &[u8] = br"# Preserve video memory through suspend -options nvidia NVreg_EnableS0ixPowerManagement=1 +static MODPROBE_NO_GC6: &[u8] = br"# Automatically generated by system76-power +options nvidia NVreg_DynamicPowerManagement=0x01 "; -// Systems using S3 had suspend issues with WebRender. -static SYSTEM_SLEEP_S3: &[u8] = br"# Preserve video memory through suspend -options nvidia NVreg_PreserveVideoMemoryAllocations=1 -"; - -// The use of hybrid or discrete is determined by the "PrimaryGPU" option. -static XORG_CONF_DISCRETE: &[u8] = br#"# Automatically generated by system76-power -Section "OutputClass" - Identifier "NVIDIA" - MatchDriver "nvidia-drm" - Driver "nvidia" - Option "PrimaryGPU" "Yes" - ModulePath "/lib/x86_64-linux-gnu/nvidia/xorg" -EndSection -"#; - -const PRIME_DISCRETE_PATH: &str = "/etc/prime-discrete"; - const EXTERNAL_DISPLAY_REQUIRES_NVIDIA: &[&str] = &[ "addw1", "addw2", @@ -105,8 +49,6 @@ const EXTERNAL_DISPLAY_REQUIRES_NVIDIA: &[&str] = &[ "serw14", ]; -const SYSTEMCTL_CMD: &str = "systemctl"; - #[derive(Debug, thiserror::Error)] pub enum GraphicsDeviceError { #[error("failed to execute {} command: {}", cmd, why)] @@ -115,20 +57,12 @@ pub enum GraphicsDeviceError { DeviceInUse { func: String, driver: String }, #[error("failed to probe driver features: {}", _0)] Json(io::Error), - #[error("failed to open system76-power modprobe file: {}", _0)] - ModprobeFileOpen(io::Error), - #[error("failed to write to system76-power modprobe file: {}", _0)] - ModprobeFileWrite(io::Error), #[error("failed to fetch list of active kernel modules: {}", _0)] ModulesFetch(io::Error), #[error("does not have switchable graphics")] NotSwitchable, #[error("PCI driver error on {}: {}", device, why)] PciDriver { device: String, why: io::Error }, - #[error("failed to get PRIME value: {}", _0)] - PrimeModeRead(io::Error), - #[error("failed to set PRIME value: {}", _0)] - PrimeModeWrite(io::Error), #[error("failed to remove PCI device {}: {}", device, why)] Remove { device: String, why: io::Error }, #[error("failed to rescan PCI bus: {}", _0)] @@ -144,8 +78,8 @@ pub enum GraphicsDeviceError { } pub struct GraphicsDevice { - id: String, - devid: u16, + id: String, + devid: u16, functions: Vec, } @@ -156,10 +90,14 @@ impl GraphicsDevice { } #[must_use] - pub fn exists(&self) -> bool { self.functions.iter().any(|func| func.path().exists()) } + pub fn exists(&self) -> bool { + self.functions.iter().any(|func| func.path().exists()) + } #[must_use] - pub const fn device(&self) -> u16 { self.devid } + pub const fn device(&self) -> u16 { + self.devid + } pub unsafe fn unbind(&self) -> Result<(), GraphicsDeviceError> { for func in &self.functions { @@ -196,7 +134,7 @@ impl GraphicsDevice { Ok(driver) => { log::error!("{}: in use by {}", func.id(), driver.id()); return Err(GraphicsDeviceError::DeviceInUse { - func: func.id().to_owned(), + func: func.id().to_owned(), driver: driver.id().to_owned(), }); } @@ -228,12 +166,12 @@ impl GraphicsDevice { // supported-gpus.json #[derive(Serialize, Deserialize, Debug)] struct NvidiaDevice { - devid: String, - subdeviceid: Option, - subvendorid: Option, - name: String, + devid: String, + subdeviceid: Option, + subvendorid: Option, + name: String, legacybranch: Option, - features: Vec, + features: Vec, } #[derive(Serialize, Deserialize, Debug)] @@ -241,42 +179,12 @@ struct SupportedGpus { chips: Vec, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum GraphicsMode { - Integrated, - Compute, - Hybrid, - Discrete, -} - -impl From for &'static str { - fn from(mode: GraphicsMode) -> &'static str { - match mode { - GraphicsMode::Integrated => "integrated", - GraphicsMode::Compute => "compute", - GraphicsMode::Hybrid => "hybrid", - GraphicsMode::Discrete => "nvidia", - } - } -} - -impl From<&str> for GraphicsMode { - fn from(vendor: &str) -> Self { - match vendor { - "nvidia" => GraphicsMode::Discrete, - "hybrid" => GraphicsMode::Hybrid, - "compute" => GraphicsMode::Compute, - _ => GraphicsMode::Integrated, - } - } -} - pub struct Graphics { - pub bus: PciBus, - pub amd: Vec, - pub intel: Vec, + pub bus: PciBus, + pub amd: Vec, + pub intel: Vec, pub nvidia: Vec, - pub other: Vec, + pub other: Vec, } impl Graphics { @@ -347,157 +255,6 @@ impl Graphics { } } - Ok(Self { bus, amd, intel, nvidia, other }) - } - - pub fn is_desktop(&self) -> bool { - let chassis = fs::read_to_string("/sys/class/dmi/id/chassis_type") - .map_err(GraphicsDeviceError::SysFs) - .unwrap_or_default(); - - chassis.trim() == "3" - } - - #[must_use] - pub fn can_switch(&self) -> bool { - !self.is_desktop() - && (!self.nvidia.is_empty() && (!self.intel.is_empty() || !self.amd.is_empty())) - } - - pub fn get_external_displays_require_dgpu(&self) -> Result { - self.switchable_or_fail()?; - - let model = fs::read_to_string("/sys/class/dmi/id/product_version") - .map_err(GraphicsDeviceError::SysFs)?; - - Ok(EXTERNAL_DISPLAY_REQUIRES_NVIDIA.contains(&model.trim())) - } - - fn get_nvidia_device(id: u16) -> Result { - let supported_gpus: Vec = fs::read_dir("/usr/share/doc") - .map_err(|e| { - GraphicsDeviceError::Json(io::Error::new(io::ErrorKind::InvalidData, e.to_string())) - })? - .filter_map(Result::ok) - .map(|f| f.path()) - .filter(|f| f.to_str().unwrap_or_default().contains("nvidia-driver-")) - .map(|f| f.join("supported-gpus.json")) - .filter(|f| f.exists()) - .collect(); - - // There should be only 1 driver version installed. - if supported_gpus.len() != 1 { - return Err(GraphicsDeviceError::Json(io::Error::new( - io::ErrorKind::InvalidData, - "NVIDIA drivers misconfigured", - ))); - } - - let raw = fs::read_to_string(&supported_gpus[0]).map_err(GraphicsDeviceError::Json)?; - let gpus: SupportedGpus = serde_json::from_str(&raw).map_err(|e| { - GraphicsDeviceError::Json(io::Error::new(io::ErrorKind::InvalidData, e.to_string())) - })?; - - // There may be multiple entries that share the same device ID. - for dev in gpus.chips { - let did = dev.devid.trim_start_matches("0x").trim(); - let did = u16::from_str_radix(did, 16).unwrap_or_default(); - if did == id { - return Ok(dev); - } - } - - Err(GraphicsDeviceError::Json(io::Error::new( - io::ErrorKind::NotFound, - "GPU device not found", - ))) - } - - fn gpu_supports_runtimepm(&self) -> Result { - if self.nvidia.is_empty() { - Ok(false) - } else { - let id = self.nvidia[0].device(); - let dev = Self::get_nvidia_device(id)?; - log::info!("Device 0x{:04} features: {:?}", id, dev.features); - Ok(dev.features.contains(&"runtimepm".to_string())) - } - } - - pub fn get_default_graphics(&self) -> Result { - // Models that should default to discrete graphics only - const DEFAULT_DISCRETE: &[&str] = &["bonw16"]; - - self.switchable_or_fail()?; - - let vendor = fs::read_to_string("/sys/class/dmi/id/sys_vendor") - .map_err(GraphicsDeviceError::SysFs) - .map(|s| s.trim().to_string())?; - - let product = fs::read_to_string("/sys/class/dmi/id/product_version") - .map_err(GraphicsDeviceError::SysFs) - .map(|s| s.trim().to_string())?; - - let runtimepm = match self.gpu_supports_runtimepm() { - Ok(ok) => ok, - Err(err) => { - log::warn!("could not determine GPU runtimepm support: {}", err); - false - } - }; - - // Only default to hybrid on System76 models - if vendor != "System76" || DEFAULT_DISCRETE.contains(&product.as_str()) { - Ok(GraphicsMode::Discrete) - } else if runtimepm { - Ok(GraphicsMode::Hybrid) - } else { - Ok(GraphicsMode::Integrated) - } - } - - fn get_prime_discrete() -> Result { - fs::read_to_string(PRIME_DISCRETE_PATH) - .map_err(GraphicsDeviceError::PrimeModeRead) - .map(|mode| mode.trim().to_owned()) - } - - fn set_prime_discrete(mode: &str) -> Result<(), GraphicsDeviceError> { - fs::write(PRIME_DISCRETE_PATH, mode).map_err(GraphicsDeviceError::PrimeModeWrite) - } - - pub fn get_vendor(&self) -> Result { - let modules = Module::all().map_err(GraphicsDeviceError::ModulesFetch)?; - 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" { - GraphicsMode::Hybrid - } else if mode == "off" { - GraphicsMode::Compute - } else { - GraphicsMode::Discrete - } - } else { - GraphicsMode::Integrated - }; - - Ok(vendor) - } - - pub fn set_vendor(&self, vendor: GraphicsMode) -> Result<(), GraphicsDeviceError> { - self.switchable_or_fail()?; - - let mode = match vendor { - GraphicsMode::Hybrid => "on-demand\n", - GraphicsMode::Discrete => "on\n", - _ => "off\n", - }; - let bonw15_hack = { let dmi_vendor = fs::read_to_string("/sys/class/dmi/id/sys_vendor").unwrap_or_default(); let dmi_model = @@ -509,168 +266,42 @@ impl Graphics { } }; - // Configure X server - if vendor == GraphicsMode::Discrete { - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(get_xorg_conf_path()) - .map_err(GraphicsDeviceError::XserverConf)?; - - file.write_all(XORG_CONF_DISCRETE) - .and_then(|()| file.sync_all()) - .map_err(GraphicsDeviceError::XserverConf)?; - } else if path::Path::new(get_xorg_conf_path()).exists() { - fs::remove_file(get_xorg_conf_path()).map_err(GraphicsDeviceError::XserverConf)?; - } - - { + if bonw15_hack { log::info!("Creating {}", MODPROBE_PATH); let mut file = fs::OpenOptions::new() .create(true) .truncate(true) .write(true) - .open(MODPROBE_PATH) - .map_err(GraphicsDeviceError::ModprobeFileOpen)?; - - let text = match vendor { - GraphicsMode::Integrated => MODPROBE_INTEGRATED, - GraphicsMode::Compute => MODPROBE_COMPUTE, - GraphicsMode::Hybrid => MODPROBE_HYBRID, - GraphicsMode::Discrete => MODPROBE_NVIDIA, - }; - - file.write_all(text) - .and_then(|()| file.sync_all()) - .map_err(GraphicsDeviceError::ModprobeFileWrite)?; - - // Power management must be configured depending on if the system - // uses S0ix or S3 for suspend. - if vendor != GraphicsMode::Integrated { - // XXX: Better way to check? - let s0ix = fs::read_to_string("/sys/power/mem_sleep") - .unwrap_or_default() - .contains("[s2idle]"); - - let (sleep, action) = if bonw15_hack { - (SYSTEM_SLEEP_EMPTY, "disable") - } else if s0ix { - (SYSTEM_SLEEP_S0IX, "enable") - } else { - (SYSTEM_SLEEP_S3, "enable") - }; - - // We should also check if the GPU supports Video Memory Self - // Refresh, but that requires already being in hybrid or nvidia - // graphics mode. In compute mode, it just reports '?'. - - file.write_all(sleep) - .and_then(|()| file.sync_all()) - .map_err(GraphicsDeviceError::ModprobeFileWrite)?; - - for service in - &["nvidia-hibernate.service", "nvidia-resume.service", "nvidia-suspend.service"] - { - let status = process::Command::new(SYSTEMCTL_CMD) - .arg(action) - .arg(service) - .status() - .map_err(|why| GraphicsDeviceError::Command { cmd: SYSTEMCTL_CMD, why })?; - - if !status.success() { - // Error is ignored in case this service is removed - log::warn!( - "systemctl {} {}: failed with {} (not an error if service does not \ - exist!)", - action, - service, - status - ); - } - } - } - } - - log::info!("Setting {} to {}", PRIME_DISCRETE_PATH, mode); - Self::set_prime_discrete(mode)?; + .open(MODPROBE_PATH)?; - let action = if vendor == GraphicsMode::Discrete { - log::info!("Enabling nvidia-fallback.service"); - "enable" - } else { - log::info!("Disabling nvidia-fallback.service"); - "disable" - }; - - let status = process::Command::new(SYSTEMCTL_CMD) - .arg(action) - .arg("nvidia-fallback.service") - .status() - .map_err(|why| GraphicsDeviceError::Command { cmd: SYSTEMCTL_CMD, why })?; - - if !status.success() { - // Error is ignored in case this service is removed - log::warn!( - "systemctl: failed with {} (not an error if service does not exist!)", - status - ); + file.write_all(MODPROBE_NO_GC6).and_then(|()| file.sync_all())?; } - log::info!("Updating initramfs"); - let (cmd, arg) = update_initramfs_cmd(); - let status = process::Command::new(cmd) - .arg(arg) - .status() - .map_err(|why| GraphicsDeviceError::Command { cmd, why })?; + Ok(Self { bus, amd, intel, nvidia, other }) + } - if !status.success() { - return Err(GraphicsDeviceError::UpdateInitramfs(status)); - } + pub fn is_desktop(&self) -> bool { + let chassis = fs::read_to_string("/sys/class/dmi/id/chassis_type") + .map_err(GraphicsDeviceError::SysFs) + .unwrap_or_default(); - Ok(()) + chassis.trim() == "3" } - pub fn get_power(&self) -> Result { - self.switchable_or_fail()?; - Ok(self.nvidia.iter().any(GraphicsDevice::exists)) + #[must_use] + pub fn can_switch(&self) -> bool { + !self.is_desktop() + && (!self.nvidia.is_empty() && (!self.intel.is_empty() || !self.amd.is_empty())) } - pub fn set_power(&self, power: bool) -> Result<(), GraphicsDeviceError> { + pub fn get_external_displays_require_dgpu(&self) -> Result { self.switchable_or_fail()?; - if power { - log::info!("Enabling graphics power"); - self.bus.rescan().map_err(GraphicsDeviceError::Rescan)?; - - sysfs_power_control(self.nvidia[0].id.clone(), self.get_vendor()?); - } else { - log::info!("Disabling graphics power"); - - // TODO: Don't allow turning off power if nvidia_drm modeset is enabled - - unsafe { - // 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()); - - unbinds.chain(removes).collect::>()?; - } - } - - Ok(()) - } - - pub fn auto_power(&self) -> Result<(), GraphicsDeviceError> { - // Only disable power if in integrated mode and the device does not - // support runtime power management. - let vendor = self.get_vendor()?; - let power = vendor != GraphicsMode::Integrated || self.gpu_supports_runtimepm()?; + let model = fs::read_to_string("/sys/class/dmi/id/product_version") + .map_err(GraphicsDeviceError::SysFs)?; - self.set_power(power) + Ok(EXTERNAL_DISPLAY_REQUIRES_NVIDIA.contains(&model.trim())) } fn switchable_or_fail(&self) -> Result<(), GraphicsDeviceError> { @@ -681,45 +312,3 @@ impl Graphics { } } } - -fn update_initramfs_cmd() -> (&'static str, &'static str) { - if path::Path::new("/usr/bin/dracut").exists() { - ("dracut", "--force") - } else { - ("update-initramfs", "-u") - } -} - -fn get_xorg_conf_path() -> &'static str { - if path::Path::new("/etc/X11/xorg.conf.d").exists() { - "/etc/X11/xorg.conf.d/11-nvidia-discrete.conf" - } else { - "/usr/share/X11/xorg.conf.d/11-nvidia-discrete.conf" - } -} - -// HACK -// Normally, power/control would be set to "auto" by a udev rule in nvidia-drivers, but because -// of a bug we cannot enable automatic power management too early after turning on the GPU. -// Otherwise it will turn off before the NVIDIA driver finishes initializing, leaving the -// system in an invalid state that will eventually lock up. So defer setting power management -// using a thread. -// -// Ref: pop-os/nvidia-graphics-drivers@f9815ed603bd -// Ref: system76/firmware-open#160 -fn sysfs_power_control(pciid: String, mode: GraphicsMode) { - std::thread::spawn(move || { - std::thread::sleep(std::time::Duration::from_millis(5000)); - - let pm = if mode == GraphicsMode::Discrete { "on\n" } else { "auto\n" }; - log::info!("Setting power management to {}", pm); - - let control = format!("/sys/bus/pci/devices/{}/power/control", pciid); - let file = fs::OpenOptions::new().create(false).truncate(false).write(true).open(control); - - #[allow(unused_must_use)] - if let Ok(mut file) = file { - file.write_all(pm.as_bytes()).and_then(|()| file.sync_all()); - } - }); -} diff --git a/src/lib.rs b/src/lib.rs index 13b5624a..815bd652 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ pub mod hotplug; pub mod kernel_parameters; pub mod logging; pub mod modprobe; -pub mod module; pub mod pci; pub mod radeon; pub mod runtime_pm; diff --git a/src/module.rs b/src/module.rs deleted file mode 100644 index c0a27061..00000000 --- a/src/module.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018-2021 System76 -// -// SPDX-License-Identifier: GPL-3.0-only - -use std::{fs::read_to_string, io}; - -pub struct Module { - pub name: String, -} - -impl Module { - pub fn all() -> io::Result> { - read_to_string("/proc/modules")?.lines().map(parse).collect() - } -} - -fn parse(line: &str) -> io::Result { - let name = line - .split(' ') - .next() - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "module name not found"))? - .to_string(); - - Ok(Module { name }) -} From 76e8d5df9baab13aaf6d2774e4691538ff85f532 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 25 Sep 2025 17:29:26 +0200 Subject: [PATCH 2/2] Remove bonw15 nvidia quirk (handled by system76-driver) --- src/graphics.rs | 66 +++++++++++-------------------------------------- 1 file changed, 15 insertions(+), 51 deletions(-) diff --git a/src/graphics.rs b/src/graphics.rs index f7577922..b631c3da 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -4,18 +4,9 @@ use crate::pci::PciBus; use serde::{Deserialize, Serialize}; -use std::{ - fs, - io::{self, Write}, - process::ExitStatus, -}; +use std::{fs, io, process::ExitStatus}; use sysfs_class::{PciDevice, SysClass}; -const MODPROBE_PATH: &str = "/etc/modprobe.d/system76-power.conf"; -static MODPROBE_NO_GC6: &[u8] = br"# Automatically generated by system76-power -options nvidia NVreg_DynamicPowerManagement=0x01 -"; - const EXTERNAL_DISPLAY_REQUIRES_NVIDIA: &[&str] = &[ "addw1", "addw2", @@ -78,8 +69,8 @@ pub enum GraphicsDeviceError { } pub struct GraphicsDevice { - id: String, - devid: u16, + id: String, + devid: u16, functions: Vec, } @@ -90,14 +81,10 @@ impl GraphicsDevice { } #[must_use] - pub fn exists(&self) -> bool { - self.functions.iter().any(|func| func.path().exists()) - } + pub fn exists(&self) -> bool { self.functions.iter().any(|func| func.path().exists()) } #[must_use] - pub const fn device(&self) -> u16 { - self.devid - } + pub const fn device(&self) -> u16 { self.devid } pub unsafe fn unbind(&self) -> Result<(), GraphicsDeviceError> { for func in &self.functions { @@ -134,7 +121,7 @@ impl GraphicsDevice { Ok(driver) => { log::error!("{}: in use by {}", func.id(), driver.id()); return Err(GraphicsDeviceError::DeviceInUse { - func: func.id().to_owned(), + func: func.id().to_owned(), driver: driver.id().to_owned(), }); } @@ -166,12 +153,12 @@ impl GraphicsDevice { // supported-gpus.json #[derive(Serialize, Deserialize, Debug)] struct NvidiaDevice { - devid: String, - subdeviceid: Option, - subvendorid: Option, - name: String, + devid: String, + subdeviceid: Option, + subvendorid: Option, + name: String, legacybranch: Option, - features: Vec, + features: Vec, } #[derive(Serialize, Deserialize, Debug)] @@ -180,11 +167,11 @@ struct SupportedGpus { } pub struct Graphics { - pub bus: PciBus, - pub amd: Vec, - pub intel: Vec, + pub bus: PciBus, + pub amd: Vec, + pub intel: Vec, pub nvidia: Vec, - pub other: Vec, + pub other: Vec, } impl Graphics { @@ -255,29 +242,6 @@ impl Graphics { } } - let bonw15_hack = { - let dmi_vendor = fs::read_to_string("/sys/class/dmi/id/sys_vendor").unwrap_or_default(); - let dmi_model = - fs::read_to_string("/sys/class/dmi/id/product_version").unwrap_or_default(); - match (dmi_vendor.trim(), dmi_model.trim()) { - ("System76", "bonw15") => true, - ("System76", "bonw15-b") => true, - _ => false, - } - }; - - if bonw15_hack { - log::info!("Creating {}", MODPROBE_PATH); - - let mut file = fs::OpenOptions::new() - .create(true) - .truncate(true) - .write(true) - .open(MODPROBE_PATH)?; - - file.write_all(MODPROBE_NO_GC6).and_then(|()| file.sync_all())?; - } - Ok(Self { bus, amd, intel, nvidia, other }) }