ASoC: wcd-dsp-mgr: add support for subsystem restart

It is possible that wcd codec DSP can crash or become unresponsive.
During such case, an error interrupt is generated by the codec. Add
support in manager driver to handle this interrupt and perform
subsystem restart to shutdown and reboot the DSP so that the DSP
can be recovered from the crash.

CRs-fixed: 1071949
Change-Id: I4662b5120bf7f731e399a27d8a613e2f3b648b00
Signed-off-by: Bhalchandra Gajare <gajare@codeaurora.org>
This commit is contained in:
Bhalchandra Gajare 2016-09-26 21:49:12 -07:00 committed by Gerrit - the friendly Code Review server
parent 67436f2353
commit d728aa9031
2 changed files with 271 additions and 41 deletions

View file

@ -36,6 +36,9 @@ enum wdsp_cmpnt_type {
};
enum wdsp_event_type {
/* Initialization related */
WDSP_EVENT_POST_INIT,
/* Image download related */
WDSP_EVENT_PRE_DLOAD_CODE,
WDSP_EVENT_DLOAD_SECTION,

View file

@ -77,6 +77,32 @@ static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type);
#define WDSP_STATUS_IS_SET(wdsp, state) (wdsp->status & state)
/* SSR relate status macros */
#define WDSP_SSR_STATUS_WDSP_READY BIT(0)
#define WDSP_SSR_STATUS_CDC_READY BIT(1)
#define WDSP_SSR_STATUS_READY \
(WDSP_SSR_STATUS_WDSP_READY | WDSP_SSR_STATUS_CDC_READY)
#define WDSP_SSR_READY_WAIT_TIMEOUT (10 * HZ)
enum wdsp_ssr_type {
/* Init value, indicates there is no SSR in progress */
WDSP_SSR_TYPE_NO_SSR = 0,
/*
* Indicates WDSP crashed. The manager driver internally
* decides when to perform WDSP restart based on the
* users of wdsp. Hence there is no explicit WDSP_UP.
*/
WDSP_SSR_TYPE_WDSP_DOWN,
/* Indicates codec hardware is down */
WDSP_SSR_TYPE_CDC_DOWN,
/* Indicates codec hardware is up, trigger to restart WDSP */
WDSP_SSR_TYPE_CDC_UP,
};
struct wdsp_cmpnt {
/* OF node of the phandle */
@ -108,6 +134,9 @@ struct wdsp_ramdump_data {
/* Virtual address of the dump */
void *rd_v_addr;
/* Data provided through error interrupt */
struct wdsp_err_intr_arg err_data;
};
struct wdsp_mgr_priv {
@ -146,8 +175,33 @@ struct wdsp_mgr_priv {
struct mutex api_mutex;
struct wdsp_ramdump_data dump_data;
/* SSR related */
enum wdsp_ssr_type ssr_type;
struct mutex ssr_mutex;
struct work_struct ssr_work;
u16 ready_status;
struct completion ready_compl;
};
static char *wdsp_get_ssr_type_string(enum wdsp_ssr_type type)
{
switch (type) {
case WDSP_SSR_TYPE_NO_SSR:
return "NO_SSR";
case WDSP_SSR_TYPE_WDSP_DOWN:
return "WDSP_DOWN";
case WDSP_SSR_TYPE_CDC_DOWN:
return "CDC_DOWN";
case WDSP_SSR_TYPE_CDC_UP:
return "CDC_UP";
default:
pr_err("%s: Invalid ssr_type %d\n",
__func__, type);
return "Invalid";
}
}
static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type type)
{
switch (type) {
@ -164,6 +218,26 @@ static char *wdsp_get_cmpnt_type_string(enum wdsp_cmpnt_type type)
}
}
static void __wdsp_clr_ready_locked(struct wdsp_mgr_priv *wdsp,
u16 value)
{
wdsp->ready_status &= ~(value);
WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status);
}
static void __wdsp_set_ready_locked(struct wdsp_mgr_priv *wdsp,
u16 value, bool mark_complete)
{
wdsp->ready_status |= value;
WDSP_DBG(wdsp, "ready_status = 0x%x", wdsp->ready_status);
if (mark_complete &&
wdsp->ready_status == WDSP_SSR_STATUS_READY) {
WDSP_DBG(wdsp, "marking ready completion");
complete(&wdsp->ready_compl);
}
}
static void wdsp_broadcast_event_upseq(struct wdsp_mgr_priv *wdsp,
enum wdsp_event_type event,
void *data)
@ -215,6 +289,18 @@ static int wdsp_unicast_event(struct wdsp_mgr_priv *wdsp,
return ret;
}
static void wdsp_deinit_components(struct wdsp_mgr_priv *wdsp)
{
struct wdsp_cmpnt *cmpnt;
int i;
for (i = WDSP_CMPNT_TYPE_MAX - 1; i >= 0; i--) {
cmpnt = WDSP_GET_COMPONENT(wdsp, i);
if (cmpnt && cmpnt->ops && cmpnt->ops->deinit)
cmpnt->ops->deinit(cmpnt->cdev, cmpnt->priv_data);
}
}
static int wdsp_init_components(struct wdsp_mgr_priv *wdsp)
{
struct wdsp_cmpnt *cmpnt;
@ -246,6 +332,8 @@ static int wdsp_init_components(struct wdsp_mgr_priv *wdsp)
cmpnt->ops->deinit(cmpnt->cdev,
cmpnt->priv_data);
}
} else {
wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_INIT, NULL);
}
return ret;
@ -337,42 +425,49 @@ done:
return ret;
}
static void wdsp_load_fw_image(struct work_struct *work)
static int wdsp_init_and_dload_code_sections(struct wdsp_mgr_priv *wdsp)
{
struct wdsp_mgr_priv *wdsp;
struct wdsp_cmpnt *cmpnt;
int ret, idx;
int ret;
bool is_initialized;
wdsp = container_of(work, struct wdsp_mgr_priv, load_fw_work);
if (!wdsp) {
pr_err("%s: Invalid private_data\n", __func__);
goto done;
is_initialized = WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_INITIALIZED);
if (!is_initialized) {
/* Components are not initialized yet, initialize them */
ret = wdsp_init_components(wdsp);
if (IS_ERR_VALUE(ret)) {
WDSP_ERR(wdsp, "INIT failed, err = %d", ret);
goto done;
}
WDSP_SET_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
}
/* Initialize the components first */
ret = wdsp_init_components(wdsp);
if (IS_ERR_VALUE(ret))
goto done;
/* Set init done status */
WDSP_SET_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
/* Download the read-execute sections of image */
ret = wdsp_download_segments(wdsp, WDSP_ELF_FLAG_RE);
if (IS_ERR_VALUE(ret)) {
WDSP_ERR(wdsp, "Error %d to download code sections", ret);
for (idx = 0; idx < WDSP_CMPNT_TYPE_MAX; idx++) {
cmpnt = WDSP_GET_COMPONENT(wdsp, idx);
if (cmpnt->ops && cmpnt->ops->deinit)
cmpnt->ops->deinit(cmpnt->cdev,
cmpnt->priv_data);
}
WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
goto done;
}
WDSP_SET_STATUS(wdsp, WDSP_STATUS_CODE_DLOADED);
done:
return;
return ret;
}
static void wdsp_load_fw_image(struct work_struct *work)
{
struct wdsp_mgr_priv *wdsp;
int ret;
wdsp = container_of(work, struct wdsp_mgr_priv, load_fw_work);
if (!wdsp) {
pr_err("%s: Invalid private_data\n", __func__);
return;
}
ret = wdsp_init_and_dload_code_sections(wdsp);
if (IS_ERR_VALUE(ret))
WDSP_ERR(wdsp, "dload code sections failed, err = %d", ret);
}
static int wdsp_enable_dsp(struct wdsp_mgr_priv *wdsp)
@ -415,6 +510,21 @@ static int wdsp_disable_dsp(struct wdsp_mgr_priv *wdsp)
{
int ret;
WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
/*
* If Disable happened while SSR is in progress, then set the SSR
* ready status indicating WDSP is now ready. Ignore the disable
* event here and let the SSR handler go through shutdown.
*/
if (wdsp->ssr_type != WDSP_SSR_TYPE_NO_SSR) {
__wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY, true);
WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
return 0;
}
WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
/* Make sure wdsp is in good state */
if (!WDSP_STATUS_IS_SET(wdsp, WDSP_STATUS_BOOTED)) {
WDSP_ERR(wdsp, "wdsp in invalid state 0x%x", wdsp->status);
@ -494,34 +604,30 @@ static struct device *wdsp_get_dev_for_cmpnt(struct device *wdsp_dev,
return cmpnt->cdev;
}
static int wdsp_collect_ramdumps(struct wdsp_mgr_priv *wdsp,
struct wdsp_err_intr_arg *data)
static void wdsp_collect_ramdumps(struct wdsp_mgr_priv *wdsp)
{
struct wdsp_img_section img_section;
struct wdsp_err_intr_arg *data = &wdsp->dump_data.err_data;
struct ramdump_segment rd_seg;
int ret = 0;
if (!data) {
WDSP_ERR(wdsp, "Invalid data argument");
return -EINVAL;
}
if (!data->mem_dumps_enabled) {
WDSP_DBG(wdsp, "mem dumps are disabled");
return 0;
if (wdsp->ssr_type != WDSP_SSR_TYPE_WDSP_DOWN ||
!data->mem_dumps_enabled) {
WDSP_DBG(wdsp, "cannot dump memory, ssr_type %s, dumps %s",
wdsp_get_ssr_type_string(wdsp->ssr_type),
!(data->mem_dumps_enabled) ? "disabled" : "enabled");
goto done;
}
if (data->dump_size == 0 ||
data->remote_start_addr < wdsp->base_addr) {
WDSP_ERR(wdsp, "Invalid start addr 0x%x or dump_size 0x%zx",
data->remote_start_addr, data->dump_size);
ret = -EINVAL;
goto done;
}
if (!wdsp->dump_data.rd_dev) {
WDSP_ERR(wdsp, "Ramdump device is not setup");
ret = -ENODEV;
goto done;
}
@ -533,10 +639,8 @@ static int wdsp_collect_ramdumps(struct wdsp_mgr_priv *wdsp,
data->dump_size,
&wdsp->dump_data.rd_addr,
GFP_KERNEL);
if (!wdsp->dump_data.rd_v_addr) {
ret = -ENOMEM;
if (!wdsp->dump_data.rd_v_addr)
goto done;
}
img_section.addr = data->remote_start_addr - wdsp->base_addr;
img_section.size = data->dump_size;
@ -563,7 +667,123 @@ err_read_dumps:
dma_free_coherent(wdsp->mdev, data->dump_size,
wdsp->dump_data.rd_v_addr, wdsp->dump_data.rd_addr);
done:
return ret;
return;
}
static void wdsp_ssr_work_fn(struct work_struct *work)
{
struct wdsp_mgr_priv *wdsp;
int ret;
wdsp = container_of(work, struct wdsp_mgr_priv, ssr_work);
if (!wdsp) {
pr_err("%s: Invalid private_data\n", __func__);
return;
}
WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
wdsp_collect_ramdumps(wdsp);
/* In case of CDC_DOWN event, the DSP is already shutdown */
if (wdsp->ssr_type != WDSP_SSR_TYPE_CDC_DOWN) {
ret = wdsp_unicast_event(wdsp, WDSP_CMPNT_CONTROL,
WDSP_EVENT_DO_SHUTDOWN, NULL);
if (IS_ERR_VALUE(ret))
WDSP_ERR(wdsp, "Failed WDSP shutdown, err = %d", ret);
}
wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_POST_SHUTDOWN, NULL);
WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_BOOTED);
WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
ret = wait_for_completion_timeout(&wdsp->ready_compl,
WDSP_SSR_READY_WAIT_TIMEOUT);
WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
if (ret == 0) {
WDSP_ERR(wdsp, "wait_for_ready timed out, status = 0x%x",
wdsp->ready_status);
goto done;
}
/* Data sections are to downloaded per WDSP boot */
WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_DATA_DLOADED);
/*
* Even though code section could possible be retained on DSP
* crash, go ahead and still re-download just to avoid any
* memory corruption from previous crash.
*/
WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_CODE_DLOADED);
/* If codec went down, then all components must be re-initialized */
if (wdsp->ssr_type == WDSP_SSR_TYPE_CDC_DOWN) {
wdsp_deinit_components(wdsp);
WDSP_CLEAR_STATUS(wdsp, WDSP_STATUS_INITIALIZED);
}
ret = wdsp_init_and_dload_code_sections(wdsp);
if (IS_ERR_VALUE(ret)) {
WDSP_ERR(wdsp, "Failed to dload code sections err = %d",
ret);
goto done;
}
/* SSR handling is finished, mark SSR type as NO_SSR */
wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR;
done:
WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
}
static int wdsp_ssr_handler(struct wdsp_mgr_priv *wdsp, void *arg,
enum wdsp_ssr_type ssr_type)
{
enum wdsp_ssr_type current_ssr_type;
struct wdsp_err_intr_arg *err_data;
WDSP_MGR_MUTEX_LOCK(wdsp, wdsp->ssr_mutex);
current_ssr_type = wdsp->ssr_type;
WDSP_DBG(wdsp, "Current ssr_type %s, handling ssr_type %s",
wdsp_get_ssr_type_string(current_ssr_type),
wdsp_get_ssr_type_string(ssr_type));
wdsp->ssr_type = ssr_type;
if (arg) {
err_data = (struct wdsp_err_intr_arg *) arg;
memcpy(&wdsp->dump_data.err_data, err_data,
sizeof(*err_data));
} else {
memset(&wdsp->dump_data.err_data, 0,
sizeof(wdsp->dump_data.err_data));
}
switch (ssr_type) {
case WDSP_SSR_TYPE_WDSP_DOWN:
case WDSP_SSR_TYPE_CDC_DOWN:
__wdsp_clr_ready_locked(wdsp, WDSP_SSR_STATUS_WDSP_READY);
if (ssr_type == WDSP_SSR_TYPE_CDC_DOWN)
__wdsp_clr_ready_locked(wdsp,
WDSP_SSR_STATUS_CDC_READY);
wdsp_broadcast_event_downseq(wdsp, WDSP_EVENT_PRE_SHUTDOWN,
NULL);
schedule_work(&wdsp->ssr_work);
break;
case WDSP_SSR_TYPE_CDC_UP:
__wdsp_set_ready_locked(wdsp, WDSP_SSR_STATUS_CDC_READY, true);
break;
default:
WDSP_ERR(wdsp, "undefined ssr_type %d\n", ssr_type);
/* Revert back the ssr_type for undefined events */
wdsp->ssr_type = current_ssr_type;
break;
}
WDSP_MGR_MUTEX_UNLOCK(wdsp, wdsp->ssr_mutex);
return 0;
}
static int wdsp_intr_handler(struct device *wdsp_dev,
@ -584,7 +804,7 @@ static int wdsp_intr_handler(struct device *wdsp_dev,
WDSP_EVENT_IPC1_INTR, NULL);
break;
case WDSP_ERR_INTR:
ret = wdsp_collect_ramdumps(wdsp, arg);
ret = wdsp_ssr_handler(wdsp, arg, WDSP_SSR_TYPE_WDSP_DOWN);
break;
default:
ret = -EINVAL;
@ -847,6 +1067,11 @@ static int wdsp_mgr_probe(struct platform_device *pdev)
INIT_WORK(&wdsp->load_fw_work, wdsp_load_fw_image);
INIT_LIST_HEAD(wdsp->seg_list);
mutex_init(&wdsp->api_mutex);
mutex_init(&wdsp->ssr_mutex);
wdsp->ssr_type = WDSP_SSR_TYPE_NO_SSR;
wdsp->ready_status = WDSP_SSR_STATUS_READY;
INIT_WORK(&wdsp->ssr_work, wdsp_ssr_work_fn);
init_completion(&wdsp->ready_compl);
arch_setup_dma_ops(wdsp->mdev, 0, 0, NULL, 0);
dev_set_drvdata(mdev, wdsp);
@ -861,6 +1086,7 @@ static int wdsp_mgr_probe(struct platform_device *pdev)
err_master_add:
mutex_destroy(&wdsp->api_mutex);
mutex_destroy(&wdsp->ssr_mutex);
err_dt_parse:
devm_kfree(mdev, wdsp->seg_list);
devm_kfree(mdev, wdsp);
@ -877,6 +1103,7 @@ static int wdsp_mgr_remove(struct platform_device *pdev)
component_master_del(mdev, &wdsp_master_ops);
mutex_destroy(&wdsp->api_mutex);
mutex_destroy(&wdsp->ssr_mutex);
devm_kfree(mdev, wdsp->seg_list);
devm_kfree(mdev, wdsp);
dev_set_drvdata(mdev, NULL);