Major update to supergfx and others

This commit is contained in:
Luke D. Jones
2021-08-26 11:43:47 +12:00
parent 60b7f3be69
commit 498e604531
33 changed files with 535 additions and 278 deletions

View File

@@ -12,6 +12,26 @@ keywords = ["graphics", "nvidia", "switching"]
edition = "2018"
exclude = ["data"]
[features]
daemon = ["env_logger"]
cli = ["gumdrop"]
default = ["daemon", "cli"]
[lib]
name = "supergfxctl"
path = "src/lib.rs"
[[bin]]
name = "supergfxd"
path = "src/daemon.rs"
required-features = ["daemon"]
[[bin]]
name = "supergfxctl"
path = "src/cli.rs"
required-features = ["cli"]
default-features = ["cli"]
[dependencies]
serde = "^1.0"
serde_derive = "^1.0"
@@ -23,4 +43,7 @@ zvariant = "^2.8"
zvariant_derive = "^2.8"
logind-zbus = "^0.7.1"
sysfs-class = "^0.1.2"
sysfs-class = "^0.1.2"
env_logger = { version = "^0.8", optional = true }
gumdrop = { version = "^0.8", optional = true }

74
supergfx/Makefile Normal file
View File

@@ -0,0 +1,74 @@
VERSION := $(shell grep -Pm1 'version = "(\d.\d.\d)"' daemon/Cargo.toml | cut -d'"' -f2)
INSTALL = install
INSTALL_PROGRAM = ${INSTALL} -D -m 0755
INSTALL_DATA = ${INSTALL} -D -m 0644
prefix = /usr
exec_prefix = $(prefix)
bindir = $(exec_prefix)/bin
datarootdir = $(prefix)/share
libdir = $(exec_prefix)/lib
BIN_SD := supergfxd
BIN_SC := supergfxctl
X11CFG := 90-nvidia-screen-G05.conf
PMRULES := 90-asusd-nvidia-pm.rules
SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs')
DEBUG ?= 0
ifeq ($(DEBUG),0)
ARGS += --release
TARGET = release
endif
VENDORED ?= 0
ifeq ($(VENDORED),1)
ARGS += --frozen
endif
all: build
clean:
cargo clean
distclean:
rm -rf .cargo vendor vendor.tar.xz
install:
$(INSTALL_PROGRAM) "./target/release/$(BIN_SD)" "$(DESTDIR)$(bindir)/$(BIN_SD)"
$(INSTALL_PROGRAM) "./target/release/$(BIN_SC)" "$(DESTDIR)$(bindir)/$(BIN_SC)"
$(INSTALL_DATA) "./data/$(BIN_SD).service" "$(DESTDIR)$(libdir)/systemd/system/$(BIN_SD).service"
$(INSTALL_DATA) "./data/org.supergfxctl.Daemon.conf" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/org.supergfxctl.Daemon.conf"
$(INSTALL_DATA) "./data/$(X11CFG)" "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
$(INSTALL_DATA) "./data/$(PMRULES)" "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
uninstall:
rm -f "$(DESTDIR)$(bindir)/$(BIN_SC)"
rm -f "$(DESTDIR)$(bindir)/$(BIN_SD)"
rm -f "$(DESTDIR)$(libdir)/systemd/system/$(BIN_SD).service"
rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/org.supergfxctl.Daemon.conf"
rm -f "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)"
rm -f "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)"
update:
cargo update
vendor:
mkdir -p .cargo
cargo vendor | head -n -1 > .cargo/config
echo 'directory = "vendor"' >> .cargo/config
mv .cargo/config ./cargo-config
rm -rf .cargo
tar pcfJ vendor_asusctl_$(VERSION).tar.xz vendor
rm -rf vendor
build:
ifeq ($(VENDORED),1)
@echo "version = $(VERSION)"
tar pxf vendor_asusctl_$(VERSION).tar.xz
endif
cargo build $(ARGS)
.PHONY: all clean distclean install uninstall update build

View File

@@ -0,0 +1,7 @@
# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto"
ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto"
# Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on"
ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"

View File

@@ -0,0 +1,4 @@
Section "ServerLayout"
Identifier "layout"
Option "AllowNVIDIAGPUScreens"
EndSection

View File

@@ -0,0 +1,26 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy group="adm">
<allow send_destination="org.supergfxctl.Daemon"/>
<allow receive_sender="org.supergfxctl.Daemon"/>
</policy>
<policy group="sudo">
<allow send_destination="org.supergfxctl.Daemon"/>
<allow receive_sender="org.supergfxctl.Daemon"/>
</policy>
<policy group="users">
<allow send_destination="org.supergfxctl.Daemon"/>
<allow receive_sender="org.supergfxctl.Daemon"/>
</policy>
<policy group="wheel">
<allow send_destination="org.supergfxctl.Daemon"/>
<allow receive_sender="org.supergfxctl.Daemon"/>
</policy>
<policy user="root">
<allow own="org.supergfxctl.Daemon"/>
<allow send_destination="org.supergfxctl.Daemon"/>
<allow receive_sender="org.supergfxctl.Daemon"/>
</policy>
</busconfig>

View File

@@ -0,0 +1,16 @@
[Unit]
Description=SUPERGFX
StartLimitInterval=200
StartLimitBurst=2
Before=display-manager.service
[Service]
Environment=IS_SUPERGFX_SERVICE=1
ExecStart=/usr/bin/supergfxd
Restart=on-failure
Restart=always
RestartSec=1
Type=dbus
BusName=org.supergfxctl.Daemon
SELinuxContext=system_u:system_r:unconfined_t:s0
#SELinuxContext=system_u:object_r:modules_object_t:s0

105
supergfx/src/cli.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::{env::args, sync::mpsc::channel};
use supergfxctl::{
gfx_vendors::{GfxRequiredUserAction, GfxVendors},
special::{get_asus_gsync_gfx_mode, has_asus_gsync_gfx_mode},
zbus_proxy::GfxProxy,
};
use gumdrop::Options;
use zbus::Connection;
#[derive(Default, Options)]
struct CliStart {
#[options(help = "print help message")]
help: bool,
#[options(
meta = "",
help = "Set graphics mode: <nvidia, hybrid, compute, integrated>"
)]
mode: Option<GfxVendors>,
#[options(help = "Get the current mode")]
get: bool,
#[options(help = "Get the current power status")]
pow: bool,
#[options(help = "Do not ask for confirmation")]
force: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = args().skip(1).collect();
match CliStart::parse_args_default(&args) {
Ok(command) => {
do_gfx(command)?;
}
Err(err) => {
eprintln!("source {}", err);
std::process::exit(2);
}
}
Ok(())
}
fn do_gfx(command: CliStart) -> Result<(), Box<dyn std::error::Error>> {
if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help {
println!("{}", command.self_usage());
}
let conn = Connection::new_system()?;
let proxy = GfxProxy::new(&conn)?;
let (tx, rx) = channel();
proxy.connect_notify_action(tx)?;
if let Some(mode) = command.mode {
if has_asus_gsync_gfx_mode() && get_asus_gsync_gfx_mode()? == 1 {
println!("You can not change modes until you turn dedicated/G-Sync off and reboot");
std::process::exit(-1);
}
println!("If anything fails check `journalctl -b -u supergfxd`\n");
proxy.gfx_write_mode(&mode).map_err(|err|{
println!("Graphics mode change error. You may be in an invalid state.");
println!("Check mode with `-g` and switch to opposite\nmode to correct it, e.g: if integrated, switch to hybrid, or if nvidia, switch to integrated.\n");
err
})?;
loop {
proxy.next_signal()?;
if let Ok(res) = rx.try_recv() {
match res {
GfxRequiredUserAction::Integrated => {
println!(
"You must change to Integrated before you can change to {}",
<&str>::from(mode)
);
}
GfxRequiredUserAction::Logout | GfxRequiredUserAction::Reboot => {
println!(
"Graphics mode changed to {}. User action required is: {}",
<&str>::from(mode),
<&str>::from(&res)
);
}
GfxRequiredUserAction::None => {
println!("Graphics mode changed to {}", <&str>::from(mode));
}
}
}
std::process::exit(0)
}
}
if command.get {
let res = proxy.gfx_get_mode()?;
println!("Current graphics mode: {}", <&str>::from(res));
}
if command.pow {
let res = proxy.gfx_get_pwr()?;
println!("Current power status: {}", <&str>::from(&res));
}
Ok(())
}

105
supergfx/src/daemon.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::{
env,
error::Error,
sync::{Arc, Mutex},
};
use log::{error, info, warn, LevelFilter};
use std::io::Write;
use supergfxctl::{
config::GfxConfig,
controller::CtrlGraphics,
error::GfxError,
gfx_vendors::GfxVendors,
special::{get_asus_gsync_gfx_mode, has_asus_gsync_gfx_mode},
DBUS_DEST_NAME, GFX_CONFIG_PATH,
};
use zbus::{fdo, Connection, ObjectServer};
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut logger = env_logger::Builder::new();
logger
.target(env_logger::Target::Stdout)
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
.filter(None, LevelFilter::Info)
.init();
let is_service = match env::var_os("IS_SUPERGFX_SERVICE") {
Some(val) => val == "1",
None => false,
};
if !is_service {
println!("supergfxd schould be only run from the right systemd service");
println!(
"do not run in your terminal, if you need an logs please use journalctl -b -u supergfxd"
);
println!("supergfxd will now exit");
return Ok(());
}
start_daemon()
}
fn start_daemon() -> Result<(), Box<dyn Error>> {
// Start zbus server
let connection = Connection::new_system()?;
fdo::DBusProxy::new(&connection)?.request_name(
DBUS_DEST_NAME,
fdo::RequestNameFlags::ReplaceExisting.into(),
)?;
let mut object_server = ObjectServer::new(&connection);
let config = GfxConfig::load(GFX_CONFIG_PATH.into());
let enable_gfx_switching = config.gfx_managed;
let config = Arc::new(Mutex::new(config));
// Graphics switching requires some checks on boot specifically for g-sync capable laptops
if enable_gfx_switching {
match CtrlGraphics::new(config.clone()) {
Ok(mut ctrl) => {
// Need to check if a laptop has the dedicated gfx switch
if has_asus_gsync_gfx_mode() {
do_asus_laptop_checks(&ctrl, config)?;
}
ctrl.reload()
.unwrap_or_else(|err| error!("Gfx controller: {}", err));
ctrl.add_to_server(&mut object_server);
}
Err(err) => {
error!("Gfx control: {}", err);
}
}
}
// Loop to check errors and iterate zbus server
loop {
if let Err(err) = object_server.try_handle_next() {
error!("{}", err);
}
}
}
fn do_asus_laptop_checks(
ctrl: &CtrlGraphics,
config: Arc<Mutex<GfxConfig>>,
) -> Result<(), GfxError> {
if let Ok(ded) = get_asus_gsync_gfx_mode() {
if let Ok(config) = config.lock() {
if ded == 1 {
warn!("Dedicated GFX toggle is on but driver mode is not nvidia \nSetting to nvidia driver mode");
let devices = ctrl.devices();
let bus = ctrl.bus();
CtrlGraphics::do_mode_setup_tasks(GfxVendors::Nvidia, false, &devices, &bus)?;
} else if ded == 0 {
info!("Dedicated GFX toggle is off");
let devices = ctrl.devices();
let bus = ctrl.bus();
CtrlGraphics::do_mode_setup_tasks(config.gfx_mode, false, &devices, &bus)?;
}
}
}
Ok(())
}

View File

@@ -69,4 +69,4 @@ impl From<std::io::Error> for GfxError {
fn from(err: std::io::Error) -> Self {
GfxError::Io(err)
}
}
}

View File

@@ -1,12 +1,17 @@
pub mod error;
pub mod config;
pub mod gfx_vendors;
pub mod controller;
pub mod system;
pub mod error;
pub mod gfx_vendors;
/// Special-case functions for check/read/write of key functions on unique laptops
/// such as the G-Sync mode available on some ASUS ROG laptops
pub(crate) mod special;
pub mod zbus;
pub mod special;
pub mod system;
pub mod zbus_iface;
pub mod zbus_proxy;
pub const GFX_CONFIG_PATH: &str = "/etc/supergfxd.conf";
pub const DBUS_DEST_NAME: &str = "org.supergfxctl.Daemon";
pub const DBUS_IFACE_PATH: &str = "/org/supergfxctl/Gfx";
const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"];

View File

