blob: acb0cec79338e039e95d10333f0d45a18646bc39 [file] [log] [blame]
/* st_session.c
*
* This file contains the state machine for a single sound trigger
* user session. This state machine implements logic for handling all user
* interactions, detectinos, SSR and Audio Concurencies.
*
* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "sound_trigger_hw"
#define ATRACE_TAG (ATRACE_TAG_HAL)
/* #define LOG_NDEBUG 0 */
#define LOG_NDDEBUG 0
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <cutils/log.h>
#include <cutils/trace.h>
#include "st_session.h"
#include "st_hw_session.h"
#include "st_hw_session_lsm.h"
#include "st_hw_session_gcs.h"
#include "sound_trigger_hw.h"
#include "st_hw_session_pcm.h"
#include "st_hw_extn.h"
#include "st_hw_common.h"
#include "st_second_stage.h"
#ifdef LINUX_ENABLED
#define ST_SES_DEFERRED_STOP_DELAY_MS 0
#else
#define ST_SES_DEFERRED_STOP_DELAY_MS 1000
#endif
#define IS_SS_DETECTION_PENDING(det)\
(det & (KEYWORD_DETECTION_PENDING | USER_VERIFICATION_PENDING))
#define IS_SS_DETECTION_SUCCESS(det)\
!(det & (KEYWORD_DETECTION_REJECT | USER_VERIFICATION_REJECT))
/* below enum used in cleanup in error scenarios */
enum hw_session_err_mask {
HW_SES_ERR_MASK_DEVICE_SET = 0x1,
HW_SES_ERR_MASK_REG_SM = 0x2,
HW_SES_ERR_MASK_REG_SM_PARAM = 0x4,
HW_SES_ERR_MASK_STARTED = 0x8,
HW_SES_ERR_MASK_BUFFERING = 0x10,
};
#define STATE_TRANSITION(st_session, new_state_fn)\
do {\
st_session->current_state = new_state_fn;\
ALOGD("session[%d]: %s ---> %s %s", st_session->sm_handle, __func__, \
#new_state_fn, st_session->paused ? "(paused)" : "");\
} while(0)
#define DISPATCH_EVENT(ST_SESSION, EVENT, STATUS)\
do {\
STATUS = ST_SESSION->current_state(ST_SESSION, &EVENT);\
} while (0)
#define REG_SM_RETRY_CNT 5
#define REG_SM_WAIT_TIME_MS 100
static inline int process_detection_event
(
st_session_t *st_ses, uint64_t timestamp, int detect_status,
void *payload, size_t payload_size,
struct sound_trigger_recognition_event **event
);
static inline void enable_second_stage_processing
(
st_session_t *st_ses,
st_hw_session_t *hw_ses
)
{
hw_ses->enable_second_stage = st_ses->enable_second_stage;
st_ses->lab_enabled =
(st_ses->capture_requested || st_ses->enable_second_stage);
}
static inline void disable_second_stage_processing
(
st_session_t *st_ses,
st_hw_session_t *hw_ses
)
{
hw_ses->enable_second_stage = false;
st_ses->lab_enabled = st_ses->capture_requested;
}
static int idle_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
static int loaded_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
static int active_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
static int detected_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
static int buffering_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
static int ssr_state_fn(st_session_t *st_ses, st_session_ev_t *ev);
typedef struct st_session_loadsm_payload {
struct sound_trigger_phrase_sound_model *sm_data;
} st_session_loadsm_payload_t;
typedef struct st_session_start_payload {
void *config;
size_t config_size;
recognition_callback_t callback;
void *cookie;
} st_session_start_payload_t;
typedef struct st_session_read_pcm_payload {
void *out_buff;
size_t out_buff_size;
size_t *actual_read_size;
} st_session_readpcm_payload_t;
typedef struct st_session_get_param_payload {
const char *param;
void *payload;
size_t payload_size;
size_t *param_data_size;
} st_session_getparam_payload_t;
struct st_session_ev {
st_session_event_id_t ev_id;
union {
st_session_loadsm_payload_t loadsm;
st_session_start_payload_t start;
st_hw_sess_detected_ev_t detected;
st_exec_mode_t exec_mode;
st_session_readpcm_payload_t readpcm;
enum ssr_event_status ssr;
char *chmix_coeff_str;
bool enable;
st_session_getparam_payload_t getparam;
} payload;
};
ST_DBG_DECLARE(FILE *lab_fp = NULL; static int file_cnt = 0);
void hw_sess_cb(st_hw_sess_event_t *hw_event, void *cookie)
{
st_session_t *st_ses = (st_session_t *)cookie;
int status = 0;
int lock_status = 0;
if (!hw_event || !cookie) {
ALOGE("%s: received NULL params", __func__);
return;
}
switch (hw_event->event_id) {
case ST_HW_SESS_EVENT_DETECTED:
{
st_session_ev_t ev;
ev.ev_id = ST_SES_EV_DETECTED;
ev.payload.detected = hw_event->payload.detected;
do {
lock_status = pthread_mutex_trylock(&st_ses->lock);
} while (lock_status && !st_ses->device_disabled &&
(st_ses->exec_mode != ST_EXEC_MODE_NONE) &&
(st_ses->current_state != ssr_state_fn));
/*
* TODO: Add RECOGNITION_STATUS_GET_STATE_RESPONSE to
* the SoundTrigger API header.
*/
if (st_ses->detection_requested)
ev.payload.detected.detect_status = 3;
if (st_ses->device_disabled) {
ALOGV("%s:[%d] device switch in progress, ignore event",
__func__, st_ses->sm_handle);
} else if (st_ses->exec_mode == ST_EXEC_MODE_NONE) {
ALOGV("%s:[%d] transition in progress, ignore event",
__func__, st_ses->sm_handle);
} else if (st_ses->current_state == ssr_state_fn) {
ALOGV("%s:[%d] SSR handling in progress, ignore event",
__func__, st_ses->sm_handle);
} else if (!lock_status) {
DISPATCH_EVENT(st_ses, ev, status);
}
if (!lock_status)
pthread_mutex_unlock(&st_ses->lock);
break;
}
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
}
static void do_hw_sess_cleanup(st_session_t *st_ses, st_hw_session_t *hw_ses,
enum hw_session_err_mask err)
{
if (err & HW_SES_ERR_MASK_BUFFERING)
hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (err & HW_SES_ERR_MASK_STARTED) {
hw_ses->fptrs->stop(hw_ses);
st_ses->hw_session_started = false;
}
if (err & HW_SES_ERR_MASK_REG_SM_PARAM)
hw_ses->fptrs->dereg_sm_params(hw_ses);
if (err & HW_SES_ERR_MASK_DEVICE_SET)
hw_ses->fptrs->set_device(hw_ses, false);
if (err & HW_SES_ERR_MASK_REG_SM)
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
}
static void reg_hal_event_session(st_session_t *p_ses, st_hw_session_t *hw_ses)
{
struct sound_trigger_event_info event_info;
/* Pass the pcm information to audio hal for capturing LAB */
if (p_ses->lab_enabled && p_ses->stdev->audio_hal_cb) {
ALOGD("%s: ST_EVENT_SESSION_REGISTER capture_handle %d p_ses %p",
__func__, p_ses->capture_handle, (void *)p_ses);
event_info.st_ses.p_ses = (void *)p_ses;
event_info.st_ses.config = hw_ses->config;
event_info.st_ses.capture_handle = p_ses->capture_handle;
/*
* set pcm to NULL as this version of st_hal doesn't pass pcm to
* audio HAL
*/
event_info.st_ses.pcm = NULL;
p_ses->stdev->audio_hal_cb(ST_EVENT_SESSION_REGISTER, &event_info);
}
}
static void dereg_hal_event_session(st_session_t *p_ses)
{
struct sound_trigger_event_info event_info;
/* Indicate to audio hal that buffering is stopped to stop reading LAB data */
if (p_ses->lab_enabled && p_ses->stdev->audio_hal_cb) {
ALOGD("%s: ST_EVENT_SESSION_DEREGISTER capture_handle %d p_ses %p",
__func__, p_ses->capture_handle, p_ses);
event_info.st_ses.p_ses = (void *)p_ses;
event_info.st_ses.capture_handle = p_ses->capture_handle;
event_info.st_ses.pcm = NULL;
p_ses->stdev->audio_hal_cb(ST_EVENT_SESSION_DEREGISTER, &event_info);
}
}
static int start_hw_session(st_session_t *st_ses, st_hw_session_t *hw_ses, bool load_sm)
{
int status = 0, err = 0;
/*
* Force reload sound model if LPI enabled for BackEnd but not for session.
* Note:
* 1. FE LPI enabled + BE LPI disabled is supported in ADSP
* and if needed used to simplify concurrency handling, But
* FE LPI disabled + BE LPI enabled is not supported in ADSP
* and hence cannot be used to simplify concurrency handling.
* 2. When ever possible FE LPI mode is kept enabled, but due to
* LPI memory constraints, a session would need to be loaded
* with FE LPI disabled when system session count exceeds
* max that can be supported in LPI mode, these sessions when
* continued beyond unloading of any other session, triggering
* BE LPI to be enabled again, would need this handling.
*/
if ((st_ses->hw_ses_adsp == hw_ses) && hw_ses->stdev->lpi_enable &&
st_ses->vendor_uuid_info->lpi_enable && !hw_ses->lpi_enable) {
hw_ses->lpi_enable = true;
if (!load_sm) {
load_sm = true;
status = hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
if (status)
ALOGW("%s:[%d] failed to dereg_sm err %d", __func__,
st_ses->sm_handle, status);
}
}
if (load_sm) {
status = hw_ses->fptrs->reg_sm(hw_ses, st_ses->sm_data,
st_ses->sm_type);
if (status) {
ALOGE("%s:[%d] failed to reg_sm err %d", __func__,
st_ses->sm_handle, status);
goto cleanup;
}
err |= HW_SES_ERR_MASK_REG_SM;
}
status = hw_ses->fptrs->set_device(hw_ses, true);
if (status) {
ALOGE("%s:[%d] failed to set_device err %d", __func__,
st_ses->sm_handle, status);
goto cleanup;
}
err |= HW_SES_ERR_MASK_DEVICE_SET;
/*
* Check for rc_config update before reg_sm_param,
* as hw session can change from transitions,
* and hence related config might also need to be updated.
*/
if (hw_ses->rc_config != st_ses->rc_config ||
hw_ses->rc_config_update_counter != st_ses->rc_config_update_counter) {
status = st_hw_ses_update_config(st_ses, hw_ses);
if (status) {
ALOGE("%s: ERROR. updating rc_config, returned status %d",
__func__, status);
goto cleanup;
}
}
status = hw_ses->fptrs->reg_sm_params(hw_ses,
st_ses->recognition_mode, st_ses->lab_enabled,
st_ses->rc_config, st_ses->sm_type, st_ses->sm_data);
if (status) {
ALOGE("%s:[%d] failed to reg_sm_params err %d", __func__,
st_ses->sm_handle, status);
goto cleanup;
}
err |= HW_SES_ERR_MASK_REG_SM_PARAM;
status = hw_ses->fptrs->start(hw_ses);
if (status) {
ALOGE("%s:[%d] failed to start err %d", __func__,
st_ses->sm_handle, status);
goto cleanup;
}
err |= HW_SES_ERR_MASK_STARTED;
st_ses->hw_session_started = true;
return status;
cleanup:
do_hw_sess_cleanup(st_ses, hw_ses, err);
return status;
}
static int stop_hw_session(st_session_t *st_ses, st_hw_session_t *hw_ses, bool unload_sm)
{
int status = 0;
int rc = 0;
status = hw_ses->fptrs->stop(hw_ses);
if (status) {
ALOGE("%s:[%d] failed to stop err %d", __func__,
st_ses->sm_handle, status);
rc = status;
}
status = hw_ses->fptrs->dereg_sm_params(hw_ses);
if (status) {
ALOGE("%s:[%d] failed to dereg_sm_params err %d", __func__,
st_ses->sm_handle, status);
rc = status;
}
status = hw_ses->fptrs->set_device(hw_ses, false);
if (status) {
ALOGE("%s:[%d] failed to set_device err %d", __func__,
st_ses->sm_handle, status);
rc = status;
}
if (unload_sm) {
status = hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
if (status) {
ALOGE("%s:[%d] failed to dereg_sm, err %d", __func__,
st_ses->sm_handle, status);
rc = status;
}
}
/* This must be set to false irrespective as the above calls may
* return error (esp for SSR)
*/
st_ses->hw_session_started = false;
return rc;
}
static int start_session(st_session_t *st_ses, st_hw_session_t *hw_ses, bool load_sm)
{
int status = 0;
if (st_ses->hw_session_started) {
ALOGE("%s:[%d] already started", __func__, st_ses->sm_handle);
return -1;
}
/*
* The reg_hal_event_session call must be after start_hw_session. This is
* important for when load_sm is true, because reg_sm sets the correct pcm
* config for the current hw session. That pcm config is then sent to audio hal.
*/
status = start_hw_session(st_ses, hw_ses, load_sm);
if (!status)
reg_hal_event_session(st_ses, hw_ses);
return status;
}
static int restart_session(st_session_t *st_ses, st_hw_session_t *hw_ses)
{
int status = hw_ses->fptrs->restart(hw_ses, st_ses->recognition_mode,
st_ses->lab_enabled, st_ses->rc_config,
st_ses->sm_type, st_ses->sm_data);
if (status == 0) {
st_ses->hw_session_started = true;
} else {
ALOGE("%s:[%d] failed to restart", __func__, st_ses->sm_handle);
st_ses->hw_session_started = false;
}
return status;
}
static int stop_session(st_session_t *st_ses, st_hw_session_t *hw_ses, bool unload_sm)
{
if (!st_ses->hw_session_started) {
ALOGV("%s:[%d] already stopped", __func__, st_ses->sm_handle);
return 0;
}
dereg_hal_event_session(st_ses);
if (st_ses->detection_requested) {
st_ses->detection_requested = false;
enable_second_stage_processing(st_ses, hw_ses);
}
return stop_hw_session(st_ses, hw_ses, unload_sm);
}
/*
* This function gets the first stage detection keyword indices, which are needed
* by the second stage sessions. If the legacy DSP is used, which does not provide
* keyword indices, set the indices to include the entire keyword duration. This
* function also gets the user confidence level if there is an active voiceprint
* session.
*/
static int get_first_stage_detection_params(st_session_t *st_ses, void *payload,
size_t payload_size)
{
size_t count_size = 0;
uint8_t *payload_ptr = (uint8_t *)payload;
uint32_t key_id = 0, key_payload_size = 0;
uint32_t kw_start_ms = 0, kw_end_ms = 0;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
bool is_active_vop_session = false;
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_detection_type == ST_SM_TYPE_USER_VERIFICATION) {
is_active_vop_session = true;
break;
}
}
if (hw_ses->is_generic_event) {
/*
* This case is for the generic detection event from the DSP. Set the
* keyword start and end indices and user confidence level based on key
* id, if applicable.
*/
while (count_size < payload_size) {
key_id = *(uint32_t *)payload_ptr;
key_payload_size = *((uint32_t *)payload_ptr + 1);
switch (key_id) {
case KEY_ID_CONFIDENCE_LEVELS:
if (is_active_vop_session) {
hw_ses->user_level = (int32_t)(*(payload_ptr +
GENERIC_DET_EVENT_USER_LEVEL_OFFSET));
}
break;
case KEY_ID_KEYWORD_POSITION_STATS:
hw_ses->kw_start_idx = *((uint32_t *)payload_ptr +
GENERIC_DET_EVENT_KW_START_OFFSET);
hw_ses->kw_end_idx = *((uint32_t *)payload_ptr +
GENERIC_DET_EVENT_KW_END_OFFSET);
break;
default:
ALOGW("%s: Unsupported generic detection event key id",
__func__);
break;
}
count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size;
payload_ptr += count_size;
}
} else {
/*
* This case is for the DSP detection events which are not the generic
* detection event. There will be no keyword indices from first stage
* detection, so the start index will be 0 and the end index will be the
* buffer duration sent from the app. If this is not sent, the keyword
* duration from platform xml will be used.
*/
hw_ses->kw_start_idx = 0;
if (hw_ses->client_req_hist_buf) {
hw_ses->kw_end_idx =
convert_ms_to_bytes(hw_ses->client_req_hist_buf,
&hw_ses->config);
} else {
hw_ses->kw_end_idx =
convert_ms_to_bytes(st_ses->vendor_uuid_info->kw_duration,
&hw_ses->config);
}
if (is_active_vop_session) {
if ((st_ses->exec_mode == ST_EXEC_MODE_CPE) &&
st_ses->stdev->is_gcs) {
hw_ses->user_level = (int32_t)(*(payload_ptr +
GCS_NON_GENERIC_USER_LEVEL_OFFSET));
} else if ((st_ses->exec_mode == ST_EXEC_MODE_ADSP) ||
!st_ses->stdev->is_gcs) {
hw_ses->user_level = (int32_t)(*(payload_ptr +
LSM_NON_GENERIC_USER_LEVEL_OFFSET));
}
}
}
kw_start_ms = convert_bytes_to_ms(hw_ses->kw_start_idx, &hw_ses->config);
kw_end_ms = convert_bytes_to_ms(hw_ses->kw_end_idx, &hw_ses->config);
ALOGD("%s:[%d] 1st stage kw_start = %dms, kw_end = %dms, is_generic_event %d",
__func__, st_ses->sm_handle, kw_start_ms, kw_end_ms,
hw_ses->is_generic_event);
return 0;
}
static inline void stop_second_stage_session(st_session_t *st_ses)
{
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
st_second_stage_stop_session(st_sec_stage);
}
}
static int idle_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
int ret = 0;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
/* skip parameter check as this is an internal funciton */
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle, ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_LOAD_SM:
if (!st_ses->sm_data) {
ALOGE("%s: sound model data is not initialzed", __func__);
status = -EINVAL;
break;
}
/*
* Do retry to handle a corner case that when ADSP SSR ONLINE is received,
* sometimes ADSP is still not ready to receive cmd from HLOS and thus
* fails, so try more times to recover the session from SSR state.
*/
for (int i = 0; i < REG_SM_RETRY_CNT; i++) {
status = ret = hw_ses->fptrs->reg_sm(hw_ses, st_ses->sm_data,
st_ses->sm_type);
if (ret) {
if (st_ses->stdev->ssr_offline_received) {
st_ses->client_req_state = ST_STATE_LOADED;
STATE_TRANSITION(st_ses, ssr_state_fn);
/*
* Send success to client because the failure is recovered
* internally from SSR.
*/
status = 0;
break;
} else {
ALOGE("%s:[%d] failed to reg sm, err %d, retry cnt %d", __func__,
st_ses->sm_handle, status, i);
usleep(REG_SM_WAIT_TIME_MS * 1000);
}
} else {
break;
}
}
if (ret)
break;
if (st_ses->enable_second_stage) {
hw_ses->enable_second_stage = true;
hw_ses->second_stage_list = &(st_ses->second_stage_list);
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
st_sec_stage->ss_session->st_ses = st_ses;
st_second_stage_prepare_session(st_sec_stage);
}
}
STATE_TRANSITION(st_ses, loaded_state_fn);
break;
case ST_SES_EV_SET_EXEC_MODE:
st_ses->exec_mode = ev->payload.exec_mode;
if (ST_EXEC_MODE_CPE == st_ses->exec_mode)
st_ses->hw_ses_current = st_ses->hw_ses_cpe;
else if (ST_EXEC_MODE_ADSP == st_ses->exec_mode)
st_ses->hw_ses_current = st_ses->hw_ses_adsp;
/* remain in current state */
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
break;
case ST_SES_EV_RESUME:
st_ses->paused = false;
break;
case ST_SES_EV_SSR_OFFLINE:
STATE_TRANSITION(st_ses, ssr_state_fn);
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = -EIO;
break;
case ST_SES_EV_GET_PARAM_DATA:
status = -EIO;
break;
case ST_SES_EV_REQUEST_DET:
ALOGE("%s:[%d] Event not supported in this state",
__func__, st_ses->sm_handle);
status = -EINVAL;
break;
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
return status;
}
static int loaded_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
st_hw_session_t *new_hw_ses = NULL;
st_exec_mode_t new_exec_mode = 0;
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
/* skip parameter check as this is an internal funciton */
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle, ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_RESUME:
if (!st_ses->paused)
break;
st_ses->paused = false;
if (st_ses->client_req_state != ST_STATE_ACTIVE)
break;
/* If the session is paused and client_req_state is active, fall through
* and handle similarly to start/restart.
*/
case ST_SES_EV_START:
case ST_SES_EV_RESTART:
st_ses->client_req_state = ST_STATE_ACTIVE;
if (!st_ses->paused) {
/*
* There is a need to be able to differentiate between LAB due to a client
* request, and LAB due to second stage enablement.
*/
st_ses->capture_requested = st_ses->rc_config->capture_requested;
st_ses->lab_enabled =
(st_ses->capture_requested || st_ses->enable_second_stage);
status = start_session(st_ses, hw_ses, false);
if (status) {
if (st_ses->stdev->ssr_offline_received) {
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
STATE_TRANSITION(st_ses, ssr_state_fn);
/* Send success to client because the failure is recovered
* internally from SSR.
*/
status = 0;
} else {
ALOGE("%s:[%d] failed to start session, err %d", __func__,
st_ses->sm_handle, status);
}
break;
}
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
status = st_second_stage_start_session(st_sec_stage);
if (status) {
ALOGE("%s: Failed to start second stage session, exiting", __func__);
status = -EINVAL;
goto cleanup;
}
}
}
STATE_TRANSITION(st_ses, active_state_fn);
}
break;
case ST_SES_EV_UNLOAD_SM:
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
status = hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
if (status) {
/* since this is a teardown scenario dont fail here */
ALOGE("%s:[%d] dereg_sm failed with err %d", __func__,
st_ses->sm_handle, status);
status = 0;
}
STATE_TRANSITION(st_ses, idle_state_fn);
break;
case ST_SES_EV_SSR_OFFLINE:
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
/* exec mode can be none if ssr occurs during a transition */
if (st_ses->exec_mode != ST_EXEC_MODE_NONE)
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
/*
* When the session is first loaded, the client_req_state remains
* in idle state. In this case, client_req_state must be set to
* loaded before the SSR handling. In other usecases,
* client_req_state can be either loaded or active, so it should
* not be changed here.
*/
if (st_ses->client_req_state == ST_STATE_IDLE)
st_ses->client_req_state = ST_STATE_LOADED;
STATE_TRANSITION(st_ses, ssr_state_fn);
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
break;
case ST_SES_EV_STOP:
st_ses->client_req_state = ST_STATE_LOADED;
break;
case ST_SES_EV_SET_EXEC_MODE:
new_exec_mode = ev->payload.exec_mode;
if ((st_ses->exec_mode != new_exec_mode) &&
st_ses->enable_trans) {
if (st_ses->exec_mode != ST_EXEC_MODE_NONE) {
st_ses->exec_mode = ST_EXEC_MODE_NONE;
/* unload sm for current hw session */
status = hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
if (status) {
ALOGE("%s:[%d] dereg_sm failed with err %d", __func__,
st_ses->sm_handle, status);
break;
}
}
if (new_exec_mode == ST_EXEC_MODE_NONE)
break;
/* load sm to new hw_ses */
if (ST_EXEC_MODE_CPE == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_cpe;
st_ses->hw_ses_cpe->enable_second_stage =
st_ses->hw_ses_adsp->enable_second_stage;
st_ses->hw_ses_cpe->second_stage_list =
st_ses->hw_ses_adsp->second_stage_list;
} else if (ST_EXEC_MODE_ADSP == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_adsp;
st_ses->hw_ses_adsp->enable_second_stage =
st_ses->hw_ses_cpe->enable_second_stage;
st_ses->hw_ses_adsp->second_stage_list =
st_ses->hw_ses_cpe->second_stage_list;
} else {
ALOGE("%s: unknown execution mode %d", __func__,
new_exec_mode);
status = -EINVAL;
break;
}
status = new_hw_ses->fptrs->reg_sm(new_hw_ses,
st_ses->sm_data, st_ses->sm_type);
if (status) {
ALOGE("%s:[%d] reg_sm failed with err %d", __func__,
st_ses->sm_handle, status);
break;
}
/* switch hw sessions only if successful*/
st_ses->exec_mode = new_exec_mode;
st_ses->hw_ses_current = new_hw_ses;
/* remain in current state */
}
break;
case ST_SES_EV_SET_DEVICE:
/*
* This event handling is needed for certain graphs which
* have multiple buffering modules with a single voice wakeup
* module in each usecase.
*/
if (!ev->payload.enable)
status = hw_ses->fptrs->disable_device(hw_ses, false);
else
status = hw_ses->fptrs->enable_device(hw_ses, false);
break;
case ST_SES_EV_READ_PCM:
/*
* set status to failure this will tell AHAL to
* provide zero buffers to client
*/
status = -EIO;
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = -EIO;
break;
case ST_SES_EV_GET_PARAM_DATA:
status = hw_ses->fptrs->get_param_data(hw_ses,
ev->payload.getparam.param, ev->payload.getparam.payload,
ev->payload.getparam.payload_size,
ev->payload.getparam.param_data_size);
break;
case ST_SES_EV_REQUEST_DET:
ALOGE("%s:[%d] Event not supported in this state",
__func__, st_ses->sm_handle);
status = -EINVAL;
break;
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
return status;
cleanup:
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
st_second_stage_module_deinit(st_sec_stage);
}
return status;
}
static int active_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
st_session_ev_t deferred_ev = { .ev_id = ST_SES_EV_DEFERRED_STOP };
st_hw_session_t *new_hw_ses = NULL;
st_exec_mode_t new_exec_mode;
/* skip parameter check as this is an internal funciton */
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle, ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_SET_EXEC_MODE:
new_exec_mode = ev->payload.exec_mode;
/* if no change in mode or dynamic transition not enabled then noop */
if ((new_exec_mode == st_ses->exec_mode) || !st_ses->enable_trans)
break;
if (st_ses->exec_mode != ST_EXEC_MODE_NONE) {
ALOGV("%s: disable current session", __func__);
st_ses->exec_mode = ST_EXEC_MODE_NONE;
status = stop_session(st_ses, hw_ses, true);
if (status)
break;
}
if (new_exec_mode == ST_EXEC_MODE_NONE)
break;
if (ST_EXEC_MODE_CPE == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_cpe;
st_ses->hw_ses_cpe->enable_second_stage =
st_ses->hw_ses_adsp->enable_second_stage;
st_ses->hw_ses_cpe->second_stage_list =
st_ses->hw_ses_adsp->second_stage_list;
} else if (ST_EXEC_MODE_ADSP == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_adsp;
st_ses->hw_ses_adsp->enable_second_stage =
st_ses->hw_ses_cpe->enable_second_stage;
st_ses->hw_ses_adsp->second_stage_list =
st_ses->hw_ses_cpe->second_stage_list;
} else {
ALOGE("%s: unknown execution mode %d", __func__,
new_exec_mode);
status = -EINVAL;
break;
}
ALOGV("%s: enable current session", __func__);
status = start_session(st_ses, new_hw_ses, true);
if (status)
break;
/* set new exec mode and current session */
st_ses->exec_mode = new_exec_mode;
st_ses->hw_ses_current = new_hw_ses;
ALOGV("%s: end transition", __func__);
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
/* Fall through to handle pause events similarly to stop events. */
case ST_SES_EV_STOP:
if (st_ses->paused)
st_ses->client_req_state = ST_STATE_ACTIVE;
else
st_ses->client_req_state = ST_STATE_LOADED;
status = stop_session(st_ses, hw_ses, false);
if (status) {
if (st_ses->stdev->ssr_offline_received) {
STATE_TRANSITION(st_ses, ssr_state_fn);
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
/* Send success to client because the failure is recovered
* internally from SSR.
*/
status = 0;
} else {
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
}
break;
}
STATE_TRANSITION(st_ses, loaded_state_fn);
break;
case ST_SES_EV_DETECTED:
{
size_t payload_size = ev->payload.detected.payload_size;
struct sound_trigger_recognition_event *event = NULL;
recognition_callback_t callback;
bool lab_enabled = false, enable_second_stage = false;
if (!st_ses->enable_second_stage ||
st_ses->detection_requested) {
status = process_detection_event(st_ses,
ev->payload.detected.timestamp,
ev->payload.detected.detect_status,
ev->payload.detected.detect_payload,
payload_size, &event);
if (status || !event) {
ALOGE("%s:[%d] process_detection_event failed err %d", __func__,
st_ses->sm_handle, status);
/* Stop buffering if this is not a successful detection and
LAB is triggered in hw automatically */
hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (event)
free(event);
break;
}
} else {
enable_second_stage = true;
st_ses->sent_detection_to_client = false;
get_first_stage_detection_params(st_ses,
ev->payload.detected.detect_payload, payload_size);
memcpy(st_ses->det_session_ev, ev, sizeof(st_session_ev_t));
}
/*
* change to new state before invoking user callback, this will
* ensure that if user calls start_recognition immediately from the
* callback it will be handled by one of the two states below
*/
if (!status && st_ses->lab_enabled) {
ST_DBG_FILE_OPEN_WR(lab_fp, ST_DEBUG_DUMP_LOCATION, "lab_capture",
"bin", file_cnt++);
STATE_TRANSITION(st_ses, buffering_state_fn);
} else {
STATE_TRANSITION(st_ses, detected_state_fn);
}
if (!st_ses->callback) {
ALOGE("%s:[%d] received detection event but no callback",
__func__, st_ses->sm_handle);
status = -EINVAL;
if (event && !enable_second_stage)
free(event);
break;
}
callback = st_ses->callback;
/*
* store the current capture requested in-case a new start comes
* once we exist the critical-section.
* In this case we continue to operate based on previous capture requested
* setting untill the new session start is processed and resets the state
*/
lab_enabled = st_ses->lab_enabled;
pthread_mutex_unlock(&st_ses->lock);
/*
* callback to user, assumption is that client does not
* block in the callback waiting for data otherwise will be a deadlock
*/
if (!enable_second_stage) {
ALOGD("%s:[%d] invoking the client callback",
__func__, st_ses->sm_handle);
ATRACE_ASYNC_END("sthal: detection success",
st_ses->sm_handle);
ATRACE_BEGIN("sthal: client detection callback");
callback(event, st_ses->cookie);
ATRACE_END();
}
/*
* TODO: Add RECOGNITION_STATUS_GET_STATE_RESPONSE to
* the SoundTrigger API header.
*/
if (lab_enabled &&
((ev->payload.detected.detect_status ==
RECOGNITION_STATUS_SUCCESS) ||
(ev->payload.detected.detect_status == 3))) {
/* Cache lab data to internal buffers (blocking call) */
hw_ses->fptrs->process_lab_capture(hw_ses);
}
/*
* It is possible that the client may start/stop/unload the session
* with the same lock held, before we aqcuire lock here.
* We need further processing only if client starts in detected state
* or buffering state if lab was enabled, else return gracefully.
*/
do {
status = pthread_mutex_trylock(&st_ses->lock);
} while (status && ((st_ses->current_state == detected_state_fn) ||
(st_ses->current_state == buffering_state_fn)));
if (st_ses->current_state != detected_state_fn) {
ALOGV("%s:[%d] client not in detected state, lock status %d",
__func__, st_ses->sm_handle, status);
if (!status) {
/*
* Stop session if still in buffering state and no pending
* stop to be handled i.e. internally buffering was stopped.
* This is required to avoid further detections in wrong state.
* Client is expected to issue start recognition for current
* detection event which will restart the session.
*/
if ((st_ses->current_state == buffering_state_fn) &&
!st_ses->pending_stop) {
ALOGD("%s:[%d] buffering stopped internally, stop session",
__func__, st_ses->sm_handle);
stop_session(st_ses, hw_ses, false);
STATE_TRANSITION(st_ses, loaded_state_fn);
}
pthread_mutex_unlock(&st_ses->lock);
}
if (!st_ses->enable_second_stage)
free(event);
status = 0;
if (st_ses->detection_requested) {
st_ses->detection_requested = false;
enable_second_stage_processing(st_ses, hw_ses);
}
break;
}
if (st_ses->detection_requested) {
st_ses->detection_requested = false;
enable_second_stage_processing(st_ses, hw_ses);
}
/*
* If we are not buffering (i.e capture is not requested), then
* trigger a deferred stop. Most applications issue (re)start
* almost immediately. Delaying stop allows unnecessary teardown
* and reinitialization of backend.
*/
if (!lab_enabled) {
/*
* Note that this event will only be posted to the detected state
* The current state may switch to active if the client
* issues start/restart before control of the callback thread
* reaches this point.
*/
DISPATCH_EVENT(st_ses, deferred_ev, status);
} else {
ALOGE("%s:[%d] capture is requested but state is still detected!?",
__func__, st_ses->sm_handle);
}
if (!enable_second_stage)
free(event);
break;
}
case ST_SES_EV_SSR_OFFLINE:
STATE_TRANSITION(st_ses, ssr_state_fn);
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
/* exec mode can be none if ssr occurs during a transition */
if (st_ses->exec_mode != ST_EXEC_MODE_NONE)
stop_hw_session(st_ses, hw_ses, true /* unload_sm */);
st_ses->client_req_state = ST_STATE_ACTIVE;
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = hw_ses->fptrs->send_custom_chmix_coeff(hw_ses,
ev->payload.chmix_coeff_str);
break;
case ST_SES_EV_SET_DEVICE:
if (!ev->payload.enable)
status = hw_ses->fptrs->disable_device(hw_ses, true);
else
status = hw_ses->fptrs->enable_device(hw_ses, true);
break;
case ST_SES_EV_READ_PCM:
/*
* buffering could have been stopped internally
* and switched to active state ex: transitions.
* set status to failure this will tell AHAL to
* provide zero buffers to client
*/
status = -EIO;
break;
case ST_SES_EV_GET_PARAM_DATA:
status = hw_ses->fptrs->get_param_data(hw_ses,
ev->payload.getparam.param, ev->payload.getparam.payload,
ev->payload.getparam.payload_size,
ev->payload.getparam.param_data_size);
break;
case ST_SES_EV_REQUEST_DET:
status = hw_ses->fptrs->send_detection_request(hw_ses);
if (!status) {
st_ses->detection_requested = true;
/*
* Disable second stage processing if a forced detection is
* requested.
*/
disable_second_stage_processing(st_ses, hw_ses);
}
break;
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
return status;
}
static int detected_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
st_exec_mode_t new_exec_mode;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
st_hw_session_t *new_hw_ses = NULL;
/* skip parameter check as this is an internal funciton */
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle, ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_START:
/* session already started but config has changed stop and restart */
status = stop_session(st_ses, hw_ses, false);
if (status)
break;
status = start_session(st_ses, hw_ses, false);
if (status)
break;
STATE_TRANSITION(st_ses, active_state_fn);
break;
case ST_SES_EV_RESTART:
/* session already restarted without any config changes */
restart_session(st_ses, hw_ses);
STATE_TRANSITION(st_ses, active_state_fn);
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
st_ses->client_req_state = ST_STATE_LOADED;
/* Fall through to handle pause events similarly to stop event. */
case ST_SES_EV_STOP:
/*
* It is possible that the client can issue stop after detection
* callback. This even can be issued internally as part of
* deferred stop as well.
*/
status = stop_session(st_ses, hw_ses, false);
if (status) {
if (st_ses->stdev->ssr_offline_received) {
STATE_TRANSITION(st_ses, ssr_state_fn);
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
st_ses->client_req_state = ST_STATE_LOADED;
/* Send success to client because the failure is recovered
* internally from SSR.
*/
status = 0;
} else {
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
}
break;
}
STATE_TRANSITION(st_ses, loaded_state_fn);
break;
case ST_SES_EV_SSR_OFFLINE:
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
/*
* Ignore return status during SSR handling
* as the ADSP or CPE might be down so these
* calls would fail. Exec mode can be none if
* ssr occurs during a transition.
*/
if (st_ses->exec_mode != ST_EXEC_MODE_NONE)
stop_hw_session(st_ses, hw_ses, true /* unload sm */);
st_ses->client_req_state = ST_STATE_ACTIVE;
STATE_TRANSITION(st_ses, ssr_state_fn);
break;
case ST_SES_EV_SET_EXEC_MODE:
new_exec_mode = ev->payload.exec_mode;
/* if no change in mode or dynamic transition not enabled then noop */
if ((new_exec_mode == st_ses->exec_mode) || !st_ses->enable_trans)
break;
if (st_ses->exec_mode != ST_EXEC_MODE_NONE) {
st_ses->exec_mode = ST_EXEC_MODE_NONE;
status = stop_session(st_ses, hw_ses, true);
if (status)
break;
}
if (new_exec_mode == ST_EXEC_MODE_NONE)
break;
/* switch to new hw session */
if (ST_EXEC_MODE_CPE == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_cpe;
st_ses->hw_ses_cpe->enable_second_stage =
st_ses->hw_ses_adsp->enable_second_stage;
st_ses->hw_ses_cpe->second_stage_list =
st_ses->hw_ses_adsp->second_stage_list;
} else if (ST_EXEC_MODE_ADSP == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_adsp;
st_ses->hw_ses_adsp->enable_second_stage =
st_ses->hw_ses_cpe->enable_second_stage;
st_ses->hw_ses_adsp->second_stage_list =
st_ses->hw_ses_cpe->second_stage_list;
} else {
ALOGE("%s: unknown execution mode %d", __func__,
new_exec_mode);
status = -EINVAL;
break;
}
/*
* start new hw session and stay in detected state as
* client restart and stop concurrency scenarios are handled
* in this state
*/
status = start_session(st_ses, new_hw_ses, true);
if (status)
break;
st_ses->exec_mode = new_exec_mode;
st_ses->hw_ses_current = new_hw_ses;
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = -EINVAL;
break;
case ST_SES_EV_SET_DEVICE:
/*
* set device is a no-op in detected state due to the following reasons
* A set device is a sequence of disable and enable device commands.
* set device sequence is triggered with dev lock held. Therefore there
* cannot be a concurrency with other client issued events.
* As a deferred stop is posted prior to entering detected state,
* one of the two events are possible
* 1) timer expires and stop is issued : this implies stop_session
* 2) timer is cancelled and start is issued by client: this implies
* new device is set as part of start_session
*/
break;
case ST_SES_EV_GET_PARAM_DATA:
status = hw_ses->fptrs->get_param_data(hw_ses,
ev->payload.getparam.param, ev->payload.getparam.payload,
ev->payload.getparam.payload_size,
ev->payload.getparam.param_data_size);
break;
case ST_SES_EV_DEFERRED_STOP:
ALOGD("%s:[%d] post deferred stop from detected state", __func__,
st_ses->sm_handle);
status = hw_session_notifier_enqueue(st_ses->sm_handle,
ST_SES_EV_DEFERRED_STOP,
ST_SES_DEFERRED_STOP_DELAY_MS);
if (!status)
st_ses->pending_stop = true;
break;
case ST_SES_EV_REQUEST_DET:
ALOGE("%s:[%d] Event not supported in this state",
__func__, st_ses->sm_handle);
status = -EINVAL;
break;
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
return status;
}
static int buffering_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
st_exec_mode_t new_exec_mode = 0;
st_hw_session_t *new_hw_ses = NULL;
st_session_ev_t set_dev_ev = { .ev_id = ST_SES_EV_SET_DEVICE };
/* skip parameter check as this is an internal function */
ALOGVV("%s:[%d] handle event id %d", __func__, st_ses->sm_handle,
ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_READ_PCM:
/* Note: this function may block if there is no PCM data ready*/
hw_ses->fptrs->read_pcm(hw_ses, ev->payload.readpcm.out_buff,
ev->payload.readpcm.out_buff_size);
ST_DBG_FILE_WRITE(lab_fp, ev->payload.readpcm.out_buff,
ev->payload.readpcm.out_buff_size);
break;
case ST_SES_EV_END_BUFFERING:
hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (!st_ses->pending_stop) {
ALOGD("%s:[%d] post deferred stop on buffering end", __func__,
st_ses->sm_handle);
status = hw_session_notifier_enqueue(st_ses->sm_handle,
ST_SES_EV_DEFERRED_STOP,
ST_SES_DEFERRED_STOP_DELAY_MS);
if (!status)
st_ses->pending_stop = true;
} else {
ALOGD("%s:[%d] skip deferred stop on buffering as already set", __func__,
st_ses->sm_handle);
}
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
/* Fall through to handle pause events similarly to stop event. */
case ST_SES_EV_STOP:
ALOGD("%s:[%d] handle event STOP %s", __func__, st_ses->sm_handle,
st_ses->paused ? "(paused)" : "");
/*
* These events are related to a tear down sequence, so transition to
* loaded state even if there is a failure.
*/
status = hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (status)
ALOGE("%s:[%d] failed to stop_buffering err %d", __func__,
st_ses->sm_handle, status);
/*
* We treat pause during buffering with the same semantics as stop.
* With second stage enabled, the session can be in buffering state
* without the app requesting it. So if the PAUSE event comes when
* session is in buffering state, client_req_state needs to be set to
* active to ensure the session can make it back to the active state.
*/
if (st_ses->paused)
st_ses->client_req_state = ST_STATE_ACTIVE;
else
st_ses->client_req_state = ST_STATE_LOADED;
status = stop_session(st_ses, hw_ses, false);
if (status)
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
ST_DBG_FILE_CLOSE(lab_fp);
STATE_TRANSITION(st_ses, loaded_state_fn);
break;
case ST_SES_EV_SET_DEVICE:
/*
* Device switch will not wait for buffering to finish. It will instead
* interrupt and stop the buffering and transition to the loaded state.
* The loaded state will then take care of the device switch.
*/
hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
status = stop_session(st_ses, hw_ses, false);
if (status && !st_ses->stdev->ssr_offline_received) {
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
break;
}
STATE_TRANSITION(st_ses, loaded_state_fn);
DISPATCH_EVENT(st_ses, set_dev_ev, status);
break;
case ST_SES_EV_START:
case ST_SES_EV_RESTART:
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle,
ev->ev_id);
/*
* Client starts detection again.
* This implies a previous deferred stop hasn't completed yet as
* stop would have changed state to loaded.
* For a restart event, issue stop buffering and restart the session
* For a start event, stop buffering then stop and start the session
* so that any new parameters take effect.
*/
hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (ev->ev_id == ST_SES_EV_START) {
status = stop_session(st_ses, hw_ses, false);
if (status && !st_ses->stdev->ssr_offline_received) {
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
break;
}
status = start_session(st_ses, hw_ses, false);
} else {
status = restart_session(st_ses, hw_ses);
}
if (status) {
if (st_ses->stdev->ssr_offline_received) {
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
/*
* if restart failed during SSR, needs to reset the st
* device to make sure that it can be brought up again
* when SSR online is received.
*/
if (ev->ev_id == ST_SES_EV_RESTART)
stop_session(st_ses, hw_ses, true);
else
hw_ses->fptrs->dereg_sm(hw_ses, st_ses->lab_enabled);
st_ses->client_req_state = ST_STATE_ACTIVE;
STATE_TRANSITION(st_ses, ssr_state_fn);
/* Send success to client because the failure is recovered
* internally from SSR.
*/
status = 0;
} else {
ALOGE("%s:[%d] failed to start session, err %d", __func__,
st_ses->sm_handle, status);
/* move to active anyways to allow unload sm */
STATE_TRANSITION(st_ses, active_state_fn);
}
} else {
/* Move state back to active for restart and start events */
STATE_TRANSITION(st_ses, active_state_fn);
}
break;
case ST_SES_EV_SSR_OFFLINE:
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle,
ev->ev_id);
if (st_ses->enable_second_stage) {
/*
* Second stage sessions will always buffer in order to be
* able to make detections. If the SSR occurs before the
* client is notified of a detection, it needs to recover to
* the active state. If the SSR occurs after this, or if the
* second stage detection is rejected, then recovering to
* the loaded state is sufficient.
*/
if (!st_ses->sent_detection_to_client || st_ses->paused)
st_ses->client_req_state = ST_STATE_ACTIVE;
else
st_ses->client_req_state = ST_STATE_LOADED;
} else {
if (st_ses->paused)
st_ses->client_req_state = ST_STATE_ACTIVE;
else
st_ses->client_req_state = ST_STATE_LOADED;
}
/*
* Ignore return status during SSR handling
* as the ADSP or CPE might be down so these
* calls would fail. Exec mode can be none if
* ssr occurs during a transition.
*/
if (st_ses->exec_mode != ST_EXEC_MODE_NONE) {
hw_ses->fptrs->stop_buffering(hw_ses,
st_ses->lab_enabled);
if (st_ses->enable_second_stage)
stop_second_stage_session(st_ses);
stop_hw_session(st_ses, hw_ses, true /* unload sm */);
}
STATE_TRANSITION(st_ses, ssr_state_fn);
break;
case ST_SES_EV_SET_EXEC_MODE:
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle,
ev->ev_id);
new_exec_mode = ev->payload.exec_mode;
/* if no change in mode or dynamic transition not enabled then noop */
if ((new_exec_mode == st_ses->exec_mode) || !st_ses->enable_trans)
break;
if (st_ses->exec_mode != ST_EXEC_MODE_NONE) {
st_ses->exec_mode = ST_EXEC_MODE_NONE;
status = hw_ses->fptrs->stop_buffering(hw_ses, st_ses->lab_enabled);
if (status) {
ALOGE("%s:[%d] failed to stop_buffering err %d", __func__,
st_ses->sm_handle, status);
break;
}
status = stop_session(st_ses, hw_ses, true);
if (status) {
ALOGE("%s:[%d] failed to stop session, err %d", __func__,
st_ses->sm_handle, status);
break;
}
}
if (new_exec_mode == ST_EXEC_MODE_NONE)
break;
/* switch to new hw session */
if (ST_EXEC_MODE_CPE == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_cpe;
st_ses->hw_ses_cpe->enable_second_stage =
st_ses->hw_ses_adsp->enable_second_stage;
st_ses->hw_ses_cpe->second_stage_list =
st_ses->hw_ses_adsp->second_stage_list;
} else if (ST_EXEC_MODE_ADSP == new_exec_mode) {
new_hw_ses = st_ses->hw_ses_adsp;
st_ses->hw_ses_adsp->enable_second_stage =
st_ses->hw_ses_cpe->enable_second_stage;
st_ses->hw_ses_adsp->second_stage_list =
st_ses->hw_ses_cpe->second_stage_list;
} else {
ALOGE("%s: unknown execution mode %d", __func__,
new_exec_mode);
status = -EINVAL;
break;
}
status = start_session(st_ses, new_hw_ses, true);
if (status) {
ALOGE("%s:[%d] failed to start hw ses, err %d", __func__,
st_ses->sm_handle, status);
break;
}
st_ses->exec_mode = new_exec_mode;
st_ses->hw_ses_current = new_hw_ses;
STATE_TRANSITION(st_ses, active_state_fn);
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = hw_ses->fptrs->send_custom_chmix_coeff(hw_ses,
ev->payload.chmix_coeff_str);
break;
case ST_SES_EV_GET_PARAM_DATA:
status = hw_ses->fptrs->get_param_data(hw_ses,
ev->payload.getparam.param, ev->payload.getparam.payload,
ev->payload.getparam.payload_size,
ev->payload.getparam.param_data_size);
break;
case ST_SES_EV_REQUEST_DET:
ALOGE("%s:[%d] Event %d not supported in this state",
__func__, st_ses->sm_handle, ev->ev_id);
status = -EINVAL;
break;
default:
ALOGD("%s:[%d] unhandled event, id %d", __func__, st_ses->sm_handle,
ev->ev_id);
break;
};
return status;
}
static int ssr_state_fn(st_session_t *st_ses, st_session_ev_t *ev)
{
int status = 0;
st_session_ev_t load_ev = { .ev_id = ST_SES_EV_LOAD_SM };
st_session_ev_t start_ev = { .ev_id = ST_SES_EV_START };
st_session_ev_t exec_mode_ev = { .ev_id = ST_SES_EV_SET_EXEC_MODE };
/* skip parameter check as this is an internal funciton */
ALOGD("%s:[%d] handle event id %d", __func__, st_ses->sm_handle, ev->ev_id);
switch (ev->ev_id) {
case ST_SES_EV_SSR_ONLINE:
ALOGV("%s:[%d] SSR ONLINE received", __func__, st_ses->sm_handle);
STATE_TRANSITION(st_ses, idle_state_fn);
if ((st_ses->ssr_transit_exec_mode == ST_EXEC_MODE_CPE) ||
(st_ses->ssr_transit_exec_mode == ST_EXEC_MODE_ADSP)) {
exec_mode_ev.payload.exec_mode = st_ses->ssr_transit_exec_mode;
DISPATCH_EVENT(st_ses, exec_mode_ev, status);
st_ses->ssr_transit_exec_mode = ST_EXEC_MODE_NONE;
}
if ((ST_STATE_ACTIVE == st_ses->client_req_state) ||
(ST_STATE_LOADED == st_ses->client_req_state)) {
DISPATCH_EVENT(st_ses, load_ev, status);
if (status)
break;
}
if ((ST_STATE_ACTIVE == st_ses->client_req_state) &&
!st_ses->paused) {
DISPATCH_EVENT(st_ses, start_ev, status);
if (status)
break;
}
break;
case ST_SES_EV_LOAD_SM:
if (ST_STATE_IDLE == st_ses->client_req_state) {
st_ses->client_req_state = ST_STATE_LOADED;
} else {
ALOGE("%s: received unexpected event, client_req_state = %d",
__func__, st_ses->client_req_state);
}
break;
case ST_SES_EV_UNLOAD_SM:
if (ST_STATE_LOADED == st_ses->client_req_state) {
st_ses->client_req_state = ST_STATE_IDLE;
} else {
ALOGE("%s: received unexpected event, client_req_state = %d",
__func__, st_ses->client_req_state);
}
break;
case ST_SES_EV_START:
case ST_SES_EV_RESTART:
if (ST_STATE_LOADED == st_ses->client_req_state) {
st_ses->client_req_state = ST_STATE_ACTIVE;
} else {
ALOGE("%s: received unexpected event, client_req_state = %d",
__func__, st_ses->client_req_state);
}
break;
case ST_SES_EV_STOP:
if (ST_STATE_ACTIVE == st_ses->client_req_state) {
st_ses->client_req_state = ST_STATE_LOADED;
} else {
ALOGE("%s: received unexpected event, client_req_state = %d",
__func__, st_ses->client_req_state);
}
break;
case ST_SES_EV_PAUSE:
st_ses->paused = true;
break;
case ST_SES_EV_RESUME:
st_ses->paused = false;
break;
case ST_SES_EV_READ_PCM:
status = -EIO;
break;
case ST_SES_EV_SEND_CHMIX_COEFF:
status = -EIO;
break;
case ST_SES_EV_GET_PARAM_DATA:
status = -EIO;
break;
case ST_SES_EV_SET_EXEC_MODE:
st_ses->exec_mode = ev->payload.exec_mode;
if (ST_EXEC_MODE_CPE == st_ses->exec_mode)
st_ses->hw_ses_current = st_ses->hw_ses_cpe;
else if (ST_EXEC_MODE_ADSP == st_ses->exec_mode)
st_ses->hw_ses_current = st_ses->hw_ses_adsp;
/* remain in current state */
break;
case ST_SES_EV_REQUEST_DET:
ALOGE("%s:[%d] Event not supported in this state",
__func__, st_ses->sm_handle);
status = -EINVAL;
break;
default:
ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle);
break;
};
return status;
}
/*
* This function sets the opaque data size for the DSP's generic detection
* events. This opaque data can now have varying size based on the requested
* params.
*/
static size_t set_opaque_data_size(void *payload, size_t payload_size,
uint32_t version)
{
size_t count_size = 0, opaque_size = 0;
uint8_t *payload_ptr = (uint8_t *)payload;
uint32_t key_id = 0, key_payload_size = 0;
while (count_size < payload_size) {
key_id = *(uint32_t *)payload_ptr;
key_payload_size = *((uint32_t *)payload_ptr + 1);
switch (key_id) {
case KEY_ID_CONFIDENCE_LEVELS:
opaque_size += sizeof(struct st_param_header);
if (version != CONF_LEVELS_INTF_VERSION_0002) {
opaque_size +=
sizeof(struct st_confidence_levels_info);
} else {
opaque_size +=
sizeof(struct st_confidence_levels_info_v2);
}
count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size;
payload_ptr += count_size;
break;
case KEY_ID_KEYWORD_POSITION_STATS:
opaque_size += sizeof(struct st_param_header) +
sizeof(struct st_keyword_indices_info);
count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size;
payload_ptr += count_size;
break;
default:
ALOGE("%s: Unsupported generic detection event key id", __func__);
}
}
opaque_size += sizeof(struct st_param_header) + sizeof(struct st_timestamp_info);
return opaque_size;
}
/*
* This function packs the updated opaque data confidence levels which are
* passed to the client via callback.
*/
static int pack_opaque_data_conf_levels(
st_session_t *st_ses, void *opaque_data,
uint8_t *payload)
{
uint8_t *payload_ptr = payload;
unsigned int i, j, k, user_id;
st_arm_second_stage_t *st_sec_stage;
struct listnode *node = NULL, *tmp_node = NULL;
struct st_confidence_levels_info *conf_levels = NULL;
struct st_confidence_levels_info_v2 *conf_levels_v2 = NULL;
int32_t kw_level = 0, user_level = 0;
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_CNN) {
kw_level = st_sec_stage->ss_session->confidence_score;
} else if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_VOP) {
user_level = st_sec_stage->ss_session->confidence_score;
}
}
}
if (st_ses->conf_levels_intf_version != CONF_LEVELS_INTF_VERSION_0002) {
conf_levels = (struct st_confidence_levels_info *)opaque_data;
for (i = 0; i < conf_levels->num_sound_models; i++) {
if (conf_levels->conf_levels[i].sm_id == ST_SM_ID_SVA_GMM) {
for (j = 0;
j < conf_levels->conf_levels[i].num_kw_levels; j++) {
conf_levels->conf_levels[i].kw_levels[j].kw_level =
payload_ptr[j];
for (k = 0;
k < conf_levels->conf_levels[i].kw_levels[j].num_user_levels;
k++) {
user_id =
conf_levels->conf_levels[i].kw_levels[j].
user_levels[k].user_id;
conf_levels->conf_levels[i].kw_levels[j].
user_levels[k].level = payload_ptr[user_id];
}
}
} else if (conf_levels->conf_levels[i].sm_id == ST_SM_ID_SVA_CNN) {
conf_levels->conf_levels[i].kw_levels[0].kw_level = kw_level;
} else if (conf_levels->conf_levels[i].sm_id == ST_SM_ID_SVA_VOP) {
/*
* Fill both the keyword and user confidence level with the
* confidence score returned from the voiceprint algorithm.
*/
conf_levels->conf_levels[i].kw_levels[0].kw_level =
(uint8_t)user_level;
conf_levels->conf_levels[i].kw_levels[0].user_levels[0].level =
(uint8_t)user_level;
}
}
} else {
conf_levels_v2 = (struct st_confidence_levels_info_v2 *)opaque_data;
for (i = 0; i < conf_levels_v2->num_sound_models; i++) {
if (conf_levels_v2->conf_levels[i].sm_id == ST_SM_ID_SVA_GMM) {
for (j = 0;
j < conf_levels_v2->conf_levels[i].num_kw_levels; j++) {
conf_levels_v2->conf_levels[i].kw_levels[j].kw_level =
payload_ptr[j];
for (k = 0;
k < conf_levels_v2->conf_levels[i].kw_levels[j].num_user_levels;
k++) {
user_id =
conf_levels_v2->conf_levels[i].kw_levels[j].
user_levels[k].user_id;
conf_levels_v2->conf_levels[i].kw_levels[j].
user_levels[k].level = payload_ptr[user_id];
}
}
} else if (conf_levels_v2->conf_levels[i].sm_id == ST_SM_ID_SVA_CNN) {
conf_levels_v2->conf_levels[i].kw_levels[0].kw_level = kw_level;
} else if (conf_levels_v2->conf_levels[i].sm_id == ST_SM_ID_SVA_VOP) {
/*
* Fill both the keyword and user confidence level with the
* confidence score returned from the voiceprint algorithm.
*/
conf_levels_v2->conf_levels[i].kw_levels[0].kw_level = user_level;
conf_levels_v2->conf_levels[i].kw_levels[0].user_levels[0].level =
user_level;
}
}
}
return 0;
}
/* This function packs the sound trigger API confidence levels */
static int pack_recognition_event_conf_levels(
st_session_t *st_ses, uint8_t *payload_ptr,
struct sound_trigger_phrase_recognition_event *local_event)
{
unsigned int j = 0, k = 0, user_id = 0;
st_arm_second_stage_t *st_sec_stage;
struct listnode *node = NULL, *tmp_node = NULL;
struct sound_trigger_phrase_sound_model *phrase_sm =
(struct sound_trigger_phrase_sound_model *)st_ses->sm_data;
/*
* Fill in the GMM confidence levels to the sound trigger recognition event
* APIs first. If any second stage session is enabled, overwrite the APIs
* with the second stage confidence levels.
*/
for (j = 0; j < st_ses->rc_config->num_phrases; j++) {
local_event->phrase_extras[j].id = st_ses->rc_config->phrases[j].id;
local_event->phrase_extras[j].recognition_modes =
phrase_sm->phrases[j].recognition_mode;
local_event->phrase_extras[j].num_levels =
st_ses->rc_config->phrases[j].num_levels;
local_event->phrase_extras[j].confidence_level = payload_ptr[j];
for (k = 0; k < st_ses->rc_config->phrases[j].num_levels; k++) {
user_id = st_ses->rc_config->phrases[j].levels[k].user_id;
local_event->phrase_extras[j].levels[k].user_id = user_id;
local_event->phrase_extras[j].levels[k].level =
payload_ptr[user_id];
}
}
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_CNN) {
local_event->phrase_extras[0].confidence_level =
(uint8_t)st_sec_stage->ss_session->confidence_score;
} else if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_VOP) {
local_event->phrase_extras[0].levels[0].level =
(uint8_t)st_sec_stage->ss_session->confidence_score;
}
}
}
return 0;
}
static int parse_generic_event_and_pack_opaque_data(
st_session_t *st_ses, uint8_t **opaque_data,
uint8_t *payload_ptr, size_t payload_size,
struct sound_trigger_phrase_recognition_event *local_event)
{
uint32_t key_id = 0, key_payload_size = 0;
struct st_param_header *param_hdr = NULL;
struct st_keyword_indices_info *kw_indices = NULL;
struct st_timestamp_info *timestamps = NULL;
size_t count_size = 0;
st_arm_second_stage_t *st_sec_stage;
struct listnode *node = NULL, *tmp_node = NULL;
st_hw_session_t *st_hw_ses = st_ses->hw_ses_current;
int status = 0;
uint8_t *conf_payload_ptr = NULL;
while (count_size < payload_size) {
key_id = *(uint32_t *)payload_ptr;
key_payload_size = *((uint32_t *)payload_ptr + 1);
switch (key_id) {
case KEY_ID_CONFIDENCE_LEVELS:
/* Pack the opaque data confidence levels structure */
param_hdr = (struct st_param_header *)(*opaque_data);
param_hdr->key_id = ST_PARAM_KEY_CONFIDENCE_LEVELS;
*opaque_data += sizeof(struct st_param_header);
if (st_ses->conf_levels_intf_version !=
CONF_LEVELS_INTF_VERSION_0002) {
param_hdr->payload_size =
sizeof(struct st_confidence_levels_info);
} else {
param_hdr->payload_size =
sizeof(struct st_confidence_levels_info_v2);
}
memcpy(*opaque_data, st_hw_ses->conf_levels_info,
param_hdr->payload_size);
conf_payload_ptr = payload_ptr + (4 * sizeof(uint32_t));
pack_opaque_data_conf_levels(st_ses, (void *)*opaque_data,
conf_payload_ptr);
pack_recognition_event_conf_levels(st_ses, conf_payload_ptr,
local_event);
*opaque_data += param_hdr->payload_size;
break;
case KEY_ID_KEYWORD_POSITION_STATS:
/* Pack the opaque data keyword indices structure */
param_hdr = (struct st_param_header *)(*opaque_data);
param_hdr->key_id = ST_PARAM_KEY_KEYWORD_INDICES;
param_hdr->payload_size = sizeof(struct st_keyword_indices_info);
*opaque_data += sizeof(struct st_param_header);
kw_indices = (struct st_keyword_indices_info *)(*opaque_data);
kw_indices->version = 0x1;
kw_indices->start_index = *((uint32_t *)payload_ptr + 3);
kw_indices->end_index = *((uint32_t *)payload_ptr + 4);
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_CNN) {
kw_indices->start_index = st_sec_stage->ss_session->kw_start_idx;
kw_indices->end_index = st_sec_stage->ss_session->kw_end_idx;
}
}
}
*opaque_data += sizeof(struct st_keyword_indices_info);
break;
default:
ALOGE("%s: Unsupported generic detection event key id", __func__);
status = -EINVAL;
goto exit;
}
count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size;
payload_ptr += count_size;
}
/* Pack the opaque data detection timestamp structure */
param_hdr = (struct st_param_header *)(*opaque_data);
param_hdr->key_id = ST_PARAM_KEY_TIMESTAMP;
param_hdr->payload_size = sizeof(struct st_timestamp_info);
*opaque_data += sizeof(struct st_param_header);
timestamps = (struct st_timestamp_info *)(*opaque_data);
timestamps->version = 0x1;
timestamps->first_stage_det_event_time =
st_ses->hw_ses_current->first_stage_det_event_time;
if (st_ses->enable_second_stage)
timestamps->second_stage_det_event_time =
st_ses->hw_ses_current->second_stage_det_event_time;
*opaque_data += sizeof(struct st_timestamp_info);
exit:
return status;
}
static int parse_generic_event_without_opaque_data(
st_session_t *st_ses, uint8_t *payload_ptr, size_t payload_size,
struct sound_trigger_phrase_recognition_event *local_event)
{
uint32_t key_id = 0, key_payload_size = 0;
size_t count_size = 0;
int status = 0;
uint8_t *conf_payload_ptr;
while (count_size < payload_size) {
key_id = *(uint32_t *)payload_ptr;
key_payload_size = *((uint32_t *)payload_ptr + 1);
switch (key_id) {
case KEY_ID_CONFIDENCE_LEVELS:
conf_payload_ptr = payload_ptr + (4 * sizeof(uint32_t));
pack_recognition_event_conf_levels(st_ses, conf_payload_ptr,
local_event);
return status;
case KEY_ID_KEYWORD_POSITION_STATS:
count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size;
payload_ptr += count_size;
break;
default:
ALOGE("%s: Unsupported generic detection event key id", __func__);
status = -EINVAL;
return status;
}
}
return status;
}
/*
* This function handles detection payloads in the format of the DSP's
* generic detection event.
*/
int process_detection_event_keyphrase_v2(
st_session_t *st_ses, int detect_status,
void *payload, size_t payload_size,
struct sound_trigger_phrase_recognition_event **event)
{
st_hw_session_t *st_hw_ses = st_ses->hw_ses_current;
unsigned int i, j;
int status = 0;
uint8_t *opaque_data = NULL, *payload_ptr = NULL;
size_t opaque_size = 0;
struct sound_trigger_phrase_recognition_event *local_event = NULL;
if (st_ses->vendor_uuid_info->is_qcva_uuid)
opaque_size = set_opaque_data_size(payload, payload_size,
st_ses->conf_levels_intf_version);
else
opaque_size = payload_size;
local_event = calloc(1, sizeof(struct sound_trigger_phrase_recognition_event) + opaque_size);
if (!local_event) {
ALOGE("%s: local_event allocation failed, opaque data size = %d", __func__,
(unsigned int)opaque_size);
return -ENOMEM;
}
local_event->num_phrases = st_ses->rc_config->num_phrases;
local_event->common.data_offset = sizeof(struct sound_trigger_phrase_recognition_event);
local_event->common.data_size = opaque_size;
opaque_data = (uint8_t *)local_event + local_event->common.data_offset;
payload_ptr = (uint8_t *)payload;
if (st_ses->vendor_uuid_info->is_qcva_uuid) {
if (st_ses->rc_config->data_size > CUSTOM_CONFIG_OPAQUE_DATA_SIZE) {
status = parse_generic_event_and_pack_opaque_data(st_ses, &opaque_data,
payload_ptr, payload_size, local_event);
if (status) {
ALOGE("%s: Failed to parse generic detection event with opaque data %d",
__func__, status);
goto exit;
}
ST_DBG_DECLARE(FILE *opaque_fd = NULL; static int opaque_cnt = 0);
ST_DBG_FILE_OPEN_WR(opaque_fd, ST_DEBUG_DUMP_LOCATION,
"detection_opaque_data", "bin", opaque_cnt++);
ST_DBG_FILE_WRITE(opaque_fd, (uint8_t *)local_event +
local_event->common.data_offset, opaque_size);
ST_DBG_FILE_CLOSE(opaque_fd);
} else {
status = parse_generic_event_without_opaque_data(st_ses, payload_ptr,
payload_size, local_event);
if (status) {
ALOGE("%s: Failed to parse generic detection event without opaque data %d",
__func__, status);
goto exit;
}
}
} else {
local_event = calloc(1, sizeof(*local_event) + payload_size);
if (!local_event) {
ALOGE("%s: event allocation failed, size %zd", __func__,
payload_size);
status = -ENOMEM;
goto exit;
}
memcpy(local_event->phrase_extras,
st_ses->rc_config->phrases, st_ses->rc_config->num_phrases *
sizeof(struct sound_trigger_phrase_recognition_extra));
local_event->num_phrases = st_ses->rc_config->num_phrases;
local_event->common.data_offset = sizeof(*local_event);
local_event->common.data_size = payload_size;
memcpy((char *)local_event + local_event->common.data_offset, payload,
payload_size);
opaque_data += opaque_size;
}
/* fill the remaining recognition event parameters not specific
to soundmodel lib */
local_event->common.status = detect_status;
local_event->common.type = st_ses->sm_data->common.type;
local_event->common.model = st_ses->sm_handle;
local_event->common.capture_available = st_ses->capture_requested;
local_event->common.capture_delay_ms = 0;
local_event->common.capture_preamble_ms = 0;
local_event->common.audio_config.sample_rate =
SOUND_TRIGGER_SAMPLING_RATE_16000;
local_event->common.audio_config.format = AUDIO_FORMAT_PCM_16_BIT;
local_event->common.audio_config.channel_mask =
audio_channel_in_mask_from_count(st_hw_ses->config.channels);
for (i = 0; i < local_event->num_phrases; ++i) {
ALOGV("%s: [%d] kw_id %d level %d", __func__, i,
local_event->phrase_extras[i].id,
local_event->phrase_extras[i].confidence_level);
for (j = 0; j < local_event->phrase_extras[i].num_levels; ++j) {
ALOGV("%s: [%d] user_id %d level %d ", __func__, i,
local_event->phrase_extras[i].levels[j].user_id,
local_event->phrase_extras[i].levels[j].level);
}
}
ALOGI("%s:[%d]", __func__, st_ses->sm_handle);
ALOGV("%s:[%d] status=%d, type=%d, model=%d, capture_avaiable=%d, "
"num_phrases=%d id=%d", __func__,
st_ses->sm_handle, local_event->common.status, local_event->common.type,
local_event->common.model, local_event->common.capture_available,
local_event->num_phrases, local_event->phrase_extras[0].id);
*event = local_event;
return 0;
exit:
if (local_event)
free(local_event);
return status;
}
/*
* This function handles detection payloads in the format of the DSP's
* legacy (non-generic) detection event.
*/
static int process_detection_event_keyphrase(
st_session_t *st_ses, int detect_status,
void *payload, size_t payload_size,
struct sound_trigger_phrase_recognition_event **event)
{
st_hw_session_t *st_hw_ses = st_ses->hw_ses_current;
unsigned int i, j;
int status = 0;
struct sound_trigger_phrase_recognition_event *local_event = NULL;
size_t opaque_size = 0;
uint8_t *opaque_data = NULL, *payload_ptr = NULL;
struct st_param_header *param_hdr = NULL;
st_arm_second_stage_t *st_sec_stage;
struct listnode *node = NULL, *tmp_node = NULL;
struct st_keyword_indices_info *kw_indices = NULL;
struct st_timestamp_info *timestamps = NULL;
bool enable_kw_indices = false;
if ((st_ses->rc_config->data_size > CUSTOM_CONFIG_OPAQUE_DATA_SIZE) &&
st_ses->vendor_uuid_info->is_qcva_uuid) {
/*
* This logic is for the updated opaque data format. Sound trigger recognition
* event APIs are filled along with the opaque data's confidence levels, keyword
* indices, and timestamp parameters.
*/
opaque_size = (2 * sizeof(struct st_param_header)) +
sizeof(struct st_timestamp_info);
if (st_ses->conf_levels_intf_version != CONF_LEVELS_INTF_VERSION_0002)
opaque_size += sizeof(struct st_confidence_levels_info);
else
opaque_size += sizeof(struct st_confidence_levels_info_v2);
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_CNN) {
enable_kw_indices = true;
opaque_size += sizeof(struct st_param_header) +
sizeof(struct st_keyword_indices_info);
break;
}
}
}
local_event = calloc(1,
sizeof(struct sound_trigger_phrase_recognition_event) + opaque_size);
if (!local_event) {
ALOGE("%s: local_event allocation failed, opaque data size = %d", __func__,
(unsigned int)opaque_size);
return -ENOMEM;
}
local_event->num_phrases = st_ses->rc_config->num_phrases;
local_event->common.data_offset = sizeof(struct sound_trigger_phrase_recognition_event);
local_event->common.data_size = opaque_size;
opaque_data = (uint8_t *)local_event + local_event->common.data_offset;
if ((st_ses->exec_mode == ST_EXEC_MODE_CPE) && st_ses->stdev->is_gcs) {
payload_ptr = (uint8_t *)payload + 2;
} else if ((st_ses->exec_mode == ST_EXEC_MODE_ADSP) || !st_ses->stdev->is_gcs) {
payload_ptr = (uint8_t *)payload;
} else {
ALOGE("%s: Invalid execution mode, exiting", __func__);
status = -EINVAL;
goto err_exit;
}
/* Pack the opaque data confidence levels structure */
param_hdr = (struct st_param_header *)opaque_data;
param_hdr->key_id = ST_PARAM_KEY_CONFIDENCE_LEVELS;
opaque_data += sizeof(struct st_param_header);
if (st_ses->conf_levels_intf_version != CONF_LEVELS_INTF_VERSION_0002) {
param_hdr->payload_size =
sizeof(struct st_confidence_levels_info);
} else {
param_hdr->payload_size =
sizeof(struct st_confidence_levels_info_v2);
}
memcpy(opaque_data, st_hw_ses->conf_levels_info,
param_hdr->payload_size);
pack_opaque_data_conf_levels(st_ses, (void *)opaque_data, payload_ptr);
pack_recognition_event_conf_levels(st_ses, payload_ptr,
local_event);
opaque_data += param_hdr->payload_size;
/* Pack the opaque data keyword indices structure */
if (enable_kw_indices) {
param_hdr = (struct st_param_header *)opaque_data;
param_hdr->key_id = ST_PARAM_KEY_KEYWORD_INDICES;
param_hdr->payload_size = sizeof(struct st_keyword_indices_info);
opaque_data += sizeof(struct st_param_header);
kw_indices = (struct st_keyword_indices_info *)opaque_data;
kw_indices->version = 0x1;
if (st_ses->enable_second_stage) {
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
if (st_sec_stage->ss_info->sm_id == ST_SM_ID_SVA_CNN) {
kw_indices->start_index = st_sec_stage->ss_session->kw_start_idx;
kw_indices->end_index = st_sec_stage->ss_session->kw_end_idx;
}
}
}
opaque_data += sizeof(struct st_keyword_indices_info);
}
/* Pack the opaque data detection timestamp structure */
param_hdr = (struct st_param_header *)opaque_data;
param_hdr->key_id = ST_PARAM_KEY_TIMESTAMP;
param_hdr->payload_size = sizeof(struct st_timestamp_info);
opaque_data += sizeof(struct st_param_header);
timestamps = (struct st_timestamp_info *)opaque_data;
timestamps->version = 0x1;
timestamps->first_stage_det_event_time =
st_ses->hw_ses_current->first_stage_det_event_time;
if (st_ses->enable_second_stage)
timestamps->second_stage_det_event_time =
st_ses->hw_ses_current->second_stage_det_event_time;
opaque_data += sizeof(struct st_timestamp_info);
ST_DBG_DECLARE(FILE *opaque_fd = NULL; static int opaque_cnt = 0);
ST_DBG_FILE_OPEN_WR(opaque_fd, ST_DEBUG_DUMP_LOCATION,
"detection_opaque_data", "bin", opaque_cnt++);
ST_DBG_FILE_WRITE(opaque_fd, (opaque_data - opaque_size), opaque_size);
ST_DBG_FILE_CLOSE(opaque_fd);
} else {
/* This logic is for the legacy opaque data format or third party vendors */
if (st_ses->vendor_uuid_info &&
st_ses->vendor_uuid_info->smlib_handle) {
/* if smlib is present, get the event from it else send the
DSP recieved payload as it is to App */
/* TODO: checking is_gcs should be avoided here */
if (st_ses->stdev->is_gcs &&
ST_EXEC_MODE_CPE == st_ses->exec_mode &&
!st_ses->hw_ses_current->is_generic_event) {
ALOGD("%s: about to call generate_st_phrase_recognition_event_v2",
__func__);
status = st_ses->vendor_uuid_info->
generate_st_phrase_recognition_event_v2(st_ses->sm_data,
st_ses->rc_config, payload, payload_size, &local_event);
} else {
status = st_ses->vendor_uuid_info->
generate_st_phrase_recognition_event(st_ses->sm_data,
st_ses->rc_config, payload, payload_size, &local_event);
}
if (status) {
ALOGW("%s: smlib fill recognition event failed, status %d",
__func__, status);
goto exit;
}
} else if (!st_ses->vendor_uuid_info &&
st_ses->stdev->smlib_handle) {
/* This is SVA non topology solution */
/* TODO: checking is_gcs should be avoided here */
if (st_ses->stdev->is_gcs) {
status = st_ses->stdev->generate_st_phrase_recognition_event_v2(
st_ses->sm_data, st_ses->rc_config, payload, payload_size,
&local_event);
} else {
status = st_ses->stdev->generate_st_phrase_recognition_event(
st_ses->sm_data, st_ses->rc_config, payload, payload_size,
&local_event);
}
if (status) {
ALOGW("%s: SVA smlib fill recognition event failed, status\
%d", __func__, status);
goto exit;
}
} else {
local_event = calloc(1, sizeof(*local_event) + payload_size);
if (!local_event) {
ALOGE("%s: event allocation failed, size %zd", __func__,
payload_size);
status = -ENOMEM;
goto exit;
}
memcpy(local_event->phrase_extras,
st_ses->rc_config->phrases, st_ses->rc_config->num_phrases *
sizeof(struct sound_trigger_phrase_recognition_extra));
local_event->num_phrases = st_ses->rc_config->num_phrases;
local_event->common.data_offset = sizeof(*local_event);
local_event->common.data_size = payload_size;
memcpy((char *)local_event + local_event->common.data_offset, payload,
payload_size);
}
}
/* fill the remaining recognition event parameters not specific
to soundmodel lib */
local_event->common.status = detect_status;
local_event->common.type = st_ses->sm_data->common.type;
local_event->common.model = st_ses->sm_handle;
local_event->common.capture_available = st_ses->capture_requested;
local_event->common.capture_delay_ms = 0;
local_event->common.capture_preamble_ms = 0;
local_event->common.audio_config.sample_rate =
SOUND_TRIGGER_SAMPLING_RATE_16000;
local_event->common.audio_config.format = AUDIO_FORMAT_PCM_16_BIT;
local_event->common.audio_config.channel_mask =
audio_channel_in_mask_from_count(st_hw_ses->config.channels);
for (i = 0; i < local_event->num_phrases; ++i) {
ALOGV("%s: [%d] kw_id %d level %d", __func__, i,
local_event->phrase_extras[i].id,
local_event->phrase_extras[i].confidence_level);
for (j = 0; j < local_event->phrase_extras[i].num_levels; ++j) {
ALOGV("%s: [%d] user_id %d level %d ", __func__, i,
local_event->phrase_extras[i].levels[j].user_id,
local_event->phrase_extras[i].levels[j].level);
}
}
ALOGI("%s:[%d]", __func__, st_ses->sm_handle);
ALOGV("%s:[%d] status=%d, type=%d, model=%d, capture_avaiable=%d, "
"num_phrases=%d id=%d", __func__,
st_ses->sm_handle, local_event->common.status, local_event->common.type,
local_event->common.model, local_event->common.capture_available,
local_event->num_phrases, local_event->phrase_extras[0].id);
*event = local_event;
return 0;
err_exit:
if (local_event)
free(local_event);
exit:
return status;
}
static int process_detection_event_generic(st_session_t *st_ses,
int detect_status,
void *payload, size_t payload_size,
struct sound_trigger_recognition_event **event)
{
st_hw_session_t *st_hw_ses = st_ses->hw_ses_current;
struct st_vendor_info *v_info = st_ses->vendor_uuid_info;
int status = 0;
struct sound_trigger_recognition_event *local_event = NULL;
local_event = calloc(1, sizeof(*local_event) + payload_size);
if (!local_event) {
ALOGE("%s: event allocation failed, size %zd", __func__,
payload_size);
status = -ENOMEM;
goto exit;
}
local_event->status = detect_status;
local_event->type = st_ses->sm_type;
local_event->model = st_ses->sm_handle;
local_event->capture_available = st_ses->capture_requested;
local_event->capture_delay_ms = 0;
local_event->capture_preamble_ms = 0;
local_event->audio_config.sample_rate = v_info ?
v_info->sample_rate : SOUND_TRIGGER_SAMPLING_RATE_16000;
local_event->audio_config.format = AUDIO_FORMAT_PCM_16_BIT;
local_event->audio_config.channel_mask =
audio_channel_in_mask_from_count(st_hw_ses->config.channels);
local_event->data_offset = sizeof(*local_event);
local_event->data_size = payload_size;
memcpy((char *)local_event + local_event->data_offset,
payload, payload_size);
ALOGI("%s:[%d]", __func__, st_ses->sm_handle);
ALOGV("%s:[%d] status=%d, type=%d, model=%d, capture_avaiable=%d",
__func__, st_ses->sm_handle, local_event->status,
local_event->type, local_event->model,
local_event->capture_available);
*event = local_event;
exit:
return status;
}
static inline int process_detection_event(st_session_t *st_ses,
uint64_t timestamp __unused,
int detect_status,
void *payload, size_t payload_size,
struct sound_trigger_recognition_event **event)
{
int ret;
struct sound_trigger_phrase_recognition_event *phrase_event = NULL;
*event = NULL;
if (st_ses->sm_type == SOUND_MODEL_TYPE_KEYPHRASE) {
if (sthw_extn_check_process_det_ev_support())
ret = sthw_extn_process_detection_event_keyphrase(st_ses,
timestamp, detect_status,
payload, payload_size, &phrase_event);
else if (st_ses->hw_ses_current->is_generic_event &&
!st_ses->vendor_uuid_info->is_qcmd_uuid)
ret = process_detection_event_keyphrase_v2(st_ses, detect_status,
payload, payload_size,
&phrase_event);
else
ret = process_detection_event_keyphrase(st_ses, detect_status,
payload, payload_size,
&phrase_event);
if (phrase_event)
*event = &phrase_event->common;
} else {
ret = process_detection_event_generic(st_ses, detect_status, payload,
payload_size, event);
}
return ret;
}
int st_session_load_sm(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
/* SM is stored in the session by st_device, hence set NULL below */
st_session_loadsm_payload_t payload = { NULL };
st_session_ev_t ev = { .ev_id = ST_SES_EV_LOAD_SM,
.payload.loadsm = payload };
/*
* no need to lock mutex when loading sm as session is just being
* being created and handle not returned to caller yet
*/
DISPATCH_EVENT(st_ses, ev, status);
return status;
}
int st_session_unload_sm(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_UNLOAD_SM };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_start(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_START };
/* lock to serialize event handling */
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_stop(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_STOP };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_restart(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_RESTART };
/* lock to serialize event handling */
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_ssr_offline(st_session_t *st_ses,
enum ssr_event_status ssr_type)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_SSR_OFFLINE,
.payload.ssr = ssr_type };
pthread_mutex_lock(&st_ses->lock);
/*
* In typical usecases, handle SSR only if it occured on the core we are
* currently using. In cases that have an SSR event during transitions,
* the exec_mode can be NONE. For these cases, handle SSR on the core
* which was in use prior to the transition. For example, if the
* ssr_transit_exec_mode is ADSP, then the core prior to the transition
* is CPE, so we handle the CPE SSR event.
*
* On 8909w BG uses CPE mode for detection. So add BG specific
* conditon check to handle SSR event.
*/
if (((ST_EXEC_MODE_CPE == st_ses->exec_mode) &&
(CPE_STATUS_OFFLINE == ssr_type)) ||
((ST_EXEC_MODE_ADSP == st_ses->exec_mode) &&
(SND_CARD_STATUS_OFFLINE == ssr_type)) ||
((ST_EXEC_MODE_NONE == st_ses->exec_mode) &&
(((ST_EXEC_MODE_CPE == st_ses->ssr_transit_exec_mode) &&
(SND_CARD_STATUS_OFFLINE == ssr_type)) ||
((ST_EXEC_MODE_ADSP == st_ses->ssr_transit_exec_mode) &&
(CPE_STATUS_OFFLINE == ssr_type)))) ||
((ST_EXEC_MODE_CPE == st_ses->exec_mode) &&
(SND_CARD_STATUS_OFFLINE == ssr_type) &&
(st_ses->stdev->bg_kwd)))
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_ssr_online(st_session_t *st_ses,
enum ssr_event_status ssr_type)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_SSR_ONLINE,
.payload.ssr = ssr_type };
pthread_mutex_lock(&st_ses->lock);
/*
* In typical usecases, handle SSR only if it occured on the core we are
* currently using. In cases that have an SSR event during transitions,
* the exec_mode can be NONE. For these cases, handle SSR on the core
* which was in use prior to the transition. For example, if the
* ssr_transit_exec_mode is ADSP, then the core prior to the transition
* is CPE, so we handle the CPE SSR event.
*
* On 8909w BG uses CPE mode for detection. So add BG specific
* conditon check to handle SSR event.
*/
if (((ST_EXEC_MODE_CPE == st_ses->exec_mode) &&
(CPE_STATUS_ONLINE == ssr_type)) ||
((ST_EXEC_MODE_ADSP == st_ses->exec_mode) &&
(SND_CARD_STATUS_ONLINE == ssr_type)) ||
((ST_EXEC_MODE_NONE == st_ses->exec_mode) &&
(((ST_EXEC_MODE_CPE == st_ses->ssr_transit_exec_mode) &&
(SND_CARD_STATUS_ONLINE == ssr_type)) ||
((ST_EXEC_MODE_ADSP == st_ses->ssr_transit_exec_mode) &&
(CPE_STATUS_ONLINE == ssr_type)))) ||
((ST_EXEC_MODE_CPE == st_ses->exec_mode) &&
(SND_CARD_STATUS_ONLINE == ssr_type) &&
(st_ses->stdev->bg_kwd)))
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_pause(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_PAUSE };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_resume(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_RESUME };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_disable_device(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_SET_DEVICE,
.payload.enable = false };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_enable_device(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_SET_DEVICE,
.payload.enable = true };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
bool st_session_is_detected(st_session_t *st_ses)
{
bool ret;
pthread_mutex_lock(&st_ses->lock);
ret = (st_ses->current_state == detected_state_fn) ? true : false;
pthread_mutex_unlock(&st_ses->lock);
return ret;
}
bool st_session_is_active(st_session_t *st_ses)
{
bool ret;
pthread_mutex_lock(&st_ses->lock);
ret = (st_ses->current_state == active_state_fn) ? true : false;
pthread_mutex_unlock(&st_ses->lock);
return ret;
}
bool st_session_is_buffering(st_session_t *st_ses)
{
bool ret;
pthread_mutex_lock(&st_ses->lock);
ret = (st_ses->current_state == buffering_state_fn) ? true : false;
pthread_mutex_unlock(&st_ses->lock);
return ret;
}
bool st_session_is_ssr_state(st_session_t *st_ses)
{
bool ret;
pthread_mutex_lock(&st_ses->lock);
ret = (st_ses->current_state == ssr_state_fn) ? true : false;
pthread_mutex_unlock(&st_ses->lock);
return ret;
}
int st_session_read_pcm(st_session_t *st_ses, uint8_t *buff,
size_t buff_size, size_t *read_size)
{
int status = 0;
if (!st_ses || !buff || buff_size == 0 || read_size == 0)
return -EINVAL;
st_session_readpcm_payload_t payload = { .out_buff = buff,
.out_buff_size = buff_size, .actual_read_size = read_size };
st_session_ev_t ev = { .ev_id = ST_SES_EV_READ_PCM,
.payload.readpcm = payload };
/* Do not lock when handling this event, this event
can go in parallel with other events */
DISPATCH_EVENT(st_ses, ev, status);
return status;
}
int st_session_stop_lab(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_END_BUFFERING };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_set_exec_mode(st_session_t *st_ses, st_exec_mode_t exec)
{
int status = 0;
if (!st_ses)
return -EINVAL;
ALOGV("%s: exec mode %d", __func__, exec);
st_session_ev_t ev = { .ev_id = ST_SES_EV_SET_EXEC_MODE,
.payload.exec_mode = exec };
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_send_custom_chmix_coeff(st_session_t *st_ses, char *str)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_SEND_CHMIX_COEFF,
.payload.chmix_coeff_str = str};
pthread_mutex_lock(&st_ses->lock);
if (ST_EXEC_MODE_ADSP == st_ses->exec_mode)
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_get_config(st_session_t *st_ses, struct pcm_config *config)
{
if (!st_ses)
return -EINVAL;
st_hw_session_t *hw_ses = st_ses->hw_ses_current;
memcpy(config, &hw_ses->config, sizeof(struct pcm_config));
return 0;
}
int st_session_get_param_data(st_session_t *st_ses, const char *param,
void *payload, size_t payload_size,
size_t *param_data_size)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_getparam_payload_t getparam_payload = { .param = param,
.payload = payload,
.payload_size = payload_size,
.param_data_size = param_data_size};
st_session_ev_t ev = { .ev_id = ST_SES_EV_GET_PARAM_DATA,
.payload.getparam = getparam_payload};
pthread_mutex_lock(&st_ses->lock);
/* Currently get param data supported for ARM & ADSP mode */
if ((ST_EXEC_MODE_ARM == st_ses->exec_mode) ||
(ST_EXEC_MODE_ADSP == st_ses->exec_mode))
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
/*
* If the keyword detection session detects before the user verification
* session, signal to process user verification. If the keyword detection
* session rejects before the user verification session, signal to stop
* processing user verification.
*/
static void handle_vop_pending_detection(st_arm_ss_session_t *ss_session,
unsigned int det_status,
unsigned int kw_det_buff_sz)
{
if (det_status & KEYWORD_DETECTION_SUCCESS) {
if (kw_det_buff_sz > ss_session->unread_bytes)
ss_session->buff_sz = kw_det_buff_sz;
else
ss_session->buff_sz = ss_session->unread_bytes;
/*
* It is possible that VOP started processing by already consuming
* data from unread_bytes while CNN detects. In this case, it does
* not need to be signaled.
*/
if (ss_session->unread_bytes >= ss_session->buff_sz) {
ALOGD("%s: Processing UV due to KW detection success", __func__);
pthread_cond_signal(&ss_session->cond);
}
} else if (det_status & KEYWORD_DETECTION_REJECT) {
ss_session->exit_buffering = true;
ALOGD("%s: Exiting from UV due to KW detection rejection", __func__);
pthread_cond_signal(&ss_session->cond);
}
}
/*
* If the user verification session rejects before the keyword detection
* session, signal to stop processing keyword detection.
*/
static void handle_cnn_pending_detection(st_arm_ss_session_t *ss_session,
unsigned int det_status)
{
if (det_status & USER_VERIFICATION_REJECT) {
ss_session->exit_buffering = true;
ALOGD("%s: Exiting from KW detection due to UV rejection", __func__);
pthread_cond_signal(&ss_session->cond);
}
}
/*
* This thread handles detection events from the second stage sessions
* and aggregates them into 1 final decision. It will call the client callback
* or restart the first stage session based on this decision.
*/
static void *aggregator_thread_loop(void *st_session)
{
st_session_t *st_ses = (st_session_t *)st_session;
recognition_callback_t callback = NULL;
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
int status = 0, lock_status = 0;
unsigned int kw_det_buff_sz = 0, det_status = 0;
struct timespec tspec = {0};
struct sound_trigger_recognition_event *event = NULL;
st_session_ev_t restart_ev = { .ev_id = ST_SES_EV_RESTART };
ALOGV("%s: Enter", __func__);
pthread_mutex_lock(&st_ses->ss_detections_lock);
while (!st_ses->exit_aggregator_loop) {
det_status = 0;
lock_status = 0;
ALOGV("%s: waiting on cond", __func__);
pthread_cond_wait(&st_ses->ss_detections_cond,
&st_ses->ss_detections_lock);
ALOGV("%s: done waiting on cond", __func__);
if (st_ses->exit_aggregator_loop) {
pthread_mutex_unlock(&st_ses->ss_detections_lock);
return NULL;
}
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t,
list_node);
pthread_mutex_lock(&st_sec_stage->ss_session->lock);
det_status |= st_sec_stage->ss_session->det_status;
if (st_sec_stage->ss_session->det_status ==
KEYWORD_DETECTION_SUCCESS)
kw_det_buff_sz = st_sec_stage->ss_session->bytes_processed;
pthread_mutex_unlock(&st_sec_stage->ss_session->lock);
}
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t,
list_node);
pthread_mutex_lock(&st_sec_stage->ss_session->lock);
if ((st_sec_stage->ss_info->sm_detection_type ==
ST_SM_TYPE_USER_VERIFICATION) &&
(det_status & USER_VERIFICATION_PENDING)) {
handle_vop_pending_detection(st_sec_stage->ss_session,
det_status, kw_det_buff_sz);
} else if ((st_sec_stage->ss_info->sm_detection_type ==
ST_SM_TYPE_KEYWORD_DETECTION) &&
(det_status & KEYWORD_DETECTION_PENDING)) {
handle_cnn_pending_detection(st_sec_stage->ss_session,
det_status);
}
pthread_mutex_unlock(&st_sec_stage->ss_session->lock);
}
if (!IS_SS_DETECTION_PENDING(det_status)) {
pthread_mutex_lock(&st_ses->lock);
/*
* If the client stops before 2nd stage finishes processing, or a
* transition is in progress, the detection event should not be
* handled.
*/
if ((st_ses->current_state != buffering_state_fn) ||
(st_ses->exec_mode == ST_EXEC_MODE_NONE)) {
ALOGW("%s: First stage is not in a valid state, continuing",
__func__);
pthread_mutex_unlock(&st_ses->lock);
continue;
}
if (IS_SS_DETECTION_SUCCESS(det_status)) {
clock_gettime(CLOCK_MONOTONIC, &tspec);
st_ses->hw_ses_current->second_stage_det_event_time =
get_current_time_ns();
ATRACE_ASYNC_END("sthal: detection success",
st_ses->sm_handle);
status = process_detection_event(st_ses,
st_ses->det_session_ev->payload.detected.timestamp,
st_ses->det_session_ev->payload.detected.detect_status,
st_ses->det_session_ev->payload.detected.detect_payload,
st_ses->det_session_ev->payload.detected.payload_size,
&event);
if (status || !event) {
ALOGE("%s:[%d] process_detection_event failed err %d",
__func__, st_ses->sm_handle, status);
/* Stop buffering if this is not a successful detection and
LAB is triggered in hw automatically */
st_ses->hw_ses_current->fptrs->stop_buffering(
st_ses->hw_ses_current, st_ses->lab_enabled);
pthread_mutex_unlock(&st_ses->lock);
if (event) {
free(event);
event = NULL;
}
goto exit;
}
callback = st_ses->callback;
ALOGD("%s: Second stage detected successfully"
", calling client callback", __func__);
st_ses->sent_detection_to_client = true;
pthread_mutex_unlock(&st_ses->lock);
ATRACE_BEGIN("sthal: client detection callback");
callback(event, st_ses->cookie);
ATRACE_END();
/*
* The client could unload the sound model during the callback,
* which would join this thread and wait for this thread exit
* as part of st_session_deinit() with st_session_lock held. By
* this time, the state is also moved to idle. To avoid
* deadlock, upon return from client callback, try acquiring
* lock only if not in idle state, else exit right away.
*/
do {
lock_status = pthread_mutex_trylock(&st_ses->lock);
} while (lock_status && (st_ses->current_state !=
idle_state_fn));
if (st_ses->current_state == idle_state_fn) {
ALOGV("%s:[%d] client unloaded after callback"
", lock status %d", __func__, st_ses->sm_handle,
lock_status);
if (!lock_status)
pthread_mutex_unlock(&st_ses->lock);
free(event);
event = NULL;
goto exit;
}
if (!st_ses->capture_requested)
st_ses->hw_ses_current->fptrs->stop_buffering(
st_ses->hw_ses_current, st_ses->lab_enabled);
free(event);
event = NULL;
} else {
ATRACE_ASYNC_END("sthal: detection reject",
st_ses->sm_handle);
ALOGD("%s: Second stage did NOT detect, restarting st_session",
__func__);
st_ses->hw_ses_current->fptrs->stop_buffering(
st_ses->hw_ses_current, st_ses->lab_enabled);
DISPATCH_EVENT(st_ses, restart_ev, status);
}
pthread_mutex_unlock(&st_ses->lock);
} else {
ALOGV("%s: There is a second stage session pending, continuing",
__func__);
}
}
exit:
pthread_mutex_unlock(&st_ses->ss_detections_lock);
ALOGV("%s: Exit", __func__);
return NULL;
}
static void init_det_event_aggregator(st_session_t *st_ses)
{
int status = 0;
pthread_condattr_t attr;
st_ses->exit_aggregator_loop = false;
pthread_mutex_init(&(st_ses->ss_detections_lock), NULL);
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&(st_ses->ss_detections_cond), &attr);
pthread_condattr_destroy(&attr);
status = pthread_create(&st_ses->aggregator_thread, NULL,
aggregator_thread_loop, st_ses);
if (status)
ALOGE("%s: Error creating aggregator thread. status = %d",
__func__, status);
}
static void destroy_det_event_aggregator(st_session_t *st_ses)
{
int status = 0;
st_ses->exit_aggregator_loop = true;
pthread_mutex_lock(&st_ses->ss_detections_lock);
pthread_cond_signal(&st_ses->ss_detections_cond);
pthread_mutex_unlock(&st_ses->ss_detections_lock);
status = pthread_join(st_ses->aggregator_thread, NULL);
if (status)
ALOGE("%s: Error joining aggregator thread. status = %d",
__func__, status);
pthread_cond_destroy(&(st_ses->ss_detections_cond));
pthread_mutex_destroy(&(st_ses->ss_detections_lock));
}
int st_session_ss_init(st_session_t *st_ses)
{
int status = 0;
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
init_det_event_aggregator(st_ses);
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
status = st_second_stage_module_init(st_sec_stage,
(void *)st_sec_stage->ss_info->lib_name);
if (status) {
ALOGE("%s: initializing second stage session failed %d",
__func__, status);
goto ss_cleanup;
}
}
st_ses->det_session_ev = calloc(1, sizeof(st_session_ev_t));
if (!st_ses->det_session_ev) {
ALOGE("%s: Failed to allocate st_session_ev_t, exiting", __func__);
status = -ENOMEM;
goto ss_cleanup;
}
return 0;
ss_cleanup:
destroy_det_event_aggregator(st_ses);
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
st_second_stage_module_deinit(st_sec_stage);
}
return status;
}
int st_session_ss_deinit(st_session_t *st_ses)
{
struct listnode *node = NULL, *tmp_node = NULL;
st_arm_second_stage_t *st_sec_stage = NULL;
destroy_det_event_aggregator(st_ses);
list_for_each_safe(node, tmp_node, &st_ses->second_stage_list) {
st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node);
st_second_stage_module_deinit(st_sec_stage);
}
if (st_ses->det_session_ev)
free(st_ses->det_session_ev);
return 0;
}
int st_session_request_detection(st_session_t *st_ses)
{
int status = 0;
if (!st_ses)
return -EINVAL;
st_session_ev_t ev = { .ev_id = ST_SES_EV_REQUEST_DET };
/* lock to serialize event handling */
pthread_mutex_lock(&st_ses->lock);
DISPATCH_EVENT(st_ses, ev, status);
pthread_mutex_unlock(&st_ses->lock);
return status;
}
int st_session_init(st_session_t *st_ses, struct sound_trigger_device *stdev,
st_exec_mode_t exec_mode, sound_model_handle_t sm_handle)
{
int status = 0;
struct st_vendor_info *v_info;
pthread_mutexattr_t attr;
if (!st_ses || !stdev) {
status = -EINVAL;
return status;
}
st_ses->stdev = stdev;
/* caller must set vendor_uuid_info directly if present */
v_info = st_ses->vendor_uuid_info;
if (v_info && (EXEC_MODE_CFG_DYNAMIC == v_info->exec_mode_cfg)) {
st_ses->enable_trans = true;
if (stdev->is_gcs) {
/* alloc and init cpe session*/
st_ses->hw_ses_cpe = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_gcs_t));
if (!st_ses->hw_ses_cpe) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_gcs_init(st_ses->hw_ses_cpe, hw_sess_cb,
(void *)st_ses, ST_EXEC_MODE_CPE, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing gcs hw session failed %d", __func__,
status);
goto cleanup;
}
/* alloc and init adsp session*/
st_ses->hw_ses_adsp = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_adsp) {
st_hw_sess_gcs_deinit(st_ses->hw_ses_cpe);
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_adsp, hw_sess_cb,
(void *)st_ses, ST_EXEC_MODE_ADSP, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing lsm session failed", __func__);
st_hw_sess_gcs_deinit(st_ses->hw_ses_cpe);
goto cleanup;
}
} else {
/* alloc and init cpe session*/
st_ses->hw_ses_cpe = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_cpe) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_cpe, hw_sess_cb,
(void *)st_ses, ST_EXEC_MODE_CPE, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initialzing lsm hw session failed %d", __func__,
status);
goto cleanup;
}
/* alloc and init adsp session*/
st_ses->hw_ses_adsp = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_adsp) {
status = -ENOMEM;
st_hw_sess_lsm_deinit(st_ses->hw_ses_cpe);
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_adsp, hw_sess_cb,
(void *)st_ses, ST_EXEC_MODE_ADSP, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing lsm session failed", __func__);
st_hw_sess_lsm_deinit(st_ses->hw_ses_cpe);
goto cleanup;
}
}
/* set current hw_session */
if (exec_mode == ST_EXEC_MODE_CPE)
st_ses->hw_ses_current = st_ses->hw_ses_cpe;
else if (exec_mode == ST_EXEC_MODE_ADSP)
st_ses->hw_ses_current = st_ses->hw_ses_adsp;
} else if (v_info && (EXEC_MODE_CFG_CPE == v_info->exec_mode_cfg)) {
st_ses->enable_trans = false;
if (stdev->is_gcs) {
ALOGD("%s: initializing gcs hw session", __func__);
st_ses->hw_ses_cpe = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_gcs_t));
if (!st_ses->hw_ses_cpe) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_gcs_init(st_ses->hw_ses_cpe, hw_sess_cb,
(void *)st_ses, exec_mode, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing gcs hw session failed %d",
__func__, status);
goto cleanup;
}
} else {
st_ses->hw_ses_cpe = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_cpe) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_cpe, hw_sess_cb,
(void *)st_ses, exec_mode, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing lsm hw session failed %d",
__func__, status);
goto cleanup;
}
}
st_ses->hw_ses_current = st_ses->hw_ses_cpe;
} else if (v_info && (EXEC_MODE_CFG_APE == v_info->exec_mode_cfg)) {
st_ses->enable_trans = false;
st_ses->hw_ses_adsp = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_adsp) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_adsp, hw_sess_cb,
(void *)st_ses, exec_mode, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing lsm hw session failed %d",
__func__, status);
goto cleanup;
}
st_ses->hw_ses_current = st_ses->hw_ses_adsp;
} else if (v_info && (EXEC_MODE_CFG_ARM == v_info->exec_mode_cfg)) {
st_ses->enable_trans = false;
st_ses->hw_ses_arm = calloc(1, sizeof(st_hw_session_pcm_t));
if (!st_ses->hw_ses_arm) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_pcm_init(st_ses->hw_ses_arm, hw_sess_cb,
(void *)st_ses, exec_mode, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing pcm hw session failed %d",
__func__, status);
goto cleanup;
}
st_ses->hw_ses_current = st_ses->hw_ses_arm;
} else if (!v_info) {
st_ses->hw_ses_cpe = (st_hw_session_t *)calloc(1, sizeof(st_hw_session_lsm_t));
if (!st_ses->hw_ses_cpe) {
status = -ENOMEM;
goto cleanup;
}
status = st_hw_sess_lsm_init(st_ses->hw_ses_cpe, hw_sess_cb,
(void *)st_ses, exec_mode, v_info, sm_handle, stdev);
if (status) {
ALOGE("%s: initializing lsm hw session failed %d",
__func__, status);
goto cleanup;
}
}
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&st_ses->lock, (const pthread_mutexattr_t *)&attr);
st_ses->exec_mode = exec_mode;
st_ses->sm_handle = sm_handle;
st_ses->ssr_transit_exec_mode = ST_EXEC_MODE_NONE;
/* start in idle state */
STATE_TRANSITION(st_ses, idle_state_fn);
return status;
cleanup:
if (st_ses->hw_ses_cpe)
free(st_ses->hw_ses_cpe);
if (st_ses->hw_ses_adsp)
free(st_ses->hw_ses_adsp);
return status;
}
int st_session_deinit(st_session_t *st_ses)
{
/* deinit cpe session */
if (st_ses->hw_ses_cpe) {
if (st_ses->stdev->is_gcs)
st_hw_sess_gcs_deinit(st_ses->hw_ses_cpe);
else
st_hw_sess_lsm_deinit(st_ses->hw_ses_cpe);
free((void *)st_ses->hw_ses_cpe);
st_ses->hw_ses_cpe = NULL;
}
/* deinit adsp session */
if (st_ses->hw_ses_adsp) {
st_hw_sess_lsm_deinit(st_ses->hw_ses_adsp);
free((void *)st_ses->hw_ses_adsp);
st_ses->hw_ses_adsp = NULL;
}
pthread_mutex_destroy(&st_ses->lock);
return 0;
}