OSDN Git Service

icnss: For WDOG bite, shutdown after FW ready
authorPrashanth Bhatta <bhattap@codeaurora.org>
Wed, 8 Feb 2017 17:27:57 +0000 (09:27 -0800)
committerPrashanth Bhatta <bhattap@codeaurora.org>
Fri, 10 Feb 2017 00:41:22 +0000 (16:41 -0800)
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 <bhattap@codeaurora.org>
drivers/soc/qcom/icnss.c
include/soc/qcom/icnss.h

index d86957a..cd03c6a 100644 (file)
@@ -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);
+       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 SERVREG_NOTIF_SERVICE_STATE_UP_V01:
-               icnss_pr_dbg("Service up, state: 0x%lx\n", priv->state);
+       case ROOT_PD_SHUTDOWN:
                break;
        default:
-               icnss_pr_dbg("Service state Unknown, notification: 0x%lx, state: 0x%lx\n",
-                            notification, priv->state);
-               return NOTIFY_DONE;
+               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);
index 6b567d7..731fa69 100644 (file)
 #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);
 };