From 9ec3a284bcdd7294b16a0d5acbd819c109b28508 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 26 Aug 2026 13:48:38 +0200 Subject: [PATCH 1/3] docs: move remaining docs to docs' directory --- README.md | 28 +++++++++++-------- .../developers/design_patterns.md | 0 .../developers/gpu_mode_switching_summary.md | 0 MANUAL.md => docs/developers/manual.md | 0 PACKAGING.md => docs/developers/packaging.md | 0 5 files changed, 16 insertions(+), 12 deletions(-) rename design-patterns.md => docs/developers/design_patterns.md (100%) rename GPU_MODE_SWITCHING_SUMMARY.md => docs/developers/gpu_mode_switching_summary.md (100%) rename MANUAL.md => docs/developers/manual.md (100%) rename PACKAGING.md => docs/developers/packaging.md (100%) diff --git a/README.md b/README.md index 3bcc6cd97..28aa59118 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# asusctl for ASUS ROG +# asusctl for ASUS

Become a Patron @@ -7,9 +7,6 @@ Discord

-> [!WARNING] -> **Kernel Patch Requirement:** Many features are developed alongside Linux kernel updates. If an expected feature is missing, ensure your system is running the latest stable kernel or a kernel containing the required patches. - `asusctl` is a system control utility for Linux designed primarily for ASUS laptops, with reduced functionality available for non-ASUS hardware. The project consists of three core components: @@ -44,15 +41,19 @@ Devices displaying these hardware IDs typically function without extra configura Features such as battery charge thresholds use generic kernel interfaces and work on non-ASUS hardware, but platform and fan controls require ASUS-specific `asus-nb-wmi` or `asus-armoury` drivers. -### Kernel requirements +> [NOTE] +> For models that does not expose N-Key on lsusb, that mostly means your device need lamparray support. You can follow this [issue](https://github.com/OpenGamingCollective/asusctl/issues/119) for more info. -Due to ongoing development, the minimum suggested kernel version is always **the latest**, as improvements are merged upstream continuously. +### Kernel requirements -Support for Thermal Design Power (TDP) is tied to the new `asus-armoury` driver: available mainline since Linux 6.19: everything older is not supported. +> [WARNING] +> **Kernel Patch Requirement:** Many features are developed alongside Linux kernel updates. Due to ongoing upstream development, the recommended kernel version is always **the latest stable release**. +- **General features:** Require a recent upstream kernel or distribution kernel containing the necessary ASUS WMI patches. +- **TDP & modern platform controls:** Require the `asus-armoury` driver, available in mainline Linux **6.19 or greater**. Older kernels are not supported for these features. ### Display server support (X11) -> [!NOTE] +> [NOTE] > X11 is officially unsupported. Technical assistance is not provided for X11 environments due to developer resource constraints and the unmaintained status of X11 itself. > > Users who require X11 integration may compile the GUI application with X11 support enabled using `cargo build --features "rog-control-center/x11"`. Operation on unmaintained display servers remains the responsibility of the user. @@ -63,17 +64,20 @@ Feature availability depends on upstream Linux kernel support and specific hardw ### Power and performance -- [x] **Battery charge thresholds:** Configure maximum charging limits (requires kernel support) +- [x] **Battery charge thresholds:** Configure maximum charging limits (requires kernel and EC support) - [x] **Custom fan curves:** Adjust fan profiles on supported hardware - [x] **GPU MUX toggling:** Switch GPU operational modes (G-Sync / MUX) on 2022 and newer laptops - [x] **Power profile management:** Control system performance profiles as detailed in [MANUAL.md](MANUAL.md) +> [NOTE] +> The battery charge threshold requires a supported EC. The EC may not expose all the charging limit: that means you may be able to only limit it to 80% and not every percentage. See this [issue](https://github.com/OpenGamingCollective/asusctl/issues/153) for more info. + ### Lighting and displays - [x] **Built-in LED controls:** Adjust integrated keyboard lighting modes - [x] **Per-key RGB configuration:** Customize individual key backlight settings - [x] **Advanced lighting effects:** Apply custom animation modes (currently undergoing revision) -- [x] **AniMe Matrix displays:** Control panel rendering on equipped G14, M16, and Strix Scar 16/18 models +- [x] **AniMe Matrix displays:** Control panel rendering on equipped G14, M16, and Strix Scar 16/18 models ### System integration @@ -83,7 +87,7 @@ Feature availability depends on upstream Linux kernel support and specific hardw ### Additional hardware configuration notes -Keyboard backlight support relies on hardware mappings defined in [`./rog-aura/data/aura_support.ron`](./rog-aura/data/aura_support.ron), installed to `/usr/share/asusd/aura_support.ron`. Because keyboard controller configurations vary across model generations and firmware revisions, explicit layout definitions prevent misconfigurations. Refer to the [rog-aura README](./rog-aura/README.md) for configuration details. +Keyboard backlight support relies on hardware mappings defined in [`aura_support.ron`](./rog-aura/data/aura_support.ron), installed to `/usr/share/asusd/aura_support.ron`. Because keyboard controller configurations vary across model generations and firmware revisions, explicit layout definitions prevent misconfigurations. Refer to the [rog-aura README](./rog-aura/README.md) for configuration details. ## Installation and setup @@ -229,4 +233,4 @@ References to ASUS products, services, or trademarks within this repository do n ## AI Disclaimer -We do not accept code blindly written with just AI or "vibecoding". We encourage use of AI for finding bugs and as a tool used to assist development, but all of these must be verified by a human as AI makes mistakes and gives false bug reports as well. For further details, refer to [our contribution policy](./CONTRIBUTING.md) +We do not accept code blindly written with just AI or "vibecoding". We encourage use of AI for finding bugs and as a tool used to assist development, but all of these must be verified by a human as AI makes mistakes and gives false bug reports as well. For further details, refer to our [contribution policy](CONTRIBUTING.md) diff --git a/design-patterns.md b/docs/developers/design_patterns.md similarity index 100% rename from design-patterns.md rename to docs/developers/design_patterns.md diff --git a/GPU_MODE_SWITCHING_SUMMARY.md b/docs/developers/gpu_mode_switching_summary.md similarity index 100% rename from GPU_MODE_SWITCHING_SUMMARY.md rename to docs/developers/gpu_mode_switching_summary.md diff --git a/MANUAL.md b/docs/developers/manual.md similarity index 100% rename from MANUAL.md rename to docs/developers/manual.md diff --git a/PACKAGING.md b/docs/developers/packaging.md similarity index 100% rename from PACKAGING.md rename to docs/developers/packaging.md From c9f91340ce0944530aeb1629023aad44a27d1bb6 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 26 Aug 2026 14:12:03 +0200 Subject: [PATCH 2/3] docs: update SUMMARY.md --- docs/SUMMARY.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 4340c9385..afa7dc23b 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -25,6 +25,13 @@ - [asusctl](usage/asusctl.md) +# Developers' docs + +- [Design patterns](docs/developers/design_patterns.md) +- [GPU Mode Switching summary](docs/developers/gpu_mode_switching_summary.md) +- [Developers' manual](docs/developers/manual.md) +- [Packaging](docs/developers/packaging.md) + # FAQ - [General](faq/general.md) From 9b48bf67a60627e8f17b9aa27c7e3fb53db01e10 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Wed, 26 Aug 2026 17:00:38 +0200 Subject: [PATCH 3/3] refactor(design_patterns.md): update the doc using sync-only workers/functions --- docs/developers/design_patterns.md | 293 +++++++++++++++++++---------- 1 file changed, 194 insertions(+), 99 deletions(-) diff --git a/docs/developers/design_patterns.md b/docs/developers/design_patterns.md index 1557b6de5..e23f05dc6 100644 --- a/docs/developers/design_patterns.md +++ b/docs/developers/design_patterns.md @@ -1,147 +1,242 @@ # Daemon -## Controller pattern +## Synchronous Controller Architecture -There are a series of traits in the daemon for use with controller objects. Not all traits are required: +Controllers in the daemon manage hardware features, user configurations, and platform events using a **purely synchronous (`sync`) concurrency and state model**. -- `Reloadable`, for controllers that need the ability to reload (typically on start) -- `ZbusAdd`, for controllers that have zbus derive. These need to run on the zbus server. -- `CtrlTask`, for controllers that need to run tasks every loop. -- `GetSupported`, see if the hardware/functions this controller requires are supported. +In Linux, kernel interactions via sysfs (`/sys`), debugfs, ACPI WMI, and raw device nodes are inherently synchronous and blocking file operations. Managing controller state with standard library synchronous primitives (`std::sync`) ensures predictable execution, eliminates async runtime overhead, and keeps domain logic straightforward. -The first 3 trait objects get owned by the daemon methods that required them, which is why an `Arc>` is required. +### Controller Traits -Generally the actual controller object will need to live in its own world as its own struct. -Then for each trait that is required a new struct is required that can have the trait implemented, and that struct would have a reference to the main controller via `Arc>`. +Controllers can implement the standard daemon lifecycle and capability traits: -### Example +- `GetSupported`: Checks if the hardware or kernel features required by the controller are supported on the current machine. +- `Reloadable`: For controllers that need the ability to reload state (typically on startup or upon receiving configuration reload signals). +- `CtrlTask`: For background workers handling system lifecycle events (boot, suspend, resume, shutdown) or watching sysfs/udev nodes. +- `ZbusRun`: For exposing controller interfaces to the D-Bus system bus via `zbus`. -Main controller: +Depending on the controller's complexity and concurrency requirements, these traits can be implemented directly on the controller struct or via dedicated wrappers. -For a very simple controller that doesn't need exclusive access you can clone across threads +### Synchronous Concurrency & State Ownership Models -```rust -#[derive(Clone)] -pub struct CtrlAnime { - - config: Arc>, -} +When sharing state across controller traits, background threads, or D-Bus handlers, choose the concurrency model based on ownership and access patterns: -// This is the task trait used for such things as file watches, or logind -// notifications (boot/suspend/shutdown etc) -impl crate::CtrlTask for CtrlAnime {} +- **Thread Worker / Actor Pattern (`std::thread` + `std::sync::mpsc`)**: Preferred for controllers that manage sequential hardware I/O (such as Aura RGB USB HID, AniMe Matrix, or event queues). A dedicated OS worker thread retains single-ownership of the physical device handle and processes incoming commands from a `std::sync::mpsc::channel`. This naturally avoids concurrent hardware access, serializes I/O, and eliminates deadlocks by construction. +- **Shared Memory with Standard Synchronous Locks (`Arc>` / `Arc>`)**: Suitable for lightweight in-memory state (such as configuration files or cached status). Prefer `std::sync::RwLock` for read-heavy state to allow concurrent readers without lock contention, and `std::sync::Mutex` when writes are frequent. +- **Lock-Free Synchronization with Atomics (`std::sync::atomic`)**: For simple status flags, counters, or mode indicators (e.g. device connection status, active power mode, suspension state), prefer atomic types (`AtomicBool`, `AtomicU32`, `AtomicUsize`) with appropriate memory ordering (`Ordering::Relaxed`, `Ordering::Acquire`, `Ordering::Release`) to eliminate lock contention and mutex allocations entirely. +- **Static & Write-Once Initialization (`std::sync::LazyLock` / `std::sync::OnceLock`)**: For immutable lookup tables, regexes, and device capability maps initialized lazily or once at startup, use `std::sync::LazyLock` or `std::sync::OnceLock` instead of dynamic locking primitives. +- **Fast Critical Sections (Copy-on-Read)**: Keep locked critical sections minimal. Acquire the lock, copy or extract the required value into a local variable, and immediately drop the lock guard before performing hardware I/O or long-running operations. +- **No Spin Locks or Busy-Waiting**: Never use spin locks or polling loops (`loop { try_lock() }`) for shared state or event dispatching. Use blocking synchronization (`std::sync::mpsc::Receiver::recv()`, condition variables, or OS event polling) to avoid wasting CPU cycles. -// The trait to easily add the controller to Zbus to enable the zbus derived functions -// to be polled, run, react etc. -impl crate::ZbusAdd for CtrlAnime {} +### Examples -impl CtrlAnime {} -``` +#### 1. Controller with Shared In-Memory State (`Arc>`) - Otherwise, you will need to share the controller via mutex +For controllers that manage device settings and respond to system events (e.g. `CtrlPlatform`), keep the controller cheaply cloneable by wrapping read-heavy configuration in `std::sync::RwLock` and passing references to underlying platform handles: ```rust -pub struct CtrlAnime { - -} -// Like this -#[derive(Clone)] -pub struct CtrlAnimeTask(Arc>); +use std::sync::{Arc, RwLock}; +use zbus::Connection; +use zbus::object_server::SignalEmitter; +use crate::error::RogError; +use crate::{CtrlTask, Reloadable, ZbusRun}; #[derive(Clone)] -pub struct CtrlAnimeZbus(Arc>); - -impl CtrlAnime {} -``` +pub struct CtrlPlatform { + platform: RogPlatform, + power: AsusPower, + config: Arc>, +} -The task trait: +// Zbus interface registration +impl ZbusRun for CtrlPlatform { + async fn add_to_server(self, server: &mut Connection) { + Self::add_to_server_helper(self, "/xyz/ljones/Platform", server).await; + } +} -```rust -// Mutex should always be async mutex -pub struct CtrlAnimeTask(Arc>); - -impl crate::CtrlTask for CtrlAnimeTask { - // This will run once only - async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> { - let lock self.inner.lock().await; - +// Synchronous configuration reload handler +impl Reloadable for CtrlPlatform { + async fn reload(&mut self) -> Result<(), RogError> { + // Read configuration and extract value immediately to minimize critical section + let charge_limit = self + .config + .read() + .map_err(|e| RogError::LockError(e.to_string()))? + .charge_control_end_threshold; + + // Perform hardware sysfs write without holding the lock + self.power.set_charge_control_end_threshold(charge_limit)?; Ok(()) } +} + +// Background task and system lifecycle event handling +impl CtrlTask for CtrlPlatform { + fn zbus_path() -> &'static str { + "/xyz/ljones/Platform" + } + + async fn create_tasks(&self, _signal_ctxt: SignalEmitter<'static>) -> Result<(), RogError> { + let ctrl = self.clone(); - // This will run until the notification stream closes (which in most cases will be never) - async fn create_tasks(&self, signal_ctxt: SignalContext<'static>) -> Result<(), RogError> { - let inner1 = self.inner.clone(); - let inner2 = self.inner.clone(); - let inner3 = self.inner.clone(); - let inner4 = self.inner.clone(); - // This is a free method on CtrlTask trait + // Register event handlers for system lifecycle events self.create_sys_event_tasks( - // Loop is required to try an attempt to get the mutex *without* blocking - // other threads - it is possible to end up with deadlocks otherwise. - move || loop { - if let Some(lock) = inner1.try_lock() { - run_action(true, lock, inner1.clone()); - break; - } - }, - move || loop { - if let Some(lock) = inner2.try_lock() { - run_action(false, lock, inner2.clone()); - break; - } - }, - move || loop { - if let Some(lock) = inner3.try_lock() { - run_action(true, lock, inner3.clone()); - break; - } - }, - move || loop { - if let Some(lock) = inner4.try_lock() { - run_action(false, lock, inner4.clone()); - break; + move |sleeping| { + let ctrl = ctrl.clone(); + async move { + if !sleeping { + // Re-apply charge limit when resuming from sleep + if let Ok(config) = ctrl.config.read() { + let limit = config.charge_control_end_threshold; + drop(config); + ctrl.power.set_charge_control_end_threshold(limit).ok(); + } + } } }, + move |_shutdown| async move { /* handle shutdown */ }, + move |_lid_closed| async move { /* handle lid switch event */ }, + move |_on_ac| async move { /* handle AC/battery power transition */ }, ) .await; + + Ok(()) } } ``` -The reloader trait +#### 2. Synchronous Worker Thread for Hardware I/O (`std::thread` + `std::sync::mpsc`) + +For hardware devices that require exclusive, serialized access (e.g. Aura RGB USB HID, AniMe Matrix animations), use a dedicated background OS worker thread with `std::sync::mpsc`: ```rust -pub struct CtrlAnimeReloader(Arc>); +use std::sync::mpsc::{self, Sender, SyncSender}; +use std::thread; +use zbus::interface; +use crate::error::RogError; + +enum DeviceMsg { + SetBrightness { + brightness: u8, + reply: Sender>, + }, + SetMode { + mode: AuraMode, + reply: Sender>, + }, +} -impl crate::Reloadable for CtrlAnimeReloader { - async fn reload(&mut self) -> Result<(), RogError> { - let lock = self.inner.lock().await; - - Ok(()) +#[derive(Clone)] +pub struct CtrlAura { + tx: SyncSender, +} + +impl CtrlAura { + pub fn new(mut device: AuraDevice) -> Self { + let (tx, rx) = mpsc::sync_channel::(32); + + // Dedicated OS worker thread retains single-ownership of the physical device + thread::Builder::new() + .name("aura-worker".into()) + .spawn(move || { + // Blocking loop waits for incoming commands without busy-waiting + while let Ok(msg) = rx.recv() { + match msg { + DeviceMsg::SetBrightness { brightness, reply } => { + let res = device.write_brightness(brightness); + let _ = reply.send(res); + } + DeviceMsg::SetMode { mode, reply } => { + let res = device.write_mode(mode); + let _ = reply.send(res); + } + } + } + }) + .expect("Failed to spawn aura-worker thread"); + + Self { tx } + } +} + +#[interface(name = "xyz.ljones.Aura")] +impl CtrlAura { + async fn set_brightness(&self, val: u8) -> zbus::fdo::Result<()> { + let (reply_tx, reply_rx) = mpsc::channel(); + + self.tx + .send(DeviceMsg::SetBrightness { + brightness: val, + reply: reply_tx, + }) + .map_err(|e| zbus::fdo::Error::Failed(e.to_string()))?; + + // Wait for synchronous worker response + reply_rx + .recv() + .map_err(|e| zbus::fdo::Error::Failed(e.to_string()))? + .map_err(|e| zbus::fdo::Error::Failed(e.to_string())) } } ``` -The Zbus requirements: +#### 3. Lock-Free Status & Direct Kernel Sysfs Management (`std::sync::atomic`) + +For controllers with simple state flags or high-frequency status queries, combine lock-free atomics with synchronous in-memory caching: ```rust -pub struct CtrlAnimeZbus(Arc>); +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use zbus::interface; +use crate::error::RogError; -#[async_trait] -impl crate::ZbusAdd for CtrlAnimeZbus { - fn add_to_server(self, server: &mut zbus::ObjectServer) { - // This is a provided free helper trait with pre-set body. It will move self in-to. - Self::add_to_server_helper(self, "/org/asuslinux/Anime", server).await; +#[derive(Clone)] +pub struct CtrlXgm { + // Lock-free atomic indicator for high-frequency status queries + is_active: Arc, + // Synchronous mutex for fast in-memory caching + cached_name: Arc>>, +} + +impl CtrlXgm { + pub fn new() -> Self { + Self { + is_active: Arc::new(AtomicBool::new(false)), + cached_name: Arc::new(Mutex::new(None)), + } + } + + /// Fast lock-free query with zero lock contention + pub fn is_active(&self) -> bool { + self.is_active.load(Ordering::Acquire) + } + + /// Fast synchronous cache update without async lock allocation + pub fn update_cached_name(&self, name: String) { + if let Ok(mut lock) = self.cached_name.lock() { + *lock = Some(name); + } + } + + /// Synchronous sysfs kernel interaction + pub fn write_xgm_active(&self, active: bool) -> Result<(), RogError> { + std::fs::write( + "/sys/devices/platform/asus-nb-wmi/xgm_active", + if active { "1" } else { "0" }, + ) + .map_err(|e| RogError::SysfsWrite(e.to_string()))?; + + self.is_active.store(active, Ordering::Release); + Ok(()) } } -#[dbus_interface(name = "xyz.ljones.Asusd")] -impl CtrlAnimeZbus { - async fn () { - let lock = self.inner.lock().await; - +#[interface(name = "xyz.ljones.Xgm")] +impl CtrlXgm { + async fn set_active(&self, active: bool) -> zbus::fdo::Result<()> { + self.write_xgm_active(active) + .map_err(|e| zbus::fdo::Error::Failed(e.to_string())) } } ``` - -The controller can then be added to the daemon parts as required.