Merge "ath10k: Enable Subsystem Restart for ath10k WCN3990 driver"

This commit is contained in:
Linux Build Service Account 2017-03-24 12:13:04 -07:00 committed by Gerrit - the friendly Code Review server
commit 3e5a44882b
9 changed files with 338 additions and 6 deletions

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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:

View file

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

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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) {