summaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
authorGovind Singh <govinds@codeaurora.org>2017-02-27 13:19:17 +0530
committerGerrit - the friendly Code Review server <code-review@localhost>2017-03-23 22:40:04 -0700
commit15608bf63fb265d6dcc857992139188d7e733703 (patch)
tree88f0636e6f71b3a8a1c0cf71d54bdc1039bedd39 /drivers/net
parent27489753bffb6dcf909a49e4e32a2c974675824a (diff)
ath10k: Enable Subsystem Restart for ath10k WCN3990 driver
Add service registry and PD indication support Add support for USER PD service discovery and associated notifications. Enable recovery when USER PD DOWN indication is received. Change-Id: I548cd231f3d2090c5aa0ed328fbfb909d17467e8 Signed-off-by: Govind Singh <govinds@codeaurora.org>
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/wireless/ath/ath10k/Makefile1
-rw-r--r--drivers/net/wireless/ath/ath10k/ce.c6
-rw-r--r--drivers/net/wireless/ath/ath10k/core.c1
-rw-r--r--drivers/net/wireless/ath/ath10k/mac.c3
-rw-r--r--drivers/net/wireless/ath/ath10k/qmi.c230
-rw-r--r--drivers/net/wireless/ath/ath10k/qmi.h19
-rw-r--r--drivers/net/wireless/ath/ath10k/snoc.c48
-rw-r--r--drivers/net/wireless/ath/ath10k/snoc.h33
-rw-r--r--drivers/net/wireless/ath/ath10k/wmi.c3
9 files changed, 338 insertions, 6 deletions
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 <soc/qcom/subsystem_notif.h>
+#include <soc/qcom/subsystem_restart.h>
+#include <soc/qcom/service-notifier.h>
+#include "core.h"
+#include "qmi.h"
+#include "snoc.h"
+#include <soc/qcom/icnss.h>
+
+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 <soc/qcom/icnss.h>
#include <linux/of.h>
#include <linux/platform_device.h>
@@ -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 <soc/qcom/service-locator.h>
#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) {