| /* 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-2021, 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 |
| #define ST_SES_DEFERRED_STOP_SS_DELAY_MS 0 |
| #else |
| #define ST_SES_DEFERRED_STOP_DELAY_MS 1000 |
| #define ST_SES_DEFERRED_STOP_SS_DELAY_MS 250 |
| #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)) |
| |
| #define IS_KEYWORD_DETECTION_MODEL(sm_id) (sm_id & ST_SM_ID_SVA_KWD) |
| |
| #define IS_USER_VERIFICATION_MODEL(sm_id) (sm_id & ST_SM_ID_SVA_VOP) |
| |
| #define IS_SECOND_STAGE_MODEL(sm_id)\ |
| ((sm_id & ST_SM_ID_SVA_KWD) || (sm_id & ST_SM_ID_SVA_VOP)) |
| |
| #define IS_MATCHING_SS_MODEL(usecase_sm_id, levels_sm_id)\ |
| ((usecase_sm_id & levels_sm_id) ||\ |
| ((usecase_sm_id & ST_SM_ID_SVA_RNN) && (levels_sm_id & ST_SM_ID_SVA_CNN))) |
| |
| #define STATE_TRANSITION(st_session, new_state_fn)\ |
| do {\ |
| if (st_session->current_state != new_state_fn) {\ |
| st_session->current_state = new_state_fn;\ |
| ALOGD("session[%d]: %s ---> %s", st_session->sm_handle, __func__, \ |
| #new_state_fn);\ |
| }\ |
| } 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 |
| |
| #define MAX_CONF_LEVEL_VALUE (100) |
| #define MAX_KW_USERS_NAME_LEN (2 * MAX_STRING_LEN) |
| |
| /* 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, |
| }; |
| |
| typedef struct st_session_loadsm_payload { |
| struct sound_trigger_phrase_sound_model *phrase_sm; |
| } 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; |
| char *module_version; |
| } payload; |
| st_session_t *stc_ses; |
| }; |
| |
| static int idle_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| static int loaded_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| static int active_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| static int detected_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| static int buffering_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| static int ssr_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev); |
| |
| static inline int process_detection_event |
| ( |
| st_proxy_session_t *st_ses, uint64_t timestamp, int detect_status, |
| void *payload, size_t payload_size, |
| struct sound_trigger_recognition_event **event |
| ); |
| |
| ST_DBG_DECLARE(static int file_cnt = 0); |
| |
| void hw_sess_cb(st_hw_sess_event_t *hw_event, void *cookie) |
| { |
| st_proxy_session_t *st_ses = (st_proxy_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 == active_state_fn)); |
| |
| 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 != active_state_fn) { |
| ALOGV("%s:[%d] Session not in active state, ignore event", |
| __func__, st_ses->sm_handle); |
| } else if (!lock_status) { |
| /* |
| * TODO: Add RECOGNITION_STATUS_GET_STATE_RESPONSE to |
| * the SoundTrigger API header. |
| */ |
| if (st_ses->detection_requested) |
| ev.payload.detected.detect_status = 3; |
| |
| DISPATCH_EVENT(st_ses, ev, status); |
| } |
| |
| if (!lock_status) |
| pthread_mutex_unlock(&st_ses->lock); |
| break; |
| } |
| |
| case ST_HW_SESS_EVENT_BUFFERING_STOPPED: |
| { |
| st_session_ev_t ev; |
| ev.ev_id = ST_SES_EV_DEFERRED_STOP; |
| ev.stc_ses = st_ses->det_stc_ses; |
| |
| /* |
| * If detection is sent to client while in buffering state, |
| * and if internal buffering is stopped due to errors, stop |
| * session internally as client is expected to restart the |
| * detection if required. |
| * Note: It is possible that detection event is not sent to |
| * client if second stage is not yet detected during internal |
| * buffering stop, in which case restart is posted from second |
| * stage thread for further detections. Only if the second |
| * stage detection hasn't be started due to internal buffering |
| * stop too early, restart session should be explictily issued. |
| */ |
| |
| do { |
| lock_status = pthread_mutex_trylock(&st_ses->lock); |
| } while (lock_status && !st_ses->det_stc_ses->pending_stop && |
| (st_ses->current_state == buffering_state_fn) && |
| !st_ses->stdev->ssr_offline_received); |
| |
| if (st_ses->det_stc_ses->pending_stop) { |
| ALOGV("%s:[%d] pending stop already queued, ignore event", |
| __func__, st_ses->sm_handle); |
| } else if (!lock_status && !st_ses->det_stc_ses->detection_sent && |
| st_ses->current_state == buffering_state_fn) { |
| ev.ev_id = ST_SES_EV_RESTART; |
| DISPATCH_EVENT(st_ses, ev, status); |
| ALOGV("%s:[%d] client callback hasn't been called, restart detection evt_id(%d)", |
| __func__, st_ses->sm_handle, ev.ev_id); |
| } else if (st_ses->current_state != buffering_state_fn) { |
| ALOGV("%s:[%d] session already stopped buffering, ignore event", |
| __func__, st_ses->sm_handle); |
| } else if (st_ses->stdev->ssr_offline_received) { |
| 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 inline struct st_proxy_ses_sm_info_wrapper *get_sm_info_for_model_id |
| ( |
| st_proxy_session_t *st_ses, |
| uint32_t model_id |
| ) |
| { |
| struct listnode *node = NULL; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| |
| list_for_each(node, &st_ses->sm_info_list) { |
| p_info = node_to_item(node, struct st_proxy_ses_sm_info_wrapper, |
| sm_list_node); |
| |
| if (p_info->sm_info.model_id == model_id) |
| return p_info; |
| } |
| |
| return NULL; |
| } |
| |
| static inline struct st_hw_ses_config *get_sthw_cfg_for_model_id |
| ( |
| st_hw_session_t *hw_ses, |
| uint32_t model_id |
| ) |
| { |
| struct listnode *node = NULL; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| |
| list_for_each(node, &hw_ses->sthw_cfg_list) { |
| sthw_cfg = node_to_item(node, struct st_hw_ses_config, |
| sthw_cfg_list_node); |
| |
| if (sthw_cfg->model_id == model_id) |
| return sthw_cfg; |
| } |
| |
| return NULL; |
| } |
| |
| static inline void free_array_ptrs(char **arr, unsigned int arr_len) |
| { |
| int i = 0; |
| |
| if (!arr) |
| return; |
| |
| for (i = 0; i < arr_len; i++) { |
| if (arr[i]) { |
| free(arr[i]); |
| arr[i] = NULL; |
| } |
| } |
| free(arr); |
| arr = NULL; |
| } |
| |
| static inline void alloc_array_ptrs(char ***arr, unsigned int arr_len, |
| unsigned int elem_len) |
| { |
| char **str_arr = NULL; |
| int i = 0; |
| |
| str_arr = (char **) calloc(arr_len, sizeof(char *)); |
| |
| if (!str_arr) { |
| *arr = NULL; |
| return; |
| } |
| |
| for (i = 0; i < arr_len; i++) { |
| str_arr[i] = (char *) calloc(elem_len, sizeof(char)); |
| if (str_arr[i] == NULL) { |
| free_array_ptrs(str_arr, i); |
| *arr = NULL; |
| return; |
| } |
| } |
| *arr = str_arr; |
| ALOGV("%s: string array %p", __func__, *arr); |
| for (i = 0; i < arr_len; i++) |
| ALOGV("%s: string array[%d] %p", __func__, i, (*arr)[i]); |
| } |
| |
| static int merge_sound_models(struct sound_trigger_device *stdev, |
| unsigned int num_models, listen_model_type *in_models[], |
| listen_model_type *out_model) |
| { |
| listen_status_enum sm_ret = 0; |
| int status = 0; |
| |
| ALOGV("%s: num_models to merge %d", __func__, num_models); |
| |
| if (!stdev->smlib_handle) { |
| ALOGE("%s: NULL sound model lib handle", __func__); |
| return -ENOSYS; |
| } |
| |
| sm_ret = stdev->smlib_getMergedModelSize(num_models, in_models, |
| &out_model->size); |
| if ((sm_ret != kSucess) || !out_model->size) { |
| ALOGE("%s: smlib_getMergedModelSize failed, err %d, size %d", __func__, |
| sm_ret, out_model->size); |
| return -EINVAL; |
| } |
| ALOGD("%s: merge sound model size %d", __func__, out_model->size); |
| |
| out_model->data = calloc(1, out_model->size * sizeof(char)); |
| if (!out_model->data) { |
| ALOGE("%s: Merged sound model allocation failed", __func__); |
| return -ENOMEM; |
| } |
| |
| sm_ret = stdev->smlib_mergeModels(num_models, in_models, out_model); |
| if (sm_ret != kSucess) { |
| ALOGE("%s: smlib_mergeModels failed, err %d", __func__, sm_ret); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| if (!out_model->data || !out_model->size) { |
| ALOGE("%s: MergeModels returned NULL data or size %d", __func__, |
| out_model->size); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| if (stdev->enable_debug_dumps) { |
| ST_DBG_DECLARE(FILE *sm_fd = NULL; static int sm_cnt = 0); |
| ST_DBG_FILE_OPEN_WR(sm_fd, ST_DEBUG_DUMP_LOCATION, |
| "st_smlib_output_merged_sm", "bin", sm_cnt); |
| ST_DBG_FILE_WRITE(sm_fd, out_model->data, out_model->size); |
| ST_DBG_FILE_CLOSE(sm_fd); |
| ALOGD("%s: SM returned from SML merge stored in: st_smlib_output_merged_sm_%d.bin", |
| __func__, sm_cnt); |
| sm_cnt++; |
| } |
| ALOGV("%s: Exit", __func__); |
| return 0; |
| |
| cleanup: |
| if (out_model->data) { |
| free(out_model->data); |
| out_model->data = NULL; |
| out_model->size = 0; |
| } |
| return status; |
| } |
| |
| static int delete_from_merged_sound_model(struct sound_trigger_device *stdev, |
| char **keyphrases, unsigned int num_keyphrases, |
| listen_model_type *in_model, listen_model_type *out_model) |
| { |
| listen_model_type merge_model = {0}; |
| listen_status_enum sm_ret = 0; |
| unsigned int out_model_sz = 0; |
| int status = 0, i = 0; |
| |
| out_model->data = NULL; |
| out_model->size = 0; |
| merge_model.data = in_model->data; |
| merge_model.size = in_model->size; |
| |
| for (i = 0; i < num_keyphrases; i++) { |
| sm_ret = stdev->smlib_getSizeAfterDeleting(&merge_model, keyphrases[i], |
| NULL, &out_model_sz); |
| if (sm_ret != kSucess) { |
| ALOGE("%s: smlib_getSizeAfterDeleting failed %d", __func__, sm_ret); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| if (out_model_sz >= in_model->size) { |
| ALOGE("%s: unexpected, smlib_getSizeAfterDeleting returned size %d" |
| "not less than merged model size %d", __func__, |
| out_model_sz, in_model->size); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| ALOGV("%s: Size after deleting kw[%d] = %d", __func__, i, out_model_sz); |
| if (!out_model->data) { |
| /* Valid if deleting multiple keyphrases one after other */ |
| free (out_model->data); |
| out_model->size = 0; |
| } |
| out_model->data = calloc(1, out_model_sz * sizeof(char)); |
| if (!out_model->data) { |
| ALOGE("%s: Merge sound model allocation failed, size %d ", __func__, |
| out_model_sz); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| out_model->size = out_model_sz; |
| |
| sm_ret = stdev->smlib_deleteFromModel(&merge_model, keyphrases[i], |
| NULL, out_model); |
| if (sm_ret != kSucess) { |
| ALOGE("%s: smlib_getSizeAfterDeleting failed %d", __func__, sm_ret); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| if (out_model->size != out_model_sz) { |
| ALOGE("%s: unexpected, out_model size %d != expected size %d", |
| __func__, out_model->size, out_model_sz); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| /* Used if deleting multiple keyphrases one after other */ |
| merge_model.data = out_model->data; |
| merge_model.size = out_model->size; |
| } |
| if (stdev->enable_debug_dumps && out_model->data && out_model->size) { |
| ST_DBG_DECLARE(FILE *sm_fd = NULL; static int sm_cnt = 0); |
| ST_DBG_FILE_OPEN_WR(sm_fd, ST_DEBUG_DUMP_LOCATION, |
| "st_smlib_output_deleted_sm", "bin", sm_cnt); |
| ST_DBG_FILE_WRITE(sm_fd, out_model->data, out_model->size); |
| ST_DBG_FILE_CLOSE(sm_fd); |
| ALOGD("%s: SM returned from SML delete stored in: st_smlib_output_deleted_sm_%d.bin", |
| __func__, sm_cnt); |
| sm_cnt++; |
| } |
| return 0; |
| |
| cleanup: |
| if (out_model->data) { |
| free(out_model->data); |
| out_model->data = NULL; |
| } |
| return status; |
| } |
| |
| static void release_sound_model_info(struct sound_model_info *sm_info) |
| { |
| ALOGV("%s", __func__); |
| |
| if (sm_info->cf_levels) { |
| free(sm_info->cf_levels); |
| sm_info->cf_levels = NULL; |
| sm_info->det_cf_levels = NULL; |
| } |
| free_array_ptrs(sm_info->keyphrases, sm_info->num_keyphrases); |
| sm_info->keyphrases = NULL; |
| |
| free_array_ptrs(sm_info->users, sm_info->num_users); |
| sm_info->users = NULL; |
| |
| free_array_ptrs(sm_info->cf_levels_kw_users, sm_info->cf_levels_size); |
| sm_info->cf_levels_kw_users = NULL; |
| } |
| |
| static inline void stdbg_print_sound_model_header( |
| listen_sound_model_header *smh) |
| { |
| int i = 0, j = 0; |
| |
| ALOGV("%s", __func__); |
| ALOGV("numKeywords = %d", smh->numKeywords); |
| ALOGV("numUsers = %d", smh->numUsers); |
| ALOGV("numActiveUserKeywordPairs = %d", smh->numActiveUserKeywordPairs); |
| ALOGV("isStripped = %d", smh->isStripped); |
| ALOGV("model_indicator = %d", smh->model_indicator); |
| |
| for (i = 0; i < smh->numKeywords; i++) { |
| ALOGV("kw-%d langPerKw = %d", i, smh->langPerKw[i]); |
| ALOGV("kw-%d numUsersSetPerKw = %d", i, smh->numUsersSetPerKw[i]); |
| ALOGV("kw-%d isUserDefinedKeyword = %d", i, |
| smh->isUserDefinedKeyword[i]); |
| } |
| if (smh->userKeywordPairFlags) { |
| for (i = 0; i < smh->numUsers; i++) { |
| for (j = 0; j < smh->numKeywords; j++) |
| ALOGV("userKeywordPairFlags[%d][%d] = %d", i, j, |
| smh->userKeywordPairFlags[i][j]); |
| } |
| } |
| } |
| |
| static int query_sound_model(struct sound_trigger_device *stdev, |
| struct sound_model_info *sm_info, unsigned char *sm_data, |
| unsigned int sm_size) |
| { |
| listen_sound_model_header sm_header = {0}; |
| listen_model_type model = {0}; |
| listen_status_enum sm_ret = 0; |
| int status = 0, i = 0, j = 0, k = 0; |
| uint16_t tmp = 0; |
| |
| ALOGV("%s: enter sm_size %d", __func__, sm_size); |
| |
| if (!stdev->smlib_handle) { |
| ALOGE("%s: NULL sound model lib handle", __func__); |
| return -ENOSYS; |
| } |
| |
| model.data = sm_data; |
| model.size = sm_size; |
| |
| sm_ret = stdev->smlib_getSoundModelHeader(&model, &sm_header); |
| if (sm_ret != kSucess) { |
| ALOGE("%s: smlib_getSoundModelHeader failed, err %d ", __func__, sm_ret); |
| return -EINVAL; |
| } |
| if (sm_header.numKeywords == 0) { |
| ALOGE("%s: num keywords zero!!", __func__); |
| return -EINVAL; |
| } |
| if (sm_header.numActiveUserKeywordPairs < sm_header.numUsers) { |
| ALOGE("%s: smlib activeUserKwPairs(%d) < total users (%d)", __func__, |
| sm_header.numActiveUserKeywordPairs, sm_header.numUsers); |
| goto cleanup; |
| } |
| if (sm_header.numUsers && !sm_header.userKeywordPairFlags) { |
| ALOGE("%s: userKeywordPairFlags is NULL, numUsers (%d)", __func__, |
| sm_header.numUsers); |
| goto cleanup; |
| } |
| stdbg_print_sound_model_header(&sm_header); |
| |
| /* MAX_STRING_LEN is part of listen sound model header file */ |
| alloc_array_ptrs(&sm_info->keyphrases, sm_header.numKeywords, |
| MAX_STRING_LEN); |
| if (!sm_info->keyphrases) { |
| ALOGE("%s: keyphrases allocation failed", __func__); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| sm_info->num_keyphrases = sm_header.numKeywords; |
| sm_info->num_users = sm_header.numUsers; |
| |
| tmp = sm_header.numKeywords; |
| ALOGV("%s: stdb: model.data %pK, model.size %d", __func__, model.data, |
| model.size); |
| sm_ret = stdev->smlib_getKeywordPhrases(&model, &tmp, sm_info->keyphrases); |
| if (sm_ret) { |
| ALOGE("%s: smlib_getKeywordPhrases failed, err %d ", __func__, sm_ret); |
| goto cleanup; |
| } |
| if (tmp != sm_header.numKeywords) { |
| ALOGE("%s: smlib_getkeywordPhrases(%d) != sml header (%d)", __func__, |
| tmp, sm_header.numKeywords); |
| goto cleanup; |
| } |
| for (i = 0; i < sm_header.numKeywords; i++) |
| ALOGV("%s: keyphrases names = %s", __func__, sm_info->keyphrases[i]); |
| |
| if (sm_header.numUsers) { |
| alloc_array_ptrs(&sm_info->users, sm_header.numUsers, MAX_STRING_LEN); |
| if (!sm_info->users) { |
| ALOGE("%s: users allocation failed", __func__); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| |
| tmp = sm_header.numUsers; |
| sm_ret = stdev->smlib_getUserNames(&model, &tmp, sm_info->users); |
| if (sm_ret) { |
| ALOGE("%s: smlib_getUserNames failed, err %d ", __func__, sm_ret); |
| goto cleanup; |
| } |
| if (tmp != sm_header.numUsers) { |
| ALOGE("%s: smlib_getUserNames(%d) != sml header (%d)", __func__, |
| tmp, sm_header.numUsers); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| for (i = 0; i < sm_header.numUsers; i++) |
| ALOGV("%s: users names = %s", __func__, sm_info->users[i]); |
| } |
| |
| sm_info->cf_levels_size = sm_header.numKeywords + |
| sm_header.numActiveUserKeywordPairs; |
| alloc_array_ptrs(&sm_info->cf_levels_kw_users, sm_info->cf_levels_size, |
| MAX_KW_USERS_NAME_LEN); |
| if (!sm_info->cf_levels_kw_users) { |
| ALOGE("%s: cf_levels_kw_users allocation failed", __func__); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| |
| /* Used later for mapping client to/from merged DSP confidence levels */ |
| sm_info->cf_levels = calloc(1, 2 * sm_info->cf_levels_size); |
| if (!sm_info->cf_levels) { |
| ALOGE("%s: cf_levels allocation failed", __func__); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| /* |
| * Used for updating detection confidence level values from DSP merged |
| * detection conf levels |
| */ |
| sm_info->det_cf_levels = sm_info->cf_levels + sm_info->cf_levels_size; |
| |
| /* |
| * Used for conf level setting to DSP. Reset the conf value to max value, |
| * so that the keyword of a loaded and in-active model in a merged model |
| * doesn't detect. |
| */ |
| memset(sm_info->cf_levels, MAX_CONF_LEVEL_VALUE, sm_info->cf_levels_size); |
| |
| /* |
| * Derive the confidence level payload for keyword and user pairs. |
| * Store the user-keyword pair names in an array that will be used for |
| * mapping the DSP detection and confidence levels to the client. |
| */ |
| char **kw_names = sm_info->cf_levels_kw_users; |
| char **ukw_names = &sm_info->cf_levels_kw_users[sm_header.numKeywords]; |
| int ukw_idx = 0; |
| |
| for (i = 0; i < sm_header.numKeywords; i++) { |
| strlcpy(kw_names[i], sm_info->keyphrases[i], MAX_KW_USERS_NAME_LEN); |
| if (!sm_header.numUsersSetPerKw) |
| continue; |
| for (j = 0, k = 0; j < sm_header.numUsers; j++) { |
| if (k >= sm_header.numUsersSetPerKw[i]) |
| break; |
| if (sm_header.userKeywordPairFlags[j][i]) { |
| strlcpy(ukw_names[ukw_idx], sm_info->users[j], |
| MAX_KW_USERS_NAME_LEN); |
| strlcat(ukw_names[ukw_idx], sm_info->keyphrases[i], |
| MAX_KW_USERS_NAME_LEN); |
| ukw_idx++; |
| k++; |
| } |
| } |
| } |
| for (i = 0; i < sm_info->cf_levels_size; i++) |
| ALOGV("%s: cf_levels_kw_users = %s", __func__, |
| sm_info->cf_levels_kw_users[i]); |
| |
| sm_ret = stdev->smlib_releaseSoundModelHeader(&sm_header); |
| if (sm_ret != kSucess) { |
| ALOGE("%s: smlib_releaseSoundModelHeader failed, err %d ", __func__, |
| sm_ret); |
| status = -EINVAL; |
| goto cleanup_1; |
| } |
| ALOGV("%s: exit", __func__); |
| return 0; |
| |
| cleanup: |
| sm_ret = stdev->smlib_releaseSoundModelHeader(&sm_header); |
| if (sm_ret != kSucess) |
| ALOGE("%s: smlib_releaseSoundModelHeader failed, err %d ", __func__, |
| sm_ret); |
| |
| cleanup_1: |
| release_sound_model_info(sm_info); |
| |
| return status; |
| } |
| |
| static int add_sound_model(st_session_t *stc_ses, unsigned char *sm_data, |
| unsigned int sm_size) |
| { |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| listen_model_type **in_models = NULL; |
| listen_model_type out_model = {0}; |
| struct sound_model_info sm_info = {0}; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| int status = 0, num_models = 0; |
| |
| ALOGV("%s:[c%d]", __func__, stc_ses->sm_handle); |
| if (stc_ses->sm_info.sm_data) { |
| ALOGD("%s:[c%d] Already added", __func__, stc_ses->sm_handle); |
| return 0; |
| } |
| if (!st_ses->vendor_uuid_info->merge_fs_soundmodels || |
| stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| stc_ses->sm_info.sm_data = sm_data; |
| stc_ses->sm_info.sm_size = sm_size; |
| stc_ses->sm_info.sm_type = stc_ses->sm_type; |
| stc_ses->sm_info.model_id = |
| (stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) ? |
| stc_ses->sm_handle : 0; |
| |
| p_info = calloc(1, sizeof(struct st_proxy_ses_sm_info_wrapper)); |
| if (!p_info) { |
| ALOGE("%s: failed to alloc struct st_proxy_ses_sm_info_wrapper", |
| __func__); |
| return -ENOMEM; |
| } |
| |
| memcpy((uint8_t *)&p_info->sm_info, (uint8_t *)&stc_ses->sm_info, |
| sizeof(struct sound_model_info)); |
| |
| if (stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| st_ses->recognition_mode |= stc_ses->recognition_mode; |
| p_info->sm_info.cf_levels = calloc(1, 2 * MAX_MULTI_SM_CONF_LEVELS); |
| if (!p_info->sm_info.cf_levels) { |
| ALOGE("%s: failed to alloc cf_levels", |
| __func__); |
| free(p_info); |
| return -ENOMEM; |
| } |
| memset(p_info->sm_info.cf_levels, MAX_CONF_LEVEL_VALUE, |
| MAX_MULTI_SM_CONF_LEVELS); |
| p_info->sm_info.det_cf_levels = p_info->sm_info.cf_levels + |
| MAX_MULTI_SM_CONF_LEVELS; |
| memset(p_info->sm_info.det_cf_levels, 0, |
| MAX_MULTI_SM_CONF_LEVELS); |
| stc_ses->sm_info.cf_levels = p_info->sm_info.cf_levels; |
| stc_ses->sm_info.det_cf_levels = p_info->sm_info.det_cf_levels; |
| } |
| list_add_tail(&st_ses->sm_info_list, &p_info->sm_list_node); |
| if (stc_ses->f_stage_version == ST_MODULE_TYPE_GMM) |
| ALOGD("%s:[c%d] no merge", __func__, stc_ses->sm_handle); |
| return 0; |
| } |
| /* get sound model header information for client model */ |
| status = query_sound_model(st_ses->stdev, &stc_ses->sm_info, |
| sm_data, sm_size); |
| if (status) |
| return status; |
| |
| stc_ses->sm_info.sm_data = sm_data; |
| stc_ses->sm_info.sm_size = sm_size; |
| ALOGV("%s: stc_ses %pK - sm_data %pK, sm_size %d", __func__, |
| stc_ses, stc_ses->sm_info.sm_data, |
| stc_ses->sm_info.sm_size); |
| |
| /* Check for remaining client sound models to merge */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses != stc_ses) && c_ses->sm_info.sm_data) |
| num_models++; |
| } |
| if (!num_models) { |
| p_info = calloc(1, sizeof(struct st_proxy_ses_sm_info_wrapper)); |
| if (!p_info) { |
| ALOGE("%s: failed to alloc struct st_proxy_ses_sm_info_wrapper", |
| __func__); |
| return -ENOMEM; |
| } |
| st_ses->recognition_mode = stc_ses->recognition_mode; |
| stc_ses->sm_info.sm_type = stc_ses->sm_type; |
| stc_ses->sm_info.model_id = 0; |
| memcpy((uint8_t *)&p_info->sm_info, (uint8_t *)&stc_ses->sm_info, |
| sizeof(struct sound_model_info)); |
| st_ses->sm_merged = false; |
| list_add_tail(&st_ses->sm_info_list, &p_info->sm_list_node); |
| ALOGD("%s: Copy from single client c%d model, size %d", __func__, |
| stc_ses->sm_handle, stc_ses->sm_info.sm_size); |
| return 0; |
| } |
| ALOGV("%s: num existing models %d", __func__, num_models); |
| /* |
| * Merge this client model with already existing merged model due to other |
| * clients models. |
| */ |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching model_id in sm_info list," |
| "num current models %d", __func__, num_models); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| |
| if (!p_info->sm_info.sm_data) { |
| if (num_models == 1) { |
| /* |
| * Its not a merged model yet, but proxy ses sm_data is valid |
| * and must be pointing to client sm_data |
| */ |
| ALOGE("%s: Unexpected, sm_data NULL, num current" |
| "models %d", __func__, num_models); |
| status = -EINVAL; |
| goto cleanup; |
| } else if (!st_ses->sm_merged) { |
| ALOGE("%s: Unexpected, no pre-existing merged model," |
| "num current models %d", __func__, num_models); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| } |
| |
| /* Merge this client model with remaining clients models */ |
| num_models = 2;/* re-use */ |
| alloc_array_ptrs((char***)&in_models, num_models, sizeof(listen_model_type)); |
| if (!in_models) { |
| ALOGE("%s: in_models allocation failed", __func__); |
| status = -ENOMEM; |
| goto cleanup; |
| } |
| /* Add existing model */ |
| in_models[0]->data = p_info->sm_info.sm_data; |
| in_models[0]->size = p_info->sm_info.sm_size; |
| /* Add this client model */ |
| in_models[1]->data = sm_data; |
| in_models[1]->size = sm_size; |
| |
| status = merge_sound_models(st_ses->stdev, 2, in_models, &out_model); |
| if (status) |
| goto cleanup; |
| |
| sm_info.sm_data = out_model.data; |
| sm_info.sm_size = out_model.size; |
| sm_info.model_id = 0; |
| |
| status = query_sound_model(st_ses->stdev, &sm_info, |
| out_model.data, out_model.size); |
| if (status) |
| goto cleanup; |
| |
| if (out_model.size < p_info->sm_info.sm_size) { |
| ALOGE("%s: Unexpected, merged model sz %d < current sz %d", |
| __func__, out_model.size, p_info->sm_info.sm_size); |
| release_sound_model_info(&sm_info); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| free_array_ptrs((char **)in_models, num_models); |
| in_models = NULL; |
| |
| /* Update the new merged model */ |
| if (st_ses->sm_merged && p_info->sm_info.sm_data) { |
| release_sound_model_info(&p_info->sm_info); |
| free(p_info->sm_info.sm_data); |
| } |
| ALOGD("%s: Updated sound model: current size %d, new size %d", __func__, |
| p_info->sm_info.sm_size, out_model.size); |
| memcpy((uint8_t *)&p_info->sm_info, (uint8_t *)&sm_info, |
| sizeof(struct sound_model_info)); |
| st_ses->sm_merged = true; |
| |
| /* |
| * If any of the clients has user identificaiton enabled, underlying |
| * hw session has to operate with user identification enabled. |
| */ |
| if (stc_ses->recognition_mode & RECOGNITION_MODE_USER_IDENTIFICATION) |
| st_ses->recognition_mode |= stc_ses->recognition_mode; |
| |
| return 0; |
| |
| cleanup: |
| release_sound_model_info(&stc_ses->sm_info); |
| stc_ses->sm_info.sm_data = NULL; |
| |
| if (out_model.data) |
| free(out_model.data); |
| |
| if (in_models) |
| free_array_ptrs((char **)in_models, num_models); |
| |
| return status; |
| } |
| |
| static int delete_sound_model(st_session_t *stc_ses) |
| { |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL, *c_ses_rem = NULL; |
| listen_model_type in_model = {0}; |
| listen_model_type out_model = {0}; |
| struct sound_model_info sm_info = {0}; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| int status = 0, num_models = 0; |
| unsigned int rec_mode = RECOGNITION_MODE_VOICE_TRIGGER; |
| |
| ALOGV("%s:[c%d]", __func__, stc_ses->sm_handle); |
| if (!stc_ses->sm_info.sm_data) { |
| ALOGD("%s:[c%d] Already deleted", __func__, stc_ses->sm_handle); |
| return 0; |
| } |
| |
| p_info = get_sm_info_for_model_id(st_ses, stc_ses->sm_info.model_id); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| return -EINVAL; |
| } |
| |
| if (!st_ses->vendor_uuid_info->merge_fs_soundmodels || |
| stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| list_remove(&p_info->sm_list_node); |
| /* |
| * As it directly points to client model, just set sm_data |
| * as NULL without freeing |
| */ |
| if (stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| /* Update overall recogniton mode from remaining clients */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses != stc_ses) && c_ses->sm_info.sm_data) { |
| if (c_ses->recognition_mode & |
| RECOGNITION_MODE_USER_IDENTIFICATION) |
| rec_mode |= RECOGNITION_MODE_USER_IDENTIFICATION; |
| } |
| } |
| st_ses->recognition_mode = rec_mode; |
| |
| if (p_info->sm_info.cf_levels) { |
| free(p_info->sm_info.cf_levels); |
| p_info->sm_info.cf_levels = NULL; |
| } |
| } |
| p_info->sm_info.sm_data = NULL; |
| free(p_info); |
| stc_ses->sm_info.sm_data = NULL; |
| if (stc_ses->f_stage_version == ST_MODULE_TYPE_GMM) |
| ALOGD("%s:[c%d] no merge", __func__, stc_ses->sm_handle); |
| return 0; |
| } |
| |
| ALOGV("%s: stc_ses %pK - sm_data %pK, sm_size %d", __func__, |
| stc_ses, stc_ses->sm_info.sm_data, |
| stc_ses->sm_info.sm_size); |
| |
| /* Check for remaining clients sound models to merge */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses != stc_ses) && c_ses->sm_info.sm_data) { |
| c_ses_rem = c_ses; |
| num_models++; |
| } |
| } |
| |
| if (num_models == 0) { |
| ALOGD("%s: No remaining models", __func__); |
| /* Delete current client model */ |
| release_sound_model_info(&p_info->sm_info); |
| list_remove(&p_info->sm_list_node); |
| p_info->sm_info.sm_data = NULL; |
| free(p_info); |
| stc_ses->sm_info.sm_data = NULL; |
| return 0; |
| } |
| |
| if (num_models == 1) { |
| ALOGD("%s: reuse only remaining client model, size %d", __func__, |
| c_ses_rem->sm_info.sm_size); |
| /* If only one remaining client model exists, re-use it */ |
| if (st_ses->sm_merged) { |
| release_sound_model_info(&p_info->sm_info); |
| if (p_info->sm_info.sm_data) |
| free(p_info->sm_info.sm_data); |
| } |
| memcpy((uint8_t *)&p_info->sm_info, (uint8_t *)&c_ses_rem->sm_info, |
| sizeof(struct sound_model_info)); |
| st_ses->sm_merged = false; |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(st_ses->hw_ses_current, 0); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| return -EINVAL; |
| } |
| |
| sthw_cfg->conf_levels = p_info->sm_info.cf_levels; |
| sthw_cfg->num_conf_levels = |
| p_info->sm_info.cf_levels_size; |
| st_ses->recognition_mode = c_ses_rem->recognition_mode; |
| /* Delete current client model */ |
| release_sound_model_info(&stc_ses->sm_info); |
| stc_ses->sm_info.sm_data = NULL; |
| return 0; |
| } |
| /* Update overall recogniton mode from remaining clients */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses != stc_ses) && c_ses->sm_info.sm_data) { |
| if (c_ses->recognition_mode & RECOGNITION_MODE_USER_IDENTIFICATION) |
| rec_mode |= RECOGNITION_MODE_USER_IDENTIFICATION; |
| } |
| } |
| |
| /* |
| * Delete this client model with already existing merged model due to other |
| * clients models. |
| */ |
| if (!st_ses->sm_merged || !p_info->sm_info.sm_data) { |
| ALOGE("%s: Unexpected, no pre-existing merged model to delete from," |
| "num current models %d", __func__, num_models); |
| goto cleanup; |
| } |
| |
| /* Existing merged model from which the current client model to be deleted */ |
| in_model.data = p_info->sm_info.sm_data; |
| in_model.size = p_info->sm_info.sm_size; |
| |
| status = delete_from_merged_sound_model(st_ses->stdev, |
| stc_ses->sm_info.keyphrases, stc_ses->sm_info.num_keyphrases, |
| &in_model, &out_model); |
| |
| if (status) |
| goto cleanup; |
| |
| sm_info.sm_data = out_model.data; |
| sm_info.sm_size = out_model.size; |
| sm_info.model_id = 0; |
| |
| /* Update existing merged model info with new merged model */ |
| status = query_sound_model(st_ses->stdev, &sm_info, out_model.data, |
| out_model.size); |
| if (status) |
| goto cleanup; |
| |
| if (out_model.size > p_info->sm_info.sm_size) { |
| ALOGE("%s: Unexpected, merged model sz %d > current sz %d", |
| __func__, out_model.size, p_info->sm_info.sm_size); |
| release_sound_model_info(&sm_info); |
| status = -EINVAL; |
| goto cleanup; |
| } |
| |
| if (st_ses->sm_merged && p_info->sm_info.sm_data) { |
| release_sound_model_info(&p_info->sm_info); |
| free(p_info->sm_info.sm_data); |
| } |
| |
| ALOGD("%s: Updated sound model: current size %d, new size %d", __func__, |
| p_info->sm_info.sm_size, out_model.size); |
| memcpy((uint8_t *)&p_info->sm_info, (uint8_t *)&sm_info, |
| sizeof(struct sound_model_info)); |
| st_ses->sm_merged = true; |
| /* |
| * If any of the remaining clients has user identificaiton enabled, |
| * underlying hw session has to operate with user identificaiton enabled. |
| */ |
| st_ses->recognition_mode = rec_mode; |
| |
| /* Release current client model */ |
| release_sound_model_info(&stc_ses->sm_info); |
| stc_ses->sm_info.sm_data = NULL; |
| |
| return 0; |
| |
| cleanup: |
| release_sound_model_info(&stc_ses->sm_info); |
| stc_ses->sm_info.sm_data = NULL; |
| |
| if (out_model.data) |
| free(out_model.data); |
| |
| return status; |
| } |
| |
| static int update_sound_model(st_session_t *stc_ses, bool add) |
| { |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| struct sound_trigger_phrase_sound_model *phrase_sm = stc_ses->phrase_sm; |
| struct sound_trigger_sound_model *common_sm = |
| (struct sound_trigger_sound_model*)stc_ses->phrase_sm; |
| unsigned char *sm_data = NULL; |
| unsigned int sm_size = 0; |
| int status = 0; |
| |
| ALOGV("%s: Enter", __func__); |
| |
| if (stc_ses->sm_type == SOUND_MODEL_TYPE_KEYPHRASE) { |
| sm_data = (unsigned char*)phrase_sm + phrase_sm->common.data_offset; |
| sm_size = phrase_sm->common.data_size; |
| } else { |
| sm_data = (unsigned char*)common_sm + common_sm->data_offset; |
| sm_size = common_sm->data_size; |
| } |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (add) |
| status = add_sound_model(stc_ses, sm_data, sm_size); |
| else |
| status = delete_sound_model(stc_ses); |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| ALOGV("%s: Exit", __func__); |
| return status; |
| } |
| |
| static int update_merge_conf_levels_payload(st_proxy_session_t *st_ses, |
| struct sound_model_info *src_sm_info, unsigned char *src, |
| unsigned int src_size, bool set) |
| { |
| int i = 0, j = 0; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| |
| if (!st_ses || !src) { |
| ALOGE("%s: NULL pointer", __func__); |
| return -EINVAL; |
| } |
| |
| if (!st_ses->sm_merged) |
| return 0; |
| |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| return -EINVAL; |
| } |
| |
| if (src_size > p_info->sm_info.cf_levels_size) { |
| ALOGE("%s:[%d] Unexpected, client conf levels %d > " |
| "merged conf levels %d", __func__, st_ses->sm_handle, |
| src_size, p_info->sm_info.cf_levels_size); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < src_size; i++) |
| ALOGV("%s: src cf_levels[%d]=%d", __func__, i, src[i]); |
| |
| /* Populate DSP merged sound model conf levels */ |
| for (i = 0; i < src_size; i++) { |
| for (j = 0; j < p_info->sm_info.cf_levels_size; j++) { |
| if (!strcmp(p_info->sm_info.cf_levels_kw_users[j], |
| src_sm_info->cf_levels_kw_users[i])) { |
| if (set) { |
| p_info->sm_info.cf_levels[j] = src[i]; |
| ALOGV("%s: set: cf_levels[%d]=%d", __func__, |
| j, p_info->sm_info.cf_levels[j]); |
| } else { |
| p_info->sm_info.cf_levels[j] = MAX_CONF_LEVEL_VALUE; |
| ALOGV("%s: reset: cf_levels[%d]=%d", __func__, |
| j, p_info->sm_info.cf_levels[j]); |
| } |
| } |
| } |
| } |
| return 0; |
| } |
| |
| static int update_merge_conf_levels_payload_with_active_clients( |
| st_proxy_session_t *st_ses) |
| { |
| int status = 0; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| ALOGV("%s: update merge conf levels with other active" |
| "client %d ", __func__, c_ses->sm_handle); |
| status = update_merge_conf_levels_payload(st_ses, |
| &c_ses->sm_info, c_ses->sm_info.cf_levels, |
| c_ses->sm_info.cf_levels_size, true); |
| if (status) |
| return status; |
| } |
| } |
| return status; |
| } |
| |
| static void check_and_extract_det_conf_levels_payload( |
| st_proxy_session_t *st_ses, |
| unsigned char *src, unsigned int src_size, |
| unsigned char **dst, unsigned int *dst_size) |
| { |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| int i = 0, j = 0; |
| |
| *dst = src; |
| *dst_size = src_size; |
| |
| if (!st_ses->vendor_uuid_info->merge_fs_soundmodels || |
| !st_ses->sm_merged || |
| stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| ALOGV("%s:[%d] not merged", __func__, st_ses->sm_handle); |
| return; |
| } |
| |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| return; |
| } |
| |
| if (src_size < p_info->sm_info.cf_levels_size) { |
| ALOGE("%s:[%d] Unexpected, detection conf payload size %d < %d", |
| __func__, st_ses->sm_handle, src_size, |
| p_info->sm_info.cf_levels_size); |
| return; |
| } |
| |
| /* Reset any cached previous detection values */ |
| memset(stc_ses->sm_info.det_cf_levels, 0, stc_ses->sm_info.cf_levels_size); |
| |
| /* Extract the client conf level values from DSP payload */ |
| for(i = 0; i < p_info->sm_info.cf_levels_size; i++) { |
| if (!src[i]) |
| continue; |
| for(j = 0; j < stc_ses->sm_info.cf_levels_size; j++) { |
| if (!strcmp(stc_ses->sm_info.cf_levels_kw_users[j], |
| p_info->sm_info.cf_levels_kw_users[i])) { |
| stc_ses->sm_info.det_cf_levels[j] = src[i]; |
| } |
| } |
| } |
| for (i = 0; i < stc_ses->sm_info.cf_levels_size; i++) |
| ALOGD("%s: c%d det_cf_levels[%d]=%d", __func__, stc_ses->sm_handle, i, |
| stc_ses->sm_info.det_cf_levels[i]); |
| |
| *dst = stc_ses->sm_info.det_cf_levels; |
| *dst_size = stc_ses->sm_info.cf_levels_size; |
| } |
| |
| static inline bool check_for_multi_clients(st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| int cnt = 0; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| if (++cnt > 1) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline bool is_other_client_attached(st_proxy_session_t *st_ses, |
| st_session_t *stc_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses != stc_ses) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline void reset_clients_pending_load(st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->pending_load = false; |
| } |
| } |
| |
| static inline int is_any_client_not_pending_load(st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (!c_ses->pending_load) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline int is_any_client_not_pending_set_device( |
| st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (!c_ses->pending_set_device) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline void reset_clients_pending_set_device(st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->pending_set_device = false; |
| } |
| } |
| |
| static inline bool is_any_client_paused(st_proxy_session_t *st_ses) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses->paused) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline bool is_any_client_in_state(st_proxy_session_t *st_ses, |
| enum client_states_t state) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses->state == state) |
| return true; |
| } |
| return false; |
| } |
| |
| static void update_hw_config_on_restart(st_proxy_session_t *st_ses, |
| st_session_t *stc_ses) |
| { |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| int hb_sz = 0, pr_sz = 0; |
| bool enable_lab = false; |
| |
| /* |
| * Adjust history buffer and preroll durations to highest of |
| * all clients, including current restarting client. |
| * If any of the clients requested capture or enabled the |
| * second stage, the underlying hw session buffering needs to be |
| * enabled. |
| */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses == stc_ses) || (c_ses->state == ST_STATE_ACTIVE)) { |
| if (hb_sz < c_ses->hist_buf_duration) |
| hb_sz = c_ses->hist_buf_duration; |
| if (pr_sz < c_ses->preroll_duration) |
| pr_sz = c_ses->preroll_duration; |
| if (!enable_lab) |
| enable_lab = (c_ses->rc_config && |
| c_ses->rc_config->capture_requested) || |
| !list_empty(&c_ses->second_stage_list); |
| } |
| } |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, stc_ses->sm_info.model_id); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| return; |
| } |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| if (!st_ses->vendor_uuid_info->merge_fs_soundmodels || |
| !st_ses->sm_merged) |
| return; |
| |
| sthw_cfg->client_req_hist_buf = hb_sz; |
| hw_ses->max_hist_buf = hb_sz; |
| sthw_cfg->client_req_preroll = pr_sz; |
| hw_ses->max_preroll = pr_sz; |
| st_ses->lab_enabled = enable_lab; |
| |
| update_merge_conf_levels_payload(st_ses, &stc_ses->sm_info, |
| stc_ses->sm_info.cf_levels, |
| stc_ses->sm_info.cf_levels_size, |
| true); |
| } else { |
| sthw_cfg->client_req_hist_buf = stc_ses->hist_buf_duration; |
| hw_ses->max_hist_buf = hb_sz; |
| sthw_cfg->client_req_preroll = stc_ses->preroll_duration; |
| hw_ses->max_preroll = pr_sz; |
| st_ses->lab_enabled = enable_lab; |
| |
| /* |
| * During stop, the conf levels get set to the max value |
| * to prevent detections while its client state is loaded |
| * and another sound model's client state is active. So |
| * during restart, the conf levels need to be reset from |
| * the cached stc_values to enable detections again. |
| */ |
| memset(sthw_cfg->conf_levels, MAX_CONF_LEVEL_VALUE, |
| MAX_MULTI_SM_CONF_LEVELS); |
| memcpy(sthw_cfg->conf_levels, stc_ses->sm_info.cf_levels, |
| stc_ses->sm_info.cf_levels_size); |
| sthw_cfg->num_conf_levels = stc_ses->sm_info.cf_levels_size; |
| } |
| |
| hw_ses->sthw_cfg_updated = true; |
| |
| ALOGV("%s:[%d] lab_enabled %d, hb_sz %d, pr_sz %d", __func__, |
| st_ses->sm_handle, st_ses->lab_enabled, |
| sthw_cfg->client_req_hist_buf, sthw_cfg->client_req_preroll); |
| } |
| |
| static bool update_hw_config_on_stop(st_proxy_session_t *st_ses, |
| st_session_t *stc_ses) |
| { |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| int hb_sz = 0, pr_sz = 0; |
| bool active = false, enable_lab = false; |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, stc_ses->sm_info.model_id); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| return false; |
| } |
| |
| if (!st_ses->vendor_uuid_info->merge_fs_soundmodels && |
| stc_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| |
| if (sthw_cfg->conf_levels) { |
| ALOGV("%s: free hw conf_levels", __func__); |
| free(sthw_cfg->conf_levels); |
| sthw_cfg->conf_levels = NULL; |
| } |
| return false; |
| } |
| /* |
| * Adjust history buffer and preroll durations to highest of |
| * remaining clients. |
| * If any of the remaining clients requested capture or enabled the |
| * second stage, the underlying hw session buffering needs to be |
| * enabled. |
| */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if ((c_ses != stc_ses) && (c_ses->state == ST_STATE_ACTIVE)) { |
| active = true; |
| if (hb_sz < c_ses->hist_buf_duration) |
| hb_sz = c_ses->hist_buf_duration; |
| if (pr_sz < c_ses->preroll_duration) |
| pr_sz = c_ses->preroll_duration; |
| if (!enable_lab) |
| enable_lab = c_ses->rc_config->capture_requested || |
| !list_empty(&c_ses->second_stage_list); |
| } |
| } |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| if (!active) { |
| sthw_cfg->client_req_hist_buf = 0; |
| hw_ses->max_hist_buf = 0; |
| sthw_cfg->client_req_preroll = 0; |
| hw_ses->max_preroll = 0; |
| st_ses->lab_enabled = false; |
| hw_ses->custom_data = NULL; |
| hw_ses->custom_data_size = 0; |
| hw_ses->sthw_cfg_updated = true; |
| ALOGV("%s:[%d] no active client hw cfg is reset", __func__, |
| st_ses->sm_handle); |
| return false; |
| } |
| |
| sthw_cfg->client_req_hist_buf = hb_sz; |
| hw_ses->max_hist_buf = hb_sz; |
| sthw_cfg->client_req_preroll = pr_sz; |
| hw_ses->max_preroll = pr_sz; |
| st_ses->lab_enabled = enable_lab; |
| |
| update_merge_conf_levels_payload(st_ses, &stc_ses->sm_info, |
| stc_ses->sm_info.cf_levels, |
| stc_ses->sm_info.cf_levels_size, |
| false); |
| } else { |
| if (!active) { |
| hw_ses->max_hist_buf = 0; |
| hw_ses->max_preroll = 0; |
| st_ses->lab_enabled = false; |
| hw_ses->custom_data = NULL; |
| hw_ses->custom_data_size = 0; |
| hw_ses->sthw_cfg_updated = true; |
| return false; |
| } |
| |
| sthw_cfg->client_req_hist_buf = 0; |
| hw_ses->max_hist_buf = hb_sz; |
| sthw_cfg->client_req_preroll = 0; |
| hw_ses->max_preroll = pr_sz; |
| st_ses->lab_enabled = enable_lab; |
| |
| memset(sthw_cfg->conf_levels, MAX_CONF_LEVEL_VALUE, |
| MAX_MULTI_SM_CONF_LEVELS); |
| sthw_cfg->num_conf_levels = 0; |
| } |
| |
| hw_ses->sthw_cfg_updated = true; |
| |
| ALOGV("%s:[%d] lab_enabled %d, hb_sz %d, pr_sz %d", __func__, |
| st_ses->sm_handle, st_ses->lab_enabled, |
| sthw_cfg->client_req_hist_buf, sthw_cfg->client_req_preroll); |
| |
| return active; |
| } |
| |
| static void get_conf_levels_from_dsp_payload(st_proxy_session_t *st_ses, |
| unsigned char *payload, unsigned int payload_size, |
| unsigned char **conf_levels, unsigned int *conf_levels_size) |
| { |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| uint32_t key_id = 0, key_payload_size = 0; |
| unsigned int i = 0; |
| |
| if (hw_ses->is_generic_event) { |
| while (i < payload_size) { |
| key_id = *(uint32_t *)payload; |
| key_payload_size = *((uint32_t *)payload + 1); |
| |
| if (key_id == KEY_ID_CONFIDENCE_LEVELS) { |
| *conf_levels = payload + (4 * sizeof(uint32_t)); |
| *conf_levels_size = *((uint32_t *)payload + 3);; |
| ALOGV("%s: generic_event: DSP num conf levels %d", __func__, |
| *conf_levels_size); |
| break; |
| } |
| i += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| } |
| } else { |
| if (st_ses->exec_mode == ST_EXEC_MODE_CPE) { |
| *conf_levels = payload + 2; |
| *conf_levels_size = payload_size - 2; |
| } else { |
| *conf_levels = payload; |
| *conf_levels_size = payload_size; |
| } |
| } |
| } |
| |
| st_session_t* get_detected_client(st_proxy_session_t *st_ses, |
| unsigned char *payload, unsigned int payload_size) |
| { |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| unsigned char *conf_levels = NULL; |
| unsigned int conf_levels_size = 0, key_id = 0, key_payload_size = 0; |
| int i = 0, j = 0; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| multi_model_result_info_t *result_info = NULL; |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| return NULL; |
| } |
| |
| if (list_empty(&st_ses->clients_list)) { |
| ALOGE("%s:[%d] no clients attached!!", __func__, |
| st_ses->sm_handle); |
| return NULL; |
| } |
| /* |
| * If only single client exist, this detection is not for merged |
| * sound model, hence return this as only available client |
| */ |
| if (!check_for_multi_clients(st_ses)) { |
| ALOGV("%s:[%d] single client detection", __func__, |
| st_ses->sm_handle); |
| node = list_head(&st_ses->clients_list); |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| ALOGD("%s: detected c%d", __func__, c_ses->sm_handle); |
| return c_ses; |
| } else { |
| ALOGE("%s: detected c%d is not active", __func__, |
| c_ses->sm_handle); |
| return NULL; |
| } |
| } |
| |
| get_conf_levels_from_dsp_payload(st_ses, payload, payload_size, |
| &conf_levels, &conf_levels_size); |
| if (!conf_levels) { |
| ALOGE("%s:[%d] no conf levels payload found!!", __func__, |
| st_ses->sm_handle); |
| return NULL; |
| } |
| if (conf_levels_size < p_info->sm_info.num_keyphrases) { |
| ALOGE("%s:[%d] detection conf levels size %d < num of keywords %d", |
| __func__, st_ses->sm_handle, conf_levels_size, |
| p_info->sm_info.num_keyphrases); |
| return NULL; |
| } |
| |
| /* |
| * The DSP payload contains the keyword conf levels from the beginning. |
| * Only one keyword conf level is expected to be non-zero from keyword |
| * detection. Find non-zero conf level up to number of keyphrases and |
| * if one is found, match it to the corresponding keyphrase from list |
| * of clients to obtain the detected client. |
| */ |
| for (i = 0; i < p_info->sm_info.num_keyphrases; i++) { |
| if (!conf_levels[i]) |
| continue; |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| for (j = 0; j < c_ses->sm_info.num_keyphrases; j++) { |
| if (!strcmp(p_info->sm_info.keyphrases[i], |
| c_ses->sm_info.keyphrases[j])) { |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| ALOGV("%s: detected c%d", __func__, |
| c_ses->sm_handle); |
| return c_ses; |
| } else { |
| ALOGE("%s: detected c%d is not active", __func__, |
| c_ses->sm_handle); |
| return NULL; |
| } |
| } |
| } |
| } |
| } |
| } else { |
| while (i < payload_size) { |
| key_id = *(uint32_t *)payload; |
| key_payload_size = *((uint32_t *)payload + 1); |
| |
| if (key_id == KEY_ID_MULTI_MODEL_RESULT_INFO) { |
| result_info = (multi_model_result_info_t *)(payload + |
| GENERIC_DET_EVENT_HEADER_SIZE); |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses->sm_info.model_id == |
| result_info->detected_model_id) { |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| ALOGD("%s: detected c%d, 1st stage conf level = %d", |
| __func__, c_ses->sm_handle, |
| result_info->best_confidence_level); |
| return c_ses; |
| } else { |
| ALOGE("%s: detected c%d is not active", __func__, |
| c_ses->sm_handle); |
| return NULL; |
| } |
| } |
| } |
| break; |
| } else { |
| ALOGE("%s: Unexpected key id for PDK5 0x%x", __func__, |
| key_id); |
| break; |
| } |
| i += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| } |
| } |
| return c_ses; |
| } |
| |
| static int fill_conf_levels_payload_from_rc_config |
| ( |
| const struct sound_trigger_phrase_sound_model *phrase_sm, |
| const struct sound_trigger_recognition_config *rc_config, |
| unsigned char *conf_levels, |
| unsigned int total_num_users |
| ) |
| { |
| int status = 0; |
| unsigned int user_level, user_id; |
| unsigned int i = 0, j = 0; |
| unsigned int num_conf_levels = 0; |
| unsigned char *user_id_tracker; |
| |
| if (!phrase_sm || !rc_config || !conf_levels) { |
| ALOGE("%s: ERROR. Invalid inputs",__func__); |
| return -EINVAL; |
| } |
| |
| if ((UINT32_MAX - total_num_users) > rc_config->num_phrases) |
| num_conf_levels = total_num_users + rc_config->num_phrases; |
| |
| if (!num_conf_levels) { |
| ALOGE("%s: ERROR. Invalid num_conf_levels input", __func__); |
| return -EINVAL; |
| } |
| |
| /* Example: Say the recognition structure has 3 keywords with users |
| * |kid| |
| * [0] k1 |uid| |
| * [3] u1 - 1st trainer |
| * [4] u2 - 4th trainer |
| * [6] u3 - 3rd trainer |
| * [1] k2 |
| * [5] u2 - 2nd trainer |
| * [7] u3 - 5th trainer |
| * [2] k3 |
| * [8] u4 - 6th trainer |
| * |
| * Output confidence level array will be |
| * [k1, k2, k3, u1k1, u2k1, u2k2, u3k1, u3k2, u4k3] |
| */ |
| |
| user_id_tracker = calloc(1, num_conf_levels); |
| if (!user_id_tracker) { |
| ALOGE("%s: failed to allocate user_id_tracker", __func__); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < rc_config->num_phrases; i++) { |
| ALOGV("%s: [%d] kw level %d", __func__, i, |
| rc_config->phrases[i].confidence_level); |
| for (j = 0; j < rc_config->phrases[i].num_levels; j++) { |
| ALOGV("%s: [%d] user_id %d level %d ", __func__, i, |
| rc_config->phrases[i].levels[j].user_id, |
| rc_config->phrases[i].levels[j].level); |
| } |
| } |
| |
| for (i = 0; i < rc_config->num_phrases; i++) { |
| if (i < num_conf_levels) { |
| conf_levels[i] = rc_config->phrases[i].confidence_level; |
| } else { |
| ALOGE("%s: ERROR. Invalid number of phrases", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (j = 0; j < rc_config->phrases[i].num_levels; j++) { |
| user_level = rc_config->phrases[i].levels[j].level; |
| user_id = rc_config->phrases[i].levels[j].user_id; |
| if ((user_id < rc_config->num_phrases) || |
| (user_id >= num_conf_levels)) { |
| ALOGE("%s: ERROR. Invalid params user id %d>%d", |
| __func__, user_id, total_num_users); |
| status = -EINVAL; |
| goto exit; |
| } else { |
| if (user_id_tracker[user_id] == 1) { |
| ALOGE("%s: ERROR. Duplicate user id %d", |
| __func__, user_id); |
| status = -EINVAL; |
| goto exit; |
| } |
| conf_levels[user_id] = (user_level < 100) ? user_level: 100; |
| user_id_tracker[user_id] = 1; |
| ALOGV("%s: user_conf_levels[%d] = %d", __func__, |
| user_id, conf_levels[user_id]); |
| } |
| } |
| } |
| |
| exit: |
| free(user_id_tracker); |
| return status; |
| } |
| |
| int generate_conf_levels_payload_from_rc_config |
| ( |
| const struct sound_trigger_phrase_sound_model *phrase_sm, |
| const struct sound_trigger_recognition_config *rc_config, |
| unsigned char **out_payload, |
| unsigned int *out_payload_size |
| ) |
| { |
| int status = 0; |
| unsigned int total_num_users = 0, num_conf_levels = 0; |
| unsigned int i = 0, j = 0; |
| unsigned char *conf_levels = NULL; |
| |
| if (!phrase_sm || !rc_config || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs",__func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if((rc_config->num_phrases == 0) || |
| (rc_config->num_phrases > phrase_sm->num_phrases)) { |
| ALOGE("%s: ERROR. Invalid phrases %d!=%d",__func__, |
| rc_config->num_phrases, phrase_sm->num_phrases); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < rc_config->num_phrases; i++) { |
| for (j = 0; j < rc_config->phrases[i].num_levels; j++) |
| total_num_users++; |
| } |
| |
| num_conf_levels = total_num_users + rc_config->num_phrases; |
| conf_levels = calloc(1, num_conf_levels); |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed",__func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| |
| status = fill_conf_levels_payload_from_rc_config(phrase_sm, rc_config, |
| conf_levels, total_num_users); |
| if (status) { |
| ALOGE("%s: fill config payload failed, error %d", __func__, status); |
| goto exit; |
| } |
| |
| *out_payload = conf_levels; |
| *out_payload_size = num_conf_levels; |
| |
| return status; |
| |
| exit: |
| if (conf_levels) |
| free(conf_levels); |
| return status; |
| } |
| |
| int generate_conf_levels_payload_from_rc_config_v2 |
| ( |
| const struct sound_trigger_phrase_sound_model *phrase_sm, |
| const struct sound_trigger_recognition_config *rc_config, |
| unsigned char **out_payload, |
| unsigned int *out_payload_size |
| ) |
| { |
| int status = 0; |
| unsigned int total_num_users = 0, num_conf_levels = 0; |
| unsigned char *conf_levels = NULL; |
| unsigned int i = 0, j = 0; |
| |
| ALOGV("%s: Enter...", __func__); |
| |
| if (!phrase_sm || !rc_config || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs",__func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if((rc_config->num_phrases == 0) || |
| (rc_config->num_phrases > phrase_sm->num_phrases)) { |
| ALOGE("%s: ERROR. Invalid phrases %d!=%d",__func__, |
| rc_config->num_phrases, phrase_sm->num_phrases); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < rc_config->num_phrases; i++) { |
| for (j = 0; j < rc_config->phrases[i].num_levels; j++) |
| total_num_users++; |
| } |
| |
| num_conf_levels = total_num_users + rc_config->num_phrases; |
| /* |
| * allocate dsp payload w/additional 2 bytes for minor_version and |
| * num_active_models and additional num_conf_levels for KW enable |
| * fields |
| */ |
| conf_levels = calloc(1, 2 + 2 * num_conf_levels); |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed",__func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| |
| conf_levels[0] = 1; /* minor version */ |
| conf_levels[1] = num_conf_levels; /* num_active_models */ |
| status = fill_conf_levels_payload_from_rc_config(phrase_sm, rc_config, |
| conf_levels + 2, total_num_users); |
| if (status) { |
| ALOGE("%s: fill config payload failed, error %d", __func__, status); |
| goto exit; |
| } |
| |
| /* |
| * set KW enable fields to 1 for now |
| * TODO: set appropriately based on what client is passing in rc_config |
| */ |
| memset(&conf_levels[num_conf_levels + 2], 0x1, num_conf_levels); |
| ALOGV("%s: here", __func__); |
| *out_payload = conf_levels; |
| /* add size of minor version and num_active_models */ |
| *out_payload_size = 2 + 2 * num_conf_levels; |
| return status; |
| |
| exit: |
| if (conf_levels) |
| free(conf_levels); |
| return status; |
| } |
| |
| static int fill_sound_trigger_recognition_config_payload |
| ( |
| const void *sm_levels_generic, |
| unsigned char *conf_levels, |
| unsigned int total_num_users, |
| uint32_t version |
| ) |
| { |
| int status = 0; |
| unsigned int user_level = 0, user_id = 0; |
| unsigned int i = 0, j = 0; |
| unsigned int num_conf_levels = 0; |
| unsigned char *user_id_tracker = NULL; |
| struct st_sound_model_conf_levels *sm_levels = NULL; |
| struct st_sound_model_conf_levels_v2 *sm_levels_v2 = NULL; |
| |
| /* Example: Say the recognition structure has 3 keywords with users |
| * |kid| |
| * [0] k1 |uid| |
| * [3] u1 - 1st trainer |
| * [4] u2 - 4th trainer |
| * [6] u3 - 3rd trainer |
| * [1] k2 |
| * [5] u2 - 2nd trainer |
| * [7] u3 - 5th trainer |
| * [2] k3 |
| * [8] u4 - 6th trainer |
| * |
| * Output confidence level array will be |
| * [k1, k2, k3, u1k1, u2k1, u2k2, u3k1, u3k2, u4k3] |
| */ |
| |
| if (version != CONF_LEVELS_INTF_VERSION_0002) { |
| sm_levels = (struct st_sound_model_conf_levels *)sm_levels_generic; |
| if (!sm_levels || !conf_levels) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| return -EINVAL; |
| } |
| |
| if ((UINT32_MAX - total_num_users) > sm_levels->num_kw_levels) |
| num_conf_levels = total_num_users + sm_levels->num_kw_levels; |
| |
| if (!num_conf_levels) { |
| ALOGE("%s: ERROR. Invalid num_conf_levels input", __func__); |
| return -EINVAL; |
| } |
| |
| user_id_tracker = calloc(1, num_conf_levels); |
| if (!user_id_tracker) { |
| ALOGE("%s: failed to allocate user_id_tracker", __func__); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < sm_levels->num_kw_levels; i++) { |
| ALOGV("%s: [%d] kw level %d", __func__, i, |
| sm_levels->kw_levels[i].kw_level); |
| for (j = 0; j < sm_levels->kw_levels[i].num_user_levels; j++) { |
| ALOGV("%s: [%d] user_id %d level %d ", __func__, i, |
| sm_levels->kw_levels[i].user_levels[j].user_id, |
| sm_levels->kw_levels[i].user_levels[j].level); |
| } |
| } |
| |
| for (i = 0; i < sm_levels->num_kw_levels; i++) { |
| if (i < num_conf_levels) { |
| conf_levels[i] = sm_levels->kw_levels[i].kw_level; |
| } else { |
| ALOGE("%s: ERROR. Invalid numver of kw levels", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (j = 0; j < sm_levels->kw_levels[i].num_user_levels; j++) { |
| user_level = sm_levels->kw_levels[i].user_levels[j].level; |
| user_id = sm_levels->kw_levels[i].user_levels[j].user_id; |
| if ((user_id < sm_levels->num_kw_levels) || |
| (user_id >= num_conf_levels)) { |
| ALOGE("%s: ERROR. Invalid params user id %d>%d", |
| __func__, user_id, total_num_users); |
| status = -EINVAL; |
| goto exit; |
| } else { |
| if (user_id_tracker[user_id] == 1) { |
| ALOGE("%s: ERROR. Duplicate user id %d", |
| __func__, user_id); |
| status = -EINVAL; |
| goto exit; |
| } |
| conf_levels[user_id] = (user_level < 100) ? |
| user_level: 100; |
| user_id_tracker[user_id] = 1; |
| ALOGV("%s: user_conf_levels[%d] = %d", __func__, |
| user_id, conf_levels[user_id]); |
| } |
| } |
| } |
| } else { |
| sm_levels_v2 = |
| (struct st_sound_model_conf_levels_v2 *)sm_levels_generic; |
| if (!sm_levels_v2 || !conf_levels) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| return -EINVAL; |
| } |
| |
| if ((UINT32_MAX - total_num_users) > sm_levels_v2->num_kw_levels) |
| num_conf_levels = total_num_users + sm_levels_v2->num_kw_levels; |
| |
| if (!num_conf_levels) { |
| ALOGE("%s: ERROR. Invalid num_conf_levels input", __func__); |
| return -EINVAL; |
| } |
| |
| user_id_tracker = calloc(1, num_conf_levels); |
| if (!user_id_tracker) { |
| ALOGE("%s: failed to allocate user_id_tracker", __func__); |
| return -ENOMEM; |
| } |
| |
| for (i = 0; i < sm_levels_v2->num_kw_levels; i++) { |
| ALOGV("%s: [%d] kw level %d", __func__, i, |
| sm_levels_v2->kw_levels[i].kw_level); |
| for (j = 0; j < sm_levels_v2->kw_levels[i].num_user_levels; j++) { |
| ALOGV("%s: [%d] user_id %d level %d ", __func__, i, |
| sm_levels_v2->kw_levels[i].user_levels[j].user_id, |
| sm_levels_v2->kw_levels[i].user_levels[j].level); |
| } |
| } |
| |
| for (i = 0; i < sm_levels_v2->num_kw_levels; i++) { |
| if (i < num_conf_levels) { |
| conf_levels[i] = sm_levels_v2->kw_levels[i].kw_level; |
| } else { |
| ALOGE("%s: ERROR. Invalid numver of kw levels", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (j = 0; j < sm_levels_v2->kw_levels[i].num_user_levels; j++) { |
| user_level = sm_levels_v2->kw_levels[i].user_levels[j].level; |
| user_id = sm_levels_v2->kw_levels[i].user_levels[j].user_id; |
| if ((user_id < sm_levels_v2->num_kw_levels) || |
| (user_id >= num_conf_levels)) { |
| ALOGE("%s: ERROR. Invalid params user id %d>%d", |
| __func__, user_id, total_num_users); |
| status = -EINVAL; |
| goto exit; |
| } else { |
| if (user_id_tracker[user_id] == 1) { |
| ALOGE("%s: ERROR. Duplicate user id %d", |
| __func__, user_id); |
| status = -EINVAL; |
| goto exit; |
| } |
| conf_levels[user_id] = (user_level < 100) ? |
| user_level: 100; |
| user_id_tracker[user_id] = 1; |
| ALOGV("%s: user_conf_levels[%d] = %d", __func__, |
| user_id, conf_levels[user_id]); |
| } |
| } |
| } |
| } |
| |
| exit: |
| free(user_id_tracker); |
| return status; |
| } |
| |
| static int generate_sound_trigger_recognition_config_payload |
| ( |
| const void *sm_levels_generic, |
| unsigned char **out_payload, |
| unsigned int *out_payload_size, |
| uint32_t version |
| ) |
| { |
| int status = 0; |
| unsigned int total_num_users = 0, num_conf_levels = 0; |
| unsigned char *conf_levels = NULL; |
| unsigned int i = 0, j = 0; |
| struct st_sound_model_conf_levels *sm_levels = NULL; |
| struct st_sound_model_conf_levels_v2 *sm_levels_v2 = NULL; |
| |
| ALOGV("%s: Enter...", __func__); |
| |
| if (version != CONF_LEVELS_INTF_VERSION_0002) { |
| sm_levels = (struct st_sound_model_conf_levels *)sm_levels_generic; |
| if (!sm_levels || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if (sm_levels->num_kw_levels == 0) { |
| ALOGE("%s: ERROR. No confidence levels present", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < sm_levels->num_kw_levels; i++) { |
| for (j = 0; j < sm_levels->kw_levels[i].num_user_levels; j++) |
| total_num_users++; |
| } |
| |
| num_conf_levels = total_num_users + sm_levels->num_kw_levels; |
| conf_levels = calloc(1, num_conf_levels); |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed", __func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| } else { |
| sm_levels_v2 = |
| (struct st_sound_model_conf_levels_v2 *)sm_levels_generic; |
| if (!sm_levels_v2 || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if (sm_levels_v2->num_kw_levels == 0) { |
| ALOGE("%s: ERROR. No confidence levels present", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < sm_levels_v2->num_kw_levels; i++) { |
| for (j = 0; j < sm_levels_v2->kw_levels[i].num_user_levels; j++) |
| total_num_users++; |
| } |
| |
| num_conf_levels = total_num_users + sm_levels_v2->num_kw_levels; |
| conf_levels = calloc(1, num_conf_levels); |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed", __func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| } |
| |
| status = fill_sound_trigger_recognition_config_payload(sm_levels_generic, |
| conf_levels, total_num_users, version); |
| if (status) { |
| ALOGE("%s: fill config payload failed, error %d", __func__, status); |
| goto exit; |
| } |
| |
| *out_payload = conf_levels; |
| *out_payload_size = num_conf_levels; |
| |
| return status; |
| |
| exit: |
| if (conf_levels) |
| free(conf_levels); |
| |
| return status; |
| } |
| |
| static int generate_sound_trigger_recognition_config_payload_v2 |
| ( |
| const void *sm_levels_generic, |
| unsigned char **out_payload, |
| unsigned int *out_payload_size, |
| uint32_t version |
| ) |
| { |
| int status = 0; |
| unsigned int total_num_users = 0, num_conf_levels = 0; |
| unsigned char *conf_levels = NULL; |
| unsigned int i = 0, j = 0; |
| struct st_sound_model_conf_levels *sm_levels = NULL; |
| struct st_sound_model_conf_levels_v2 *sm_levels_v2 = NULL; |
| |
| ALOGV("%s: Enter...", __func__); |
| |
| if (version != CONF_LEVELS_INTF_VERSION_0002) { |
| sm_levels = (struct st_sound_model_conf_levels *)sm_levels_generic; |
| if (!sm_levels || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if (sm_levels->num_kw_levels == 0) { |
| ALOGE("%s: ERROR. No confidence levels present", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < sm_levels->num_kw_levels; i++) { |
| for (j = 0; j < sm_levels->kw_levels[i].num_user_levels; j++) |
| total_num_users++; |
| } |
| |
| num_conf_levels = total_num_users + sm_levels->num_kw_levels; |
| } else { |
| sm_levels_v2 = |
| (struct st_sound_model_conf_levels_v2 *)sm_levels_generic; |
| if (!sm_levels_v2 || !out_payload || !out_payload_size) { |
| ALOGE("%s: ERROR. Invalid inputs", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| *out_payload = NULL; |
| *out_payload_size = 0; |
| |
| if (sm_levels_v2->num_kw_levels == 0) { |
| ALOGE("%s: ERROR. No confidence levels present", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < sm_levels_v2->num_kw_levels; i++) { |
| for (j = 0; j < sm_levels_v2->kw_levels[i].num_user_levels; j++) |
| total_num_users++; |
| } |
| num_conf_levels = total_num_users + sm_levels_v2->num_kw_levels; |
| } |
| |
| /* |
| * allocate dsp payload w/additional 2 bytes for minor_version and |
| * num_active_models and additional num_conf_levels for KW enable |
| * fields |
| */ |
| conf_levels = calloc(1, 2 + 2 * num_conf_levels); |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed", __func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| |
| conf_levels[0] = 1; /* minor version */ |
| conf_levels[1] = num_conf_levels; /* num_active_models */ |
| status = fill_sound_trigger_recognition_config_payload(sm_levels_generic, |
| conf_levels + 2, total_num_users, version); |
| if (status) { |
| ALOGE("%s: fill config payload failed, error %d", __func__, status); |
| goto exit; |
| } |
| |
| /* set KW enable fields to 1 for now |
| * TODO set appropriately based on what client is passing in rc_config |
| */ |
| memset(&conf_levels[num_conf_levels + 2], 0x1, num_conf_levels); |
| ALOGV("%s: here", __func__); |
| *out_payload = conf_levels; |
| /* add size of minor version and num_active_models */ |
| *out_payload_size = 2 + 2 * num_conf_levels; |
| |
| return status; |
| |
| exit: |
| if (conf_levels) |
| free(conf_levels); |
| |
| return status; |
| } |
| |
| static int parse_rc_config_key_conf_levels |
| ( |
| st_session_t *stc_ses, |
| st_hw_session_t *st_hw_ses, |
| void *opaque_conf_levels, |
| unsigned char **out_conf_levels, |
| unsigned int *out_num_conf_levels |
| ) |
| { |
| struct st_confidence_levels_info *conf_levels = NULL; |
| struct st_confidence_levels_info_v2 *conf_levels_v2 = NULL; |
| struct st_sound_model_conf_levels *sm_levels = NULL; |
| struct st_sound_model_conf_levels_v2 *sm_levels_v2 = NULL; |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| struct listnode *node = NULL; |
| st_lsm_ss_config_t *ss_cfg = NULL; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| int status = 0; |
| uint32_t i = 0; |
| bool gmm_conf_found = false; |
| uint8_t confidence_level = 0; |
| int32_t confidence_level_v2 = 0; |
| bool arm_second_stage = !list_empty(&stc_ses->second_stage_list); |
| bool adsp_second_stage = (st_hw_ses == st_ses->hw_ses_adsp && |
| !list_empty(&st_hw_ses->lsm_ss_cfg_list)); |
| |
| if (arm_second_stage || adsp_second_stage) { |
| if (stc_ses->rc_config->num_phrases > 1) { |
| ALOGE("%s: Multi keyword is unsupported with 2nd stage detection", |
| __func__); |
| return -EINVAL; |
| } |
| |
| if (stc_ses->rc_config->phrases[0].num_levels > 1) { |
| ALOGE("%s: Multi user is unsupported with 2nd stage detection", |
| __func__); |
| return -EINVAL; |
| } |
| } |
| |
| if (stc_ses->st_conf_levels) { |
| free(stc_ses->st_conf_levels); |
| stc_ses->st_conf_levels = NULL; |
| } |
| |
| if (stc_ses->conf_levels_intf_version != CONF_LEVELS_INTF_VERSION_0002) { |
| conf_levels = (struct st_confidence_levels_info *) |
| ((char *)opaque_conf_levels + sizeof(struct st_param_header)); |
| |
| stc_ses->st_conf_levels = |
| calloc(1, sizeof(struct st_confidence_levels_info)); |
| if (!stc_ses->st_conf_levels) { |
| ALOGE("%s: failed to alloc st_conf_levels", __func__); |
| return -ENOMEM; |
| } |
| /* Cache to use during detection event processing */ |
| memcpy(stc_ses->st_conf_levels, (char *)conf_levels, |
| sizeof(struct st_confidence_levels_info)); |
| |
| for (i = 0; i < conf_levels->num_sound_models; i++) { |
| sm_levels = &conf_levels->conf_levels[i]; |
| if (sm_levels->sm_id == ST_SM_ID_SVA_GMM) { |
| if (st_hw_ses == st_ses->hw_ses_cpe) |
| status = |
| generate_sound_trigger_recognition_config_payload_v2( |
| (void *)sm_levels, out_conf_levels, out_num_conf_levels, |
| stc_ses->conf_levels_intf_version); |
| else |
| status = |
| generate_sound_trigger_recognition_config_payload( |
| (void *)sm_levels, out_conf_levels, out_num_conf_levels, |
| stc_ses->conf_levels_intf_version); |
| gmm_conf_found = true; |
| } else if (IS_SECOND_STAGE_MODEL(sm_levels->sm_id)) { |
| confidence_level = IS_KEYWORD_DETECTION_MODEL(sm_levels->sm_id) ? |
| sm_levels->kw_levels[0].kw_level: |
| sm_levels->kw_levels[0].user_levels[0].level; |
| if (arm_second_stage) { |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, |
| list_node); |
| if (IS_MATCHING_SS_MODEL(st_sec_stage->ss_info->sm_id, |
| sm_levels->sm_id)) |
| st_sec_stage->ss_session->confidence_threshold = |
| confidence_level; |
| } |
| } else if (adsp_second_stage) { |
| list_for_each(node, &st_hw_ses->lsm_ss_cfg_list) { |
| ss_cfg = node_to_item(node, st_lsm_ss_config_t, |
| list_node); |
| if (IS_MATCHING_SS_MODEL(ss_cfg->ss_info->sm_id, |
| sm_levels->sm_id)) |
| ss_cfg->confidence_threshold = confidence_level; |
| } |
| } |
| } else { |
| ALOGE("%s: Unsupported sm id (%d), exiting", __func__, |
| sm_levels->sm_id); |
| status = -EINVAL; |
| break; |
| } |
| } |
| } else { |
| conf_levels_v2 = (struct st_confidence_levels_info_v2 *) |
| ((char *)opaque_conf_levels + sizeof(struct st_param_header)); |
| |
| stc_ses->st_conf_levels = |
| calloc(1, sizeof(struct st_confidence_levels_info_v2)); |
| if (!stc_ses->st_conf_levels) { |
| ALOGE("%s: failed to alloc st_conf_levels", __func__); |
| return -ENOMEM; |
| } |
| /* Cache to use during detection event processing */ |
| memcpy(stc_ses->st_conf_levels, (char *)conf_levels_v2, |
| sizeof(struct st_confidence_levels_info_v2)); |
| |
| for (i = 0; i < conf_levels_v2->num_sound_models; i++) { |
| sm_levels_v2 = &conf_levels_v2->conf_levels[i]; |
| if (sm_levels_v2->sm_id == ST_SM_ID_SVA_GMM) { |
| if (st_hw_ses == st_ses->hw_ses_cpe) |
| status = |
| generate_sound_trigger_recognition_config_payload_v2( |
| (void *)sm_levels_v2, out_conf_levels, out_num_conf_levels, |
| stc_ses->conf_levels_intf_version); |
| else |
| status = |
| generate_sound_trigger_recognition_config_payload( |
| (void *)sm_levels_v2, out_conf_levels, |
| out_num_conf_levels, stc_ses->conf_levels_intf_version); |
| gmm_conf_found = true; |
| } else if (IS_SECOND_STAGE_MODEL(sm_levels_v2->sm_id)) { |
| confidence_level_v2 = |
| (IS_KEYWORD_DETECTION_MODEL(sm_levels_v2->sm_id)) ? |
| sm_levels_v2->kw_levels[0].kw_level: |
| sm_levels_v2->kw_levels[0].user_levels[0].level; |
| if (arm_second_stage) { |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, |
| list_node); |
| if (IS_MATCHING_SS_MODEL(st_sec_stage->ss_info->sm_id, |
| sm_levels_v2->sm_id)) |
| st_sec_stage->ss_session->confidence_threshold = |
| confidence_level_v2; |
| } |
| } else if (adsp_second_stage) { |
| list_for_each(node, &st_hw_ses->lsm_ss_cfg_list) { |
| ss_cfg = node_to_item(node, st_lsm_ss_config_t, |
| list_node); |
| if (IS_MATCHING_SS_MODEL(ss_cfg->ss_info->sm_id, |
| sm_levels_v2->sm_id)) |
| ss_cfg->confidence_threshold = confidence_level_v2; |
| } |
| } |
| } else { |
| ALOGE("%s: Unsupported sm id (%d), exiting", __func__, |
| sm_levels_v2->sm_id); |
| status = -EINVAL; |
| break; |
| } |
| } |
| } |
| |
| if (!gmm_conf_found) { |
| ALOGE("%s: Did not receive GMM confidence threshold, error!", __func__); |
| status = -EINVAL; |
| } |
| |
| if (status && stc_ses->st_conf_levels) { |
| free(stc_ses->st_conf_levels); |
| stc_ses->st_conf_levels = NULL; |
| } |
| return status; |
| } |
| |
| static int update_hw_config_on_start(st_session_t *stc_ses, |
| st_hw_session_t *st_hw_ses) |
| { |
| struct st_param_header *param_hdr = NULL; |
| struct st_hist_buffer_info *hist_buf = NULL; |
| struct st_det_perf_mode_info *det_perf_mode = NULL; |
| struct sound_trigger_recognition_config *rc_config = stc_ses->rc_config; |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| struct st_vendor_info *v_info = NULL; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| unsigned char *conf_levels = NULL; |
| unsigned int num_conf_levels = 0; |
| uint8_t *opaque_ptr = NULL; |
| unsigned int opaque_size = 0, conf_levels_payload_size = 0; |
| int status = 0; |
| bool enable_lab = false; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| |
| if (st_ses->stdev->enable_debug_dumps) { |
| ST_DBG_DECLARE(FILE *rc_opaque_fd = NULL; |
| static int rc_opaque_cnt = 0); |
| ST_DBG_FILE_OPEN_WR(rc_opaque_fd, ST_DEBUG_DUMP_LOCATION, |
| "rc_config_opaque_data", "bin", rc_opaque_cnt); |
| ST_DBG_FILE_WRITE(rc_opaque_fd, |
| (uint8_t *)rc_config + rc_config->data_offset, |
| rc_config->data_size); |
| ST_DBG_FILE_CLOSE(rc_opaque_fd); |
| ALOGD("%s: rc_config opaque data dump stored in: rc_config_opaque_data_%d.bin", |
| __func__, rc_opaque_cnt); |
| rc_opaque_cnt++; |
| } |
| |
| if (!st_hw_ses) { |
| ALOGE("%s: NULL hw session !!!", __func__); |
| return -EINVAL; |
| } |
| |
| v_info = st_hw_ses->vendor_uuid_info; |
| |
| if ((rc_config->data_size > CUSTOM_CONFIG_OPAQUE_DATA_SIZE) && |
| v_info->is_qcva_uuid) { |
| stc_ses->client_req_det_mode = ST_DET_UNKNOWN_MODE; |
| |
| opaque_ptr = (uint8_t *)rc_config + rc_config->data_offset; |
| while (opaque_size < rc_config->data_size) { |
| param_hdr = (struct st_param_header *)opaque_ptr; |
| ALOGV("%s: key %d, payload size %d", __func__, |
| param_hdr->key_id, param_hdr->payload_size); |
| |
| switch(param_hdr->key_id) { |
| case ST_PARAM_KEY_CONFIDENCE_LEVELS: |
| stc_ses->conf_levels_intf_version = |
| *(uint32_t *)(opaque_ptr + sizeof(struct st_param_header)); |
| |
| if (stc_ses->conf_levels_intf_version != |
| CONF_LEVELS_INTF_VERSION_0002) { |
| conf_levels_payload_size = |
| sizeof(struct st_confidence_levels_info); |
| } else { |
| conf_levels_payload_size = |
| sizeof(struct st_confidence_levels_info_v2); |
| } |
| if (param_hdr->payload_size != conf_levels_payload_size) { |
| ALOGE("%s: Conf level format error, exiting", __func__); |
| return -EINVAL; |
| } |
| status = parse_rc_config_key_conf_levels(stc_ses, st_hw_ses, |
| opaque_ptr, &conf_levels, &num_conf_levels); |
| opaque_size += sizeof(struct st_param_header) + |
| conf_levels_payload_size; |
| opaque_ptr += sizeof(struct st_param_header) + |
| conf_levels_payload_size; |
| if (status) { |
| ALOGE("%s: parsing conf levels failed(status=%d)", |
| __func__, status); |
| return -EINVAL; |
| } |
| break; |
| case ST_PARAM_KEY_HISTORY_BUFFER_CONFIG: |
| if (param_hdr->payload_size != |
| sizeof(struct st_hist_buffer_info)) { |
| ALOGE("%s: History buffer config format error, exiting", |
| __func__); |
| return -EINVAL; |
| } |
| hist_buf = (struct st_hist_buffer_info *)(opaque_ptr + |
| sizeof(struct st_param_header)); |
| stc_ses->hist_buf_duration = |
| hist_buf->hist_buffer_duration_msec; |
| stc_ses->preroll_duration = hist_buf->pre_roll_duration_msec; |
| ALOGV("%s: recognition config history buf len = %d, " |
| "preroll len = %d, minor version = %d", |
| __func__, hist_buf->hist_buffer_duration_msec, |
| hist_buf->pre_roll_duration_msec, hist_buf->version); |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_hist_buffer_info); |
| opaque_ptr += sizeof(struct st_param_header) + |
| sizeof(struct st_hist_buffer_info); |
| break; |
| case ST_PARAM_KEY_DETECTION_PERF_MODE: |
| if (param_hdr->payload_size != |
| sizeof(struct st_det_perf_mode_info)) { |
| ALOGE("%s: Opaque data format error, exiting", __func__); |
| return -EINVAL; |
| } |
| det_perf_mode = (struct st_det_perf_mode_info *)(opaque_ptr + |
| sizeof(struct st_param_header)); |
| ALOGV("set perf mode to %d", det_perf_mode->mode); |
| stc_ses->client_req_det_mode = det_perf_mode->mode; |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_det_perf_mode_info); |
| opaque_ptr += sizeof(struct st_param_header) + |
| sizeof(struct st_det_perf_mode_info); |
| break; |
| default: |
| ALOGE("%s: Unsupported opaque data key id, exiting", __func__); |
| return -EINVAL; |
| } |
| } |
| } else if (stc_ses->sm_type == SOUND_MODEL_TYPE_KEYPHRASE) { |
| struct sound_trigger_phrase_sound_model *phrase_sm = stc_ses->phrase_sm; |
| |
| ALOGV("%s: num_phrases=%d, id=%d", __func__, |
| rc_config->num_phrases, rc_config->phrases[0].id); |
| |
| if (st_ses->vendor_uuid_info->is_qcva_uuid || |
| st_ses->vendor_uuid_info->is_qcmd_uuid) { |
| if (st_hw_ses == st_ses->hw_ses_cpe) |
| status = generate_conf_levels_payload_from_rc_config_v2( |
| phrase_sm, rc_config, &conf_levels, &num_conf_levels); |
| else |
| status = generate_conf_levels_payload_from_rc_config( |
| phrase_sm, rc_config, &conf_levels, &num_conf_levels); |
| if (status || !conf_levels) { |
| ALOGE("%s: failed to get conf levels from lib handle", |
| __func__); |
| return status; |
| } |
| } else { |
| ALOGD("%s: No smlib, opaque data would be sent as is", __func__); |
| } |
| } |
| |
| enable_lab = stc_ses->rc_config->capture_requested || |
| !list_empty(&stc_ses->second_stage_list); |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(st_hw_ses, |
| stc_ses->sm_info.model_id); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| return -EINVAL; |
| } |
| |
| if (stc_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| if (v_info->merge_fs_soundmodels) { |
| /* merge_fs_soundmodels is true only for QC SVA UUID */ |
| |
| if (conf_levels == NULL) { |
| ALOGE("%s: Unexpected, conf_levels pointer is NULL", |
| __func__); |
| status = -EINVAL; |
| return status; |
| } |
| /* |
| * Note: |
| * For ADSP case, the generated conf levles size must be equal to |
| * SML queried conf levels. |
| * For WDSP gcs case, there is additional payload for KW enable |
| * fields in generated conf_levels. If merge sound model is supported |
| * on WDSP case, update logic here accordingly. |
| */ |
| if (num_conf_levels != stc_ses->sm_info.cf_levels_size) { |
| ALOGE("%s: Unexpected, client cf levels %d != sm_info cf levels %d", |
| __func__, num_conf_levels, stc_ses->sm_info.cf_levels_size); |
| return -EINVAL; |
| } |
| |
| /* |
| * If any of the active clients requested capture or enabled the |
| * second stage, the underlying hw session buffering needs to |
| * be enabled. Ignore if it is already enabled. |
| */ |
| if (!st_ses->lab_enabled && enable_lab) |
| st_ses->lab_enabled = true; |
| |
| /* Aggregate DSP configuration for highest client configuration */ |
| |
| /* SVA2.0 sound models */ |
| if (!stc_ses->hist_buf_duration && |
| stc_ses->rc_config->capture_requested && |
| (stc_ses->rc_config->data_size > 0)) { |
| stc_ses->hist_buf_duration = st_ses->vendor_uuid_info->kw_duration; |
| stc_ses->preroll_duration = 0; |
| } |
| |
| if (stc_ses->hist_buf_duration > sthw_cfg->client_req_hist_buf) { |
| sthw_cfg->client_req_hist_buf = stc_ses->hist_buf_duration; |
| st_hw_ses->max_hist_buf = stc_ses->hist_buf_duration; |
| } |
| if (stc_ses->preroll_duration > sthw_cfg->client_req_preroll) { |
| sthw_cfg->client_req_preroll = stc_ses->preroll_duration; |
| st_hw_ses->max_preroll = stc_ses->preroll_duration; |
| } |
| |
| ALOGV("%s: client hb_sz %d pr_sz %d, sthw lab %d hb_sz %d " |
| "pr_sz %d", __func__, stc_ses->hist_buf_duration, |
| stc_ses->preroll_duration, st_ses->lab_enabled, |
| sthw_cfg->client_req_hist_buf, sthw_cfg->client_req_preroll); |
| |
| /* Cache it to use when client restarts without config update or |
| * during only one remaining client model as there won't be a |
| * merged model yet. |
| */ |
| if (!conf_levels) { |
| ALOGE("%s: ERROR. conf levels alloc failed", __func__); |
| status = -ENOMEM; |
| return status; |
| } |
| memcpy(stc_ses->sm_info.cf_levels, conf_levels, |
| stc_ses->sm_info.cf_levels_size); |
| |
| status = update_merge_conf_levels_payload(st_ses, &stc_ses->sm_info, |
| conf_levels, num_conf_levels, true); |
| free(conf_levels); /* Merged model conf levels will be used further */ |
| if (status) |
| return status; |
| |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| status = -EINVAL; |
| return status; |
| } |
| |
| sthw_cfg->conf_levels = p_info->sm_info.cf_levels; |
| sthw_cfg->num_conf_levels = p_info->sm_info.cf_levels_size; |
| sthw_cfg->model_id = 0; |
| st_hw_ses->sthw_cfg_updated = true; |
| |
| /* |
| * Merging further unknown custom data is not needed, as |
| * SVA doesn't support unkown custom data. if required in future, |
| * handle here. |
| * For now just copy the the current client data which is same |
| * across SVA engines. |
| * Update the custom data for the case in which one client session |
| * does not have custom data and another one does. |
| */ |
| if (rc_config->data_size > st_hw_ses->custom_data_size) { |
| st_hw_ses->custom_data = (char *)rc_config + rc_config->data_offset; |
| st_hw_ses->custom_data_size = rc_config->data_size; |
| } |
| } else { |
| st_ses->recognition_mode = stc_ses->recognition_mode; |
| st_ses->lab_enabled = enable_lab; |
| sthw_cfg->client_req_hist_buf = stc_ses->hist_buf_duration; |
| st_hw_ses->max_hist_buf = stc_ses->hist_buf_duration; |
| sthw_cfg->client_req_preroll = stc_ses->preroll_duration; |
| st_hw_ses->max_preroll = stc_ses->preroll_duration; |
| |
| if (sthw_cfg->conf_levels) |
| free(sthw_cfg->conf_levels); |
| sthw_cfg->conf_levels = conf_levels; |
| sthw_cfg->num_conf_levels = num_conf_levels; |
| |
| st_hw_ses->custom_data = (char *)rc_config + rc_config->data_offset; |
| st_hw_ses->custom_data_size = rc_config->data_size; |
| } |
| |
| |
| } else { |
| if (conf_levels == NULL) { |
| ALOGE("%s: Unexpected, conf_levels pointer is NULL", |
| __func__); |
| status = -EINVAL; |
| return status; |
| } |
| |
| if (!st_ses->lab_enabled && enable_lab) |
| st_ses->lab_enabled = true; |
| |
| sthw_cfg->client_req_hist_buf = stc_ses->hist_buf_duration; |
| if (st_hw_ses->max_hist_buf < stc_ses->hist_buf_duration) |
| st_hw_ses->max_hist_buf = stc_ses->hist_buf_duration; |
| |
| sthw_cfg->client_req_preroll = stc_ses->preroll_duration; |
| if (st_hw_ses->max_preroll < stc_ses->preroll_duration) |
| st_hw_ses->max_preroll = stc_ses->preroll_duration; |
| |
| /* |
| * User verification confidence is not required |
| * in SVA5 PDK_UV case. As first stage doesn't |
| * support user verification. |
| */ |
| num_conf_levels = 1; |
| |
| /* |
| * Cache it to use when client restarts without |
| * config update |
| */ |
| memcpy(stc_ses->sm_info.cf_levels, conf_levels, |
| num_conf_levels); |
| stc_ses->sm_info.cf_levels_size = num_conf_levels; |
| |
| memcpy(sthw_cfg->conf_levels, conf_levels, |
| num_conf_levels); |
| sthw_cfg->num_conf_levels = num_conf_levels; |
| free(conf_levels); |
| |
| if (rc_config->data_size >= st_hw_ses->custom_data_size) { |
| st_hw_ses->custom_data = (char *)rc_config + rc_config->data_offset; |
| st_hw_ses->custom_data_size = rc_config->data_size; |
| } |
| st_hw_ses->sthw_cfg_updated = true; |
| } |
| ALOGD("%s:[%d] lab enabled %d", __func__, st_ses->sm_handle, |
| st_ses->lab_enabled); |
| |
| return status; |
| } |
| |
| static int reg_all_sm(st_proxy_session_t *st_ses, st_hw_session_t *hw_ses) |
| { |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct listnode *node = NULL; |
| int status = 0; |
| |
| list_for_each(node, &st_ses->sm_info_list) { |
| p_info = node_to_item(node, struct st_proxy_ses_sm_info_wrapper, |
| sm_list_node); |
| status = hw_ses->fptrs->reg_sm(hw_ses, p_info->sm_info.sm_data, |
| p_info->sm_info.sm_size, p_info->sm_info.model_id); |
| if (status) { |
| ALOGE("%s:[%d] reg_sm failed, model_id = %d, status = %d", __func__, |
| st_ses->sm_handle, p_info->sm_info.model_id, status); |
| return status; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int dereg_all_sm(st_proxy_session_t *st_ses, st_hw_session_t *hw_ses) |
| { |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct listnode *node = NULL; |
| int status = 0, ret = 0; |
| |
| list_for_each(node, &st_ses->sm_info_list) { |
| p_info = node_to_item(node, struct st_proxy_ses_sm_info_wrapper, |
| sm_list_node); |
| status = hw_ses->fptrs->dereg_sm(hw_ses, p_info->sm_info.model_id); |
| if (status) { |
| ALOGE("%s:[%d] dereg_sm failed, model_id = %d, status = %d", __func__, |
| st_ses->sm_handle, p_info->sm_info.model_id, status); |
| ret = status; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static void do_hw_sess_cleanup(st_proxy_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); |
| |
| 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) |
| dereg_all_sm(st_ses, hw_ses); |
| } |
| |
| static void reg_hal_event_session(st_session_t *stc_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 ((stc_ses->rc_config && |
| stc_ses->rc_config->capture_requested) && |
| stc_ses->stdev->audio_hal_cb) { |
| ALOGD("%s:[c%d] ST_EVENT_SESSION_REGISTER capture_handle %d", |
| __func__, stc_ses->sm_handle, stc_ses->capture_handle); |
| event_info.st_ses.p_ses = (void *)stc_ses; |
| event_info.st_ses.config = hw_ses->config; |
| event_info.st_ses.capture_handle = stc_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; |
| stc_ses->stdev->audio_hal_cb(ST_EVENT_SESSION_REGISTER, &event_info); |
| } |
| } |
| |
| static void dereg_hal_event_session(st_session_t *stc_ses) |
| { |
| struct sound_trigger_event_info event_info; |
| /* Indicate to audio hal that to stop reading LAB data */ |
| if ((stc_ses->rc_config && |
| stc_ses->rc_config->capture_requested) && |
| stc_ses->stdev->audio_hal_cb) { |
| ALOGD("%s:[c%d] ST_EVENT_SESSION_DEREGISTER capture_handle %d", |
| __func__, stc_ses->sm_handle, stc_ses->capture_handle); |
| event_info.st_ses.p_ses = (void *)stc_ses; |
| event_info.st_ses.capture_handle = stc_ses->capture_handle; |
| event_info.st_ses.pcm = NULL; |
| stc_ses->stdev->audio_hal_cb(ST_EVENT_SESSION_DEREGISTER, &event_info); |
| } |
| } |
| |
| static bool check_gcs_usecase_switch |
| ( |
| st_proxy_session_t *st_ses |
| ) |
| { |
| struct st_vendor_info *v_info = NULL; |
| st_hw_session_t *p_ses = NULL; |
| st_hw_session_gcs_t *p_gcs_ses = NULL; |
| int st_device = 0; |
| unsigned int device_acdb_id = 0; |
| int capture_device; |
| |
| if (!st_ses || st_ses->exec_mode != ST_EXEC_MODE_CPE) { |
| ALOGE("%s: Invalid session or non CPE session!", __func__); |
| return false; |
| } |
| |
| p_ses = st_ses->hw_ses_cpe; |
| p_gcs_ses = (st_hw_session_gcs_t *)p_ses; |
| v_info = p_ses->vendor_uuid_info; |
| |
| if (list_empty(&v_info->gcs_usecase_list)) { |
| ALOGE("%s: gcs usecase not available", __func__); |
| return false; |
| } |
| |
| /* check if need to switch gcs usecase for new capture device */ |
| capture_device = platform_stdev_get_capture_device(p_ses->stdev->platform); |
| st_device = platform_stdev_get_device(p_ses->stdev->platform, |
| v_info, capture_device, p_ses->exec_mode); |
| device_acdb_id = platform_stdev_get_acdb_id(st_device, |
| p_ses->exec_mode); |
| if (platform_stdev_get_xml_version(p_ses->stdev->platform) >= |
| PLATFORM_XML_VERSION_0x0102) { |
| int i = 0; |
| while ((i < MAX_GCS_USECASE_ACDB_IDS) && |
| p_gcs_ses->gcs_usecase->acdb_ids[i]) { |
| if (p_gcs_ses->gcs_usecase->acdb_ids[i] == device_acdb_id) |
| return false; |
| i++; |
| } |
| ALOGD("%s: gcs usecase doesn't match for new device", __func__); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static int start_hw_session(st_proxy_session_t *st_ses, st_hw_session_t *hw_ses, |
| bool load_sm) |
| { |
| int status = 0, err = 0; |
| bool do_unload = false; |
| |
| /* |
| * It is possible the BE LPI mode has been updated, but not the FE mode. |
| * DSP requires both FE and BE to be in the same mode for any configuration |
| * changes between LPI and non-LPI switch, so update the FE mode to the |
| * same as BE mode by re-opening LSM session. This is also used for |
| * other transition usecases which require dereg_sm and reg_sm. |
| */ |
| if (hw_ses->lpi_enable != hw_ses->stdev->lpi_enable || |
| (hw_ses->barge_in_mode != hw_ses->stdev->barge_in_mode && |
| !hw_ses->stdev->support_dynamic_ec_update)) { |
| hw_ses->lpi_enable = hw_ses->stdev->lpi_enable; |
| hw_ses->barge_in_mode = hw_ses->stdev->barge_in_mode; |
| do_unload = true; |
| /* |
| * When LSM is in buffering state and if we remove the power |
| * cable it will change the battery status. So LPI mode switch from |
| * NLPI to LPI should happen as a part of handle_battery_status_change(). |
| * As session is in buffering state,we can't directly change the LPI mode, |
| * so change the mode for subsequent detections, for that we have to reset |
| * backend when next detection is triggered. |
| */ |
| if (hw_ses->stdev->is_buffering) { |
| platform_stdev_reset_backend_cfg(hw_ses->stdev->platform); |
| hw_ses->stdev->is_buffering = false; |
| } |
| } |
| /* |
| * For gcs sessions, uid may be changed for new capture device, |
| * in this case, sm must be dereg and reg again. |
| */ |
| if (check_gcs_usecase_switch(st_ses)) |
| do_unload = true; |
| |
| if (do_unload) { |
| if (!load_sm) { |
| load_sm = true; |
| status = dereg_all_sm(st_ses, hw_ses); |
| if (status) |
| ALOGW("%s:[%d] failed to dereg_sm err %d", __func__, |
| st_ses->sm_handle, status); |
| } |
| } |
| |
| if (load_sm) { |
| status = reg_all_sm(st_ses, hw_ses); |
| 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; |
| |
| status = hw_ses->fptrs->reg_sm_params(hw_ses, st_ses->recognition_mode, |
| st_ses->lab_enabled, st_ses->rc_config); |
| 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_proxy_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 = dereg_all_sm(st_ses, hw_ses); |
| 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_proxy_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; |
| } |
| |
| status = start_hw_session(st_ses, hw_ses, load_sm); |
| hw_ses->sthw_cfg_updated = false; |
| |
| return status; |
| } |
| |
| static int restart_session(st_proxy_session_t *st_ses, st_hw_session_t *hw_ses) |
| { |
| int status = 0; |
| |
| status = hw_ses->fptrs->restart(hw_ses, st_ses->recognition_mode, |
| st_ses->rc_config); |
| if (status == 0) { |
| st_ses->hw_session_started = true; |
| } else { |
| ALOGE("%s:[%d] failed to restart", __func__, st_ses->sm_handle); |
| /* |
| * lower layers like gcs/lsm need to handle double stop calls properly |
| * to avoid possible crash, as some of the clean ups are already issued |
| * during fptrs->restart() when it's failed. |
| */ |
| stop_hw_session(st_ses, hw_ses, true); |
| } |
| |
| return status; |
| } |
| |
| static int stop_session(st_proxy_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; |
| } |
| st_ses->detection_requested = false; |
| 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_proxy_session_t *st_ses, |
| void *payload, size_t payload_size) |
| { |
| size_t count_size = 0; |
| uint8_t *payload_ptr = (uint8_t *)payload; |
| uint8_t *cf_levels = NULL; |
| uint32_t key_id = 0, key_payload_size = 0, cf_levels_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; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| bool is_active_vop_session = false; |
| multi_model_result_info_t *result_info = NULL; |
| |
| list_for_each(node, &stc_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_MULTI_MODEL_RESULT_INFO: |
| result_info = (multi_model_result_info_t *)(payload_ptr + |
| GENERIC_DET_EVENT_HEADER_SIZE); |
| hw_ses->kw_start_idx = result_info->keyword_start_idx_bytes; |
| hw_ses->kw_end_idx = result_info->keyword_end_idx_bytes; |
| hw_ses->channel_idx = result_info->best_channel_idx; |
| break; |
| |
| case KEY_ID_CONFIDENCE_LEVELS: |
| if (is_active_vop_session) { |
| /* |
| * It is expected that VoP is supported with single KW/user |
| * SVA3.0 model, hence get it directly with hard offset. |
| */ |
| if (!st_ses->sm_merged) { |
| hw_ses->user_level = (int32_t)(*(payload_ptr + |
| GENERIC_DET_EVENT_USER_LEVEL_OFFSET)); |
| } else { |
| /* Extract from first stage merged conf levels */ |
| check_and_extract_det_conf_levels_payload(st_ses, |
| payload_ptr + (4 * sizeof(uint32_t)), |
| *((uint32_t *)payload_ptr + 3), |
| &cf_levels, &cf_levels_size); |
| if (!cf_levels || !cf_levels_size) |
| break; |
| hw_ses->user_level = cf_levels[1]; |
| ALOGV("%s:hw_ses->user_level %d at cf_levels[1]", |
| __func__, hw_ses->user_level); |
| } |
| } |
| 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; |
| |
| case KEY_ID_KEYWORD_CHANNEL_INDEX: |
| hw_ses->channel_idx = *((uint32_t *)payload_ptr + |
| GENERIC_DET_EVENT_CHANNEL_IDX_OFFSET); |
| break; |
| |
| case KEY_ID_TIMESTAMP_INFO: |
| /* No op */ |
| break; |
| |
| default: |
| ALOGW("%s: Unsupported generic detection event key id", |
| __func__); |
| break; |
| } |
| count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload_ptr += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_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->max_hist_buf) { |
| hw_ses->kw_end_idx = |
| convert_ms_to_bytes(hw_ses->max_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) { |
| hw_ses->user_level = (int32_t)(*(payload_ptr + |
| GCS_NON_GENERIC_USER_LEVEL_OFFSET)); |
| } else if (st_ses->exec_mode == ST_EXEC_MODE_ADSP) { |
| 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, channel_idx %d", __func__, st_ses->sm_handle, |
| kw_start_ms, kw_end_ms, hw_ses->is_generic_event, hw_ses->channel_idx); |
| |
| return 0; |
| } |
| |
| static inline int prepare_second_stage_for_client(st_session_t *stc_ses) |
| { |
| struct listnode *node = NULL; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| int status = 0; |
| |
| ALOGV("%s:[c%d]", __func__, stc_ses->sm_handle); |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node); |
| status = st_second_stage_prepare_session(st_sec_stage); |
| } |
| return status; |
| } |
| |
| static inline int start_second_stage_for_client(st_session_t *stc_ses) |
| { |
| struct listnode *node = NULL; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| int status = 0; |
| |
| ALOGV("%s:[c%d]", __func__, stc_ses->sm_handle); |
| |
| list_for_each(node, &stc_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); |
| } |
| return status; |
| } |
| |
| static inline void stop_second_stage_for_client(st_session_t *stc_ses) |
| { |
| struct listnode *node = NULL; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| |
| ALOGV("%s:[c%d]", __func__, stc_ses->sm_handle); |
| |
| list_for_each(node, &stc_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 generate_legacy_st_phrase_recognition_event |
| ( |
| const struct sound_trigger_phrase_sound_model *phrase_sm, |
| const struct sound_trigger_recognition_config *rc_config, |
| const void *payload, |
| unsigned int payload_size, |
| struct sound_trigger_phrase_recognition_event **out_rc_event |
| ) |
| { |
| struct sound_trigger_phrase_recognition_event *event; |
| unsigned int i = 0, j = 0, user_id = 0; |
| |
| ALOGD("%s: Enter payload_size %d", __func__, payload_size); |
| |
| if(!payload || !phrase_sm || !rc_config || !out_rc_event) { |
| ALOGE("%s: Null params", __func__); |
| return -EINVAL; |
| } |
| |
| *out_rc_event = NULL; |
| event = calloc(1, sizeof(*event) + payload_size); |
| if (!event) { |
| ALOGE("%s: event allocation failed size %d", __func__, payload_size); |
| return -ENODEV; |
| } |
| |
| event->num_phrases = rc_config->num_phrases; |
| event->common.data_offset = sizeof(*event); |
| event->common.data_size = payload_size; |
| memcpy((char *)event + event->common.data_offset, payload, payload_size); |
| |
| /* fill confidence levels */ |
| for (i = 0; i < rc_config->num_phrases; i++) { |
| event->phrase_extras[i].id = rc_config->phrases[i].id; |
| event->phrase_extras[i].recognition_modes = |
| phrase_sm->phrases[0].recognition_mode; |
| event->phrase_extras[i].confidence_level = ((char *)payload)[i]; |
| event->phrase_extras[i].num_levels = rc_config->phrases[i].num_levels; |
| for (j = 0; j < rc_config->phrases[i].num_levels; j++) { |
| user_id = rc_config->phrases[i].levels[j].user_id; |
| event->phrase_extras[i].levels[j].user_id = user_id; |
| event->phrase_extras[i].levels[j].level = |
| ((char *)payload)[user_id]; |
| } |
| } |
| |
| *out_rc_event = event; |
| return 0; |
| } |
| |
| /* |
| * 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(char *payload, size_t payload_size, |
| uint32_t version, void *platform) |
| { |
| size_t count_size = 0, opaque_size = 0; |
| uint32_t key_id = 0, key_payload_size = 0; |
| |
| while (count_size < payload_size) { |
| key_id = *(uint32_t *)payload; |
| key_payload_size = *((uint32_t *)payload + 1); |
| |
| switch (key_id) { |
| case KEY_ID_MULTI_MODEL_RESULT_INFO: |
| 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); |
| } |
| |
| if (platform_is_best_channel_index_supported(platform)) |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_channel_index_info); |
| |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_keyword_indices_info); |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_timestamp_info); |
| break; |
| |
| 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); |
| } |
| break; |
| |
| case KEY_ID_KEYWORD_POSITION_STATS: |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_keyword_indices_info); |
| break; |
| |
| case KEY_ID_TIMESTAMP_INFO: |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_timestamp_info); |
| break; |
| |
| case KEY_ID_KEYWORD_CHANNEL_INDEX: |
| opaque_size += sizeof(struct st_param_header) + |
| sizeof(struct st_channel_index_info); |
| break; |
| |
| default: |
| ALOGE("%s: Unsupported generic detection event key id", __func__); |
| break; |
| } |
| count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| } |
| 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_proxy_session_t *st_ses, void *opaque_data, |
| uint8_t *payload, |
| unsigned int payload_size) |
| { |
| uint8_t *payload_ptr = payload; |
| unsigned int i = 0, j = 0, k = 0, user_id = 0; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| struct listnode *node = NULL; |
| struct st_confidence_levels_info *conf_levels = NULL; |
| struct st_confidence_levels_info_v2 *conf_levels_v2 = NULL; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| int32_t kw_level = 0, user_level = 0; |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| kw_level = st_sec_stage->ss_session->confidence_score; |
| } else if (IS_USER_VERIFICATION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| user_level = st_sec_stage->ss_session->confidence_score; |
| } |
| } |
| |
| if (stc_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++) { |
| if (j <= payload_size) |
| conf_levels->conf_levels[i].kw_levels[j].kw_level = |
| payload_ptr[j]; |
| else |
| ALOGE("%s: unexpected conf size %d < %d", __func__, |
| payload_size, 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; |
| if (user_id <= payload_size) |
| conf_levels->conf_levels[i].kw_levels[j]. |
| user_levels[k].level = payload_ptr[user_id]; |
| else |
| ALOGE("%s: Unexpected conf size %d < %d", __func__, |
| payload_size, user_id); |
| } |
| } |
| } else if (IS_KEYWORD_DETECTION_MODEL(conf_levels->conf_levels[i].sm_id)) { |
| conf_levels->conf_levels[i].kw_levels[0].kw_level = kw_level; |
| } else if (IS_USER_VERIFICATION_MODEL(conf_levels->conf_levels[i].sm_id)) { |
| /* |
| * 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++) { |
| if (j <= payload_size) |
| conf_levels_v2->conf_levels[i].kw_levels[j].kw_level = |
| payload_ptr[j]; |
| else |
| ALOGE("%s: unexpected conf size %d < %d", __func__, |
| payload_size, 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; |
| if (user_id <= payload_size) |
| conf_levels_v2->conf_levels[i].kw_levels[j]. |
| user_levels[k].level = payload_ptr[user_id]; |
| else |
| ALOGE("%s: Unexpected conf size %d < %d", __func__, |
| payload_size, user_id); |
| } |
| } |
| } else if (IS_KEYWORD_DETECTION_MODEL(conf_levels_v2->conf_levels[i].sm_id)) { |
| conf_levels_v2->conf_levels[i].kw_levels[0].kw_level = kw_level; |
| } else if (IS_USER_VERIFICATION_MODEL(conf_levels_v2->conf_levels[i].sm_id)) { |
| /* |
| * 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_proxy_session_t *st_ses, uint8_t *payload, |
| unsigned int payload_size, |
| 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 = NULL; |
| struct listnode *node = NULL; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| struct sound_trigger_phrase_sound_model *phrase_sm = |
| (struct sound_trigger_phrase_sound_model *)stc_ses->phrase_sm; |
| |
| /* |
| * 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 < stc_ses->rc_config->num_phrases; j++) { |
| local_event->phrase_extras[j].id = stc_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 = |
| stc_ses->rc_config->phrases[j].num_levels; |
| if (j <= payload_size) |
| local_event->phrase_extras[j].confidence_level = payload[j]; |
| else |
| ALOGE("%s: unexpected conf size %d < %d", __func__, |
| payload_size, j); |
| |
| for (k = 0; k < stc_ses->rc_config->phrases[j].num_levels; k++) { |
| user_id = stc_ses->rc_config->phrases[j].levels[k].user_id; |
| if (user_id <= payload_size) { |
| local_event->phrase_extras[j].levels[k].user_id = user_id; |
| local_event->phrase_extras[j].levels[k].level = |
| payload[user_id]; |
| } else { |
| ALOGE("%s: Unexpected conf size %d < %d", __func__, |
| payload_size, user_id); |
| } |
| } |
| } |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| local_event->phrase_extras[0].confidence_level = |
| (uint8_t)st_sec_stage->ss_session->confidence_score; |
| } else if (IS_USER_VERIFICATION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| 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_proxy_session_t *st_ses, uint8_t *opaque_data, |
| uint8_t *payload, size_t payload_size, |
| struct sound_trigger_phrase_recognition_event *local_event) |
| { |
| uint32_t key_id = 0, key_payload_size = 0; |
| uint32_t timestamp_msw = 0, timestamp_lsw = 0; |
| struct st_param_header *param_hdr = NULL; |
| struct st_keyword_indices_info *kw_indices = NULL; |
| struct st_channel_index_info *chan_info = NULL; |
| struct st_timestamp_info *timestamps = NULL; |
| size_t count_size = 0; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| struct listnode *node = NULL; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| int status = 0; |
| unsigned char *cf_levels = NULL; |
| unsigned int cf_levels_size = 0; |
| multi_model_result_info_t *result_info = NULL; |
| |
| while (count_size < payload_size) { |
| key_id = *(uint32_t *)payload; |
| key_payload_size = *((uint32_t *)payload + 1); |
| |
| switch (key_id) { |
| case KEY_ID_MULTI_MODEL_RESULT_INFO: |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| ALOGE("%s: Error. Multi sm result info supported on PDK5 only", |
| __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| /* Set confidence levels */ |
| 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 (stc_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); |
| } |
| result_info = (multi_model_result_info_t *)(payload + |
| GENERIC_DET_EVENT_HEADER_SIZE); |
| |
| memset(stc_ses->sm_info.det_cf_levels, 0, |
| MAX_MULTI_SM_CONF_LEVELS); |
| |
| cf_levels = stc_ses->sm_info.det_cf_levels; |
| cf_levels_size = stc_ses->sm_info.cf_levels_size; |
| memcpy(opaque_data, stc_ses->st_conf_levels, |
| param_hdr->payload_size); |
| *(cf_levels + result_info->detected_keyword_id) = |
| result_info->best_confidence_level; |
| pack_opaque_data_conf_levels(st_ses, opaque_data, |
| cf_levels, cf_levels_size); |
| pack_recognition_event_conf_levels(st_ses, cf_levels, |
| cf_levels_size, local_event); |
| opaque_data += param_hdr->payload_size; |
| |
| /* Set keyword 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; |
| kw_indices->start_index = result_info->keyword_start_idx_bytes; |
| kw_indices->end_index = result_info->keyword_end_idx_bytes; |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, |
| list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| 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); |
| |
| /* Set timestamp */ |
| 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 = |
| (uint64_t)result_info->timestamp_msw_us << 32 | |
| result_info->timestamp_lsw_us; |
| if (!list_empty(&stc_ses->second_stage_list)) |
| timestamps->second_stage_det_event_time = |
| st_ses->hw_ses_current->second_stage_det_event_time; |
| opaque_data += sizeof(struct st_timestamp_info); |
| |
| if (platform_is_best_channel_index_supported(st_ses->stdev->platform)) { |
| /* set best channel idx */ |
| param_hdr = (struct st_param_header *)opaque_data; |
| param_hdr->key_id = ST_PARAM_KEY_CHANNEL_INDEX; |
| param_hdr->payload_size = sizeof(struct st_channel_index_info); |
| opaque_data += sizeof(struct st_param_header); |
| chan_info = (struct st_channel_index_info *)opaque_data; |
| chan_info->version = 0x1; |
| chan_info->channel_index = result_info->best_channel_idx; |
| opaque_data += sizeof(struct st_channel_index_info); |
| } |
| break; |
| |
| 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 (stc_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); |
| } |
| check_and_extract_det_conf_levels_payload(st_ses, |
| payload + (4 * sizeof(uint32_t)), *((uint32_t *)payload + 3), |
| &cf_levels, &cf_levels_size); |
| if (!cf_levels || !cf_levels_size) { |
| status = -EINVAL; |
| goto exit; |
| } |
| memcpy(opaque_data, stc_ses->st_conf_levels, |
| param_hdr->payload_size); |
| pack_opaque_data_conf_levels(st_ses, opaque_data, |
| cf_levels, cf_levels_size); |
| pack_recognition_event_conf_levels(st_ses, cf_levels, |
| cf_levels_size, 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 + 3); |
| kw_indices->end_index = *((uint32_t *)payload + 4); |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, |
| list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| 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; |
| |
| case KEY_ID_TIMESTAMP_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); |
| timestamp_lsw = *((uint32_t *)payload + 3); |
| timestamp_msw = *((uint32_t *)payload + 4); |
| timestamps = (struct st_timestamp_info *)opaque_data; |
| timestamps->version = 0x1; |
| timestamps->first_stage_det_event_time = |
| (uint64_t)timestamp_msw << 32 | timestamp_lsw; |
| if (!list_empty(&stc_ses->second_stage_list)) |
| timestamps->second_stage_det_event_time = |
| st_ses->hw_ses_current->second_stage_det_event_time; |
| opaque_data += sizeof(struct st_timestamp_info); |
| break; |
| |
| case KEY_ID_KEYWORD_CHANNEL_INDEX: |
| /* Pack the opaque data keyword indices structure */ |
| param_hdr = (struct st_param_header *)opaque_data; |
| param_hdr->key_id = ST_PARAM_KEY_CHANNEL_INDEX; |
| param_hdr->payload_size = sizeof(struct st_channel_index_info); |
| opaque_data += sizeof(struct st_param_header); |
| chan_info = (struct st_channel_index_info *)opaque_data; |
| chan_info->version = 0x1; |
| chan_info->channel_index = *((uint32_t *)payload + 3); |
| opaque_data += sizeof(struct st_channel_index_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 += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| } |
| |
| exit: |
| return status; |
| } |
| |
| static int parse_generic_event_without_opaque_data( |
| st_proxy_session_t *st_ses, uint8_t *payload, 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; |
| unsigned char *cf_levels = NULL; |
| unsigned int cf_levels_size = 0; |
| |
| while (count_size < payload_size) { |
| key_id = *(uint32_t *)payload; |
| key_payload_size = *((uint32_t *)payload + 1); |
| |
| switch (key_id) { |
| case KEY_ID_CONFIDENCE_LEVELS: |
| check_and_extract_det_conf_levels_payload(st_ses, |
| payload + (4 * sizeof(uint32_t)), *((uint32_t *)payload + 3), |
| &cf_levels, &cf_levels_size); |
| if (!cf_levels || !cf_levels_size) { |
| status = -EINVAL; |
| return status; |
| } |
| pack_recognition_event_conf_levels(st_ses, cf_levels, |
| cf_levels_size, local_event); |
| return status; |
| |
| case KEY_ID_KEYWORD_POSITION_STATS: |
| count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| break; |
| |
| case KEY_ID_TIMESTAMP_INFO: |
| count_size += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_size; |
| payload += GENERIC_DET_EVENT_HEADER_SIZE + key_payload_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_proxy_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; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| unsigned int i = 0, j = 0; |
| int status = 0; |
| uint8_t *opaque_data = 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, |
| stc_ses->conf_levels_intf_version, stc_ses->stdev->platform); |
| 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 = stc_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->vendor_uuid_info->is_qcva_uuid) { |
| if (stc_ses->rc_config->data_size > CUSTOM_CONFIG_OPAQUE_DATA_SIZE) { |
| status = parse_generic_event_and_pack_opaque_data(st_ses, |
| opaque_data, payload, payload_size, local_event); |
| if (status) { |
| ALOGE("%s: Failed to parse generic detection event with opaque" |
| "data %d", __func__, status); |
| goto exit; |
| } |
| |
| if (st_ses->stdev->enable_debug_dumps) { |
| 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); |
| ST_DBG_FILE_CLOSE(opaque_fd); |
| ALOGD("%s: detection opaque data dump stored in: detection_opaque_data_%d.bin", |
| __func__, opaque_cnt); |
| opaque_cnt++; |
| } |
| } else { |
| status = parse_generic_event_without_opaque_data(st_ses, payload, |
| payload_size, local_event); |
| if (status) { |
| ALOGE("%s: Failed to parse generic detection event without" |
| "opaque data %d", __func__, status); |
| goto exit; |
| } |
| } |
| } else { |
| memcpy(local_event->phrase_extras, |
| stc_ses->rc_config->phrases, stc_ses->rc_config->num_phrases * |
| sizeof(struct sound_trigger_phrase_recognition_extra)); |
| local_event->num_phrases = stc_ses->rc_config->num_phrases; |
| local_event->common.data_offset = sizeof(*local_event); |
| local_event->common.data_size = opaque_size; |
| memcpy(opaque_data, payload, opaque_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 = stc_ses->phrase_sm->common.type; |
| local_event->common.model = stc_ses->sm_handle; |
| local_event->common.capture_available = |
| stc_ses->rc_config->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:[c%d]", __func__, stc_ses->sm_handle); |
| |
| ALOGV("%s:[c%d] status=%d, type=%d, model=%d, capture_avaiable=%d, " |
| "num_phrases=%d id=%d", __func__, stc_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. |
| * TODO: Deprecate this when DSP for all shared targets of this component |
| * move to generic event. |
| */ |
| static int process_detection_event_keyphrase( |
| st_proxy_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; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| unsigned int i = 0, j = 0; |
| 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 = NULL; |
| struct listnode *node = NULL; |
| struct st_keyword_indices_info *kw_indices = NULL; |
| struct st_timestamp_info *timestamps = NULL; |
| bool enable_kw_indices = false; |
| unsigned char *cf_levels = NULL; |
| unsigned int cf_levels_size = 0; |
| |
| if ((stc_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 (stc_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); |
| |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| 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 = stc_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) { |
| payload_ptr = (uint8_t *)payload + 2; |
| payload_size -= 2; /* Re-use */ |
| } else if (st_ses->exec_mode == ST_EXEC_MODE_ADSP) { |
| 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 (stc_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); |
| } |
| check_and_extract_det_conf_levels_payload(st_ses, payload_ptr, |
| payload_size, &cf_levels, &cf_levels_size); |
| if (!cf_levels || !cf_levels_size) { |
| status = -EINVAL; |
| goto err_exit; |
| } |
| memcpy(opaque_data, stc_ses->st_conf_levels, param_hdr->payload_size); |
| pack_opaque_data_conf_levels(st_ses, opaque_data, cf_levels, |
| cf_levels_size); |
| pack_recognition_event_conf_levels(st_ses, cf_levels, cf_levels_size, |
| 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; |
| list_for_each(node, &stc_ses->second_stage_list) { |
| st_sec_stage = node_to_item(node, st_arm_second_stage_t, |
| list_node); |
| if (IS_KEYWORD_DETECTION_MODEL(st_sec_stage->ss_info->sm_id)) { |
| 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_hw_ses->first_stage_det_event_time; |
| if (!list_empty(&stc_ses->second_stage_list)) |
| timestamps->second_stage_det_event_time = |
| st_hw_ses->second_stage_det_event_time; |
| opaque_data += sizeof(struct st_timestamp_info); |
| |
| if (st_ses->stdev->enable_debug_dumps) { |
| 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); |
| ALOGD("%s: detection opaque data dump stored in: detection_opaque_data_%d.bin", |
| __func__, opaque_cnt); |
| opaque_cnt++; |
| } |
| |
| } else { |
| if (st_ses->vendor_uuid_info->is_qcva_uuid || |
| st_ses->vendor_uuid_info->is_qcmd_uuid) { |
| if (ST_EXEC_MODE_CPE == st_ses->exec_mode && |
| !st_hw_ses->is_generic_event) { |
| payload_ptr = payload; |
| payload_ptr += 2; /* Skip minor_version and num_active_models */ |
| payload_size -= 2; |
| } else { |
| payload_ptr = payload; |
| } |
| status = generate_legacy_st_phrase_recognition_event( |
| stc_ses->phrase_sm, stc_ses->rc_config, payload_ptr, |
| payload_size, &local_event); |
| |
| if (status) |
| goto exit; |
| } else { |
| ALOGD("%s: Send detection payload as is", __func__); |
| |
| 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, |
| stc_ses->rc_config->phrases, stc_ses->rc_config->num_phrases * |
| sizeof(struct sound_trigger_phrase_recognition_extra)); |
| local_event->num_phrases = stc_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 = stc_ses->phrase_sm->common.type; |
| local_event->common.model = stc_ses->sm_handle; |
| local_event->common.capture_available = |
| stc_ses->rc_config->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:[c%d]", __func__, stc_ses->sm_handle); |
| |
| ALOGV("%s:[c%d] status=%d, type=%d, model=%d, capture_avaiable=%d, " |
| "num_phrases=%d id=%d", __func__, stc_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_proxy_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; |
| st_session_t *stc_ses = st_ses->det_stc_ses; |
| 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 = stc_ses->sm_type; |
| local_event->model = stc_ses->sm_handle; |
| local_event->capture_available = stc_ses->rc_config->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__, stc_ses->sm_handle); |
| ALOGV("%s:[c%d] status=%d, type=%d, model=%d, capture_avaiable=%d", |
| __func__, stc_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_proxy_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->det_stc_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; |
| } |
| |
| |
| /* |
| * 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_proxy_session_t *st_ses = (st_proxy_session_t *)st_session; |
| st_session_t *stc_ses = NULL; |
| recognition_callback_t callback = NULL; |
| void *cookie = NULL; |
| struct listnode *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; |
| bool capture_requested = false; |
| uint64_t callback_time = 0; |
| |
| ALOGV("%s: Enter", __func__); |
| |
| /* |
| * For multi-clients it is expected only one of the clients detection |
| * happens at a time. Continue processing on a run time detected client |
| */ |
| 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) { |
| ALOGV("%s: exit", __func__); |
| pthread_mutex_unlock(&st_ses->ss_detections_lock); |
| return NULL; |
| } |
| if (!st_ses->det_stc_ses) |
| continue; |
| stc_ses = st_ses->det_stc_ses; |
| |
| list_for_each(node, &stc_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(node, &stc_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)) { |
| /* |
| * The client could unload the sound model at this time, which would wait |
| * for ss_detections_lock as part of st_session_deinit() with st_session_lock |
| * held. Before waiting for ss_detections_lock, the exit_aggregator_loop flag |
| * will be set to true, so this thread can exit in that scenario and avoid |
| * deadlock. |
| */ |
| do { |
| lock_status = pthread_mutex_trylock(&st_ses->lock); |
| } while (lock_status && !st_ses->exit_aggregator_loop); |
| |
| if (st_ses->exit_aggregator_loop) { |
| ALOGV("%s:[%d] client unloaded, lock status %d", |
| __func__, st_ses->sm_handle, lock_status); |
| if (!lock_status) |
| pthread_mutex_unlock(&st_ses->lock); |
| goto exit; |
| } |
| |
| /* |
| * 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); |
| |
| stc_ses->ss_det_count++; |
| 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); |
| |
| pthread_mutex_unlock(&st_ses->lock); |
| if (event) { |
| free(event); |
| event = NULL; |
| } |
| goto exit; |
| } |
| stc_ses->detection_sent = true; |
| callback = stc_ses->callback; |
| capture_requested = stc_ses->rc_config->capture_requested; |
| cookie = stc_ses->cookie; |
| callback_time = get_current_time_ns(); |
| ALOGD("%s:[c%d] Second stage detection SUCCESS, " |
| "calling client callback", __func__, stc_ses->sm_handle); |
| ALOGD("%s: Total sthal processing time: %llums", __func__, |
| (callback_time - st_ses->detection_event_time) / |
| NSECS_PER_MSEC); |
| pthread_mutex_unlock(&st_ses->lock); |
| ATRACE_BEGIN("sthal: client detection callback"); |
| callback(event, cookie); |
| free(event); |
| 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); |
| goto exit; |
| } |
| /* |
| * If client has not requested capture data, |
| * stop hw session buffering here to resume next |
| * detection |
| */ |
| if (!capture_requested) |
| st_ses->hw_ses_current->fptrs->stop_buffering( |
| st_ses->hw_ses_current); |
| } else { |
| ATRACE_ASYNC_END("sthal: detection reject", |
| st_ses->sm_handle); |
| stc_ses->ss_rej_count++; |
| ALOGD("%s: Second stage detection REJECT, count = %d, " |
| "restarting st_session", __func__, stc_ses->ss_rej_count); |
| st_ses->hw_ses_current->fptrs->stop_buffering( |
| st_ses->hw_ses_current); |
| start_second_stage_for_client(stc_ses); |
| st_session_ev_t ev = {.ev_id = ST_SES_EV_RESTART, |
| .stc_ses = stc_ses}; |
| DISPATCH_EVENT(st_ses, 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_proxy_session_t *st_ses) |
| { |
| int status = 0; |
| pthread_condattr_t attr; |
| |
| ALOGV("%s", __func__); |
| |
| 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); |
| } else { |
| st_ses->aggregator_thread_created = true; |
| } |
| } |
| |
| static void destroy_det_event_aggregator(st_proxy_session_t *st_ses) |
| { |
| int status = 0; |
| |
| ALOGV("%s", __func__); |
| |
| 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); |
| st_ses->aggregator_thread_created = false; |
| } |
| |
| static int init_st_hw_config(st_hw_session_t *hw_ses, uint32_t model_id) |
| { |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| int status; |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, model_id); |
| if (sthw_cfg) { |
| ALOGD("%s: Already initialized sthw_cfg with m_id[%d]", |
| __func__, model_id); |
| return 0; |
| } |
| |
| sthw_cfg = calloc(1, sizeof(struct st_hw_ses_config)); |
| if (!sthw_cfg) { |
| ALOGE("%s: Failed to allocate struct st_hw_ses_config, exiting", |
| __func__); |
| return -ENOMEM; |
| } |
| sthw_cfg->model_id = model_id; |
| |
| if (hw_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| sthw_cfg->conf_levels = calloc(1, MAX_MULTI_SM_CONF_LEVELS); |
| if (!sthw_cfg->conf_levels) { |
| ALOGE("%s: Failed to allocate conf_levels, exiting", |
| __func__); |
| status = -ENOMEM; |
| goto exit; |
| } |
| memset(sthw_cfg->conf_levels, MAX_CONF_LEVEL_VALUE, |
| MAX_MULTI_SM_CONF_LEVELS); |
| } |
| |
| list_add_tail(&hw_ses->sthw_cfg_list, |
| &sthw_cfg->sthw_cfg_list_node); |
| |
| return 0; |
| |
| exit: |
| if (sthw_cfg) { |
| free(sthw_cfg); |
| sthw_cfg = NULL; |
| } |
| return status; |
| } |
| |
| static int deinit_st_hw_config(st_hw_session_t *hw_ses, uint32_t model_id) |
| { |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, model_id); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| return -EINVAL; |
| } |
| |
| if (hw_ses->f_stage_version == ST_MODULE_TYPE_PDK5 && |
| sthw_cfg->conf_levels) { |
| free(sthw_cfg->conf_levels); |
| sthw_cfg->conf_levels = NULL; |
| } |
| |
| list_remove(&sthw_cfg->sthw_cfg_list_node); |
| free(sthw_cfg); |
| sthw_cfg = NULL; |
| |
| return 0; |
| } |
| |
| /* This function is called for multi-client */ |
| static int handle_load_sm(st_proxy_session_t *st_ses, st_session_t *stc_ses) |
| { |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| st_proxy_session_state_fn_t curr_state = st_ses->current_state; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| int status = 0; |
| |
| ALOGV("%s:[c%d-%d]", __func__, stc_ses->sm_handle, st_ses->sm_handle); |
| if (!stc_ses->phrase_sm) { |
| ALOGE("%s:[c%d] sound model data is not initialzed", __func__, |
| stc_ses->sm_handle); |
| return -EINVAL; |
| } |
| |
| if (!is_other_client_attached(st_ses, stc_ses)) { |
| ALOGE("%s:[c%d] Unexpected without multi-clients", __func__, |
| stc_ses->sm_handle); |
| return -EINVAL; |
| } |
| |
| if (st_ses->current_state == buffering_state_fn) |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| |
| if (st_ses->current_state == active_state_fn || |
| st_ses->current_state == detected_state_fn || |
| st_ses->current_state == buffering_state_fn) { |
| status = stop_session(st_ses, hw_ses, false); |
| if (status) |
| ALOGE("%s:[%d] stop_session failed %d", __func__, st_ses->sm_handle, |
| status); |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| } |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| status = hw_ses->fptrs->dereg_sm(hw_ses, 0); |
| if (status) { |
| ALOGE("%s:[%d] dereg_sm failed %d", __func__, |
| st_ses->sm_handle, status); |
| } |
| } |
| /* Continue updating sound model resulting in merged model */ |
| status = update_sound_model(stc_ses, true); |
| if (status) { |
| ALOGE("%s:[c%d] update_sound_model add failed %d", __func__, |
| stc_ses->sm_handle, status); |
| goto exit; |
| } |
| |
| p_info = get_sm_info_for_model_id(st_ses, stc_ses->sm_info.model_id); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| status = -EINVAL; |
| goto exit_1; |
| } |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, 0); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| status = -EINVAL; |
| goto exit_1; |
| } |
| |
| sthw_cfg->conf_levels = p_info->sm_info.cf_levels; |
| sthw_cfg->num_conf_levels = p_info->sm_info.cf_levels_size; |
| /* |
| * Sound model merge would have changed the order of merge conf levels, |
| * which need to be re-updated for all current active clients, if any. |
| */ |
| status = update_merge_conf_levels_payload_with_active_clients(st_ses); |
| if (status) |
| goto exit_1; |
| } else { |
| status = init_st_hw_config(hw_ses, stc_ses->sm_info.model_id); |
| if (status) |
| goto exit_1; |
| } |
| hw_ses->sthw_cfg_updated = true; |
| |
| status = hw_ses->fptrs->reg_sm(hw_ses, p_info->sm_info.sm_data, |
| p_info->sm_info.sm_size, p_info->sm_info.model_id); |
| if (status) { |
| ALOGE("%s:[%d] reg_sm failed %d", __func__, |
| st_ses->sm_handle, status); |
| goto exit_1; |
| } |
| |
| if (curr_state == active_state_fn || |
| curr_state == detected_state_fn || |
| curr_state == buffering_state_fn) { |
| |
| status = start_session(st_ses, hw_ses, false); |
| if (status) |
| goto exit_2; |
| STATE_TRANSITION(st_ses, active_state_fn); |
| } |
| |
| return 0; |
| |
| exit_2: |
| if (!st_ses->stdev->ssr_offline_received) |
| hw_ses->fptrs->dereg_sm(hw_ses, p_info->sm_info.model_id); |
| |
| exit_1: |
| if (!st_ses->stdev->ssr_offline_received) { |
| update_sound_model(stc_ses, false); |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) |
| update_merge_conf_levels_payload_with_active_clients(st_ses); |
| } |
| |
| exit: |
| if (st_ses->stdev->ssr_offline_received) { |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } |
| return status; |
| } |
| |
| /* This function is called for multi-client */ |
| static int handle_unload_sm(st_proxy_session_t *st_ses, st_session_t *stc_ses) |
| { |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| st_proxy_session_state_fn_t curr_state = st_ses->current_state; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct st_hw_ses_config *sthw_cfg = NULL; |
| int status = 0; |
| |
| ALOGV("%s:[c%d-%d]", __func__, stc_ses->sm_handle, st_ses->sm_handle); |
| |
| if (!is_other_client_attached(st_ses, stc_ses)) { |
| ALOGE("%s:[c%d] Unexpected without multi-clients", __func__, |
| stc_ses->sm_handle); |
| return -EINVAL; |
| } |
| |
| if (st_ses->current_state == buffering_state_fn) |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| |
| if (st_ses->current_state == active_state_fn || |
| st_ses->current_state == detected_state_fn || |
| st_ses->current_state == buffering_state_fn) { |
| status = stop_session(st_ses, hw_ses, false); |
| if (status) |
| ALOGE("%s:[%d] stop_session failed %d", __func__, |
| st_ses->sm_handle, status); |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| } |
| |
| status = hw_ses->fptrs->dereg_sm(hw_ses, stc_ses->sm_info.model_id); |
| if (status) |
| ALOGE("%s:[%d] dereg_sm failed %d", __func__, st_ses->sm_handle, status); |
| |
| /* Continue deleting this model */ |
| status = update_sound_model(stc_ses, false); |
| if (status) |
| ALOGE("%s:[c%d] update_sound_model delete failed %d", __func__, |
| stc_ses->sm_handle, status); |
| |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| |
| sthw_cfg = get_sthw_cfg_for_model_id(hw_ses, 0); |
| if (!sthw_cfg) { |
| ALOGE("%s: Unexpected, no matching sthw_cfg", __func__); |
| status = -EINVAL; |
| goto exit; |
| } |
| |
| sthw_cfg->conf_levels = p_info->sm_info.cf_levels; |
| sthw_cfg->num_conf_levels = p_info->sm_info.cf_levels_size; |
| /* |
| * Sound model merge would have changed the order of merge conf levels, |
| * which need to be re-updated for all current active clients, if any. |
| */ |
| update_merge_conf_levels_payload_with_active_clients(st_ses); |
| |
| /* Load remaining merged sound model */ |
| status = hw_ses->fptrs->reg_sm(hw_ses, p_info->sm_info.sm_data, |
| p_info->sm_info.sm_size, 0); |
| if (status) { |
| ALOGE("%s:[%d] reg_sm failed %d", __func__, |
| st_ses->sm_handle, status); |
| goto exit; |
| } |
| } else { |
| status = deinit_st_hw_config(hw_ses, stc_ses->sm_handle); |
| if (status) |
| goto exit; |
| } |
| hw_ses->sthw_cfg_updated = true; |
| |
| if (curr_state == active_state_fn || |
| curr_state == detected_state_fn || |
| curr_state == buffering_state_fn) { |
| |
| status = start_session(st_ses, hw_ses, false); |
| if (status) |
| goto exit; |
| STATE_TRANSITION(st_ses, active_state_fn); |
| } |
| return 0; |
| |
| exit: |
| if (st_ses->stdev->ssr_offline_received) { |
| if (st_ses->f_stage_version == ST_MODULE_TYPE_PDK5) |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } |
| return status; |
| } |
| |
| static int idle_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| int ret = 0; |
| st_session_t *stc_ses = ev->stc_ses; |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| struct st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| struct version_arch_payload version_payload; |
| |
| /* skip parameter check as this is an internal funciton */ |
| ALOGD("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| |
| switch (ev->ev_id) { |
| case ST_SES_EV_LOAD_SM: |
| if (!stc_ses->phrase_sm) { |
| ALOGE("%s: sound model data is not initialzed", __func__); |
| status = -EINVAL; |
| break; |
| } |
| status = update_sound_model(stc_ses, true); |
| if (status) { |
| ALOGE("%s:[c%d] update sound model add failed %d", __func__, |
| stc_ses->sm_handle, status); |
| status = -EINVAL; |
| break; |
| } |
| |
| p_info = get_sm_info_for_model_id(st_ses, stc_ses->sm_info.model_id); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| status = -EINVAL; |
| break; |
| } |
| |
| status = init_st_hw_config(hw_ses, p_info->sm_info.model_id); |
| if (status) { |
| ALOGE("%s:[%d] failed to init sthw_cfg, exiting", |
| __func__, st_ses->sm_handle); |
| 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++) { |
| if (stc_ses->pending_load) |
| status = ret = reg_all_sm(st_ses, hw_ses); |
| else |
| status = ret = hw_ses->fptrs->reg_sm(hw_ses, p_info->sm_info.sm_data, |
| p_info->sm_info.sm_size, p_info->sm_info.model_id); |
| |
| if (ret) { |
| if (st_ses->stdev->ssr_offline_received) { |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| 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; |
| |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| break; |
| |
| case ST_SES_EV_SET_EXEC_MODE: |
| stc_ses->exec_mode = ev->payload.exec_mode; |
| if (ev->payload.exec_mode == st_ses->exec_mode) |
| break; |
| |
| 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: |
| stc_ses->paused = true; |
| break; |
| |
| case ST_SES_EV_RESUME: |
| stc_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; |
| case ST_SES_EV_GET_MODULE_VERSION: |
| /* Open Dummy LSM session for google hotword during bootup */ |
| |
| status = hw_ses->fptrs->open_session(hw_ses); |
| if (status) { |
| ALOGE("%s: failed to start lsm session with error %d", __func__, |
| status); |
| break; |
| } |
| |
| status = hw_ses->fptrs->get_module_version(hw_ses, &version_payload, |
| sizeof(struct version_arch_payload)); |
| |
| if (status) { |
| ALOGE("%s: failed to get module version %d", __func__, |
| status); |
| hw_ses->fptrs->close_session(hw_ses); |
| break; |
| } |
| hw_ses->fptrs->close_session(hw_ses); |
| snprintf(ev->payload.module_version, SOUND_TRIGGER_MAX_STRING_LEN, "%d, %s", |
| version_payload.version, version_payload.arch); |
| |
| break; |
| default: |
| ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle); |
| break; |
| }; |
| |
| return status; |
| } |
| |
| static int loaded_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| st_session_t *stc_ses = ev->stc_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| 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 st_proxy_ses_sm_info_wrapper *p_info = NULL; |
| |
| /* skip parameter check as this is an internal function */ |
| ALOGD("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| |
| switch (ev->ev_id) { |
| case ST_SES_EV_LOAD_SM: |
| /* Valid only in multi-client session usecase */ |
| status = handle_load_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_UNLOAD_SM: |
| if (is_other_client_attached(st_ses, stc_ses)) { |
| status = handle_unload_sm(st_ses, stc_ses); |
| break; |
| } |
| |
| status = hw_ses->fptrs->dereg_sm(hw_ses, stc_ses->sm_info.model_id); |
| if (status) |
| ALOGE("%s:[%d] dereg_sm failed %d", __func__, |
| st_ses->sm_handle, status); |
| |
| status = update_sound_model(stc_ses, false); |
| if (status) |
| ALOGE("%s:[c%d] update_sound_model failed %d", __func__, |
| stc_ses->sm_handle, status); |
| |
| status = deinit_st_hw_config(hw_ses, stc_ses->sm_info.model_id); |
| if (status) |
| ALOGE("%s:[c%d] failed to deinit sthw_cfg", |
| __func__, stc_ses->sm_handle); |
| |
| /* since this is a teardown scenario dont fail here */ |
| status = 0; |
| STATE_TRANSITION(st_ses, idle_state_fn); |
| break; |
| |
| case ST_SES_EV_RESUME: |
| stc_ses->paused = false; |
| if (!is_any_client_in_state(st_ses, ST_STATE_ACTIVE)) { |
| /* |
| * When a transition is needed due to lpi mode or barge-in mode, |
| * call dereg_sm and reg_sm to select the updated lsm_usecase. |
| */ |
| if (hw_ses->lpi_enable != hw_ses->stdev->lpi_enable || |
| (hw_ses->barge_in_mode != hw_ses->stdev->barge_in_mode && |
| !hw_ses->stdev->support_dynamic_ec_update)) { |
| |
| hw_ses->lpi_enable = hw_ses->stdev->lpi_enable; |
| hw_ses->barge_in_mode = hw_ses->stdev->barge_in_mode; |
| |
| status = dereg_all_sm(st_ses, hw_ses); |
| if (status) { |
| ALOGE("%s:[%d] failed to dereg_sm err %d", __func__, |
| st_ses->sm_handle, status); |
| break; |
| } |
| |
| status = reg_all_sm(st_ses, hw_ses); |
| if (status) { |
| ALOGE("%s:[%d] failed to reg_sm err %d", __func__, |
| st_ses->sm_handle, status); |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, idle_state_fn); |
| } |
| } |
| break; |
| } |
| |
| if (stc_ses->pending_client_start) { |
| ALOGV("%s: Session not resuming due to client c%d buffering", |
| __func__, stc_ses->sm_handle); |
| break; |
| } |
| /* Fall through */ |
| case ST_SES_EV_START: |
| case ST_SES_EV_RESTART: |
| if (ev->ev_id == ST_SES_EV_RESTART) |
| update_hw_config_on_restart(st_ses, stc_ses); |
| |
| /* |
| * During Resume, the first active client will start the hw sesison. |
| * During Start, check for any paused sessions to delay actual start |
| * to Resume. |
| */ |
| if ((ev->ev_id != ST_SES_EV_RESUME) && is_any_client_paused(st_ses)) |
| break; |
| |
| status = start_session(st_ses, hw_ses, false); |
| if (status) { |
| if (st_ses->stdev->ssr_offline_received) { |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } else { |
| ALOGE("%s:[%d] failed to start session, err %d", __func__, |
| st_ses->sm_handle, status); |
| } |
| break; |
| } |
| STATE_TRANSITION(st_ses, active_state_fn); |
| break; |
| |
| case ST_SES_EV_STOP: |
| /* |
| * Valid in multi-client case. |
| * Reconfig based off other active clients, if any, so that RESUME |
| * can apply this reconfig. |
| */ |
| update_hw_config_on_stop(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_SSR_OFFLINE: |
| /* exec mode can be none if ssr occurs during a transition */ |
| if (st_ses->exec_mode != ST_EXEC_MODE_NONE) |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| break; |
| |
| case ST_SES_EV_PAUSE: |
| stc_ses->paused = true; |
| break; |
| |
| case ST_SES_EV_SET_EXEC_MODE: |
| new_exec_mode = ev->payload.exec_mode; |
| |
| if (new_exec_mode == st_ses->exec_mode) { |
| stc_ses->exec_mode = st_ses->exec_mode; |
| break; |
| } |
| |
| if (st_ses->exec_mode != ST_EXEC_MODE_NONE) { |
| st_ses->exec_mode = ST_EXEC_MODE_NONE; |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = ST_EXEC_MODE_NONE; |
| } |
| /* unload sm for current hw session */ |
| status = hw_ses->fptrs->dereg_sm(hw_ses, 0); |
| 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; |
| } else if (ST_EXEC_MODE_ADSP == new_exec_mode) { |
| new_hw_ses = st_ses->hw_ses_adsp; |
| } else { |
| ALOGE("%s: unknown execution mode %d", __func__, |
| new_exec_mode); |
| status = -EINVAL; |
| break; |
| } |
| |
| p_info = get_sm_info_for_model_id(st_ses, 0); |
| if (!p_info) { |
| ALOGE("%s: Unexpected, no matching sm_info" , __func__); |
| status = -EINVAL; |
| break; |
| } |
| |
| status = new_hw_ses->fptrs->reg_sm(new_hw_ses, |
| p_info->sm_info.sm_data, p_info->sm_info.sm_size, 0); |
| if (status) { |
| ALOGE("%s:[%d] reg_sm failed with err %d", __func__, |
| st_ses->sm_handle, status); |
| break; |
| } |
| /* switch hw sessions only if successful*/ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| dereg_hal_event_session(c_ses); |
| reg_hal_event_session(c_ses, new_hw_ses); |
| } |
| } |
| 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); |
| /* |
| * Device switch might happen during active buffering. |
| * If any client is active, start hw session. |
| */ |
| if (is_any_client_in_state(st_ses, ST_STATE_ACTIVE)) { |
| st_session_ev_t start_ev = {.ev_id = ST_SES_EV_START, |
| .stc_ses = stc_ses}; |
| DISPATCH_EVENT(st_ses, start_ev, status); |
| } |
| } |
| |
| 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; |
| } |
| |
| static int active_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| st_session_t *stc_ses = ev->stc_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| 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; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| struct sound_trigger_recognition_event *event = NULL; |
| recognition_callback_t callback = NULL; |
| void *cookie = NULL; |
| bool lab_enabled = false, enable_second_stage = false, active = false; |
| |
| /* 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: |
| /* Valid in multi-client usecase */ |
| status = handle_load_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_UNLOAD_SM: |
| /* Valid in multi-client usecase */ |
| status = handle_unload_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_RESTART: |
| /* Valid in multi-client usecase */ |
| update_hw_config_on_restart(st_ses, stc_ses); |
| /* Fall through */ |
| case ST_SES_EV_START: |
| /* Valid in multi-client usecase */ |
| status = stop_session(st_ses, hw_ses, false); |
| if (!status) { |
| status = start_session(st_ses, hw_ses, false); |
| if (status) |
| ALOGE("%s:[%d] start_session failed %d", __func__, |
| st_ses->sm_handle, status); |
| } else { |
| ALOGE("%s:[%d] stop_session failed %d", __func__, |
| st_ses->sm_handle, status); |
| } |
| if (status & st_ses->stdev->ssr_offline_received) { |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } |
| break; |
| |
| case ST_SES_EV_SET_EXEC_MODE: |
| new_exec_mode = ev->payload.exec_mode; |
| |
| if (new_exec_mode == st_ses->exec_mode) { |
| stc_ses->exec_mode = st_ses->exec_mode; |
| 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); |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = ST_EXEC_MODE_NONE; |
| } |
| 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; |
| } else if (ST_EXEC_MODE_ADSP == new_exec_mode) { |
| new_hw_ses = st_ses->hw_ses_adsp; |
| } else { |
| ALOGE("%s: unknown execution mode %d", __func__, |
| new_exec_mode); |
| status = -EINVAL; |
| break; |
| } |
| /* |
| * hw session changed to/from WDSP/ADSP, hence update the |
| * related config. |
| * Not applicable for LPI<->non-LPI transtions as hw session |
| * doesn't change. |
| */ |
| status = update_hw_config_on_start(stc_ses, new_hw_ses); |
| if (status) { |
| ALOGE("%s: Update_hw_config_on_start failed %d", |
| __func__, status); |
| 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 */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| dereg_hal_event_session(c_ses); |
| reg_hal_event_session(c_ses, new_hw_ses); |
| } |
| } |
| 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: |
| /* |
| * For multi-client, the first active pausing client stops the hw |
| * session and moves to loaded state. |
| */ |
| stc_ses->paused = true; |
| if (stc_ses->state != ST_STATE_ACTIVE) |
| break; |
| |
| status = stop_session(st_ses, hw_ses, false); |
| |
| if (status) { |
| if (st_ses->stdev->ssr_offline_received) { |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| dereg_all_sm(st_ses, hw_ses); |
| status = 0; |
| } else { |
| ALOGE("%s:[%d] failed to stop session, err %d", __func__, |
| st_ses->sm_handle, status); |
| /* Move anyway to loaded state */ |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| } |
| break; |
| } |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| break; |
| |
| case ST_SES_EV_RESUME: |
| if (stc_ses->paused == true) |
| stc_ses->paused = false; |
| break; |
| |
| case ST_SES_EV_STOP: |
| status = stop_session(st_ses, hw_ses, false); |
| if (status) |
| ALOGE("%s:[%d] start_session failed %d", __func__, |
| st_ses->sm_handle, status); |
| |
| /* Continue to reconfig based off other active clients, if any */ |
| active = update_hw_config_on_stop(st_ses, stc_ses); |
| |
| if (!status) { |
| if (active) { |
| ALOGD("%s: client c%d stopped, start %d due to reconfig", |
| __func__, stc_ses->sm_handle, st_ses->sm_handle); |
| status = start_session(st_ses, hw_ses, false); |
| if (status) |
| ALOGE("%s:[%d] start_session failed %d", __func__, |
| st_ses->sm_handle, status); |
| /* Stay in active state */ |
| } else { |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| } |
| } |
| |
| if (status) { |
| if (st_ses->stdev->ssr_offline_received) { |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } else { |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| } |
| } |
| |
| break; |
| |
| case ST_SES_EV_DETECTED: |
| |
| st_ses->detection_event_time = get_current_time_ns(); |
| /* |
| * Find which client is this detection for. |
| * Note that only one keyword detection can happen at a time. |
| */ |
| stc_ses = get_detected_client(st_ses, |
| ev->payload.detected.detect_payload, |
| ev->payload.detected.payload_size); |
| |
| if (!stc_ses) { |
| ALOGW("%s:[%d] Couldn't find a matching client for detection", |
| __func__, st_ses->sm_handle); |
| /* |
| * Though we set higest conf level 100 for inactive client in merged |
| * sound model, it may be possible it still detects. In case the lab |
| * is enabled due to other active client, stop hw buffering. |
| */ |
| if (st_ses->lab_enabled) |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| pthread_mutex_unlock(&st_ses->lock); |
| break; |
| } |
| stc_ses->fs_det_count++; |
| st_ses->det_stc_ses = stc_ses; |
| st_ses->hw_ses_current->enable_second_stage = false; /* Initialize */ |
| stc_ses->detection_sent = false; |
| hw_ses->detected_preroll = stc_ses->preroll_duration; |
| |
| if (list_empty(&stc_ses->second_stage_list) || |
| st_ses->detection_requested) { |
| st_ses->detection_requested = false; |
| status = process_detection_event(st_ses, |
| ev->payload.detected.timestamp, |
| ev->payload.detected.detect_status, |
| ev->payload.detected.detect_payload, |
| 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 |
| */ |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| if (event) |
| free(event); |
| pthread_mutex_unlock(&st_ses->lock); |
| break; |
| } |
| } else { |
| ALOGV("%s:[c%d] second stage enabled, list_empty %d," |
| "det_requested %d", __func__, stc_ses->sm_handle, |
| list_empty(&stc_ses->second_stage_list), |
| st_ses->detection_requested); |
| |
| enable_second_stage = true; |
| /* |
| * Before first stage starts buffering, update the second stage info |
| * to first stage layer for further communication between first and |
| * second stage layers. |
| */ |
| st_ses->hw_ses_current->enable_second_stage = true; |
| st_ses->hw_ses_current->second_stage_list = |
| &(stc_ses->second_stage_list); |
| |
| list_for_each(node, &stc_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; |
| } |
| get_first_stage_detection_params(st_ses, |
| ev->payload.detected.detect_payload, |
| ev->payload.detected.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) { |
| if (stc_ses->rc_config->capture_requested || |
| !list_empty(&stc_ses->second_stage_list)) { |
| if (st_ses->stdev->enable_debug_dumps && |
| stc_ses->rc_config->capture_requested) { |
| ST_DBG_FILE_OPEN_WR(st_ses->lab_fp, ST_DEBUG_DUMP_LOCATION, |
| "lab_capture", "bin", file_cnt); |
| ALOGD("%s: Voice Request stored in: lab_capture_%d.bin", |
| __func__, file_cnt); |
| file_cnt++; |
| } |
| STATE_TRANSITION(st_ses, buffering_state_fn); |
| lab_enabled = true; |
| } else { |
| /* |
| * for merged model case the client detected may not have |
| * requested the capture nor enabled the second stage, but the |
| * hw lab could be enabled due to other client requested capture |
| * or enabled second stage. |
| */ |
| ALOGV("%s: stop buffering as c%d doesn't need", __func__, |
| stc_ses->sm_handle); |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| STATE_TRANSITION(st_ses, detected_state_fn); |
| lab_enabled = false; |
| } |
| } else { |
| STATE_TRANSITION(st_ses, detected_state_fn); |
| } |
| |
| if (!stc_ses->callback) { |
| ALOGE("%s:[c%d] received detection event but no callback", |
| __func__, stc_ses->sm_handle); |
| status = -EINVAL; |
| if (event) |
| free(event); |
| pthread_mutex_unlock(&st_ses->lock); |
| break; |
| } |
| /* |
| * callback to user, assumption is that client does not |
| * block in the callback waiting for data otherwise will be a deadlock. |
| * If second stage is enabled, the detection will be sent later when |
| * second stage successfully detects. |
| */ |
| if (!enable_second_stage) { |
| stc_ses->detection_sent = true; |
| callback = stc_ses->callback; |
| cookie = stc_ses->cookie; |
| ALOGD("%s:[c%d] invoking the client callback", |
| __func__, stc_ses->sm_handle); |
| ATRACE_ASYNC_END("sthal: detection success", |
| st_ses->sm_handle); |
| if (!lab_enabled) { |
| st_session_ev_t deferred_ev = { |
| .ev_id = ST_SES_EV_DEFERRED_STOP, |
| .stc_ses = stc_ses |
| }; |
| DISPATCH_EVENT(st_ses, deferred_ev, status); |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| ATRACE_BEGIN("sthal: client detection callback"); |
| callback(event, cookie); |
| ATRACE_END(); |
| if (event) |
| free(event); |
| } else { |
| pthread_mutex_unlock(&st_ses->lock); |
| } |
| |
| /* |
| * 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); |
| } |
| break; |
| |
| case ST_SES_EV_SSR_OFFLINE: |
| /* exec mode can be none if ssr occurs during a transition */ |
| if (st_ses->exec_mode != ST_EXEC_MODE_NONE) |
| stop_session(st_ses, hw_ses, true); |
| STATE_TRANSITION(st_ses, ssr_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_SET_DEVICE: |
| if (!ev->payload.enable) { |
| st_ses->device_disabled = true; |
| status = hw_ses->fptrs->disable_device(hw_ses, true); |
| } else { |
| status = hw_ses->fptrs->enable_device(hw_ses, true); |
| st_ses->device_disabled = false; |
| } |
| |
| if (status && st_ses->stdev->ssr_offline_received) { |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } |
| 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: |
| if (!list_empty(&stc_ses->second_stage_list)) { |
| ALOGE("%s:[%d] Event not supported with second stage enabled", |
| __func__, st_ses->sm_handle); |
| status = -EINVAL; |
| break; |
| } |
| status = hw_ses->fptrs->send_detection_request(hw_ses); |
| if (!status) |
| st_ses->detection_requested = true; |
| break; |
| |
| default: |
| ALOGD("%s:[%d] unhandled event", __func__, st_ses->sm_handle); |
| break; |
| }; |
| |
| return status; |
| } |
| |
| static int detected_state_fn(st_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| st_exec_mode_t new_exec_mode = ST_EXEC_MODE_NONE; |
| st_session_t *stc_ses = ev->stc_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| 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:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| |
| switch (ev->ev_id) { |
| case ST_SES_EV_LOAD_SM: |
| /* Valid event only in multi-client usecase */ |
| /* |
| * If a detected client deffered stop event is not handled yet, |
| * handle here before moving out of detected state |
| */ |
| if (st_ses->det_stc_ses && !st_ses->det_stc_ses->pending_stop) { |
| ALOGD("%s:[%d] post deferred stop for c%d", __func__, |
| st_ses->sm_handle, st_ses->det_stc_ses->sm_handle); |
| status = hw_session_notifier_enqueue( |
| st_ses->det_stc_ses->sm_handle, ST_SES_EV_DEFERRED_STOP, |
| ST_SES_DEFERRED_STOP_DELAY_MS); |
| if (!status) |
| st_ses->det_stc_ses->pending_stop = true; |
| } |
| status = handle_load_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_UNLOAD_SM: |
| /* Valid event only in multi-client usecase */ |
| /* |
| * If a detected client deffered stop event is not handled yet, |
| * handle here before moving out of detected state |
| */ |
| if (st_ses->det_stc_ses && !st_ses->det_stc_ses->pending_stop) { |
| ALOGD("%s:[%d] post deferred stop for client c%d", __func__, |
| st_ses->sm_handle, st_ses->det_stc_ses->sm_handle); |
| status = hw_session_notifier_enqueue( |
| st_ses->det_stc_ses->sm_handle, ST_SES_EV_DEFERRED_STOP, |
| ST_SES_DEFERRED_STOP_DELAY_MS); |
| if (!status) |
| st_ses->det_stc_ses->pending_stop = true; |
| } |
| status = handle_unload_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_START: |
| /* For multi-client, other loaded client may start */ |
| STATE_TRANSITION(st_ses, active_state_fn); |
| DISPATCH_EVENT(st_ses, *ev, status); |
| break; |
| |
| case ST_SES_EV_RESTART: |
| status = restart_session(st_ses, hw_ses); |
| if (status && !st_ses->stdev->ssr_offline_received) |
| ALOGE("%s:[%d] failed to start session, err %d", __func__, |
| st_ses->sm_handle, status); |
| |
| if (st_ses->stdev->ssr_offline_received) { |
| stop_session(st_ses, hw_ses, true); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } else { |
| /* Move anyways to allow client unload */ |
| STATE_TRANSITION(st_ses, active_state_fn); |
| } |
| break; |
| |
| case ST_SES_EV_PAUSE: |
| 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. For multi-client, it could be current |
| * detected client stop or other client stop. |
| */ |
| STATE_TRANSITION(st_ses, active_state_fn); |
| DISPATCH_EVENT(st_ses, *ev, status); |
| break; |
| |
| case ST_SES_EV_SSR_OFFLINE: |
| /* |
| * 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_session(st_ses, hw_ses, true); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| break; |
| |
| case ST_SES_EV_SET_EXEC_MODE: |
| new_exec_mode = ev->payload.exec_mode; |
| |
| if (new_exec_mode == st_ses->exec_mode) { |
| stc_ses->exec_mode = st_ses->exec_mode; |
| 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); |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| } |
| 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; |
| } else if (ST_EXEC_MODE_ADSP == new_exec_mode) { |
| new_hw_ses = st_ses->hw_ses_adsp; |
| } else { |
| ALOGE("%s: unknown execution mode %d", __func__, |
| new_exec_mode); |
| status = -EINVAL; |
| break; |
| } |
| |
| /* |
| * hw session changed to/from WDSP/ADSP, hence update the |
| * related config. |
| * Not applicable for LPI<->non-LPI transtions as hw session |
| * doesn't change. |
| */ |
| status = update_hw_config_on_start(stc_ses, new_hw_ses); |
| if (status) { |
| ALOGE("%s: Update_hw_config_on_start failed %d", |
| __func__, status); |
| 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; |
| |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| dereg_hal_event_session(c_ses); |
| reg_hal_event_session(c_ses, new_hw_ses); |
| } |
| } |
| 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(stc_ses->sm_handle, |
| ST_SES_EV_DEFERRED_STOP, ST_SES_DEFERRED_STOP_DELAY_MS); |
| if (!status) |
| stc_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_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| st_session_t *stc_ses = ev->stc_ses; |
| struct listnode *node = NULL; |
| st_session_t *c_ses = NULL; |
| st_hw_session_t *hw_ses = st_ses->hw_ses_current; |
| st_exec_mode_t new_exec_mode = ST_EXEC_MODE_NONE; |
| st_hw_session_t *new_hw_ses = NULL; |
| |
| /* skip parameter check as this is an internal function */ |
| ALOGVV("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| |
| switch (ev->ev_id) { |
| case ST_SES_EV_LOAD_SM: |
| /* Valid in multi-client usecase */ |
| ALOGD("%s:[c%d-%d] load sm", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle); |
| |
| status = handle_load_sm(st_ses, stc_ses); |
| break; |
| |
| case ST_SES_EV_UNLOAD_SM: |
| ALOGD("%s:[c%d-%d] unload sm", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle); |
| |
| status = handle_unload_sm(st_ses, stc_ses); |
| break; |
| |
| 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); |
| if (st_ses->stdev->enable_debug_dumps && |
| stc_ses->rc_config->capture_requested) |
| ST_DBG_FILE_WRITE(st_ses->lab_fp, ev->payload.readpcm.out_buff, |
| ev->payload.readpcm.out_buff_size); |
| break; |
| case ST_SES_EV_END_BUFFERING: |
| if (stc_ses == st_ses->det_stc_ses) { |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| if (!stc_ses->pending_stop) { |
| ALOGD("%s:[c%d] post deferred stop on buffering end", __func__, |
| stc_ses->sm_handle); |
| status = hw_session_notifier_enqueue(stc_ses->sm_handle, |
| ST_SES_EV_DEFERRED_STOP, ST_SES_DEFERRED_STOP_DELAY_MS); |
| if (!status) |
| stc_ses->pending_stop = true; |
| } else { |
| ALOGD("%s:[c%d] skip deferred stop on buffering as already set", |
| __func__, stc_ses->sm_handle); |
| } |
| } |
| break; |
| |
| case ST_SES_EV_DEFERRED_STOP: |
| ALOGD("%s:[%d] post internal deferred stop from buffering state", |
| __func__, st_ses->sm_handle); |
| status = hw_session_notifier_enqueue(stc_ses->sm_handle, |
| ST_SES_EV_DEFERRED_STOP, ST_SES_DEFERRED_STOP_DELAY_MS); |
| if (!status) |
| stc_ses->pending_stop = true; |
| break; |
| |
| case ST_SES_EV_STOP: |
| ALOGD("%s:[c%d-%d] handle event STOP", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle); |
| if (stc_ses != st_ses->det_stc_ses) { |
| ALOGD("%s: c%d buffering, delay c%d stop", __func__, |
| st_ses->det_stc_ses->sm_handle, stc_ses->sm_handle); |
| update_hw_config_on_stop(st_ses, stc_ses); |
| break; |
| } |
| /* Fall through */ |
| case ST_SES_EV_PAUSE: |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| /* |
| * The flag pending_client_start is used to prevent resume events from moving |
| * the session back to active state while the client is still buffering. |
| * Further detections while the client is buffering result in errors in the |
| * middleware state machine. When done buffering, the client can move the |
| * session back to active state when it calls start recognition. |
| */ |
| if (stc_ses->detection_sent) |
| stc_ses->pending_client_start = true; |
| STATE_TRANSITION(st_ses, active_state_fn); |
| DISPATCH_EVENT(st_ses, *ev, status); |
| if (st_ses->stdev->enable_debug_dumps && |
| stc_ses->rc_config->capture_requested) |
| ST_DBG_FILE_CLOSE(st_ses->lab_fp); |
| break; |
| |
| case ST_SES_EV_SET_DEVICE: |
| ALOGD("%s:[c%d-%d] handle SET_DEVICE", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle); |
| /* |
| * Device switch will not wait for buffering to finish. It will instead |
| * interrupt and stop the buffering and transition to the active state. |
| */ |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| STATE_TRANSITION(st_ses, active_state_fn); |
| DISPATCH_EVENT(st_ses, *ev, status); |
| |
| /* |
| * set_device event can be dispatched with any one of attached |
| * multi-clients. For current detected client, the App may or may |
| * not start next detection, so handle the state accordingly. |
| */ |
| if (st_ses->det_stc_ses->pending_stop) { |
| ALOGD("%s:[c%d] cancel ST_SES_EV_DEFERRED_STOP", __func__, |
| st_ses->det_stc_ses->sm_handle); |
| hw_session_notifier_cancel(stc_ses->sm_handle, |
| ST_SES_EV_DEFERRED_STOP); |
| stc_ses->pending_stop = false; |
| } |
| st_ses->det_stc_ses->state = ST_STATE_ACTIVE; |
| break; |
| |
| case ST_SES_EV_START: |
| case ST_SES_EV_RESTART: |
| ALOGD("%s:[c%d-%d] handle event START/RESTART", __func__, |
| stc_ses->sm_handle, st_ses->sm_handle); |
| if (stc_ses != st_ses->det_stc_ses) { |
| ALOGD("%s: c%d buffering, delay c%d start", __func__, |
| st_ses->det_stc_ses->sm_handle, stc_ses->sm_handle); |
| break; |
| } |
| /* |
| * 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. |
| * For multi-client case while the detected is buffering, |
| * the other client stop/start would have been deferred by updating |
| * the config, and later when current detected client restarts after |
| * buffreing is completed, check if hw config is updated due to other |
| * client and stop->start the hw session to apply updated config. |
| */ |
| hw_ses->fptrs->stop_buffering(hw_ses); |
| if (hw_ses->sthw_cfg_updated || ev->ev_id == ST_SES_EV_START) { |
| status = stop_session(st_ses, hw_ses, false); |
| STATE_TRANSITION(st_ses, loaded_state_fn); |
| if (status) { |
| ALOGE("%s:[%d] failed to stop session, err %d", __func__, |
| st_ses->sm_handle, status); |
| } else { |
| status = start_session(st_ses, hw_ses, false); |
| if (status) { |
| ALOGE("%s:[%d] failed to start session, err %d", __func__, |
| st_ses->sm_handle, status); |
| } |
| } |
| } else { |
| status = restart_session(st_ses, hw_ses); |
| } |
| |
| if (status) { |
| if (st_ses->stdev->ssr_offline_received) { |
| dereg_all_sm(st_ses, hw_ses); |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } else { |
| /* move to active anyways to allow unload sm */ |
| STATE_TRANSITION(st_ses, active_state_fn); |
| } |
| } else { |
| STATE_TRANSITION(st_ses, active_state_fn); |
| } |
| break; |
| |
| case ST_SES_EV_SSR_OFFLINE: |
| ALOGD("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| /* |
| * 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); |
| stop_session(st_ses, hw_ses, true); |
| } |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| break; |
| |
| case ST_SES_EV_SET_EXEC_MODE: |
| ALOGD("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| st_ses->sm_handle, ev->ev_id); |
| |
| new_exec_mode = ev->payload.exec_mode; |
| if (new_exec_mode == st_ses->exec_mode) { |
| stc_ses->exec_mode = st_ses->exec_mode; |
| 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); |
| 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); |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| } |
| 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; |
| } else if (ST_EXEC_MODE_ADSP == new_exec_mode) { |
| new_hw_ses = st_ses->hw_ses_adsp; |
| } else { |
| ALOGE("%s: unknown execution mode %d", __func__, |
| new_exec_mode); |
| status = -EINVAL; |
| break; |
| } |
| /* |
| * hw session changed to/from WDSP/ADSP, hence update the |
| * related config. |
| * Not applicable for LPI<->non-LPI transtions as hw session |
| * doesn't change. |
| */ |
| status = update_hw_config_on_start(stc_ses, new_hw_ses); |
| if (status) { |
| ALOGE("%s: Update_hw_config_on_start failed %d", |
| __func__, status); |
| 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; |
| } |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| c_ses->exec_mode = new_exec_mode; |
| if (c_ses->state == ST_STATE_ACTIVE) { |
| dereg_hal_event_session(c_ses); |
| reg_hal_event_session(c_ses, new_hw_ses); |
| } |
| } |
| 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_proxy_session_t *st_ses, st_session_ev_t *ev) |
| { |
| int status = 0; |
| st_session_t *stc_ses = ev->stc_ses; |
| 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}; |
| bool active = false; |
| |
| /* skip parameter check as this is an internal function */ |
| ALOGD("%s:[c%d-%d] handle event id %d", __func__, stc_ses->sm_handle, |
| 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); |
| /* |
| * Load and start all clients at once instead of unload/loading |
| * due to each subsequent client dispatch. It is expected the |
| * upper layer calls SSR_ONLINE for all clients. |
| */ |
| stc_ses->pending_load = true; |
| if (is_any_client_not_pending_load(st_ses)) |
| break; |
| |
| STATE_TRANSITION(st_ses, idle_state_fn); |
| |
| if ((stc_ses->ssr_transit_exec_mode == ST_EXEC_MODE_CPE) || |
| (stc_ses->ssr_transit_exec_mode == ST_EXEC_MODE_ADSP)) { |
| exec_mode_ev.stc_ses = stc_ses; |
| exec_mode_ev.payload.exec_mode = stc_ses->ssr_transit_exec_mode; |
| DISPATCH_EVENT(st_ses, exec_mode_ev, status); |
| if (status) { |
| reset_clients_pending_load(st_ses); |
| break; |
| } |
| stc_ses->ssr_transit_exec_mode = ST_EXEC_MODE_NONE; |
| } |
| active = is_any_client_in_state(st_ses, ST_STATE_ACTIVE); |
| if (active || is_any_client_in_state(st_ses, ST_STATE_LOADED)) { |
| load_ev.stc_ses = stc_ses; |
| DISPATCH_EVENT(st_ses, load_ev, status); |
| if (status) { |
| reset_clients_pending_load(st_ses); |
| break; |
| } |
| } |
| if (active) { |
| start_ev.stc_ses = stc_ses; |
| DISPATCH_EVENT(st_ses, start_ev, status); |
| } |
| |
| reset_clients_pending_load(st_ses); |
| break; |
| |
| case ST_SES_EV_LOAD_SM: |
| if (ST_STATE_IDLE == stc_ses->state) { |
| status = update_sound_model(stc_ses, true); |
| if (status) { |
| ALOGE("%s:[c%d] update sound model add failed %d", __func__, |
| stc_ses->sm_handle, status); |
| status = -EINVAL; |
| break; |
| } |
| stc_ses->state = ST_STATE_LOADED; |
| } else { |
| ALOGE("%s: received unexpected event, client state = %d", |
| __func__, stc_ses->state); |
| } |
| break; |
| |
| case ST_SES_EV_UNLOAD_SM: |
| if (ST_STATE_LOADED == stc_ses->state) { |
| status = update_sound_model(stc_ses, false); |
| if (status) |
| ALOGE("%s:[c%d] update sound_model failed %d", __func__, |
| stc_ses->sm_handle, status); |
| stc_ses->state = ST_STATE_IDLE; |
| } else { |
| ALOGE("%s: received unexpected event, client state = %d", |
| __func__, stc_ses->state); |
| } |
| break; |
| |
| case ST_SES_EV_START: |
| case ST_SES_EV_RESTART: |
| if (ST_STATE_LOADED == stc_ses->state) { |
| if (ev->ev_id == ST_SES_EV_RESTART) |
| update_hw_config_on_restart(st_ses, stc_ses); |
| stc_ses->state = ST_STATE_ACTIVE; |
| } else { |
| ALOGE("%s: received unexpected event, client state = %d", |
| __func__, stc_ses->state); |
| } |
| break; |
| |
| case ST_SES_EV_STOP: |
| if (ST_STATE_ACTIVE == stc_ses->state) { |
| update_hw_config_on_stop(st_ses, stc_ses); |
| stc_ses->state = ST_STATE_LOADED; |
| } else { |
| ALOGE("%s: received unexpected event, client state = %d", |
| __func__, stc_ses->state); |
| } |
| break; |
| |
| case ST_SES_EV_PAUSE: |
| stc_ses->paused = true; |
| break; |
| |
| case ST_SES_EV_RESUME: |
| stc_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: |
| stc_ses->exec_mode = ev->payload.exec_mode; |
| if (ev->payload.exec_mode == st_ses->exec_mode) |
| break; |
| |
| 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; |
| } |
| |
| int st_session_load_sm(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_loadsm_payload_t payload = { .phrase_sm = stc_ses->phrase_sm }; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_LOAD_SM, |
| .payload.loadsm = payload, .stc_ses = stc_ses }; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| DISPATCH_EVENT(st_ses, ev, status); |
| if (!status) { |
| prepare_second_stage_for_client(stc_ses); |
| stc_ses->state = ST_STATE_LOADED; |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return status; |
| } |
| |
| int st_session_unload_sm(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_UNLOAD_SM, .stc_ses = stc_ses }; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| DISPATCH_EVENT(st_ses, ev, status); |
| stop_second_stage_for_client(stc_ses); |
| stc_ses->state = ST_STATE_IDLE; |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return status; |
| } |
| |
| int st_session_start(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_START, .stc_ses = stc_ses }; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (stc_ses->pending_stop) { |
| ALOGV("%s:[c%d] cancel ST_SES_EV_DEFERRED_STOP", __func__, |
| stc_ses->sm_handle); |
| hw_session_notifier_cancel(stc_ses->sm_handle, ST_SES_EV_DEFERRED_STOP); |
| stc_ses->pending_stop = false; |
| } |
| stc_ses->pending_client_start = false; |
| /* |
| * detection_sent flag is set to false when successful detection |
| * happens and it will be set to true once client is notified of it. |
| * It remains true till the next detection happens. So make it false |
| * here, before we call into start/restart for next detection. |
| * Setting this flag false helps when we post pause event during |
| * buffering to pause other clients due to backend config change. |
| */ |
| stc_ses->detection_sent = false; |
| |
| DISPATCH_EVENT(st_ses, ev, status); |
| if (!status) { |
| reg_hal_event_session(stc_ses, st_ses->hw_ses_current); |
| start_second_stage_for_client(stc_ses); |
| stc_ses->state = ST_STATE_ACTIVE; |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| |
| int st_session_stop(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_STOP, .stc_ses = stc_ses }; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| DISPATCH_EVENT(st_ses, ev, status); |
| dereg_hal_event_session(stc_ses); |
| stc_ses->pending_stop = false; |
| stc_ses->state = ST_STATE_LOADED; |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return status; |
| } |
| |
| int st_session_restart(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_RESTART, .stc_ses = stc_ses }; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (stc_ses->pending_stop) { |
| ALOGV("%s:[c%d] cancel ST_SES_EV_DEFERRED_STOP", __func__, |
| stc_ses->sm_handle); |
| hw_session_notifier_cancel(stc_ses->sm_handle, ST_SES_EV_DEFERRED_STOP); |
| stc_ses->pending_stop = false; |
| } |
| stc_ses->pending_client_start = false; |
| /* |
| * detection_sent flag is set to false when successful detection |
| * happens and it will be set to true once client is notified of it. |
| * It remains true till the next detection happens. So make it false |
| * here, before we call into start/restart for next detection. |
| * Setting this flag false helps when we post pause event during |
| * buffering to pause other clients due to backend config change. |
| */ |
| stc_ses->detection_sent = false; |
| |
| DISPATCH_EVENT(st_ses, ev, status); |
| if (!status) { |
| start_second_stage_for_client(stc_ses); |
| stc_ses->state = ST_STATE_ACTIVE; |
| } else { |
| dereg_hal_event_session(stc_ses); |
| stc_ses->state = ST_STATE_LOADED; |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return status; |
| } |
| |
| int st_session_ssr_offline(st_session_t *stc_ses, |
| enum ssr_event_status ssr_type) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_SSR_OFFLINE, |
| .payload.ssr = ssr_type, .stc_ses = stc_ses }; |
| |
| 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 == stc_ses->ssr_transit_exec_mode) && |
| (SND_CARD_STATUS_OFFLINE == ssr_type)) || |
| ((ST_EXEC_MODE_ADSP == stc_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 *stc_ses, |
| enum ssr_event_status ssr_type) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_SSR_ONLINE, |
| .payload.ssr = ssr_type, .stc_ses = stc_ses }; |
| |
| 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 == stc_ses->ssr_transit_exec_mode) && |
| (SND_CARD_STATUS_ONLINE == ssr_type)) || |
| ((ST_EXEC_MODE_ADSP == stc_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 *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_PAUSE, .stc_ses = stc_ses }; |
| |
| 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 *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_RESUME, .stc_ses = stc_ses }; |
| |
| 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 *stc_ses) |
| { |
| int status = 0; |
| st_session_event_id_t ev_id = ST_SES_EV_SET_DEVICE; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| pthread_mutex_lock(&st_ses->lock); |
| if (check_gcs_usecase_switch(stc_ses->hw_proxy_ses)) |
| ev_id = ST_SES_EV_PAUSE; |
| |
| st_session_ev_t ev = {.ev_id = ev_id, |
| .payload.enable = false, .stc_ses = stc_ses}; |
| |
| /* |
| * Avoid dispatching for each attached multi-client, instead |
| * defer it until last client |
| */ |
| stc_ses->pending_set_device = true; |
| if (is_any_client_not_pending_set_device(st_ses)) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| reset_clients_pending_set_device(st_ses); |
| |
| DISPATCH_EVENT(st_ses, ev, status); |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| |
| int st_session_enable_device(st_session_t *stc_ses) |
| { |
| int status = 0; |
| st_session_event_id_t ev_id = ST_SES_EV_SET_DEVICE; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| pthread_mutex_lock(&st_ses->lock); |
| if (check_gcs_usecase_switch(stc_ses->hw_proxy_ses)) |
| ev_id = ST_SES_EV_RESUME; |
| |
| st_session_ev_t ev = { .ev_id = ev_id, |
| .payload.enable = true, .stc_ses = stc_ses }; |
| |
| /* |
| * Avoid dispatching for each attached multi-client, instead |
| * defer it until last client |
| */ |
| stc_ses->pending_set_device = true; |
| if (is_any_client_not_pending_set_device(st_ses)) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| reset_clients_pending_set_device(st_ses); |
| |
| DISPATCH_EVENT(st_ses, ev, status); |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| |
| bool st_session_is_detected(st_session_t *stc_ses) |
| { |
| bool ret = false; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return ret; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| 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 *stc_ses) |
| { |
| bool ret = false; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return ret; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| 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 *stc_ses) |
| { |
| bool ret = false; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return ret; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| 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 *stc_ses) |
| { |
| bool ret = false; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return ret; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| 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 *stc_ses, uint8_t *buff, |
| size_t buff_size, size_t *read_size) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses || !buff || buff_size == 0 || |
| read_size == 0) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| 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, .stc_ses = stc_ses}; |
| |
| /* |
| * Do not lock when handling this event, this event |
| * can go in parallel with other events as multiple |
| * sessions can buffer in parallel. |
| */ |
| DISPATCH_EVENT(st_ses, ev, status); |
| return status; |
| } |
| |
| int st_session_stop_lab(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = {.ev_id = ST_SES_EV_END_BUFFERING, .stc_ses = stc_ses}; |
| |
| 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 *stc_ses, st_exec_mode_t exec) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| ALOGV("%s: exec mode %d", __func__, exec); |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = {.ev_id = ST_SES_EV_SET_EXEC_MODE, |
| .payload.exec_mode = exec, .stc_ses = stc_ses}; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (st_ses->enable_trans) |
| DISPATCH_EVENT(st_ses, ev, status); |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| |
| int st_session_update_recongition_config(st_session_t *stc_ses) |
| { |
| |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| status = update_hw_config_on_start(stc_ses, st_ses->hw_ses_current); |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| |
| } |
| |
| int st_session_get_preroll(st_session_t *stc_ses) |
| { |
| int val = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return 0; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| val = st_ses->hw_ses_current->max_preroll; |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return val; |
| } |
| |
| int st_session_send_custom_chmix_coeff(st_session_t *stc_ses, char *str) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_SEND_CHMIX_COEFF, |
| .payload.chmix_coeff_str = str, .stc_ses = stc_ses}; |
| |
| 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 *stc_ses, struct pcm_config *config) |
| { |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| memcpy(config, &st_ses->hw_ses_current->config, sizeof(struct pcm_config)); |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return 0; |
| } |
| |
| int st_session_get_param_data(st_session_t *stc_ses, const char *param, |
| void *payload, size_t payload_size, size_t *param_data_size) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| 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, .stc_ses = stc_ses}; |
| |
| 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; |
| } |
| |
| int st_session_ss_init(st_session_t *stc_ses) |
| { |
| int status = 0; |
| struct listnode *node = NULL; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| list_for_each(node, &stc_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:[c%d] initializing second stage session failed %d", |
| __func__, stc_ses->sm_handle, status); |
| goto ss_cleanup; |
| } |
| } |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (st_ses->aggregator_thread_created) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return 0; |
| } |
| /* |
| * Aggregator is not maintatined per client as there is only one |
| * client keyword detection happens at a time in multi-client scenario. |
| * Instead use single aggregator thread at proxy level, processing the |
| * second stage for detected client at run time. |
| */ |
| init_det_event_aggregator(st_ses); |
| |
| 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; |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| return 0; |
| |
| ss_cleanup: |
| destroy_det_event_aggregator(st_ses); |
| list_for_each(node, &stc_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); |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| return status; |
| } |
| |
| int st_session_ss_deinit(st_session_t *stc_ses) |
| { |
| struct listnode *node = NULL; |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_arm_second_stage_t *st_sec_stage = NULL; |
| st_session_t *c_ses = NULL; |
| bool aggregator_needed = false; |
| |
| list_for_each(node, &stc_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); |
| } |
| |
| pthread_mutex_lock(&st_ses->lock); |
| if (!st_ses->aggregator_thread_created) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return 0; |
| } |
| /* If other client has second stage enabled, keep the aggregator */ |
| list_for_each(node, &st_ses->clients_list) { |
| c_ses = node_to_item(node, st_session_t, hw_list_node); |
| if (c_ses != stc_ses && !list_empty(&c_ses->second_stage_list)) { |
| aggregator_needed = true; |
| break; |
| } |
| } |
| if (aggregator_needed) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return 0; |
| } |
| |
| destroy_det_event_aggregator(st_ses); |
| |
| if (st_ses->det_session_ev) |
| free(st_ses->det_session_ev); |
| pthread_mutex_unlock(&st_ses->lock); |
| |
| return 0; |
| } |
| |
| int st_session_request_detection(st_session_t *stc_ses) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = {.ev_id = ST_SES_EV_REQUEST_DET, .stc_ses = stc_ses}; |
| |
| /* 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_get_module_version(st_session_t *stc_ses, char version[]) |
| { |
| int status = 0; |
| |
| if (!stc_ses || !stc_ses->hw_proxy_ses) |
| return -EINVAL; |
| |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| st_session_ev_t ev = { .ev_id = ST_SES_EV_GET_MODULE_VERSION, .stc_ses = stc_ses, |
| .payload.module_version = version}; |
| |
| 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 *stc_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 = NULL; |
| st_proxy_session_t *st_ses = NULL; |
| struct listnode *node = NULL; |
| struct st_session *c_ses = NULL; |
| pthread_mutexattr_t attr; |
| |
| if (!stc_ses || !stdev) |
| return -EINVAL; |
| |
| st_ses = calloc(1, sizeof(st_proxy_session_t)); |
| if (!st_ses) { |
| ALOGE("%s: hw_proxy_ses allocation failed", __func__); |
| return -ENOMEM; |
| } |
| st_ses->stdev = stc_ses->stdev = stdev; |
| v_info = stc_ses->vendor_uuid_info; |
| |
| if (v_info && (EXEC_MODE_CFG_DYNAMIC == v_info->exec_mode_cfg)) { |
| st_ses->enable_trans = true; |
| /* 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; |
| } |
| |
| /* 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; |
| |
| 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; |
| } |
| |
| st_ses->hw_ses_current = st_ses->hw_ses_cpe; |
| } else if (v_info && (EXEC_MODE_CFG_APE == v_info->exec_mode_cfg)) { |
| /* |
| * Check for merge sound model support and return the existing hw |
| * session. If any other clients have already created it. |
| */ |
| if (v_info->merge_fs_soundmodels && |
| stc_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| if (!v_info->is_qcva_uuid) { |
| ALOGE("%s: merge sound model not supported for non SVA engines", |
| __func__); |
| status = -ENOSYS; |
| goto cleanup; |
| } |
| list_for_each(node, &stdev->sound_model_list) { |
| c_ses = node_to_item(node, st_session_t, list_node); |
| if ((c_ses != stc_ses) && |
| c_ses->vendor_uuid_info->is_qcva_uuid && |
| c_ses->vendor_uuid_info->merge_fs_soundmodels && |
| c_ses->f_stage_version == ST_MODULE_TYPE_GMM) { |
| stc_ses->hw_proxy_ses = c_ses->hw_proxy_ses; |
| list_add_tail(&stc_ses->hw_proxy_ses->clients_list, |
| &stc_ses->hw_list_node); |
| ALOGD("%s: another client attached, merge SM: h%d <-- c%d", |
| __func__, stc_ses->hw_proxy_ses->sm_handle, sm_handle); |
| free(st_ses); |
| st_ses = NULL; |
| break; |
| } |
| } |
| } else if (stc_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| if (!v_info->is_qcva_uuid) { |
| ALOGE("%s: multi sound model not supported for non SVA engines", |
| __func__); |
| status = -ENOSYS; |
| goto cleanup; |
| } |
| list_for_each(node, &stdev->sound_model_list) { |
| c_ses = node_to_item(node, st_session_t, list_node); |
| if ((c_ses != stc_ses) && |
| c_ses->vendor_uuid_info->is_qcva_uuid && |
| c_ses->f_stage_version == ST_MODULE_TYPE_PDK5) { |
| stc_ses->hw_proxy_ses = c_ses->hw_proxy_ses; |
| list_add_tail(&stc_ses->hw_proxy_ses->clients_list, |
| &stc_ses->hw_list_node); |
| ALOGD("%s: another client attached, multi SM: h%d <-- c%d", |
| __func__, stc_ses->hw_proxy_ses->sm_handle, sm_handle); |
| free(st_ses); |
| st_ses = NULL; |
| break; |
| } |
| } |
| } |
| if (st_ses) { /* If no other client exist */ |
| 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; |
| st_ses->f_stage_version = stc_ses->f_stage_version; |
| st_ses->hw_ses_current->f_stage_version = stc_ses->f_stage_version; |
| } |
| } 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(&stc_ses->lock, (const pthread_mutexattr_t *)&attr); |
| |
| stc_ses->exec_mode = exec_mode; |
| stc_ses->sm_handle = sm_handle; |
| stc_ses->ssr_transit_exec_mode = ST_EXEC_MODE_NONE; |
| stc_ses->client_req_det_mode = ST_DET_UNKNOWN_MODE; |
| stc_ses->state = ST_STATE_IDLE; |
| |
| if (st_ses) { /* Could get freed if other client exists */ |
| st_ses->vendor_uuid_info = v_info; |
| st_ses->exec_mode = exec_mode; |
| st_ses->sm_handle = sm_handle; |
| st_ses->lab_fp = NULL; |
| pthread_mutex_init(&st_ses->lock, (const pthread_mutexattr_t *)&attr); |
| |
| stc_ses->hw_proxy_ses = st_ses; |
| list_init(&st_ses->sm_info_list); |
| list_init(&st_ses->clients_list); |
| list_add_tail(&st_ses->clients_list, &stc_ses->hw_list_node); |
| ALOGD("%s: client attached: h%d <-- c%d", __func__, |
| st_ses->sm_handle, sm_handle); |
| |
| if (!stdev->ssr_offline_received) { |
| STATE_TRANSITION(st_ses, idle_state_fn); |
| } else { |
| STATE_TRANSITION(st_ses, ssr_state_fn); |
| status = 0; |
| } |
| } |
| return status; |
| |
| cleanup: |
| if (st_ses) { |
| if (st_ses->hw_ses_cpe) |
| free(st_ses->hw_ses_cpe); |
| if (st_ses->hw_ses_adsp) |
| free(st_ses->hw_ses_adsp); |
| free(st_ses); |
| } |
| return status; |
| } |
| |
| int st_session_deinit(st_session_t *stc_ses) |
| { |
| st_proxy_session_t *st_ses = stc_ses->hw_proxy_ses; |
| |
| pthread_mutex_lock(&st_ses->lock); |
| list_remove(&stc_ses->hw_list_node); |
| ALOGV("%s: client detatched: h%d <-- c%d", __func__, st_ses->sm_handle, |
| stc_ses->sm_handle); |
| if (stc_ses == st_ses->det_stc_ses) |
| st_ses->det_stc_ses = NULL; |
| |
| if (!list_empty(&st_ses->clients_list)) { |
| pthread_mutex_unlock(&st_ses->lock); |
| return 0; |
| } |
| /* deinit cpe session */ |
| if (st_ses->hw_ses_cpe) { |
| st_hw_sess_gcs_deinit(st_ses->hw_ses_cpe); |
| free(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(st_ses->hw_ses_adsp); |
| st_ses->hw_ses_adsp = NULL; |
| } |
| pthread_mutex_unlock(&st_ses->lock); |
| pthread_mutex_destroy(&st_ses->lock); |
| free(stc_ses->hw_proxy_ses); |
| stc_ses->hw_proxy_ses = NULL; |
| |
| return 0; |
| } |