blob: 6634795fd3af87b85c3308912c962941102877da [file]
# Copyright 2024 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import asyncio
import logging
import fuchsia_wlan_base_test
import honeydew.affordances.connectivity.wlan.core as wlan_core
from honeydew.affordances.connectivity.wlan.utils.errors import (
HoneydewWlanError,
)
from mobly import test_runner
from mobly.asserts import assert_equal, assert_true
logger = logging.getLogger(__name__)
class WlanDriverRestartTest(fuchsia_wlan_base_test.FuchsiaWlanBaseTest):
phy: wlan_core.Phy | None
async def setup_test(self) -> None:
await super().setup_test()
await self.dut.wlan_core.destroy_all_ifaces()
async def _assert_no_reboot(self, initial_boot_id: str) -> None:
"""Verifies that the DUT did not reboot during the driver restart test."""
try:
current_boot_id = await self.dut.boot_id()
except Exception:
# If the device is currently offline or rebooting, wait for it to come online.
logger.info(
"Device offline or error querying boot ID; waiting for online..."
)
await self.dut.wait_for_online()
current_boot_id = await self.dut.boot_id()
assert_equal(
current_boot_id,
initial_boot_id,
f"Device unexpectedly rebooted during WLAN driver restart "
f"(boot ID changed from {initial_boot_id} to {current_boot_id}). "
f"WLAN driver crash triggered a system reboot instead of recovering in-place.",
)
async def test_driver_host_restart(self) -> None:
# TODO(b/494309251): If a driver debug side channel is created, use that to query out the
# KOID and kill that instead.
initial_boot_id = await self.dut.boot_id()
try:
logger.info("Restarting WLAN driver...")
processes = self.dut.ffx.run_ssh_cmd("ps")
wlan_driver_process = None
keywords = ["iwlwifi", "realtek", "brcmfmac", "bcmdhd"]
for line in processes.splitlines():
if any(k in line for k in keywords):
for token in line.split():
if any(k in token for k in keywords):
wlan_driver_process = token
break
if wlan_driver_process:
break
if wlan_driver_process:
logger.info(
f"Detected running DFv2 WLAN process: {wlan_driver_process}. Killing via SSH..."
)
self.dut.ffx.run_ssh_cmd(f'killall "{wlan_driver_process}*"')
elif "driver-host-#wlan" in processes:
logger.info(
"Detected legacy DFv1 driver host. Killing via SSH..."
)
self.dut.ffx.run_ssh_cmd("killall driver-host-#wlan")
else:
raise RuntimeError(
"Could not find any running WLAN driver process to restart!"
)
logger.info("Polling for PHY to be removed")
phy_removal_timeout = 10
poll_interval = 0.25
for _ in range(int(phy_removal_timeout / poll_interval)):
try:
await self.dut.wlan_core.ensure_single_phy()
except HoneydewWlanError:
logger.info("Successfully observed PHY removal")
break
await asyncio.sleep(poll_interval)
else:
assert_true(False, "Timed out waiting for PHYs to be removed")
logger.info("Polling for PHY to return")
phy_return_timeout = 30
poll_interval = 1.0
phy = None
for _ in range(int(phy_return_timeout / poll_interval)):
try:
phy = await self.dut.wlan_core.ensure_single_phy()
logger.info(f"Successfully found restored PHY: {phy}")
break
except HoneydewWlanError:
pass
await asyncio.sleep(poll_interval)
else:
assert_true(False, "Timed out waiting for PHYs to be restored")
assert phy is not None
logger.info("Creating client interface...")
iface = await phy.create_client_iface()
logger.info("Issuing scan request...")
await iface.passive_scan()
# Completing the passive_scan with no exception raised means
# the scan succeeded!
finally:
await self._assert_no_reboot(initial_boot_id)
if __name__ == "__main__":
test_runner.main()