@@ -5,11 +5,11 @@ use crate::error::GfxError;
static ASUS_SWITCH_GRAPHIC_MODE: &str =
"/sys/firmware/efi/efivars/AsusSwitchGraphicMode-607005d5-3f75-4b2e-98f0-85ba66797a3e";
pub(crate) fn has_asus_gsync_gfx_mode() -> bool {
pub fn has_asus_gsync_gfx_mode() -> bool {
Path::new(ASUS_SWITCH_GRAPHIC_MODE).exists()
}
pub(crate) fn get_asus_gsync_gfx_mode() -> Result<i8, GfxError> {
pub fn get_asus_gsync_gfx_mode() -> Result<i8, GfxError> {
let path = ASUS_SWITCH_GRAPHIC_MODE;
let mut file = OpenOptions::new()
.read(true)
@@ -22,4 +22,4 @@ pub(crate) fn get_asus_gsync_gfx_mode() -> Result<i8, GfxError> {
let idx = data.len() - 1;
Ok(data[idx] as i8)
}
}

View File

@@ -1,12 +1,15 @@
use ::zbus::dbus_interface;
use log::{error, info, warn};
use zvariant::ObjectPath;
use ::zbus::dbus_interface;
use crate::gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors};
use crate::{
gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors},
DBUS_IFACE_PATH,
};
use super::controller::CtrlGraphics;
#[dbus_interface(name = "org.asuslinux.Daemon")]
#[dbus_interface(name = "org.supergfxctl.Daemon")]
impl CtrlGraphics {
fn vendor(&self) -> zbus::fdo::Result<GfxVendors> {
self.get_gfx_mode().map_err(|err| {
@@ -28,10 +31,13 @@ impl CtrlGraphics {
error!("GFX: {}", err);
zbus::fdo::Error::Failed(format!("GFX fail: {}", err))
})?;
self.notify_gfx(&vendor)
.unwrap_or_else(|err| warn!("GFX: {}", err));
self.notify_action(&msg)
.unwrap_or_else(|err| warn!("GFX: {}", err));
self.notify_gfx(&vendor)
.unwrap_or_else(|err| warn!("GFX: {}", err));
Ok(msg)
}
@@ -45,7 +51,7 @@ impl CtrlGraphics {
impl CtrlGraphics {
pub fn add_to_server(self, server: &mut zbus::ObjectServer) {
server
.at(&ObjectPath::from_str_unchecked("/org/asuslinux/Gfx"), self)
.at(&ObjectPath::from_str_unchecked(DBUS_IFACE_PATH), self)
.map_err(|err| {
warn!("GFX: CtrlGraphics: add_to_server {}", err);
err

117
supergfx/src/zbus_proxy.rs Normal file
View File

@@ -0,0 +1,117 @@
//! # DBus interface proxy for: `org.asuslinux.Gfx`
//!
//! This code was generated by `zbus-xmlgen` `1.0.0` from DBus introspection data.
//! Source: `Interface '/org/supergfxctl/Gfx' from service 'org.asuslinux.Daemon' on system bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
//! More information can be found in the
//! [Writing a client proxy](https://zeenix.pages.freedesktop.org/zbus/client.html)
//! section of the zbus documentation.
//!
//! This DBus object implements
//! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html),
//! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used:
//!
//! * [`zbus::fdo::PropertiesProxy`]
//! * [`zbus::fdo::IntrospectableProxy`]
//! * [`zbus::fdo::PeerProxy`]
//!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use std::sync::mpsc::{Receiver, Sender};
use zbus::{dbus_proxy, Connection, Message, Result};
use crate::{
gfx_vendors::{GfxPower, GfxRequiredUserAction, GfxVendors},
DBUS_IFACE_PATH,
};
#[dbus_proxy(interface = "org.supergfxctl.Daemon")]
trait Daemon {
/// Power method
fn power(&self) -> zbus::Result<GfxPower>;
/// SetVendor method
fn set_vendor(&self, vendor: &GfxVendors) -> zbus::Result<GfxRequiredUserAction>;
/// Vendor method
fn vendor(&self) -> zbus::Result<GfxVendors>;
/// NotifyAction signal
#[dbus_proxy(signal)]
fn notify_action(&self, action: GfxRequiredUserAction) -> zbus::Result<()>;
/// NotifyGfx signal
#[dbus_proxy(signal)]
fn notify_gfx(&self, vendor: GfxVendors) -> zbus::Result<()>;
}
pub struct GfxProxy<'a>(pub DaemonProxy<'a>);
impl<'a> GfxProxy<'a> {
#[inline]
pub fn new(conn: &Connection) -> Result<Self> {
let proxy = DaemonProxy::new_for(conn, "org.supergfxctl.Daemon", DBUS_IFACE_PATH)?;
Ok(GfxProxy(proxy))
}
#[inline]
pub fn new_for(conn: &Connection, destination: &'a str, path: &'a str) -> Result<Self> {
let proxy = DaemonProxy::new_for(conn, destination, path)?;
Ok(GfxProxy(proxy))
}
#[inline]
pub fn new_for_owned(conn: Connection, destination: String, path: String) -> Result<Self> {
let proxy = DaemonProxy::new_for_owned(conn, destination, path)?;
Ok(GfxProxy(proxy))
}
#[inline]
pub fn proxy(&self) -> &DaemonProxy<'a> {
&self.0
}
#[inline]
pub fn gfx_get_pwr(&self) -> Result<GfxPower> {
self.0.power()
}
#[inline]
pub fn gfx_get_mode(&self) -> Result<GfxVendors> {
self.0.vendor()
}
#[inline]
pub fn gfx_write_mode(&self, vendor: &GfxVendors) -> Result<GfxRequiredUserAction> {
self.0.set_vendor(vendor)
}
#[inline]
pub fn connect_notify_action(
&self,
send: Sender<GfxRequiredUserAction>,
) -> zbus::fdo::Result<()> {
self.0.connect_notify_action(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn connect_notify_gfx(&self, send: Sender<GfxVendors>) -> zbus::fdo::Result<()> {
self.0.connect_notify_gfx(move |data| {
send.send(data)
.map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?;
Ok(())
})
}
#[inline]
pub fn next_signal(&self) -> Result<Option<Message>> {
self.0.next_signal()
}
}