From b70b942f246420e6165d6985f5a11f8037e6d2c9 Mon Sep 17 00:00:00 2001 From: Prashanth Bhatta Date: Wed, 8 Feb 2017 09:27:57 -0800 Subject: [PATCH] icnss: For WDOG bite, shutdown after FW ready When there is Modem WDOG bite, Q6 will not get chance to reset the hardware and if shutdown is called as part of PD down notification then freeing buffers already submitted to hardware may cause exceptions as hardware may try to access DDR location which is already freed. Fix the issue by delaying the shutdown till FW ready happens and hardware is reset to clean state this way buffers are freed after hardware is in reset state. CRs-fixed: 2000709 Change-Id: Iacea5e8b712dd4ca310e5b502e43f4beb99f6981 Signed-off-by: Prashanth Bhatta --- drivers/soc/qcom/icnss.c | 131 +++++++++++++++++++++++++++++---------- include/soc/qcom/icnss.h | 11 ++++ 2 files changed, 109 insertions(+), 33 deletions(-) diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c index d86957a7e09d..cd03c6ac1e5d 100644 --- a/drivers/soc/qcom/icnss.c +++ b/drivers/soc/qcom/icnss.c @@ -181,6 +181,7 @@ enum icnss_driver_event_type { struct icnss_event_pd_service_down_data { bool crashed; bool fw_rejuvenate; + bool wdog_bite; }; struct icnss_driver_event { @@ -205,6 +206,7 @@ enum icnss_driver_state { ICNSS_PD_RESTART, ICNSS_MSA0_ASSIGNED, ICNSS_WLFW_EXISTS, + ICNSS_WDOG_BITE, }; struct ce_irq_list { @@ -1700,6 +1702,23 @@ static int icnss_driver_event_server_exit(void *data) return 0; } +static int icnss_call_driver_uevent(struct icnss_priv *priv, + enum icnss_uevent uevent, void *data) +{ + struct icnss_uevent_data uevent_data; + + if (!priv->ops || !priv->ops->uevent) + return 0; + + icnss_pr_dbg("Calling driver uevent state: 0x%lx, uevent: %d\n", + priv->state, uevent); + + uevent_data.uevent = uevent; + uevent_data.data = data; + + return priv->ops->uevent(&priv->pdev->dev, &uevent_data); +} + static int icnss_call_driver_probe(struct icnss_priv *priv) { int ret; @@ -1727,17 +1746,39 @@ out: return ret; } +static int icnss_call_driver_shutdown(struct icnss_priv *priv) +{ + if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state)) + goto out; + + if (!priv->ops || !priv->ops->shutdown) + goto out; + + icnss_pr_dbg("Calling driver shutdown state: 0x%lx\n", priv->state); + + priv->ops->shutdown(&priv->pdev->dev); + +out: + return 0; +} + static int icnss_pd_restart_complete(struct icnss_priv *priv) { int ret; - clear_bit(ICNSS_PD_RESTART, &priv->state); icnss_pm_relax(priv); + if (test_bit(ICNSS_WDOG_BITE, &priv->state)) { + icnss_call_driver_shutdown(priv); + clear_bit(ICNSS_WDOG_BITE, &priv->state); + } + + clear_bit(ICNSS_PD_RESTART, &priv->state); + if (!priv->ops || !priv->ops->reinit) goto out; - if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state)) + if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state)) goto call_probe; icnss_pr_dbg("Calling driver reinit state: 0x%lx\n", priv->state); @@ -1774,6 +1815,8 @@ static int icnss_driver_event_fw_ready_ind(void *data) set_bit(ICNSS_FW_READY, &penv->state); + icnss_call_driver_uevent(penv, ICNSS_UEVENT_FW_READY, NULL); + icnss_pr_info("WLAN FW is ready: 0x%lx\n", penv->state); icnss_hw_power_off(penv); @@ -1873,23 +1916,30 @@ static int icnss_call_driver_remove(struct icnss_priv *priv) return 0; } -static int icnss_call_driver_shutdown(struct icnss_priv *priv) +static int icnss_fw_crashed(struct icnss_priv *priv, + struct icnss_event_pd_service_down_data *event_data) { - icnss_pr_dbg("Calling driver shutdown state: 0x%lx\n", priv->state); + icnss_pr_dbg("FW crashed, state: 0x%lx, wdog_bite: %d\n", + priv->state, event_data->wdog_bite); set_bit(ICNSS_PD_RESTART, &priv->state); clear_bit(ICNSS_FW_READY, &priv->state); icnss_pm_stay_awake(priv); - if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state)) - return 0; + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_CRASHED, NULL); - if (!priv->ops || !priv->ops->shutdown) - return 0; + if (event_data->wdog_bite) { + set_bit(ICNSS_WDOG_BITE, &priv->state); + goto out; + } - priv->ops->shutdown(&priv->pdev->dev); + icnss_call_driver_shutdown(priv); + if (event_data->fw_rejuvenate) + wlfw_rejuvenate_ack_send_sync_msg(priv); + +out: return 0; } @@ -1910,13 +1960,10 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv, } if (event_data->crashed) - icnss_call_driver_shutdown(priv); + icnss_fw_crashed(priv, event_data); else icnss_call_driver_remove(priv); - if (event_data->fw_rejuvenate) - wlfw_rejuvenate_ack_send_sync_msg(priv); - out: ret = icnss_hw_power_off(priv); @@ -2063,7 +2110,8 @@ static int icnss_modem_notifier_nb(struct notifier_block *nb, if (test_bit(ICNSS_PDR_ENABLED, &priv->state)) return NOTIFY_OK; - icnss_pr_info("Modem went down, state: %lx\n", priv->state); + icnss_pr_info("Modem went down, state: %lx, crashed: %d\n", + priv->state, notif->crashed); event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); @@ -2072,6 +2120,9 @@ static int icnss_modem_notifier_nb(struct notifier_block *nb, event_data->crashed = notif->crashed; + if (notif->crashed == CRASH_STATUS_WDOG_BITE) + event_data->wdog_bite = true; + icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, ICNSS_EVENT_SYNC, event_data); @@ -2136,30 +2187,41 @@ static int icnss_service_notifier_notify(struct notifier_block *nb, enum pd_subsys_state *state = data; struct icnss_event_pd_service_down_data *event_data; - switch (notification) { - case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01: - icnss_pr_info("Service down, data: 0x%p, state: 0x%lx\n", data, - priv->state); - event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); + icnss_pr_dbg("PD service notification: 0x%lx state: 0x%lx\n", + notification, priv->state); - if (event_data == NULL) - return notifier_from_errno(-ENOMEM); + if (notification != SERVREG_NOTIF_SERVICE_STATE_DOWN_V01) + goto done; - if (state == NULL || *state != ROOT_PD_SHUTDOWN) - event_data->crashed = true; + event_data = kzalloc(sizeof(*event_data), GFP_KERNEL); - icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, - ICNSS_EVENT_SYNC, event_data); - break; - case SERVREG_NOTIF_SERVICE_STATE_UP_V01: - icnss_pr_dbg("Service up, state: 0x%lx\n", priv->state); - break; - default: - icnss_pr_dbg("Service state Unknown, notification: 0x%lx, state: 0x%lx\n", - notification, priv->state); - return NOTIFY_DONE; + if (event_data == NULL) + return notifier_from_errno(-ENOMEM); + + if (state == NULL) { + event_data->crashed = true; + goto event_post; } + icnss_pr_info("PD service down, pd_state: %d, state: 0x%lx\n", + *state, priv->state); + + switch (*state) { + case ROOT_PD_WDOG_BITE: + event_data->crashed = true; + event_data->wdog_bite = true; + break; + case ROOT_PD_SHUTDOWN: + break; + default: + event_data->crashed = true; + break; + } + +event_post: + icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, + ICNSS_EVENT_SYNC, event_data); +done: return NOTIFY_OK; } @@ -3223,6 +3285,9 @@ static int icnss_stats_show_state(struct seq_file *s, struct icnss_priv *priv) case ICNSS_WLFW_EXISTS: seq_puts(s, "WLAN FW EXISTS"); continue; + case ICNSS_WDOG_BITE: + seq_puts(s, "MODEM WDOG BITE"); + continue; } seq_printf(s, "UNKNOWN-%d", i); diff --git a/include/soc/qcom/icnss.h b/include/soc/qcom/icnss.h index 6b567d7a08d3..731fa6970b95 100644 --- a/include/soc/qcom/icnss.h +++ b/include/soc/qcom/icnss.h @@ -17,6 +17,16 @@ #define ICNSS_MAX_IRQ_REGISTRATIONS 12 #define ICNSS_MAX_TIMESTAMP_LEN 32 +enum icnss_uevent { + ICNSS_UEVENT_FW_READY, + ICNSS_UEVENT_FW_CRASHED, +}; + +struct icnss_uevent_data { + enum icnss_uevent uevent; + void *data; +}; + struct icnss_driver_ops { char *name; int (*probe)(struct device *dev); @@ -28,6 +38,7 @@ struct icnss_driver_ops { int (*pm_resume)(struct device *dev); int (*suspend_noirq)(struct device *dev); int (*resume_noirq)(struct device *dev); + int (*uevent)(struct device *dev, struct icnss_uevent_data *uevent); };