diff --git a/drivers/net/wireless/ath/ath10k/Makefile b/drivers/net/wireless/ath/ath10k/Makefile index 25b23bf2c8e6..5fe8bc184868 100644 --- a/drivers/net/wireless/ath/ath10k/Makefile +++ b/drivers/net/wireless/ath/ath10k/Makefile @@ -26,6 +26,7 @@ ath10k_pci-y += pci.o \ ce.o obj-$(CONFIG_ATH10K_TARGET_SNOC) += ath10k_snoc.o ath10k_snoc-y += snoc.o \ + qmi.o \ ce.o ath10k_pci-$(CONFIG_ATH10K_AHB) += ahb.o diff --git a/drivers/net/wireless/ath/ath10k/ce.c b/drivers/net/wireless/ath/ath10k/ce.c index b8a3a1ecabaa..9cda1303c9e1 100644 --- a/drivers/net/wireless/ath/ath10k/ce.c +++ b/drivers/net/wireless/ath/ath10k/ce.c @@ -455,6 +455,9 @@ int ath10k_ce_send_nolock(struct ath10k_ce_pipe *ce_state, u32 desc_flags = 0; int ret = 0; + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return -ESHUTDOWN; + if (nbytes > ce_state->src_sz_max) ath10k_warn(ar, "%s: send more we can (nbytes: %d, max: %d)\n", __func__, nbytes, ce_state->src_sz_max); @@ -942,6 +945,9 @@ void ath10k_ce_per_engine_service_any(struct ath10k *ar) struct ath10k_ce_pipe *ce_state; struct bus_opaque *ar_opaque = ath10k_bus_priv(ar); + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return; + if (ar->target_version == ATH10K_HW_WCN3990) intr_summary = 0xFFF; else diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c index d37ed66d767b..c7ac209afae1 100644 --- a/drivers/net/wireless/ath/ath10k/core.c +++ b/drivers/net/wireless/ath/ath10k/core.c @@ -1536,7 +1536,6 @@ static void ath10k_core_restart(struct work_struct *work) struct ath10k *ar = container_of(work, struct ath10k, restart_work); set_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags); - ath10k_gen_set_base_mac_addr(ar, ar->base_mac_addr); /* Place a barrier to make sure the compiler doesn't reorder * CRASH_FLUSH and calling other functions. diff --git a/drivers/net/wireless/ath/ath10k/mac.c b/drivers/net/wireless/ath/ath10k/mac.c index 265744c75f82..35e5d980ed49 100644 --- a/drivers/net/wireless/ath/ath10k/mac.c +++ b/drivers/net/wireless/ath/ath10k/mac.c @@ -4450,7 +4450,8 @@ static int ath10k_start(struct ieee80211_hw *hw) ar->state = ATH10K_STATE_ON; break; case ATH10K_STATE_RESTARTING: - ath10k_halt(ar); + if (!test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + ath10k_halt(ar); ar->state = ATH10K_STATE_RESTARTED; break; case ATH10K_STATE_ON: diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c new file mode 100644 index 000000000000..c65cceb2e4cb --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/qmi.c @@ -0,0 +1,230 @@ +/* Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include +#include +#include +#include "core.h" +#include "qmi.h" +#include "snoc.h" +#include + +static int +ath10k_snoc_service_notifier_notify(struct notifier_block *nb, + unsigned long notification, void *data) +{ + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + service_notifier_nb); + enum pd_subsys_state *state = data; + struct ath10k *ar = ar_snoc->ar; + + switch (notification) { + case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Service down, data: 0x%pK\n", + data); + + if (!state || *state != ROOT_PD_SHUTDOWN) + atomic_set(&ar_snoc->fw_crashed, 1); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "PD went down %d\n", + ar_snoc->fw_crashed); + break; + case SERVREG_NOTIF_SERVICE_STATE_UP_V01: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Service up\n"); + queue_work(ar->workqueue, &ar->restart_work); + break; + default: + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Service state Unknown, notification: 0x%lx\n", + notification); + return NOTIFY_DONE; + } + return NOTIFY_OK; +} + +static int ath10k_snoc_get_service_location_notify(struct notifier_block *nb, + unsigned long opcode, + void *data) +{ + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + get_service_nb); + struct ath10k *ar = ar_snoc->ar; + struct pd_qmi_client_data *pd = data; + int curr_state; + int ret; + int i; + struct ath10k_service_notifier_context *notifier; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Get service notify opcode: %lu\n", + opcode); + + if (opcode != LOCATOR_UP) + return NOTIFY_DONE; + + if (!pd->total_domains) { + ath10k_err(ar, "Did not find any domains\n"); + ret = -ENOENT; + goto out; + } + + notifier = kcalloc(pd->total_domains, + sizeof(struct ath10k_service_notifier_context), + GFP_KERNEL); + if (!notifier) { + ret = -ENOMEM; + goto out; + } + + ar_snoc->service_notifier_nb.notifier_call = + ath10k_snoc_service_notifier_notify; + + for (i = 0; i < pd->total_domains; i++) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "%d: domain_name: %s, instance_id: %d\n", i, + pd->domain_list[i].name, + pd->domain_list[i].instance_id); + + notifier[i].handle = + service_notif_register_notifier( + pd->domain_list[i].name, + pd->domain_list[i].instance_id, + &ar_snoc->service_notifier_nb, + &curr_state); + notifier[i].instance_id = pd->domain_list[i].instance_id; + strlcpy(notifier[i].name, pd->domain_list[i].name, + QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1); + + if (IS_ERR(notifier[i].handle)) { + ath10k_err(ar, "%d: Unable to register notifier for %s(0x%x)\n", + i, pd->domain_list->name, + pd->domain_list->instance_id); + ret = PTR_ERR(notifier[i].handle); + goto free_handle; + } + } + + ar_snoc->service_notifier = notifier; + ar_snoc->total_domains = pd->total_domains; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "PD restart enabled\n"); + + return NOTIFY_OK; + +free_handle: + for (i = 0; i < pd->total_domains; i++) { + if (notifier[i].handle) { + service_notif_unregister_notifier( + notifier[i].handle, + &ar_snoc->service_notifier_nb); + } + } + kfree(notifier); + +out: + ath10k_err(ar, "PD restart not enabled: %d\n", ret); + + return NOTIFY_OK; +} + +int ath10k_snoc_pd_restart_enable(struct ath10k *ar) +{ + int ret; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Get service location\n"); + + ar_snoc->get_service_nb.notifier_call = + ath10k_snoc_get_service_location_notify; + ret = get_service_location(ATH10K_SERVICE_LOCATION_CLIENT_NAME, + ATH10K_WLAN_SERVICE_NAME, + &ar_snoc->get_service_nb); + if (ret) { + ath10k_err(ar, "Get service location failed: %d\n", ret); + goto out; + } + + return 0; +out: + ath10k_err(ar, "PD restart not enabled: %d\n", ret); + return ret; +} + +int ath10k_snoc_pdr_unregister_notifier(struct ath10k *ar) +{ + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ar_snoc->total_domains; i++) { + if (ar_snoc->service_notifier[i].handle) + service_notif_unregister_notifier( + ar_snoc->service_notifier[i].handle, + &ar_snoc->service_notifier_nb); + } + + kfree(ar_snoc->service_notifier); + + ar_snoc->service_notifier = NULL; + + return 0; +} + +static int ath10k_snoc_modem_notifier_nb(struct notifier_block *nb, + unsigned long code, + void *data) +{ + struct notif_data *notif = data; + struct ath10k_snoc *ar_snoc = container_of(nb, struct ath10k_snoc, + modem_ssr_nb); + struct ath10k *ar = ar_snoc->ar; + + if (code != SUBSYS_BEFORE_SHUTDOWN) + return NOTIFY_OK; + + if (notif->crashed) + atomic_set(&ar_snoc->fw_crashed, 1); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Modem went down %d\n", + ar_snoc->fw_crashed); + if (notif->crashed) + queue_work(ar->workqueue, &ar->restart_work); + + return NOTIFY_OK; +} + +int ath10k_snoc_modem_ssr_register_notifier(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + ar_snoc->modem_ssr_nb.notifier_call = ath10k_snoc_modem_notifier_nb; + + ar_snoc->modem_notify_handler = + subsys_notif_register_notifier("modem", &ar_snoc->modem_ssr_nb); + + if (IS_ERR(ar_snoc->modem_notify_handler)) { + ret = PTR_ERR(ar_snoc->modem_notify_handler); + ath10k_err(ar, "Modem register notifier failed: %d\n", ret); + } + + return ret; +} + +int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + subsys_notif_unregister_notifier(ar_snoc->modem_notify_handler, + &ar_snoc->modem_ssr_nb); + ar_snoc->modem_notify_handler = NULL; + + return 0; +} + diff --git a/drivers/net/wireless/ath/ath10k/qmi.h b/drivers/net/wireless/ath/ath10k/qmi.h new file mode 100644 index 000000000000..f8ba3288753b --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/qmi.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _QMI_H_ +#define _QMI_H_ +int ath10k_snoc_pd_restart_enable(struct ath10k *ar); +int ath10k_snoc_modem_ssr_register_notifier(struct ath10k *ar); +int ath10k_snoc_modem_ssr_unregister_notifier(struct ath10k *ar); +int ath10k_snoc_pdr_unregister_notifier(struct ath10k *ar); + +#endif diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index dc5f6fdaa9dc..89042dcf70a0 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -24,6 +24,7 @@ #include "htc.h" #include "ce.h" #include "snoc.h" +#include "qmi.h" #include #include #include @@ -413,6 +414,20 @@ static struct ath10k_shadow_reg_cfg target_shadow_reg_cfg_map[] = { { 11, WCN3990_DST_WR_INDEX_OFFSET}, }; +static bool ath10k_snoc_has_fw_crashed(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + return atomic_read(&ar_snoc->fw_crashed); +} + +static void ath10k_snoc_fw_crashed_clear(struct ath10k *ar) +{ + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + atomic_set(&ar_snoc->fw_crashed, 0); +} + void ath10k_snoc_write32(struct ath10k *ar, u32 offset, u32 value) { struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); @@ -655,6 +670,9 @@ static int ath10k_snoc_hif_tx_sg(struct ath10k *ar, u8 pipe_id, "snoc tx item %d paddr %pad len %d n_items %d\n", i, &items[i].paddr, items[i].len, n_items); + if (ath10k_snoc_has_fw_crashed(ar)) + return -EINVAL; + err = ath10k_ce_send_nolock(ce_pipe, items[i].transfer_context, items[i].paddr, @@ -867,11 +885,17 @@ static void ath10k_snoc_hif_stop(struct ath10k *ar) { if (!ar) return; - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif stop\n"); - ath10k_snoc_irq_disable(ar); + if (ath10k_snoc_has_fw_crashed(ar) || + test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) { + ath10k_snoc_free_irq(ar); + } else { + ath10k_snoc_irq_disable(ar); + } + ath10k_snoc_flush(ar); napi_synchronize(&ar->napi); napi_disable(&ar->napi); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif stop\n"); } static int ath10k_snoc_alloc_pipes(struct ath10k *ar) @@ -1087,9 +1111,14 @@ static int ath10k_snoc_bus_configure(struct ath10k *ar) static int ath10k_snoc_hif_start(struct ath10k *ar) { - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif start\n"); + if (ath10k_snoc_has_fw_crashed(ar)) { + ath10k_snoc_request_irq(ar); + ath10k_snoc_fw_crashed_clear(ar); + } ath10k_snoc_irq_enable(ar); ath10k_snoc_rx_post(ar); + + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot hif start\n"); return 0; } @@ -1110,7 +1139,8 @@ static int ath10k_snoc_hif_power_up(struct ath10k *ar) ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s:WCN3990 driver state = %d\n", __func__, ar->state); - if (ar->state == ATH10K_STATE_ON) { + if (ar->state == ATH10K_STATE_ON || + test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) { ret = ath10k_snoc_bus_configure(ar); if (ret) ath10k_err(ar, "failed to configure bus: %d\n", ret); @@ -1133,6 +1163,10 @@ static int ath10k_snoc_napi_poll(struct napi_struct *ctx, int budget) struct ath10k *ar = container_of(ctx, struct ath10k, napi); int done = 0; + if (ath10k_snoc_has_fw_crashed(ar)) { + napi_complete(ctx); + return done; + } ath10k_ce_per_engine_service_any(ar); done = ath10k_htt_txrx_compl_task(ar, budget); @@ -1254,6 +1288,10 @@ static int ath10k_snoc_probe(struct platform_device *pdev) ath10k_err(ar, "failed to register driver core: %d\n", ret); goto err_free_irq; } + + ath10k_snoc_modem_ssr_register_notifier(ar); + ath10k_snoc_pd_restart_enable(ar); + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s:WCN3990 probed\n", __func__); return 0; @@ -1282,6 +1320,8 @@ static int ath10k_snoc_remove(struct platform_device *pdev) return -EINVAL; ath10k_core_unregister(ar); + ath10k_snoc_pdr_unregister_notifier(ar); + ath10k_snoc_modem_ssr_unregister_notifier(ar); ath10k_snoc_free_irq(ar); ath10k_snoc_release_resource(ar); ath10k_snoc_free_pipes(ar); diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h index 0a5f5bff37ec..c62519b2a340 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.h +++ b/drivers/net/wireless/ath/ath10k/snoc.h @@ -16,8 +16,11 @@ #include "hw.h" #include "ce.h" #include "pci.h" +#include #define ATH10K_SNOC_RX_POST_RETRY_MS 50 #define CE_POLL_PIPE 4 +#define ATH10K_SERVICE_LOCATION_CLIENT_NAME "ATH10K-WLAN" +#define ATH10K_WLAN_SERVICE_NAME "wlan/fw" /* struct snoc_state: SNOC target state * @pipe_cfg_addr: pipe configuration address @@ -88,6 +91,17 @@ struct ath10k_target_info { u32 soc_version; }; +/* struct ath10k_service_notifier_context: service notification context + * @handle: notifier handle + * @instance_id: domain instance id + * @name: domain name + */ +struct ath10k_service_notifier_context { + void *handle; + u32 instance_id; + char name[QMI_SERVREG_LOC_NAME_LENGTH_V01 + 1]; +}; + /* struct ath10k_snoc: SNOC info struct * @dev: device structure * @ar:ath10k base structure @@ -101,6 +115,13 @@ struct ath10k_target_info { * @rx_post_retry: rx buffer post processing timer * @vaddr_rri_on_ddr: virtual address for RRI * @is_driver_probed: flag to indicate driver state + * @modem_ssr_nb: notifier callback for modem notification + * @modem_notify_handler: modem notification handler + * @service_notifier: notifier context for service notification + * @service_notifier_nb: notifier callback for service notification + * @total_domains: no of service domains + * @get_service_nb: notifier callback for service discovery + * @fw_crashed: fw state flag */ struct ath10k_snoc { struct bus_opaque opaque_ctx; @@ -115,6 +136,13 @@ struct ath10k_snoc { u32 ce_irqs[CE_COUNT_MAX]; u32 *vaddr_rri_on_ddr; bool is_driver_probed; + struct notifier_block modem_ssr_nb; + void *modem_notify_handler; + struct ath10k_service_notifier_context *service_notifier; + struct notifier_block service_notifier_nb; + int total_domains; + struct notifier_block get_service_nb; + atomic_t fw_crashed; }; /* struct ath10k_ce_tgt_pipe_cfg: target pipe configuration @@ -171,6 +199,11 @@ struct ath10k_wlan_enable_cfg { struct ath10k_shadow_reg_cfg *shadow_reg_cfg; }; +struct ath10k_event_pd_down_data { + bool crashed; + bool fw_rejuvenate; +}; + /* enum ath10k_driver_mode: ath10k driver mode * @ATH10K_MISSION: mission mode * @ATH10K_FTM: ftm mode diff --git a/drivers/net/wireless/ath/ath10k/wmi.c b/drivers/net/wireless/ath/ath10k/wmi.c index 4dd60b74853d..cbbba8d79e6b 100644 --- a/drivers/net/wireless/ath/ath10k/wmi.c +++ b/drivers/net/wireless/ath/ath10k/wmi.c @@ -1799,6 +1799,9 @@ int ath10k_wmi_cmd_send(struct ath10k *ar, struct sk_buff *skb, u32 cmd_id) { int ret = -EOPNOTSUPP; + if (test_bit(ATH10K_FLAG_CRASH_FLUSH, &ar->dev_flags)) + return -ESHUTDOWN; + might_sleep(); if (cmd_id == WMI_CMD_UNSUPPORTED) {