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 <bhattap@codeaurora.org>
This commit is contained in:
parent
97daa00ee4
commit
b70b942f24
2 changed files with 109 additions and 33 deletions
|
@ -181,6 +181,7 @@ enum icnss_driver_event_type {
|
||||||
struct icnss_event_pd_service_down_data {
|
struct icnss_event_pd_service_down_data {
|
||||||
bool crashed;
|
bool crashed;
|
||||||
bool fw_rejuvenate;
|
bool fw_rejuvenate;
|
||||||
|
bool wdog_bite;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct icnss_driver_event {
|
struct icnss_driver_event {
|
||||||
|
@ -205,6 +206,7 @@ enum icnss_driver_state {
|
||||||
ICNSS_PD_RESTART,
|
ICNSS_PD_RESTART,
|
||||||
ICNSS_MSA0_ASSIGNED,
|
ICNSS_MSA0_ASSIGNED,
|
||||||
ICNSS_WLFW_EXISTS,
|
ICNSS_WLFW_EXISTS,
|
||||||
|
ICNSS_WDOG_BITE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ce_irq_list {
|
struct ce_irq_list {
|
||||||
|
@ -1700,6 +1702,23 @@ static int icnss_driver_event_server_exit(void *data)
|
||||||
return 0;
|
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)
|
static int icnss_call_driver_probe(struct icnss_priv *priv)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -1727,17 +1746,39 @@ out:
|
||||||
return ret;
|
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)
|
static int icnss_pd_restart_complete(struct icnss_priv *priv)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
clear_bit(ICNSS_PD_RESTART, &priv->state);
|
|
||||||
icnss_pm_relax(priv);
|
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)
|
if (!priv->ops || !priv->ops->reinit)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))
|
if (!test_bit(ICNSS_DRIVER_PROBED, &priv->state))
|
||||||
goto call_probe;
|
goto call_probe;
|
||||||
|
|
||||||
icnss_pr_dbg("Calling driver reinit state: 0x%lx\n", priv->state);
|
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);
|
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_pr_info("WLAN FW is ready: 0x%lx\n", penv->state);
|
||||||
|
|
||||||
icnss_hw_power_off(penv);
|
icnss_hw_power_off(penv);
|
||||||
|
@ -1873,23 +1916,30 @@ static int icnss_call_driver_remove(struct icnss_priv *priv)
|
||||||
return 0;
|
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);
|
set_bit(ICNSS_PD_RESTART, &priv->state);
|
||||||
clear_bit(ICNSS_FW_READY, &priv->state);
|
clear_bit(ICNSS_FW_READY, &priv->state);
|
||||||
|
|
||||||
icnss_pm_stay_awake(priv);
|
icnss_pm_stay_awake(priv);
|
||||||
|
|
||||||
if (!test_bit(ICNSS_DRIVER_PROBED, &penv->state))
|
icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_CRASHED, NULL);
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!priv->ops || !priv->ops->shutdown)
|
if (event_data->wdog_bite) {
|
||||||
return 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1910,13 +1960,10 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event_data->crashed)
|
if (event_data->crashed)
|
||||||
icnss_call_driver_shutdown(priv);
|
icnss_fw_crashed(priv, event_data);
|
||||||
else
|
else
|
||||||
icnss_call_driver_remove(priv);
|
icnss_call_driver_remove(priv);
|
||||||
|
|
||||||
if (event_data->fw_rejuvenate)
|
|
||||||
wlfw_rejuvenate_ack_send_sync_msg(priv);
|
|
||||||
|
|
||||||
out:
|
out:
|
||||||
ret = icnss_hw_power_off(priv);
|
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))
|
if (test_bit(ICNSS_PDR_ENABLED, &priv->state))
|
||||||
return NOTIFY_OK;
|
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);
|
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;
|
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_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
|
||||||
ICNSS_EVENT_SYNC, event_data);
|
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;
|
enum pd_subsys_state *state = data;
|
||||||
struct icnss_event_pd_service_down_data *event_data;
|
struct icnss_event_pd_service_down_data *event_data;
|
||||||
|
|
||||||
switch (notification) {
|
icnss_pr_dbg("PD service notification: 0x%lx state: 0x%lx\n",
|
||||||
case SERVREG_NOTIF_SERVICE_STATE_DOWN_V01:
|
notification, priv->state);
|
||||||
icnss_pr_info("Service down, data: 0x%p, state: 0x%lx\n", data,
|
|
||||||
priv->state);
|
|
||||||
event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
|
|
||||||
|
|
||||||
if (event_data == NULL)
|
if (notification != SERVREG_NOTIF_SERVICE_STATE_DOWN_V01)
|
||||||
return notifier_from_errno(-ENOMEM);
|
goto done;
|
||||||
|
|
||||||
if (state == NULL || *state != ROOT_PD_SHUTDOWN)
|
event_data = kzalloc(sizeof(*event_data), GFP_KERNEL);
|
||||||
event_data->crashed = true;
|
|
||||||
|
|
||||||
icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN,
|
if (event_data == NULL)
|
||||||
ICNSS_EVENT_SYNC, event_data);
|
return notifier_from_errno(-ENOMEM);
|
||||||
break;
|
|
||||||
case SERVREG_NOTIF_SERVICE_STATE_UP_V01:
|
if (state == NULL) {
|
||||||
icnss_pr_dbg("Service up, state: 0x%lx\n", priv->state);
|
event_data->crashed = true;
|
||||||
break;
|
goto event_post;
|
||||||
default:
|
|
||||||
icnss_pr_dbg("Service state Unknown, notification: 0x%lx, state: 0x%lx\n",
|
|
||||||
notification, priv->state);
|
|
||||||
return NOTIFY_DONE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
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:
|
case ICNSS_WLFW_EXISTS:
|
||||||
seq_puts(s, "WLAN FW EXISTS");
|
seq_puts(s, "WLAN FW EXISTS");
|
||||||
continue;
|
continue;
|
||||||
|
case ICNSS_WDOG_BITE:
|
||||||
|
seq_puts(s, "MODEM WDOG BITE");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
seq_printf(s, "UNKNOWN-%d", i);
|
seq_printf(s, "UNKNOWN-%d", i);
|
||||||
|
|
|
@ -17,6 +17,16 @@
|
||||||
#define ICNSS_MAX_IRQ_REGISTRATIONS 12
|
#define ICNSS_MAX_IRQ_REGISTRATIONS 12
|
||||||
#define ICNSS_MAX_TIMESTAMP_LEN 32
|
#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 {
|
struct icnss_driver_ops {
|
||||||
char *name;
|
char *name;
|
||||||
int (*probe)(struct device *dev);
|
int (*probe)(struct device *dev);
|
||||||
|
@ -28,6 +38,7 @@ struct icnss_driver_ops {
|
||||||
int (*pm_resume)(struct device *dev);
|
int (*pm_resume)(struct device *dev);
|
||||||
int (*suspend_noirq)(struct device *dev);
|
int (*suspend_noirq)(struct device *dev);
|
||||||
int (*resume_noirq)(struct device *dev);
|
int (*resume_noirq)(struct device *dev);
|
||||||
|
int (*uevent)(struct device *dev, struct icnss_uevent_data *uevent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue