This implements the functions to handle different core and instance state transitions. Signed-off-by: Dikshita Agarwal <quic_dikshita@xxxxxxxxxxx> Signed-off-by: Vikash Garodia <quic_vgarodia@xxxxxxxxxxx> --- .../platform/qcom/iris/vidc/inc/msm_vidc_state.h | 102 ++ .../platform/qcom/iris/vidc/src/msm_vidc_state.c | 1607 ++++++++++++++++++++ 2 files changed, 1709 insertions(+) create mode 100644 drivers/media/platform/qcom/iris/vidc/inc/msm_vidc_state.h create mode 100644 drivers/media/platform/qcom/iris/vidc/src/msm_vidc_state.c diff --git a/drivers/media/platform/qcom/iris/vidc/inc/msm_vidc_state.h b/drivers/media/platform/qcom/iris/vidc/inc/msm_vidc_state.h new file mode 100644 index 0000000..7fe4fcc --- /dev/null +++ b/drivers/media/platform/qcom/iris/vidc/inc/msm_vidc_state.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _MSM_VIDC_STATE_H_ +#define _MSM_VIDC_STATE_H_ + +#include "msm_vidc_internal.h" + +enum msm_vidc_core_state { + MSM_VIDC_CORE_DEINIT, + MSM_VIDC_CORE_INIT_WAIT, + MSM_VIDC_CORE_INIT, + MSM_VIDC_CORE_ERROR, +}; + +enum msm_vidc_core_sub_state { + CORE_SUBSTATE_NONE = 0x0, + CORE_SUBSTATE_POWER_ENABLE = BIT(0), + CORE_SUBSTATE_GDSC_HANDOFF = BIT(1), + CORE_SUBSTATE_PM_SUSPEND = BIT(2), + CORE_SUBSTATE_FW_PWR_CTRL = BIT(3), + CORE_SUBSTATE_PAGE_FAULT = BIT(4), + CORE_SUBSTATE_CPU_WATCHDOG = BIT(5), + CORE_SUBSTATE_VIDEO_UNRESPONSIVE = BIT(6), + CORE_SUBSTATE_MAX = BIT(7), +}; + +enum msm_vidc_core_event_type { + CORE_EVENT_NONE = BIT(0), + CORE_EVENT_UPDATE_SUB_STATE = BIT(1), +}; + +enum msm_vidc_state { + MSM_VIDC_OPEN, + MSM_VIDC_INPUT_STREAMING, + MSM_VIDC_OUTPUT_STREAMING, + MSM_VIDC_STREAMING, + MSM_VIDC_CLOSE, + MSM_VIDC_ERROR, +}; + +#define MSM_VIDC_SUB_STATE_NONE 0 +#define MSM_VIDC_MAX_SUB_STATES 6 +/* + * max value of inst->sub_state if all + * the 6 valid bits are set i.e 111111==>63 + */ +#define MSM_VIDC_MAX_SUB_STATE_VALUE ((1 << MSM_VIDC_MAX_SUB_STATES) - 1) + +enum msm_vidc_sub_state { + MSM_VIDC_DRAIN = BIT(0), + MSM_VIDC_DRC = BIT(1), + MSM_VIDC_DRAIN_LAST_BUFFER = BIT(2), + MSM_VIDC_DRC_LAST_BUFFER = BIT(3), + MSM_VIDC_INPUT_PAUSE = BIT(4), + MSM_VIDC_OUTPUT_PAUSE = BIT(5), +}; + +enum msm_vidc_event { + MSM_VIDC_TRY_FMT, + MSM_VIDC_S_FMT, + MSM_VIDC_REQBUFS, + MSM_VIDC_S_CTRL, + MSM_VIDC_STREAMON, + MSM_VIDC_STREAMOFF, + MSM_VIDC_CMD_START, + MSM_VIDC_CMD_STOP, + MSM_VIDC_BUF_QUEUE, +}; + +/* core statemachine functions */ +enum msm_vidc_allow msm_vidc_allow_core_state_change(struct msm_vidc_core *core, + enum msm_vidc_core_state req_state); +int msm_vidc_update_core_state(struct msm_vidc_core *core, + enum msm_vidc_core_state request_state, const char *func); +bool core_in_valid_state(struct msm_vidc_core *core); +bool is_core_state(struct msm_vidc_core *core, enum msm_vidc_core_state state); +bool is_core_sub_state(struct msm_vidc_core *core, enum msm_vidc_core_sub_state sub_state); +const char *core_state_name(enum msm_vidc_core_state state); +const char *core_sub_state_name(enum msm_vidc_core_sub_state sub_state); + +/* inst statemachine functions */ +bool is_drc_pending(struct msm_vidc_inst *inst); +bool is_drain_pending(struct msm_vidc_inst *inst); +int msm_vidc_update_state(struct msm_vidc_inst *inst, + enum msm_vidc_state request_state, const char *func); +int msm_vidc_change_state(struct msm_vidc_inst *inst, + enum msm_vidc_state request_state, const char *func); +int msm_vidc_change_sub_state(struct msm_vidc_inst *inst, + enum msm_vidc_sub_state clear_sub_state, + enum msm_vidc_sub_state set_sub_state, + const char *func); +const char *state_name(enum msm_vidc_state state); +const char *sub_state_name(enum msm_vidc_sub_state sub_state); +bool is_state(struct msm_vidc_inst *inst, enum msm_vidc_state state); +bool is_sub_state(struct msm_vidc_inst *inst, + enum msm_vidc_sub_state sub_state); + +#endif // _MSM_VIDC_STATE_H_ diff --git a/drivers/media/platform/qcom/iris/vidc/src/msm_vidc_state.c b/drivers/media/platform/qcom/iris/vidc/src/msm_vidc_state.c new file mode 100644 index 0000000..0361e69 --- /dev/null +++ b/drivers/media/platform/qcom/iris/vidc/src/msm_vidc_state.c @@ -0,0 +1,1607 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include "msm_vidc.h" +#include "msm_vidc_control.h" +#include "msm_vidc_core.h" +#include "msm_vidc_debug.h" +#include "msm_vidc_driver.h" +#include "msm_vidc_state.h" +#include "msm_vidc_vb2.h" +#include "venus_hfi.h" + +bool core_in_valid_state(struct msm_vidc_core *core) +{ + return (core->state == MSM_VIDC_CORE_INIT || + core->state == MSM_VIDC_CORE_INIT_WAIT); +} + +bool is_core_state(struct msm_vidc_core *core, enum msm_vidc_core_state state) +{ + return core->state == state; +} + +bool is_drc_pending(struct msm_vidc_inst *inst) +{ + return is_sub_state(inst, MSM_VIDC_DRC) && + is_sub_state(inst, MSM_VIDC_DRC_LAST_BUFFER); +} + +bool is_drain_pending(struct msm_vidc_inst *inst) +{ + return is_sub_state(inst, MSM_VIDC_DRAIN) && + is_sub_state(inst, MSM_VIDC_DRAIN_LAST_BUFFER); +} + +static const char * const core_state_name_arr[] = { + [MSM_VIDC_CORE_DEINIT] = "CORE_DEINIT", + [MSM_VIDC_CORE_INIT_WAIT] = "CORE_INIT_WAIT", + [MSM_VIDC_CORE_INIT] = "CORE_INIT", + [MSM_VIDC_CORE_ERROR] = "CORE_ERROR", +}; + +const char *core_state_name(enum msm_vidc_core_state state) +{ + const char *name = "UNKNOWN STATE"; + + if (state >= ARRAY_SIZE(core_state_name_arr)) + goto exit; + + name = core_state_name_arr[state]; + +exit: + return name; +} + +static const char * const event_name_arr[] = { + [MSM_VIDC_TRY_FMT] = "TRY_FMT", + [MSM_VIDC_S_FMT] = "S_FMT", + [MSM_VIDC_REQBUFS] = "REQBUFS", + [MSM_VIDC_S_CTRL] = "S_CTRL", + [MSM_VIDC_STREAMON] = "STREAMON", + [MSM_VIDC_STREAMOFF] = "STREAMOFF", + [MSM_VIDC_CMD_START] = "CMD_START", + [MSM_VIDC_CMD_STOP] = "CMD_STOP", + [MSM_VIDC_BUF_QUEUE] = "BUF_QUEUE", +}; + +static const char *event_name(enum msm_vidc_event event) +{ + const char *name = "UNKNOWN EVENT"; + + if (event >= ARRAY_SIZE(event_name_arr)) + goto exit; + + name = event_name_arr[event]; + +exit: + return name; +} + +static int __strict_inst_check(struct msm_vidc_inst *inst, const char *function) +{ + bool fatal = !mutex_is_locked(&inst->lock); + + WARN_ON(fatal); + + return fatal ? -EINVAL : 0; +} + +struct msm_vidc_core_sub_state_allow { + enum msm_vidc_core_state state; + enum msm_vidc_allow allow; + u32 sub_state_mask; +}; + +static u32 msm_vidc_core_sub_state_mask(enum msm_vidc_core_state state, + u32 allow) +{ + int cnt; + u32 sub_state_mask = 0; + static struct msm_vidc_core_sub_state_allow sub_state[] = { + /* state, allow, sub_state */ + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_ALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_IGNORE, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_ALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_DISALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_IGNORE, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_INIT, MSM_VIDC_ALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_ALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_DISALLOW, CORE_SUBSTATE_POWER_ENABLE | + CORE_SUBSTATE_GDSC_HANDOFF | + CORE_SUBSTATE_PM_SUSPEND | + CORE_SUBSTATE_FW_PWR_CTRL | + CORE_SUBSTATE_PAGE_FAULT | + CORE_SUBSTATE_CPU_WATCHDOG | + CORE_SUBSTATE_VIDEO_UNRESPONSIVE }, + }; + + for (cnt = 0; cnt < ARRAY_SIZE(sub_state); cnt++) { + if (sub_state[cnt].state == state && sub_state[cnt].allow == allow) { + sub_state_mask = sub_state[cnt].sub_state_mask; + break; + } + } + + return sub_state_mask; +} + +static int msm_vidc_core_deinit_state(struct msm_vidc_core *core, + enum msm_vidc_core_event_type type, + struct msm_vidc_event_data *data) +{ + int rc = 0; + + switch (type) { + case CORE_EVENT_UPDATE_SUB_STATE: + { + u32 req_sub_state = data->edata.uval; + u32 allow_mask = msm_vidc_core_sub_state_mask(core->state, MSM_VIDC_ALLOW); + + req_sub_state = data->edata.uval; + + /* none of the requested substate supported */ + if (!(req_sub_state & allow_mask)) { + d_vpr_e("%s: invalid substate update request %#x\n", + __func__, req_sub_state); + return -EINVAL; + } + + /* update core substate */ + core->sub_state |= req_sub_state & allow_mask; + return rc; + } + default: { + d_vpr_e("%s: unexpected core event type %u\n", + __func__, type); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_core_init_wait_state(struct msm_vidc_core *core, + enum msm_vidc_core_event_type type, + struct msm_vidc_event_data *data) +{ + int rc = 0; + + switch (type) { + case CORE_EVENT_UPDATE_SUB_STATE: + { + u32 req_sub_state = data->edata.uval; + u32 allow_mask = msm_vidc_core_sub_state_mask(core->state, MSM_VIDC_ALLOW); + + req_sub_state = data->edata.uval; + + /* none of the requested substate supported */ + if (!(req_sub_state & allow_mask)) { + d_vpr_e("%s: invalid substate update request %#x\n", + __func__, req_sub_state); + return -EINVAL; + } + + /* update core substate */ + core->sub_state |= req_sub_state & allow_mask; + return rc; + } + default: { + d_vpr_e("%s: unexpected core event type %u\n", + __func__, type); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_core_init_state(struct msm_vidc_core *core, + enum msm_vidc_core_event_type type, + struct msm_vidc_event_data *data) +{ + int rc = 0; + + switch (type) { + case CORE_EVENT_UPDATE_SUB_STATE: + { + u32 req_sub_state = data->edata.uval; + u32 allow_mask = msm_vidc_core_sub_state_mask(core->state, MSM_VIDC_ALLOW); + + req_sub_state = data->edata.uval; + + /* none of the requested substate supported */ + if (!(req_sub_state & allow_mask)) { + d_vpr_e("%s: invalid substate update request %#x\n", + __func__, req_sub_state); + return -EINVAL; + } + + /* update core substate */ + core->sub_state |= req_sub_state & allow_mask; + return rc; + } + default: { + d_vpr_e("%s: unexpected core event type %u\n", + __func__, type); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_core_error_state(struct msm_vidc_core *core, + enum msm_vidc_core_event_type type, + struct msm_vidc_event_data *data) +{ + int rc = 0; + + switch (type) { + case CORE_EVENT_UPDATE_SUB_STATE: + { + u32 req_sub_state = data->edata.uval; + u32 allow_mask = msm_vidc_core_sub_state_mask(core->state, MSM_VIDC_ALLOW); + + req_sub_state = data->edata.uval; + + /* none of the requested substate supported */ + if (!(req_sub_state & allow_mask)) { + d_vpr_e("%s: invalid substate update request %#x\n", + __func__, req_sub_state); + return -EINVAL; + } + + /* update core substate */ + core->sub_state |= req_sub_state & allow_mask; + return rc; + } + default: { + d_vpr_e("%s: unexpected core event type %u\n", + __func__, type); + return -EINVAL; + } + } + + return rc; +} + +struct msm_vidc_core_state_handle { + enum msm_vidc_core_state state; + int (*handle)(struct msm_vidc_core *core, + enum msm_vidc_core_event_type type, + struct msm_vidc_event_data *data); +}; + +static struct msm_vidc_core_state_handle + *msm_vidc_get_core_state_handle(enum msm_vidc_core_state req_state) +{ + int cnt; + struct msm_vidc_core_state_handle *core_state_handle = NULL; + static struct msm_vidc_core_state_handle state_handle[] = { + {MSM_VIDC_CORE_DEINIT, msm_vidc_core_deinit_state }, + {MSM_VIDC_CORE_INIT_WAIT, msm_vidc_core_init_wait_state }, + {MSM_VIDC_CORE_INIT, msm_vidc_core_init_state }, + {MSM_VIDC_CORE_ERROR, msm_vidc_core_error_state }, + }; + + for (cnt = 0; cnt < ARRAY_SIZE(state_handle); cnt++) { + if (state_handle[cnt].state == req_state) { + core_state_handle = &state_handle[cnt]; + break; + } + } + + /* if req_state does not exist in the table */ + if (cnt == ARRAY_SIZE(state_handle)) { + d_vpr_e("%s: invalid core state \"%s\" requested\n", + __func__, core_state_name(req_state)); + return core_state_handle; + } + + return core_state_handle; +} + +int msm_vidc_update_core_state(struct msm_vidc_core *core, + enum msm_vidc_core_state request_state, const char *func) +{ + struct msm_vidc_core_state_handle *state_handle = NULL; + int rc = 0; + + /* get core state handler for requested state */ + state_handle = msm_vidc_get_core_state_handle(request_state); + if (!state_handle) + return -EINVAL; + + d_vpr_h("%s: core state changed to %s from %s\n", func, + core_state_name(state_handle->state), core_state_name(core->state)); + + /* finally update core state and handler */ + core->state = state_handle->state; + core->state_handle = state_handle->handle; + + return rc; +} + +struct msm_vidc_core_state_allow { + enum msm_vidc_core_state from; + enum msm_vidc_core_state to; + enum msm_vidc_allow allow; +}; + +enum msm_vidc_allow msm_vidc_allow_core_state_change(struct msm_vidc_core *core, + enum msm_vidc_core_state req_state) +{ + int cnt; + enum msm_vidc_allow allow = MSM_VIDC_DISALLOW; + static struct msm_vidc_core_state_allow state[] = { + /* from, to, allow */ + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_CORE_DEINIT, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_CORE_INIT, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CORE_DEINIT, MSM_VIDC_CORE_ERROR, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_CORE_DEINIT, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_CORE_INIT, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_CORE_ERROR, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_INIT, MSM_VIDC_CORE_DEINIT, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_INIT, MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CORE_INIT, MSM_VIDC_CORE_INIT, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_INIT, MSM_VIDC_CORE_ERROR, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_CORE_DEINIT, MSM_VIDC_ALLOW }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_CORE_INIT_WAIT, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_CORE_INIT, MSM_VIDC_IGNORE }, + {MSM_VIDC_CORE_ERROR, MSM_VIDC_CORE_ERROR, MSM_VIDC_IGNORE }, + }; + + for (cnt = 0; cnt < ARRAY_SIZE(state); cnt++) { + if (state[cnt].from == core->state && state[cnt].to == req_state) { + allow = state[cnt].allow; + break; + } + } + + return allow; +} + +int msm_vidc_change_core_state(struct msm_vidc_core *core, + enum msm_vidc_core_state request_state, const char *func) +{ + enum msm_vidc_allow allow; + int rc = 0; + + /* core must be locked */ + rc = __strict_check(core, func); + if (rc) { + d_vpr_e("%s(): core was not locked\n", func); + return rc; + } + + /* current and requested state is same */ + if (core->state == request_state) + return 0; + + /* check if requested state movement is allowed */ + allow = msm_vidc_allow_core_state_change(core, request_state); + if (allow == MSM_VIDC_IGNORE) { + d_vpr_h("%s: %s core state change %s -> %s\n", func, + allow_name(allow), core_state_name(core->state), + core_state_name(request_state)); + return 0; + } else if (allow == MSM_VIDC_DISALLOW) { + d_vpr_e("%s: %s core state change %s -> %s\n", func, + allow_name(allow), core_state_name(core->state), + core_state_name(request_state)); + return -EINVAL; + } + + /* go ahead and update core state */ + return msm_vidc_update_core_state(core, request_state, func); +} + +bool is_core_sub_state(struct msm_vidc_core *core, enum msm_vidc_core_sub_state sub_state) +{ + return !!(core->sub_state & sub_state); +} + +const char *core_sub_state_name(enum msm_vidc_core_sub_state sub_state) +{ + switch (sub_state) { + case CORE_SUBSTATE_NONE: return "NONE "; + case CORE_SUBSTATE_GDSC_HANDOFF: return "GDSC_HANDOFF "; + case CORE_SUBSTATE_PM_SUSPEND: return "PM_SUSPEND "; + case CORE_SUBSTATE_FW_PWR_CTRL: return "FW_PWR_CTRL "; + case CORE_SUBSTATE_POWER_ENABLE: return "POWER_ENABLE "; + case CORE_SUBSTATE_PAGE_FAULT: return "PAGE_FAULT "; + case CORE_SUBSTATE_CPU_WATCHDOG: return "CPU_WATCHDOG "; + case CORE_SUBSTATE_VIDEO_UNRESPONSIVE: return "VIDEO_UNRESPONSIVE "; + case CORE_SUBSTATE_MAX: return "MAX "; + } + + return "UNKNOWN "; +} + +static int prepare_core_sub_state_name(enum msm_vidc_core_sub_state sub_state, + char *buf, u32 size) +{ + int i = 0; + + if (!buf || !size) + return -EINVAL; + + strscpy(buf, "\0", size); + if (sub_state == CORE_SUBSTATE_NONE) { + strscpy(buf, "CORE_SUBSTATE_NONE", size); + return 0; + } + + for (i = 0; BIT(i) < CORE_SUBSTATE_MAX; i++) { + if (sub_state & BIT(i)) + strlcat(buf, core_sub_state_name(BIT(i)), size); + } + + return 0; +} + +static int msm_vidc_update_core_sub_state(struct msm_vidc_core *core, + enum msm_vidc_core_sub_state sub_state, + const char *func) +{ + struct msm_vidc_event_data data; + char sub_state_name[MAX_NAME_LENGTH]; + int ret = 0, rc = 0; + + /* no substate update */ + if (!sub_state) + return 0; + + /* invoke update core substate event */ + memset(&data, 0, sizeof(struct msm_vidc_event_data)); + data.edata.uval = sub_state; + rc = core->state_handle(core, CORE_EVENT_UPDATE_SUB_STATE, &data); + if (rc) { + ret = prepare_core_sub_state_name(sub_state, sub_state_name, + sizeof(sub_state_name) - 1); + if (!ret) + d_vpr_e("%s: state %s, requested invalid core substate %s\n", + func, core_state_name(core->state), sub_state_name); + return rc; + } + + return rc; +} + +int msm_vidc_change_core_sub_state(struct msm_vidc_core *core, + enum msm_vidc_core_sub_state clear_sub_state, + enum msm_vidc_core_sub_state set_sub_state, + const char *func) +{ + int rc = 0; + enum msm_vidc_core_sub_state prev_sub_state; + + /* core must be locked */ + rc = __strict_check(core, func); + if (rc) { + d_vpr_e("%s(): core was not locked\n", func); + return rc; + } + + /* sanitize core state handler */ + if (!core->state_handle) { + d_vpr_e("%s: invalid core state handle\n", __func__); + return -EINVAL; + } + + /* final value will not change */ + if (clear_sub_state == set_sub_state) + return 0; + + /* sanitize clear & set value */ + if (set_sub_state > CORE_SUBSTATE_MAX || + clear_sub_state > CORE_SUBSTATE_MAX) { + d_vpr_e("%s: invalid sub states. clear %#x or set %#x\n", + func, clear_sub_state, set_sub_state); + return -EINVAL; + } + + prev_sub_state = core->sub_state; + + /* set sub state */ + rc = msm_vidc_update_core_sub_state(core, set_sub_state, func); + if (rc) + return rc; + + /* check if all core substates updated */ + if ((core->sub_state & set_sub_state) != set_sub_state) + d_vpr_e("%s: all substates not updated %#x, expected %#x\n", + func, core->sub_state & set_sub_state, set_sub_state); + + /* clear sub state */ + core->sub_state &= ~clear_sub_state; + + /* print substates only when there is a change */ + if (core->sub_state != prev_sub_state) { + rc = prepare_core_sub_state_name(core->sub_state, core->sub_state_name, + sizeof(core->sub_state_name) - 1); + if (!rc) + d_vpr_h("%s: core sub state changed to %s\n", func, core->sub_state_name); + } + + return 0; +} + +/* do not modify the state names as it is used in test scripts */ +static const char * const state_name_arr[] = { + [MSM_VIDC_OPEN] = "OPEN", + [MSM_VIDC_INPUT_STREAMING] = "INPUT_STREAMING", + [MSM_VIDC_OUTPUT_STREAMING] = "OUTPUT_STREAMING", + [MSM_VIDC_STREAMING] = "STREAMING", + [MSM_VIDC_CLOSE] = "CLOSE", + [MSM_VIDC_ERROR] = "ERROR", +}; + +const char *state_name(enum msm_vidc_state state) +{ + const char *name = "UNKNOWN STATE"; + + if (state >= ARRAY_SIZE(state_name_arr)) + goto exit; + + name = state_name_arr[state]; + +exit: + return name; +} + +bool is_state(struct msm_vidc_inst *inst, enum msm_vidc_state state) +{ + return inst->state == state; +} + +bool is_sub_state(struct msm_vidc_inst *inst, enum msm_vidc_sub_state sub_state) +{ + return (inst->sub_state & sub_state); +} + +const char *sub_state_name(enum msm_vidc_sub_state sub_state) +{ + switch (sub_state) { + case MSM_VIDC_DRAIN: return "DRAIN "; + case MSM_VIDC_DRC: return "DRC "; + case MSM_VIDC_DRAIN_LAST_BUFFER: return "DRAIN_LAST_BUFFER "; + case MSM_VIDC_DRC_LAST_BUFFER: return "DRC_LAST_BUFFER "; + case MSM_VIDC_INPUT_PAUSE: return "INPUT_PAUSE "; + case MSM_VIDC_OUTPUT_PAUSE: return "OUTPUT_PAUSE "; + } + + return "SUB_STATE_NONE"; +} + +static int prepare_sub_state_name(enum msm_vidc_sub_state sub_state, + char *buf, u32 size) +{ + int i = 0; + + if (!buf || !size) + return -EINVAL; + + strscpy(buf, "\0", size); + if (sub_state == MSM_VIDC_SUB_STATE_NONE) { + strscpy(buf, "SUB_STATE_NONE", size); + return 0; + } + + for (i = 0; i < MSM_VIDC_MAX_SUB_STATES; i++) { + if (sub_state & BIT(i)) + strlcat(buf, sub_state_name(BIT(i)), size); + } + + return 0; +} + +struct msm_vidc_state_allow { + enum msm_vidc_state from; + enum msm_vidc_state to; + enum msm_vidc_allow allow; +}; + +static enum msm_vidc_allow msm_vidc_allow_state_change(struct msm_vidc_inst *inst, + enum msm_vidc_state req_state) +{ + int cnt; + enum msm_vidc_allow allow = MSM_VIDC_DISALLOW; + static struct msm_vidc_state_allow state[] = { + /* from, to, allow */ + {MSM_VIDC_OPEN, MSM_VIDC_OPEN, MSM_VIDC_IGNORE }, + {MSM_VIDC_OPEN, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_OPEN, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_OPEN, MSM_VIDC_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_OPEN, MSM_VIDC_CLOSE, MSM_VIDC_ALLOW }, + {MSM_VIDC_OPEN, MSM_VIDC_ERROR, MSM_VIDC_ALLOW }, + + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_OPEN, MSM_VIDC_ALLOW }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_CLOSE, MSM_VIDC_ALLOW }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_ERROR, MSM_VIDC_ALLOW }, + + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_OPEN, MSM_VIDC_ALLOW }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_CLOSE, MSM_VIDC_ALLOW }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_ERROR, MSM_VIDC_ALLOW }, + + {MSM_VIDC_STREAMING, MSM_VIDC_OPEN, MSM_VIDC_DISALLOW }, + {MSM_VIDC_STREAMING, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_STREAMING, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_ALLOW }, + {MSM_VIDC_STREAMING, MSM_VIDC_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_STREAMING, MSM_VIDC_CLOSE, MSM_VIDC_ALLOW }, + {MSM_VIDC_STREAMING, MSM_VIDC_ERROR, MSM_VIDC_ALLOW }, + + {MSM_VIDC_CLOSE, MSM_VIDC_OPEN, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CLOSE, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CLOSE, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CLOSE, MSM_VIDC_STREAMING, MSM_VIDC_DISALLOW }, + {MSM_VIDC_CLOSE, MSM_VIDC_CLOSE, MSM_VIDC_IGNORE }, + {MSM_VIDC_CLOSE, MSM_VIDC_ERROR, MSM_VIDC_IGNORE }, + + {MSM_VIDC_ERROR, MSM_VIDC_OPEN, MSM_VIDC_IGNORE }, + {MSM_VIDC_ERROR, MSM_VIDC_INPUT_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_ERROR, MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_ERROR, MSM_VIDC_STREAMING, MSM_VIDC_IGNORE }, + {MSM_VIDC_ERROR, MSM_VIDC_CLOSE, MSM_VIDC_IGNORE }, + {MSM_VIDC_ERROR, MSM_VIDC_ERROR, MSM_VIDC_IGNORE }, + }; + + for (cnt = 0; cnt < ARRAY_SIZE(state); cnt++) { + if (state[cnt].from == inst->state && state[cnt].to == req_state) { + allow = state[cnt].allow; + break; + } + } + + return allow; +} + +static int msm_vidc_open_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_TRY_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* allow try_fmt request in open state */ + rc = msm_vidc_try_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* allow s_fmt request in open state */ + rc = msm_vidc_s_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_CTRL: + { + struct v4l2_ctrl *ctrl = (struct v4l2_ctrl *)data; + + /* allow set_control request in open state */ + rc = msm_vidc_s_ctrl(inst, ctrl); + if (rc) + return rc; + break; + } + case MSM_VIDC_REQBUFS: + { + struct v4l2_requestbuffers *b = (struct v4l2_requestbuffers *)data; + + /* allow reqbufs request in open state */ + rc = msm_vidc_reqbufs(inst, b); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMON: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* allow streamon request in open state */ + rc = msm_vidc_start_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* ignore streamoff request in open state */ + i_vpr_h(inst, "%s: streamoff of (%s) ignored in state (%s)\n", + __func__, v4l2_type_name(q->type), state_name(inst->state)); + break; + } + case MSM_VIDC_CMD_START: + { + /* disallow start cmd request in open state */ + i_vpr_e(inst, "%s: (%s) not allowed, sub_state (%s)\n", + __func__, event_name(event), inst->sub_state_name); + + return -EBUSY; + } + case MSM_VIDC_CMD_STOP: + { + /* ignore stop cmd request in open state */ + i_vpr_h(inst, "%s: (%s) ignored, sub_state (%s)\n", + __func__, event_name(event), inst->sub_state_name); + break; + } + case MSM_VIDC_BUF_QUEUE: + { + struct msm_vidc_buffer *buf = (struct msm_vidc_buffer *)data; + + /* defer qbuf request in open state */ + print_vidc_buffer(VIDC_LOW, "low ", "qbuf deferred", inst, buf); + break; + } + default: + { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_input_streaming_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_BUF_QUEUE: + { + struct msm_vidc_buffer *buf = (struct msm_vidc_buffer *)data; + + /* disallow */ + if (!is_input_buffer(buf->type) && !is_output_buffer(buf->type)) { + i_vpr_e(inst, "%s: invalid buf type %u\n", __func__, buf->type); + return -EINVAL; + } + + /* defer output port */ + if (is_output_buffer(buf->type)) { + print_vidc_buffer(VIDC_LOW, "low ", "qbuf deferred", inst, buf); + return 0; + } + + rc = msm_vidc_buf_queue(inst, buf); + if (rc) + return rc; + break; + } + case MSM_VIDC_TRY_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* disallow */ + if (f->type == INPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(f->type)); + return -EBUSY; + } + + rc = msm_vidc_try_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* disallow */ + if (f->type == INPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(f->type)); + return -EBUSY; + } + + rc = msm_vidc_s_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_CTRL: + { + struct v4l2_ctrl *ctrl = (struct v4l2_ctrl *)data; + u32 cap_id = msm_vidc_get_cap_id(inst, ctrl->id); + + if (cap_id == INST_CAP_NONE) { + i_vpr_e(inst, "%s: invalid cap_id %u\n", __func__, cap_id); + return -EINVAL; + } + + /* disallow */ + if (is_decode_session(inst)) { + /* check dynamic allowed if master port is streaming */ + if (!(inst->capabilities[cap_id].flags & CAP_FLAG_DYNAMIC_ALLOWED)) { + i_vpr_e(inst, "%s: cap_id %#x not allowed in state %s\n", + __func__, cap_id, state_name(inst->state)); + return -EINVAL; + } + } + + rc = msm_vidc_s_ctrl(inst, ctrl); + if (rc) + return rc; + break; + } + case MSM_VIDC_REQBUFS: + { + struct v4l2_requestbuffers *b = (struct v4l2_requestbuffers *)data; + + /* disallow */ + if (b->type == INPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(b->type)); + return -EBUSY; + } + + rc = msm_vidc_reqbufs(inst, b); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMON: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* disallow */ + if (q->type == INPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) type\n", + __func__, event_name(event), v4l2_type_name(q->type)); + return -EBUSY; + } + + rc = msm_vidc_start_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* ignore */ + if (q->type == OUTPUT_MPLANE) { + i_vpr_h(inst, "%s: streamoff of (%s) ignored in state (%s)\n", + __func__, v4l2_type_name(q->type), state_name(inst->state)); + return 0; + } + + /* sanitize type field */ + if (q->type != INPUT_MPLANE) { + i_vpr_e(inst, "%s: invalid type %d\n", __func__, q->type); + return -EINVAL; + } + + rc = msm_vidc_stop_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_START: + { + /* disallow if START called for non DRC/drain cases */ + if (!is_drc_pending(inst) && !is_drain_pending(inst)) { + i_vpr_e(inst, "%s: (%s) not allowed, sub_state (%s)\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + + /* client would call start(resume) to complete DRC/drain sequence */ + rc = msm_vidc_start_cmd(inst); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_STOP: + { + /* back to back drain not allowed */ + if (is_sub_state(inst, MSM_VIDC_DRAIN)) { + i_vpr_e(inst, "%s: drain (%s) not allowed, sub_state (%s)\n\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + + rc = msm_vidc_stop_cmd(inst); + if (rc) + return rc; + break; + } + default: + { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_output_streaming_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_BUF_QUEUE: + { + struct msm_vidc_buffer *buf = (struct msm_vidc_buffer *)data; + + /* disallow */ + if (!is_input_buffer(buf->type) && !is_output_buffer(buf->type)) { + i_vpr_e(inst, "%s: invalid buf type %u\n", __func__, buf->type); + return -EINVAL; + } + + /* defer input port */ + if (is_input_buffer(buf->type)) { + print_vidc_buffer(VIDC_LOW, "low ", "qbuf deferred", inst, buf); + return 0; + } + + rc = msm_vidc_buf_queue(inst, buf); + if (rc) + return rc; + break; + } + case MSM_VIDC_TRY_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* disallow */ + if (f->type == OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(f->type)); + return -EBUSY; + } + + rc = msm_vidc_try_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_FMT: + { + struct v4l2_format *f = (struct v4l2_format *)data; + + /* disallow */ + if (f->type == OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(f->type)); + return -EBUSY; + } + + rc = msm_vidc_s_fmt(inst, f); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_CTRL: + { + struct v4l2_ctrl *ctrl = (struct v4l2_ctrl *)data; + u32 cap_id = msm_vidc_get_cap_id(inst, ctrl->id); + + if (cap_id == INST_CAP_NONE) { + i_vpr_e(inst, "%s: invalid cap_id %u\n", __func__, cap_id); + return -EINVAL; + } + + /* disallow */ + if (is_encode_session(inst)) { + /* check dynamic allowed if master port is streaming */ + if (!(inst->capabilities[cap_id].flags & CAP_FLAG_DYNAMIC_ALLOWED)) { + i_vpr_e(inst, "%s: cap_id %#x not allowed in state %s\n", + __func__, cap_id, state_name(inst->state)); + return -EINVAL; + } + } + + rc = msm_vidc_s_ctrl(inst, ctrl); + if (rc) + return rc; + break; + } + case MSM_VIDC_REQBUFS: + { + struct v4l2_requestbuffers *b = (struct v4l2_requestbuffers *)data; + + /* disallow */ + if (b->type == OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) port\n", + __func__, event_name(event), v4l2_type_name(b->type)); + return -EBUSY; + } + + rc = msm_vidc_reqbufs(inst, b); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMON: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* disallow */ + if (q->type == OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: (%s) not allowed for (%s) type\n", + __func__, event_name(event), v4l2_type_name(q->type)); + return -EBUSY; + } + + rc = msm_vidc_start_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* ignore */ + if (q->type == INPUT_MPLANE) { + i_vpr_h(inst, "%s: streamoff of (%s) ignored in state (%s)\n", + __func__, v4l2_type_name(q->type), state_name(inst->state)); + return 0; + } + + /* sanitize type field */ + if (q->type != OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: invalid type %d\n", __func__, q->type); + return -EINVAL; + } + + rc = msm_vidc_stop_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_START: + { + /* disallow if START called for non DRC/drain cases */ + if (!is_drc_pending(inst) && !is_drain_pending(inst)) { + i_vpr_e(inst, "%s: (%s) not allowed, sub_state (%s)\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + + /* client would call start(resume) to complete DRC/drain sequence */ + rc = msm_vidc_start_cmd(inst); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_STOP: + { + /* drain not allowed as input is not streaming */ + i_vpr_e(inst, "%s: drain (%s) not allowed, sub state %s\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + default: { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_streaming_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_BUF_QUEUE: + { + struct msm_vidc_buffer *buf = (struct msm_vidc_buffer *)data; + + /* disallow */ + if (!is_input_buffer(buf->type) && !is_output_buffer(buf->type)) { + i_vpr_e(inst, "%s: invalid buf type %u\n", __func__, buf->type); + return -EINVAL; + } + + rc = msm_vidc_buf_queue(inst, buf); + if (rc) + return rc; + break; + } + case MSM_VIDC_S_CTRL: + { + struct v4l2_ctrl *ctrl = (struct v4l2_ctrl *)data; + u32 cap_id = msm_vidc_get_cap_id(inst, ctrl->id); + + if (cap_id == INST_CAP_NONE) { + i_vpr_e(inst, "%s: invalid cap_id %u\n", __func__, cap_id); + return -EINVAL; + } + + /* disallow */ + if (!(inst->capabilities[cap_id].flags & CAP_FLAG_DYNAMIC_ALLOWED)) { + i_vpr_e(inst, "%s: cap_id %#x not allowed in state %s\n", + __func__, cap_id, state_name(inst->state)); + return -EINVAL; + } + + rc = msm_vidc_s_ctrl(inst, ctrl); + if (rc) + return rc; + break; + } + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + /* sanitize type field */ + if (q->type != INPUT_MPLANE && q->type != OUTPUT_MPLANE) { + i_vpr_e(inst, "%s: invalid type %d\n", __func__, q->type); + return -EINVAL; + } + + rc = msm_vidc_stop_streaming(inst, q); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_START: + { + /* disallow if START called for non DRC/drain cases */ + if (!is_drc_pending(inst) && !is_drain_pending(inst)) { + i_vpr_e(inst, "%s: (%s) not allowed, sub_state (%s)\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + + /* client would call start(resume) to complete DRC/drain sequence */ + rc = msm_vidc_start_cmd(inst); + if (rc) + return rc; + break; + } + case MSM_VIDC_CMD_STOP: + { + /* back to back drain not allowed */ + if (is_sub_state(inst, MSM_VIDC_DRAIN)) { + i_vpr_e(inst, "%s: drain (%s) not allowed, sub_state (%s)\n\n", + __func__, event_name(event), inst->sub_state_name); + return -EBUSY; + } + + rc = msm_vidc_stop_cmd(inst); + if (rc) + return rc; + break; + } + default: { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_close_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + rc = msm_vidc_stop_streaming(inst, q); + if (rc) + return rc; + break; + } + default: { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +static int msm_vidc_error_state(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data) +{ + int rc = 0; + + /* inst must be locked */ + rc = __strict_inst_check(inst, __func__); + if (rc) { + i_vpr_e(inst, "%s(): inst was not locked\n", __func__); + return -EINVAL; + } + + switch (event) { + case MSM_VIDC_STREAMOFF: + { + struct vb2_queue *q = (struct vb2_queue *)data; + + rc = msm_vidc_stop_streaming(inst, q); + if (rc) + return rc; + break; + } + default: { + i_vpr_e(inst, "%s: unexpected event %s\n", __func__, event_name(event)); + return -EINVAL; + } + } + + return rc; +} + +struct msm_vidc_state_handle { + enum msm_vidc_state state; + int (*handle)(struct msm_vidc_inst *inst, + enum msm_vidc_event event, void *data); +}; + +static struct msm_vidc_state_handle *msm_vidc_get_state_handle(struct msm_vidc_inst *inst, + enum msm_vidc_state req_state) +{ + int cnt; + struct msm_vidc_state_handle *inst_state_handle = NULL; + static struct msm_vidc_state_handle state_handle[] = { + {MSM_VIDC_OPEN, msm_vidc_open_state }, + {MSM_VIDC_INPUT_STREAMING, msm_vidc_input_streaming_state }, + {MSM_VIDC_OUTPUT_STREAMING, msm_vidc_output_streaming_state }, + {MSM_VIDC_STREAMING, msm_vidc_streaming_state }, + {MSM_VIDC_CLOSE, msm_vidc_close_state }, + {MSM_VIDC_ERROR, msm_vidc_error_state }, + }; + + for (cnt = 0; cnt < ARRAY_SIZE(state_handle); cnt++) { + if (state_handle[cnt].state == req_state) { + inst_state_handle = &state_handle[cnt]; + break; + } + } + + /* check if req_state does not exist in the table */ + if (cnt == ARRAY_SIZE(state_handle)) { + i_vpr_e(inst, "%s: invalid state %s\n", __func__, state_name(req_state)); + return inst_state_handle; + } + + return inst_state_handle; +} + +int msm_vidc_update_state(struct msm_vidc_inst *inst, + enum msm_vidc_state request_state, const char *func) +{ + struct msm_vidc_state_handle *state_handle = NULL; + int rc = 0; + + /* get inst state handler for requested state */ + state_handle = msm_vidc_get_state_handle(inst, request_state); + if (!state_handle) + return -EINVAL; + + if (request_state == MSM_VIDC_ERROR) + i_vpr_e(inst, FMT_STRING_STATE_CHANGE, + func, state_name(request_state), state_name(inst->state)); + else + i_vpr_h(inst, FMT_STRING_STATE_CHANGE, + func, state_name(request_state), state_name(inst->state)); + + /* finally update inst state and handler */ + inst->state = state_handle->state; + inst->event_handle = state_handle->handle; + + return rc; +} + +int msm_vidc_change_state(struct msm_vidc_inst *inst, + enum msm_vidc_state request_state, const char *func) +{ + enum msm_vidc_allow allow; + int rc; + + if (is_session_error(inst)) { + i_vpr_h(inst, + "%s: inst is in bad state, can not change state to %s\n", + func, state_name(request_state)); + return 0; + } + + /* current and requested state is same */ + if (inst->state == request_state) + return 0; + + /* check if requested state movement is allowed */ + allow = msm_vidc_allow_state_change(inst, request_state); + if (allow != MSM_VIDC_ALLOW) { + i_vpr_e(inst, "%s: %s state change %s -> %s\n", func, + allow_name(allow), state_name(inst->state), + state_name(request_state)); + return (allow == MSM_VIDC_DISALLOW ? -EINVAL : 0); + } + + /* go ahead and update inst state */ + rc = msm_vidc_update_state(inst, request_state, func); + if (rc) + return rc; + + return 0; +} + +struct msm_vidc_sub_state_allow { + enum msm_vidc_state state; + enum msm_vidc_allow allow; + u32 sub_state_mask; +}; + +static int msm_vidc_set_sub_state(struct msm_vidc_inst *inst, + enum msm_vidc_sub_state sub_state, const char *func) +{ + char sub_state_name[MAX_NAME_LENGTH]; + int cnt, rc = 0; + static struct msm_vidc_sub_state_allow sub_state_allow[] = { + /* state, allow, sub_state */ + {MSM_VIDC_OPEN, MSM_VIDC_DISALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_INPUT_PAUSE | + MSM_VIDC_OUTPUT_PAUSE }, + + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_DISALLOW, MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_OUTPUT_PAUSE }, + {MSM_VIDC_INPUT_STREAMING, MSM_VIDC_ALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_INPUT_PAUSE }, + + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_DISALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_INPUT_PAUSE }, + {MSM_VIDC_OUTPUT_STREAMING, MSM_VIDC_ALLOW, MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_OUTPUT_PAUSE }, + + {MSM_VIDC_STREAMING, MSM_VIDC_ALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_INPUT_PAUSE | + MSM_VIDC_OUTPUT_PAUSE }, + + {MSM_VIDC_CLOSE, MSM_VIDC_ALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_INPUT_PAUSE | + MSM_VIDC_OUTPUT_PAUSE }, + + {MSM_VIDC_ERROR, MSM_VIDC_ALLOW, MSM_VIDC_DRC | + MSM_VIDC_DRAIN | + MSM_VIDC_DRC_LAST_BUFFER | + MSM_VIDC_DRAIN_LAST_BUFFER | + MSM_VIDC_INPUT_PAUSE | + MSM_VIDC_OUTPUT_PAUSE }, + }; + + /* no substate to update */ + if (!sub_state) + return 0; + + /* check if any substate is disallowed */ + for (cnt = 0; cnt < ARRAY_SIZE(sub_state_allow); cnt++) { + /* skip other states */ + if (sub_state_allow[cnt].state != inst->state) + continue; + + /* continue if not disallowed */ + if (sub_state_allow[cnt].allow != MSM_VIDC_DISALLOW) + continue; + + if (sub_state_allow[cnt].sub_state_mask & sub_state) { + prepare_sub_state_name(sub_state, sub_state_name, sizeof(sub_state_name)); + i_vpr_e(inst, "%s: state (%s), disallow substate (%s)\n", + func, state_name(inst->state), sub_state_name); + return -EINVAL; + } + } + + /* remove ignorable substates from a given substate */ + for (cnt = 0; cnt < ARRAY_SIZE(sub_state_allow); cnt++) { + /* skip other states */ + if (sub_state_allow[cnt].state != inst->state) + continue; + + /* continue if not ignored */ + if (sub_state_allow[cnt].allow != MSM_VIDC_IGNORE) + continue; + + if (sub_state_allow[cnt].sub_state_mask & sub_state) { + prepare_sub_state_name(sub_state, sub_state_name, sizeof(sub_state_name)); + i_vpr_h(inst, "%s: state (%s), ignore substate (%s)\n", + func, state_name(inst->state), sub_state_name); + + /* remove ignorable substate bits from actual */ + sub_state &= ~(sub_state_allow[cnt].sub_state_mask & sub_state); + break; + } + } + + /* check if all substate bits are allowed */ + for (cnt = 0; cnt < ARRAY_SIZE(sub_state_allow); cnt++) { + /* skip other states */ + if (sub_state_allow[cnt].state != inst->state) + continue; + + /* continue if not allowed */ + if (sub_state_allow[cnt].allow != MSM_VIDC_ALLOW) + continue; + + if ((sub_state_allow[cnt].sub_state_mask & sub_state) != sub_state) { + prepare_sub_state_name(sub_state, sub_state_name, sizeof(sub_state_name)); + i_vpr_e(inst, "%s: state (%s), not all substates allowed (%s)\n", + func, state_name(inst->state), sub_state_name); + return -EINVAL; + } + } + + /* update substate */ + inst->sub_state |= sub_state; + + return rc; +} + +int msm_vidc_change_sub_state(struct msm_vidc_inst *inst, + enum msm_vidc_sub_state clear_sub_state, + enum msm_vidc_sub_state set_sub_state, const char *func) +{ + enum msm_vidc_sub_state prev_sub_state; + int rc = 0; + + if (is_session_error(inst)) { + i_vpr_h(inst, + "%s: inst is in bad state, can not change sub state\n", func); + return 0; + } + + /* final value will not change */ + if (!clear_sub_state && !set_sub_state) + return 0; + + /* sanitize clear & set value */ + if ((clear_sub_state & set_sub_state) || + set_sub_state > MSM_VIDC_MAX_SUB_STATE_VALUE || + clear_sub_state > MSM_VIDC_MAX_SUB_STATE_VALUE) { + i_vpr_e(inst, "%s: invalid sub states to clear %#x or set %#x\n", + func, clear_sub_state, set_sub_state); + return -EINVAL; + } + + prev_sub_state = inst->sub_state; + + /* set sub state */ + rc = msm_vidc_set_sub_state(inst, set_sub_state, __func__); + if (rc) + return rc; + + /* clear sub state */ + inst->sub_state &= ~clear_sub_state; + + /* print substates only when there is a change */ + if (inst->sub_state != prev_sub_state) { + rc = prepare_sub_state_name(inst->sub_state, inst->sub_state_name, + sizeof(inst->sub_state_name)); + if (!rc) + i_vpr_h(inst, "%s: state %s and sub state changed to %s\n", + func, state_name(inst->state), inst->sub_state_name); + } + + return 0; +} -- 2.7.4