mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-12 09:22:26 -04:00
wifi: Add method for generating and capturing an ath11k dump
This commit is contained in:
parent
7b7afffc46
commit
54351414fa
8 changed files with 210 additions and 5 deletions
|
@ -376,6 +376,27 @@
|
||||||
|
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
com.steampowered.SteamOSManager1.WifiDebugDump1
|
||||||
|
@short_description: Optional interface for generating Wi-Fi driver dumps.
|
||||||
|
-->
|
||||||
|
<interface name="com.steampowered.SteamOSManager1.WifiDebugDump1">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
GenerateDebugDump:
|
||||||
|
|
||||||
|
@path: The path to the generated file. This will be world-readable and
|
||||||
|
in a temporary directory, so make sure to move it to a permanent
|
||||||
|
location if keeping it is desired.
|
||||||
|
|
||||||
|
Generate a Wi-Fi driver dump and export it to a file.
|
||||||
|
-->
|
||||||
|
<method name="GenerateDebugDump">
|
||||||
|
<arg type="s" name="path" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
</interface>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
com.steampowered.SteamOSManager1.WifiPowerManagement1
|
com.steampowered.SteamOSManager1.WifiPowerManagement1
|
||||||
@short_description: Optional interface for Wi-Fi power management.
|
@short_description: Optional interface for Wi-Fi power management.
|
||||||
|
|
|
@ -16,7 +16,8 @@ use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerPr
|
||||||
use steamos_manager::proxy::{
|
use steamos_manager::proxy::{
|
||||||
AmbientLightSensor1Proxy, CpuScaling1Proxy, FactoryReset1Proxy, FanControl1Proxy,
|
AmbientLightSensor1Proxy, CpuScaling1Proxy, FactoryReset1Proxy, FanControl1Proxy,
|
||||||
GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy, Manager2Proxy, Storage1Proxy,
|
GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy, Manager2Proxy, Storage1Proxy,
|
||||||
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiPowerManagement1Proxy,
|
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy,
|
||||||
|
WifiPowerManagement1Proxy,
|
||||||
};
|
};
|
||||||
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
||||||
use zbus::fdo::{IntrospectableProxy, PropertiesProxy};
|
use zbus::fdo::{IntrospectableProxy, PropertiesProxy};
|
||||||
|
@ -145,6 +146,9 @@ enum Commands {
|
||||||
/// Get the Wi-Fi power management state
|
/// Get the Wi-Fi power management state
|
||||||
GetWifiPowerManagementState,
|
GetWifiPowerManagementState,
|
||||||
|
|
||||||
|
/// Generate a Wi-Fi debug dump
|
||||||
|
GenerateWifiDebugDump,
|
||||||
|
|
||||||
/// Get the state of HDMI-CEC support
|
/// Get the state of HDMI-CEC support
|
||||||
GetHdmiCecState,
|
GetHdmiCecState,
|
||||||
|
|
||||||
|
@ -400,6 +404,11 @@ async fn main() -> Result<()> {
|
||||||
Err(_) => println!("Got unknown value {state} from backend"),
|
Err(_) => println!("Got unknown value {state} from backend"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Commands::GenerateWifiDebugDump => {
|
||||||
|
let proxy = WifiDebugDump1Proxy::new(&conn).await?;
|
||||||
|
let path = proxy.generate_debug_dump().await?;
|
||||||
|
println!("{path}");
|
||||||
|
}
|
||||||
Commands::SetHdmiCecState { state } => {
|
Commands::SetHdmiCecState { state } => {
|
||||||
let proxy = HdmiCec1Proxy::new(&conn).await?;
|
let proxy = HdmiCec1Proxy::new(&conn).await?;
|
||||||
proxy.set_hdmi_cec_state(*state as u32).await?;
|
proxy.set_hdmi_cec_state(*state as u32).await?;
|
||||||
|
|
|
@ -29,8 +29,8 @@ use crate::power::{
|
||||||
};
|
};
|
||||||
use crate::process::{run_script, script_output};
|
use crate::process::{run_script, script_output};
|
||||||
use crate::wifi::{
|
use crate::wifi::{
|
||||||
extract_wifi_trace, set_wifi_backend, set_wifi_debug_mode, set_wifi_power_management_state,
|
extract_wifi_trace, generate_wifi_dump, set_wifi_backend, set_wifi_debug_mode,
|
||||||
WifiBackend, WifiDebugMode, WifiPowerManagement,
|
set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement,
|
||||||
};
|
};
|
||||||
use crate::{path, API_VERSION};
|
use crate::{path, API_VERSION};
|
||||||
|
|
||||||
|
@ -379,6 +379,16 @@ impl SteamOSManager {
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn generate_debug_dump(&self) -> fdo::Result<String> {
|
||||||
|
Ok(generate_wifi_dump()
|
||||||
|
.await
|
||||||
|
.inspect_err(|message| error!("Error capturing dump output: {message}"))
|
||||||
|
.map_err(to_zbus_fdo_error)?
|
||||||
|
.into_os_string()
|
||||||
|
.to_string_lossy()
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
#[zbus(property)]
|
#[zbus(property)]
|
||||||
async fn inhibit_ds(&self) -> fdo::Result<bool> {
|
async fn inhibit_ds(&self) -> fdo::Result<bool> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
|
@ -155,6 +155,10 @@ struct WifiDebug1 {
|
||||||
proxy: Proxy<'static>,
|
proxy: Proxy<'static>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WifiDebugDump1 {
|
||||||
|
proxy: Proxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
struct WifiPowerManagement1 {
|
struct WifiPowerManagement1 {
|
||||||
proxy: Proxy<'static>,
|
proxy: Proxy<'static>,
|
||||||
}
|
}
|
||||||
|
@ -492,6 +496,13 @@ impl WifiDebug1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[interface(name = "com.steampowered.SteamOSManager1.WifiDebugDump1")]
|
||||||
|
impl WifiDebugDump1 {
|
||||||
|
async fn generate_debug_dump(&self) -> fdo::Result<String> {
|
||||||
|
method!(self, "GenerateDebugDump")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[interface(name = "com.steampowered.SteamOSManager1.WifiPowerManagement1")]
|
#[interface(name = "com.steampowered.SteamOSManager1.WifiPowerManagement1")]
|
||||||
impl WifiPowerManagement1 {
|
impl WifiPowerManagement1 {
|
||||||
#[zbus(property(emits_changed_signal = "false"))]
|
#[zbus(property(emits_changed_signal = "false"))]
|
||||||
|
@ -568,6 +579,9 @@ pub(crate) async fn create_interfaces(
|
||||||
let wifi_debug = WifiDebug1 {
|
let wifi_debug = WifiDebug1 {
|
||||||
proxy: proxy.clone(),
|
proxy: proxy.clone(),
|
||||||
};
|
};
|
||||||
|
let wifi_debug_dump = WifiDebugDump1 {
|
||||||
|
proxy: proxy.clone(),
|
||||||
|
};
|
||||||
let wifi_power_management = WifiPowerManagement1 {
|
let wifi_power_management = WifiPowerManagement1 {
|
||||||
proxy: proxy.clone(),
|
proxy: proxy.clone(),
|
||||||
};
|
};
|
||||||
|
@ -579,6 +593,9 @@ pub(crate) async fn create_interfaces(
|
||||||
if is_deck().await? {
|
if is_deck().await? {
|
||||||
object_server.at(MANAGER_PATH, als).await?;
|
object_server.at(MANAGER_PATH, als).await?;
|
||||||
}
|
}
|
||||||
|
if variant().await? == HardwareVariant::Galileo {
|
||||||
|
object_server.at(MANAGER_PATH, wifi_debug_dump).await?;
|
||||||
|
}
|
||||||
|
|
||||||
object_server.at(MANAGER_PATH, cpu_scaling).await?;
|
object_server.at(MANAGER_PATH, cpu_scaling).await?;
|
||||||
|
|
||||||
|
@ -939,4 +956,13 @@ mod test {
|
||||||
.await
|
.await
|
||||||
.unwrap());
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn interface_matches_wifi_debug_dump() {
|
||||||
|
let test = start(all_config()).await.expect("start");
|
||||||
|
|
||||||
|
assert!(test_interface_matches::<WifiDebugDump1>(&test.connection)
|
||||||
|
.await
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ mod tdp_limit1;
|
||||||
mod update_bios1;
|
mod update_bios1;
|
||||||
mod update_dock1;
|
mod update_dock1;
|
||||||
mod wifi_debug1;
|
mod wifi_debug1;
|
||||||
|
mod wifi_debug_dump1;
|
||||||
mod wifi_power_management1;
|
mod wifi_power_management1;
|
||||||
pub use crate::proxy::ambient_light_sensor1::AmbientLightSensor1Proxy;
|
pub use crate::proxy::ambient_light_sensor1::AmbientLightSensor1Proxy;
|
||||||
pub use crate::proxy::cpu_scaling1::CpuScaling1Proxy;
|
pub use crate::proxy::cpu_scaling1::CpuScaling1Proxy;
|
||||||
|
@ -41,6 +42,7 @@ pub use crate::proxy::tdp_limit1::TdpLimit1Proxy;
|
||||||
pub use crate::proxy::update_bios1::UpdateBios1Proxy;
|
pub use crate::proxy::update_bios1::UpdateBios1Proxy;
|
||||||
pub use crate::proxy::update_dock1::UpdateDock1Proxy;
|
pub use crate::proxy::update_dock1::UpdateDock1Proxy;
|
||||||
pub use crate::proxy::wifi_debug1::WifiDebug1Proxy;
|
pub use crate::proxy::wifi_debug1::WifiDebug1Proxy;
|
||||||
|
pub use crate::proxy::wifi_debug_dump1::WifiDebugDump1Proxy;
|
||||||
pub use crate::proxy::wifi_power_management1::WifiPowerManagement1Proxy;
|
pub use crate::proxy::wifi_power_management1::WifiPowerManagement1Proxy;
|
||||||
|
|
||||||
// Sub-interfaces
|
// Sub-interfaces
|
||||||
|
|
24
src/proxy/wifi_debug_dump1.rs
Normal file
24
src/proxy/wifi_debug_dump1.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//! # D-Bus interface proxy for: `com.steampowered.SteamOSManager1.WifiDebugDump1`
|
||||||
|
//!
|
||||||
|
//! This code was generated by `zbus-xmlgen` `5.0.1` from D-Bus introspection data.
|
||||||
|
//! Source: `com.steampowered.SteamOSManager1.xml`.
|
||||||
|
//!
|
||||||
|
//! You may prefer to adapt it, instead of using it verbatim.
|
||||||
|
//!
|
||||||
|
//! More information can be found in the [Writing a client proxy] section of the zbus
|
||||||
|
//! documentation.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
||||||
|
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
||||||
|
use zbus::proxy;
|
||||||
|
#[proxy(
|
||||||
|
interface = "com.steampowered.SteamOSManager1.WifiDebugDump1",
|
||||||
|
default_service = "com.steampowered.SteamOSManager1",
|
||||||
|
default_path = "/com/steampowered/SteamOSManager1",
|
||||||
|
assume_defaults = true
|
||||||
|
)]
|
||||||
|
pub trait WifiDebugDump1 {
|
||||||
|
/// GenerateDebugDump method
|
||||||
|
fn generate_debug_dump(&self) -> zbus::Result<String>;
|
||||||
|
}
|
49
src/udev.rs
49
src/udev.rs
|
@ -9,6 +9,8 @@ use anyhow::{anyhow, bail, ensure, Result};
|
||||||
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
|
||||||
use nix::unistd::pipe;
|
use nix::unistd::pipe;
|
||||||
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
|
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
use tokio::net::unix::pipe::Sender;
|
use tokio::net::unix::pipe::Sender;
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
@ -16,7 +18,7 @@ use udev::{Event, EventType, MonitorBuilder};
|
||||||
use zbus::object_server::{InterfaceRef, SignalEmitter};
|
use zbus::object_server::{InterfaceRef, SignalEmitter};
|
||||||
use zbus::{self, interface, Connection};
|
use zbus::{self, interface, Connection};
|
||||||
|
|
||||||
use crate::thread::spawn;
|
use crate::thread::{spawn, AsyncJoinHandle};
|
||||||
use crate::Service;
|
use crate::Service;
|
||||||
|
|
||||||
const PATH: &str = "/com/steampowered/SteamOSManager1";
|
const PATH: &str = "/com/steampowered/SteamOSManager1";
|
||||||
|
@ -150,6 +152,51 @@ fn run_udev(tx: &UnboundedSender<UdevEvent>, rx: &OwnedFd) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn single_poll<F>(
|
||||||
|
subsystem: &str,
|
||||||
|
callback: F,
|
||||||
|
timeout: PollTimeout,
|
||||||
|
) -> AsyncJoinHandle<Result<PathBuf>>
|
||||||
|
where
|
||||||
|
F: Fn(&Event) -> bool + Send + 'static,
|
||||||
|
{
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let subsystem = subsystem.to_string();
|
||||||
|
let handle = spawn(move || {
|
||||||
|
let monitor = MonitorBuilder::new()?
|
||||||
|
.match_subsystem(subsystem)?
|
||||||
|
.listen()?;
|
||||||
|
let fd = monitor.as_fd();
|
||||||
|
let mut iter = monitor.iter();
|
||||||
|
let ev_poller = PollFd::new(fd, PollFlags::POLLIN);
|
||||||
|
let _ = tx.send(());
|
||||||
|
loop {
|
||||||
|
let fds = &mut [ev_poller];
|
||||||
|
// TODO: Subtract the time from the last loop, if relevant
|
||||||
|
let ret = poll(fds, timeout)?;
|
||||||
|
if ret < 0 {
|
||||||
|
return Err(std::io::Error::from_raw_os_error(-ret).into());
|
||||||
|
}
|
||||||
|
ensure!(ret == 1, "Udev poller timed out");
|
||||||
|
let [ev_poller] = fds;
|
||||||
|
match ev_poller.any() {
|
||||||
|
None => bail!("Udev poller encountered unknown flags"),
|
||||||
|
Some(true) => {
|
||||||
|
let ev = iter
|
||||||
|
.next()
|
||||||
|
.ok_or(anyhow!("Poller said event was present, but it was not"))?;
|
||||||
|
if callback(&ev) {
|
||||||
|
return Ok(ev.syspath().to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(false) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let _ = rx.recv();
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
fn process_usb_event(ev: &Event, tx: &UnboundedSender<UdevEvent>) -> Result<()> {
|
fn process_usb_event(ev: &Event, tx: &UnboundedSender<UdevEvent>) -> Result<()> {
|
||||||
debug!("Got USB event {ev:?}");
|
debug!("Got USB event {ev:?}");
|
||||||
if ev.event_type() != EventType::Change {
|
if ev.event_type() != EventType::Change {
|
||||||
|
|
68
src/wifi.rs
68
src/wifi.rs
|
@ -10,19 +10,24 @@ use config::builder::AsyncState;
|
||||||
use config::{ConfigBuilder, FileFormat};
|
use config::{ConfigBuilder, FileFormat};
|
||||||
use nix::sys::stat::{self, Mode};
|
use nix::sys::stat::{self, Mode};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs::Permissions;
|
use std::fs::Permissions;
|
||||||
|
use std::io::ErrorKind;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
use strum::{Display, EnumString};
|
use strum::{Display, EnumString};
|
||||||
use tempfile::Builder as TempFileBuilder;
|
use tempfile::Builder as TempFileBuilder;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
use udev::{Event, EventType};
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
|
|
||||||
use crate::process::{run_script, script_output, script_pipe_output};
|
use crate::process::{run_script, script_output, script_pipe_output};
|
||||||
use crate::systemd::{daemon_reload, SystemdUnit};
|
use crate::systemd::{daemon_reload, SystemdUnit};
|
||||||
|
use crate::udev::single_poll;
|
||||||
use crate::{path, read_config_directory};
|
use crate::{path, read_config_directory};
|
||||||
|
|
||||||
const OVERRIDE_CONTENTS: &str = "[Service]
|
const OVERRIDE_CONTENTS: &str = "[Service]
|
||||||
|
@ -98,7 +103,7 @@ pub(crate) async fn setup_iwd_config(want_override: bool) -> std::io::Result<()>
|
||||||
} else {
|
} else {
|
||||||
// Delete it
|
// Delete it
|
||||||
match fs::remove_file(path(OVERRIDE_PATH)).await {
|
match fs::remove_file(path(OVERRIDE_PATH)).await {
|
||||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
|
||||||
res => res,
|
res => res,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,6 +278,67 @@ pub(crate) async fn set_wifi_power_management_state(state: WifiPowerManagement)
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn generate_wifi_dump_inner() -> Result<PathBuf> {
|
||||||
|
fn cb(ev: &Event) -> bool {
|
||||||
|
if ev.event_type() != EventType::Add {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let path = ev.syspath();
|
||||||
|
let Ok(link) = std::fs::read_link(path.join("failing_device/driver")) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
link.file_name() == Some(OsStr::new("ath11k_pci"))
|
||||||
|
}
|
||||||
|
|
||||||
|
let poller = single_poll("devcoredump", cb, Duration::from_secs(5).try_into()?);
|
||||||
|
fs::write(
|
||||||
|
path("/sys/kernel/debug/ath11k/pci-0000:03:00.0/simulate_fw_crash"),
|
||||||
|
"mhi-rddm\n",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let devcd = poller.await?;
|
||||||
|
let data = devcd.join("data");
|
||||||
|
let (mut output, path) = make_tempfile("wifi-dump-")?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut dump = fs::File::open(&data).await?;
|
||||||
|
let mut buf = [0; 4096];
|
||||||
|
loop {
|
||||||
|
let read = dump.read(&mut buf).await?;
|
||||||
|
if read == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output.write_all(&buf[..read]).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(data, "1\n").await?;
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn generate_wifi_dump() -> Result<PathBuf> {
|
||||||
|
const DEVCD_BLOCK: &str = "/var/lib/steamos-log-submitter/data/devcd-block/ath11k_pci";
|
||||||
|
let placeholder = fs::OpenOptions::new()
|
||||||
|
.create_new(true)
|
||||||
|
.write(true)
|
||||||
|
.open(path(DEVCD_BLOCK))
|
||||||
|
.await;
|
||||||
|
if let Err(ref err) = placeholder {
|
||||||
|
ensure!(
|
||||||
|
err.kind() == ErrorKind::NotFound,
|
||||||
|
"Cound not create SLS helper block"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = generate_wifi_dump_inner().await;
|
||||||
|
|
||||||
|
if placeholder.is_ok() {
|
||||||
|
let _ = fs::remove_file(DEVCD_BLOCK).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue