[PATCH ath-next 3/6] wifi: ath10k: usb: start the firmware only once
Tomas Paukrt
tomaspaukrt at email.cz
Sat Nov 14 01:07:34 PST 2026
The ath10k core starts the firmware during the registration to read the
capabilities and again whenever the interface is brought up. A QCA9377
USB target cannot do that. The running firmware does not accept BMI
commands and only a cold reset brings the target back to the boot
loader, but the reset makes it leave the bus and enumerate again as a
new device. A warm reset keeps it on the bus, but the OTP program hangs
when it is executed again.
Add start_once to struct ath10k_bus_params. The firmware started during
the registration is then kept running until the device is unregistered.
It is not suspended before it is stopped, because a suspended target
ignores the reset.
Set start_once for USB and reset the target in power_down. Wait for the
disconnect, otherwise a driver loaded right after the unload binds to
the stale device and fails to probe it. As the firmware cannot be
restarted in place, reset the target also on a firmware crash and when
the interface fails to start, and let the device be probed again. The
same applies to a failure during the registration. Refuse the UTF mode
for the same reason and reset the target on shutdown, so that the
firmware does not keep running across a reboot.
Tested-on: QCA9377 hw1.1 USB 0.0.0.111
Signed-off-by: Tomas Paukrt <tomaspaukrt at email.cz>
---
drivers/net/wireless/ath/ath10k/core.c | 35 +++++++++++++-
drivers/net/wireless/ath/ath10k/core.h | 5 ++
drivers/net/wireless/ath/ath10k/hw.h | 1 +
drivers/net/wireless/ath/ath10k/mac.c | 12 +++--
drivers/net/wireless/ath/ath10k/testmode.c | 6 +++
drivers/net/wireless/ath/ath10k/usb.c | 55 ++++++++++++++++++++++
drivers/net/wireless/ath/ath10k/usb.h | 1 +
7 files changed, 111 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
index 7c2939cbde5f..0741b7ee073c 100644
--- a/drivers/net/wireless/ath/ath10k/core.c
+++ b/drivers/net/wireless/ath/ath10k/core.c
@@ -2603,6 +2603,12 @@ static void ath10k_core_restart(struct work_struct *work)
switch (ar->state) {
case ATH10K_STATE_ON:
+ if (ar->bus_param.start_once) {
+ ar->state = ATH10K_STATE_WEDGED;
+ ath10k_warn(ar, "firmware cannot be restarted, resetting the target\n");
+ ath10k_hif_power_down(ar);
+ break;
+ }
ar->state = ATH10K_STATE_RESTARTING;
ath10k_halt(ar);
ath10k_scan_finish(ar);
@@ -2982,6 +2988,10 @@ int ath10k_core_start(struct ath10k *ar, enum ath10k_firmware_mode mode,
lockdep_assert_held(&ar->conf_mutex);
+ /* the firmware started during the registration is still running */
+ if (ar->bus_param.start_once && ar->state != ATH10K_STATE_OFF)
+ return 0;
+
clear_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags);
ar->running_fw = fw;
@@ -3333,7 +3343,8 @@ void ath10k_core_stop(struct ath10k *ar)
/* try to suspend target */
if (ar->state != ATH10K_STATE_RESTARTING &&
- ar->state != ATH10K_STATE_UTF)
+ ar->state != ATH10K_STATE_UTF &&
+ !ar->bus_param.start_once)
ath10k_wait_for_suspend(ar, WMI_PDEV_SUSPEND_AND_DISABLE_INTR);
ath10k_hif_stop(ar);
@@ -3345,6 +3356,18 @@ void ath10k_core_stop(struct ath10k *ar)
}
EXPORT_SYMBOL(ath10k_core_stop);
+static void ath10k_core_stop_once(struct ath10k *ar)
+{
+ if (!ar->bus_param.start_once)
+ return;
+
+ mutex_lock(&ar->conf_mutex);
+ ath10k_core_stop(ar);
+ mutex_unlock(&ar->conf_mutex);
+
+ ath10k_hif_power_down(ar);
+}
+
/* mac80211 manages fw/hw initialization through start/stop hooks. However in
* order to know what hw capabilities should be advertised to mac80211 it is
* necessary to load the firmware (and tear it down immediately since start
@@ -3479,6 +3502,13 @@ static int ath10k_core_probe_fw(struct ath10k *ar)
}
ath10k_debug_print_boot_info(ar);
+
+ /* keep the firmware running if it cannot be started a second time */
+ if (ar->bus_param.start_once) {
+ mutex_unlock(&ar->conf_mutex);
+ return 0;
+ }
+
ath10k_core_stop(ar);
mutex_unlock(&ar->conf_mutex);
@@ -3564,6 +3594,7 @@ static void ath10k_core_register_work(struct work_struct *work)
err_unregister_mac:
ath10k_mac_unregister(ar);
err_release_fw:
+ ath10k_core_stop_once(ar);
ath10k_core_free_firmware_files(ar);
err:
/* TODO: It's probably a good idea to release device from the driver
@@ -3605,6 +3636,8 @@ void ath10k_core_unregister(struct ath10k *ar)
*/
ath10k_mac_unregister(ar);
+ ath10k_core_stop_once(ar);
+
ath10k_testmode_destroy(ar);
ath10k_core_free_firmware_files(ar);
diff --git a/drivers/net/wireless/ath/ath10k/core.h b/drivers/net/wireless/ath/ath10k/core.h
index dfee432615eb..7a09244bb3f2 100644
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -1006,6 +1006,11 @@ struct ath10k_bus_params {
enum ath10k_dev_type dev_type;
bool link_can_suspend;
bool hl_msdu_ids;
+
+ /* The target cannot be reset without leaving the bus, so the firmware
+ * is kept running from the registration to the unregistration.
+ */
+ bool start_once;
};
struct ath10k {
diff --git a/drivers/net/wireless/ath/ath10k/hw.h b/drivers/net/wireless/ath/ath10k/hw.h
index cd468b24bd33..6196a11b77d1 100644
--- a/drivers/net/wireless/ath/ath10k/hw.h
+++ b/drivers/net/wireless/ath/ath10k/hw.h
@@ -900,6 +900,7 @@ ath10k_is_rssi_enable(struct ath10k_hw_params *hw,
#define SOC_RESET_CONTROL_SI0_RST_MASK ar->regs->soc_reset_control_si0_rst_mask
#define SOC_RESET_CONTROL_CE_RST_MASK ar->regs->soc_reset_control_ce_rst_mask
#define SOC_RESET_CONTROL_CPU_WARM_RST_MASK 0x00000040
+#define SOC_RESET_CONTROL_COLD_RST_MASK 0x00000100
#define SOC_CPU_CLOCK_OFFSET 0x00000020
#define SOC_CPU_CLOCK_STANDARD_LSB 0
#define SOC_CPU_CLOCK_STANDARD_MASK 0x00000003
diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c
index 8e604697d6c2..65452eb258b5 100644
--- a/drivers/net/wireless/ath/ath10k/mac.c
+++ b/drivers/net/wireless/ath/ath10k/mac.c
@@ -4819,8 +4819,12 @@ void ath10k_halt(struct ath10k *ar)
ath10k_scan_finish(ar);
ath10k_peer_cleanup_all(ar);
ath10k_stop_radar_confirmation(ar);
- ath10k_core_stop(ar);
- ath10k_hif_power_down(ar);
+
+ /* stop the firmware unless it can be started only once */
+ if (!ar->bus_param.start_once) {
+ ath10k_core_stop(ar);
+ ath10k_hif_power_down(ar);
+ }
spin_lock_bh(&ar->data_lock);
list_for_each_entry(arvif, &ar->arvifs, list)
@@ -5381,7 +5385,9 @@ static int ath10k_start(struct ieee80211_hw *hw)
return 0;
err_core_stop:
- ath10k_core_stop(ar);
+ /* stop the firmware unless it can be started only once */
+ if (!ar->bus_param.start_once)
+ ath10k_core_stop(ar);
err_power_down:
ath10k_hif_power_down(ar);
diff --git a/drivers/net/wireless/ath/ath10k/testmode.c b/drivers/net/wireless/ath/ath10k/testmode.c
index 282ae6e20c8e..10ebf3c1ccbd 100644
--- a/drivers/net/wireless/ath/ath10k/testmode.c
+++ b/drivers/net/wireless/ath/ath10k/testmode.c
@@ -353,6 +353,12 @@ static int ath10k_tm_cmd_utf_start(struct ath10k *ar, struct nlattr *tb[])
goto err;
}
+ /* the running firmware cannot be replaced by the UTF firmware */
+ if (ar->bus_param.start_once) {
+ ret = -EOPNOTSUPP;
+ goto err;
+ }
+
if (WARN_ON(ar->testmode.utf_mode_fw.fw_file.firmware != NULL)) {
/* utf image is already downloaded, it shouldn't be */
ret = -EEXIST;
diff --git a/drivers/net/wireless/ath/ath10k/usb.c b/drivers/net/wireless/ath/ath10k/usb.c
index c1ba30a1437d..71de38111c5e 100644
--- a/drivers/net/wireless/ath/ath10k/usb.c
+++ b/drivers/net/wireless/ath/ath10k/usb.c
@@ -707,9 +707,46 @@ static int ath10k_usb_hif_power_up(struct ath10k *ar,
return 0;
}
+/* The running firmware does not accept BMI commands, so the target has to
+ * be cold reset before it can be probed again. The reset makes it leave
+ * the bus and enumerate as a new device.
+ */
+static void ath10k_usb_reset_target(struct ath10k *ar, bool wait)
+{
+ struct ath10k_usb *ar_usb = ath10k_usb_priv(ar);
+ __le32 val = cpu_to_le32(SOC_RESET_CONTROL_COLD_RST_MASK);
+ unsigned long timeout;
+ int ret;
+
+ if (!ar->bmi.done_sent || ar_usb->udev->state == USB_STATE_NOTATTACHED)
+ return;
+
+ ret = ath10k_usb_hif_diag_write(ar, RTC_SOC_BASE_ADDRESS +
+ SOC_RESET_CONTROL_ADDRESS,
+ &val, sizeof(val));
+ if (ret) {
+ ath10k_warn(ar, "failed to reset the target: %d\n", ret);
+ return;
+ }
+
+ if (!wait)
+ return;
+
+ /* wait for the disconnect, the stale device must not be probed again */
+ timeout = jiffies + msecs_to_jiffies(ATH10K_USB_RESET_TIMEOUT_MS);
+ while (ar_usb->udev->state != USB_STATE_NOTATTACHED) {
+ if (time_after(jiffies, timeout)) {
+ ath10k_warn(ar, "target did not leave the bus after the reset\n");
+ return;
+ }
+ msleep(20);
+ }
+}
+
static void ath10k_usb_hif_power_down(struct ath10k *ar)
{
ath10k_usb_flush_all(ar);
+ ath10k_usb_reset_target(ar, true);
}
#ifdef CONFIG_PM
@@ -1037,6 +1074,7 @@ static int ath10k_usb_probe(struct usb_interface *interface,
/* TODO: don't know yet how to get chip_id with USB */
bus_params.chip_id = 0;
bus_params.hl_msdu_ids = true;
+ bus_params.start_once = true;
ret = ath10k_core_register(ar, &bus_params);
if (ret) {
ath10k_warn(ar, "failed to register driver core: %d\n", ret);
@@ -1071,6 +1109,22 @@ static void ath10k_usb_disconnect(struct usb_interface *interface)
ath10k_core_destroy(ar_usb->ar);
}
+static void ath10k_usb_shutdown(struct usb_interface *interface)
+{
+ struct ath10k_usb *ar_usb = usb_get_intfdata(interface);
+ struct ath10k *ar;
+
+ if (!ar_usb)
+ return;
+
+ ar = ar_usb->ar;
+
+ /* a firmware left running would fail the probe after the reboot */
+ mutex_lock(&ar->conf_mutex);
+ ath10k_usb_reset_target(ar, false);
+ mutex_unlock(&ar->conf_mutex);
+}
+
#ifdef CONFIG_PM
static int ath10k_usb_pm_suspend(struct usb_interface *interface,
@@ -1114,6 +1168,7 @@ static struct usb_driver ath10k_usb_driver = {
.suspend = ath10k_usb_pm_suspend,
.resume = ath10k_usb_pm_resume,
.disconnect = ath10k_usb_disconnect,
+ .shutdown = ath10k_usb_shutdown,
.id_table = ath10k_usb_ids,
.supports_autosuspend = true,
.soft_unbind = 1,
diff --git a/drivers/net/wireless/ath/ath10k/usb.h b/drivers/net/wireless/ath/ath10k/usb.h
index 517afbd73764..47ed1bad81a6 100644
--- a/drivers/net/wireless/ath/ath10k/usb.h
+++ b/drivers/net/wireless/ath/ath10k/usb.h
@@ -13,6 +13,7 @@
#define TX_URB_COUNT 32
#define RX_URB_COUNT 32
#define ATH10K_USB_RX_BUFFER_SIZE 4096
+#define ATH10K_USB_RESET_TIMEOUT_MS 3000
#define ATH10K_USB_PIPE_INVALID ATH10K_USB_PIPE_MAX
--
2.43.0
More information about the ath10k
mailing list