diff --git a/src/daemon/root.rs b/src/daemon/root.rs index e0204a8..80fbc8a 100644 --- a/src/daemon/root.rs +++ b/src/daemon/root.rs @@ -21,6 +21,7 @@ use crate::ds_inhibit::Inhibitor; use crate::inputplumber::DeckService; use crate::manager::root::SteamOSManager; use crate::path; +use crate::power::SysfsWriterService; use crate::sls::ftrace::Ftrace; #[derive(Copy, Clone, Default, Deserialize, Debug)] @@ -127,6 +128,9 @@ impl DaemonContext for RootContext { let ip = DeckService::init(connection); daemon.add_service(ip); + let sysfs = SysfsWriterService::init()?; + daemon.add_service(sysfs); + self.reload_ds_inhibit(daemon).await?; Ok(()) diff --git a/src/power.rs b/src/power.rs index 64e7c53..6a2fa16 100644 --- a/src/power.rs +++ b/src/power.rs @@ -17,12 +17,13 @@ use std::ops::RangeInclusive; use std::os::fd::OwnedFd; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::Arc; use strum::{Display, EnumString, VariantNames}; use tokio::fs::{self, try_exists, File}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Interest}; use tokio::net::unix::pipe; use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::oneshot; +use tokio::sync::{oneshot, Mutex, Notify, OnceCell}; use tokio::task::JoinSet; use tracing::{debug, error, warn}; use zbus::Connection; @@ -61,6 +62,8 @@ lazy_static! { Regex::new(r"^\s*(?[0-9]+): (?[0-9]+)Mhz").unwrap(); } +static SYSFS_WRITER: OnceCell> = OnceCell::const_new(); + #[derive(Display, EnumString, PartialEq, Debug, Copy, Clone, TryFromPrimitive)] #[strum(serialize_all = "snake_case")] #[repr(u32)] @@ -168,6 +171,79 @@ pub(crate) enum TdpManagerCommand { ListDownloadModeHandles(oneshot::Sender>), } +#[derive(Debug)] +pub(crate) enum SysfsWritten { + Written(Result<()>), + Superseded, +} + +#[derive(Debug)] +struct SysfsWriterQueue { + values: Mutex, oneshot::Sender)>>, + notify: Notify, +} + +impl SysfsWriterQueue { + fn new() -> SysfsWriterQueue { + SysfsWriterQueue { + values: Mutex::new(HashMap::new()), + notify: Notify::new(), + } + } + + async fn send(&self, path: PathBuf, contents: Vec) -> oneshot::Receiver { + let (tx, rx) = oneshot::channel(); + if let Some((_, old_tx)) = self.values.lock().await.insert(path, (contents, tx)) { + let _ = old_tx.send(SysfsWritten::Superseded); + } + self.notify.notify_one(); + rx + } + + async fn recv(&self) -> Option<(PathBuf, Vec, oneshot::Sender)> { + // Take an arbitrary file from the map + self.notify.notified().await; + let mut values = self.values.lock().await; + if let Some(path) = values.keys().next().cloned() { + values + .remove_entry(&path) + .map(|(path, (contents, tx))| (path, contents, tx)) + } else { + None + } + } +} + +#[derive(Debug)] +pub(crate) struct SysfsWriterService { + queue: Arc, +} + +impl SysfsWriterService { + pub fn init() -> Result { + ensure!(!SYSFS_WRITER.initialized(), "sysfs writer already active"); + let queue = Arc::new(SysfsWriterQueue::new()); + SYSFS_WRITER.set(queue.clone())?; + Ok(SysfsWriterService { queue }) + } +} + +impl Service for SysfsWriterService { + const NAME: &'static str = "sysfs-writer"; + + async fn run(&mut self) -> Result<()> { + loop { + let Some((path, contents, tx)) = self.queue.recv().await else { + continue; + }; + let res = write_synced(path, &contents) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}")); + let _ = tx.send(SysfsWritten::Written(res)); + } + } +} + async fn read_gpu_sysfs_contents>(suffix: S) -> Result { // Read a given suffix for the GPU let base = find_hwmon(GPU_HWMON_NAME).await?;