wifi: Add method for generating and capturing an ath11k dump

This commit is contained in:
Vicki Pfau 2025-01-17 20:25:41 -08:00
parent 7b7afffc46
commit 54351414fa
8 changed files with 210 additions and 5 deletions

View file

@ -376,6 +376,27 @@
</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
@short_description: Optional interface for Wi-Fi power management.

View file

@ -16,7 +16,8 @@ use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerPr
use steamos_manager::proxy::{
AmbientLightSensor1Proxy, CpuScaling1Proxy, FactoryReset1Proxy, FanControl1Proxy,
GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy, Manager2Proxy, Storage1Proxy,
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiPowerManagement1Proxy,
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy,
WifiPowerManagement1Proxy,
};
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
use zbus::fdo::{IntrospectableProxy, PropertiesProxy};
@ -145,6 +146,9 @@ enum Commands {
/// Get the Wi-Fi power management state
GetWifiPowerManagementState,
/// Generate a Wi-Fi debug dump
GenerateWifiDebugDump,
/// Get the state of HDMI-CEC support
GetHdmiCecState,
@ -400,6 +404,11 @@ async fn main() -> Result<()> {
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 } => {
let proxy = HdmiCec1Proxy::new(&conn).await?;
proxy.set_hdmi_cec_state(*state as u32).await?;

View file

@ -29,8 +29,8 @@ use crate::power::{
};
use crate::process::{run_script, script_output};
use crate::wifi::{
extract_wifi_trace, set_wifi_backend, set_wifi_debug_mode, set_wifi_power_management_state,
WifiBackend, WifiDebugMode, WifiPowerManagement,
extract_wifi_trace, generate_wifi_dump, set_wifi_backend, set_wifi_debug_mode,
set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement,
};
use crate::{path, API_VERSION};
@ -379,6 +379,16 @@ impl SteamOSManager {
.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)]
async fn inhibit_ds(&self) -> fdo::Result<bool> {
let (tx, rx) = oneshot::channel();

View file

@ -155,6 +155,10 @@ struct WifiDebug1 {
proxy: Proxy<'static>,
}
struct WifiDebugDump1 {
proxy: Proxy<'static>,
}
struct WifiPowerManagement1 {
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")]
impl WifiPowerManagement1 {
#[zbus(property(emits_changed_signal = "false"))]
@ -568,6 +579,9 @@ pub(crate) async fn create_interfaces(
let wifi_debug = WifiDebug1 {
proxy: proxy.clone(),
};
let wifi_debug_dump = WifiDebugDump1 {
proxy: proxy.clone(),
};
let wifi_power_management = WifiPowerManagement1 {
proxy: proxy.clone(),
};
@ -579,6 +593,9 @@ pub(crate) async fn create_interfaces(
if is_deck().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?;
@ -939,4 +956,13 @@ mod test {
.await
.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());
}
}

View file

@ -27,6 +27,7 @@ mod tdp_limit1;
mod update_bios1;
mod update_dock1;
mod wifi_debug1;
mod wifi_debug_dump1;
mod wifi_power_management1;
pub use crate::proxy::ambient_light_sensor1::AmbientLightSensor1Proxy;
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_dock1::UpdateDock1Proxy;
pub use crate::proxy::wifi_debug1::WifiDebug1Proxy;
pub use crate::proxy::wifi_debug_dump1::WifiDebugDump1Proxy;
pub use crate::proxy::wifi_power_management1::WifiPowerManagement1Proxy;
// Sub-interfaces

View 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>;
}

View file

@ -9,6 +9,8 @@ use anyhow::{anyhow, bail, ensure, Result};
use nix::poll::{poll, PollFd, PollFlags, PollTimeout};
use nix::unistd::pipe;
use std::os::fd::{AsFd, AsRawFd, OwnedFd};
use std::path::PathBuf;
use std::sync::mpsc::channel;
use tokio::net::unix::pipe::Sender;
use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
use tracing::debug;
@ -16,7 +18,7 @@ use udev::{Event, EventType, MonitorBuilder};
use zbus::object_server::{InterfaceRef, SignalEmitter};
use zbus::{self, interface, Connection};
use crate::thread::spawn;
use crate::thread::{spawn, AsyncJoinHandle};
use crate::Service;
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<()> {
debug!("Got USB event {ev:?}");
if ev.event_type() != EventType::Change {

View file

@ -10,19 +10,24 @@ use config::builder::AsyncState;
use config::{ConfigBuilder, FileFormat};
use nix::sys::stat::{self, Mode};
use num_enum::TryFromPrimitive;
use std::ffi::OsStr;
use std::fs::Permissions;
use std::io::ErrorKind;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use strum::{Display, EnumString};
use tempfile::Builder as TempFileBuilder;
use tokio::fs;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tracing::error;
use udev::{Event, EventType};
use zbus::Connection;
use crate::process::{run_script, script_output, script_pipe_output};
use crate::systemd::{daemon_reload, SystemdUnit};
use crate::udev::single_poll;
use crate::{path, read_config_directory};
const OVERRIDE_CONTENTS: &str = "[Service]
@ -98,7 +103,7 @@ pub(crate) async fn setup_iwd_config(want_override: bool) -> std::io::Result<()>
} else {
// Delete it
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,
}
}
@ -273,6 +278,67 @@ pub(crate) async fn set_wifi_power_management_state(state: WifiPowerManagement)
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)]
mod test {
use super::*;