You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2463 lines
59 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd. 2012-2019. All rights reserved.
* Description: ai mpi function
* Author: audio
* Create: 2012-05-30
*/
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include "mpi_ai_debug.h"
#include "soc_math.h"
#include "mpi_memory_ext.h"
#include "mpi_system_ext.h"
#include "securec.h"
#include "mpi_ai_trans.h"
#include "mpi_ai_ext.h"
#include "mpi_aenc_ext.h"
#include "drv_ioctl_ai.h"
#include "mpi_ao_ext.h"
#include "iec61937_parser.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define AI_MAX_DST 4
#define AI_SLEEP_TIME_MS 5
#define AI_CHECK_DELAY_SLEEP_TIME_MS 1
#define AO_TRACK_DELAY_FOR_AI_TIME 20
#define U32_MAX_VALUE 0xffffffff
#define LINE_IN_VOLUME_MAX 100
/* when you wait delay,
* there is delay between get ai port delay and track start.
* 6ms is experience value
*/
#define EXT_AI_CMD_DELAY 6
typedef enum {
AI_CHN_STATUS_STOP = 0,
AI_CHN_STATUS_START,
AI_CHN_STATUS_RESET,
AI_CHN_STATUS_MAX,
} ai_chn_status;
typedef struct {
td_handle h_ai;
td_handle h_track; /* ai->ao by aip buff */
td_bool need_start; /* start track */
td_bool track_start;
td_bool real_pcm;
td_u32 dst_num;
td_handle h_dst[AI_MAX_DST];
td_bool pass_through;
ext_parser_handle h_parser;
ext_ai_port ai_port;
ext_ai_attr attr;
td_u32 channel;
td_u32 bit_depth;
ai_proc_info *ai_proc_info;
td_u32 proc_phy_addr;
td_bool ai_thread_run;
pthread_t ai_data_thd_inst; /* run handle of ai thread */
pthread_t ai_track_thd_inst; /* run handle of start track */
pthread_t ai_adec_thd_inst;
} ai_mpi_state;
typedef struct {
ai_mpi_state *ai[AI_MAX_TOTAL_NUM];
} ai_mpi_resource;
static td_s32 g_ai_fd = -1;
static ai_mpi_resource g_ai_res;
static pthread_mutex_t g_ai_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t g_ai_api_mutex[AI_MAX_TOTAL_NUM] = {
[0 ...(AI_MAX_TOTAL_NUM - 1)] = PTHREAD_MUTEX_INITIALIZER,
};
static pthread_mutex_t g_ai_data_mutex[AI_MAX_TOTAL_NUM] = {
[0 ...(AI_MAX_TOTAL_NUM - 1)] = PTHREAD_MUTEX_INITIALIZER,
};
static pthread_mutex_t g_ai_io_mutex[AI_MAX_TOTAL_NUM] = {
[0 ...(AI_MAX_TOTAL_NUM - 1)] = PTHREAD_MUTEX_INITIALIZER,
};
static pthread_mutex_t g_ai_cmd_mutex[AI_MAX_TOTAL_NUM] = {
[0 ...(AI_MAX_TOTAL_NUM - 1)] = PTHREAD_MUTEX_INITIALIZER,
};
#define AI_DEV_NAME "/dev/soc_ai"
#define check_ao_handle(ao) (((ao) >> 16) == SOC_ID_AO)
#define check_aenc_id(aenc) (((aenc) >> 16) == SOC_ID_AENC)
static td_void ai_mutex_lock(pthread_mutex_t *mutex)
{
if (mutex == TD_NULL) {
return;
}
if (pthread_mutex_lock(mutex) != 0) {
soc_log_err("Lock mutex failed\n");
}
}
static td_void ai_mutex_unlock(pthread_mutex_t *mutex)
{
if (mutex == TD_NULL) {
return;
}
if (pthread_mutex_unlock(mutex) != 0) {
soc_log_err("Unlock mutex failed\n");
}
}
static td_void ai_lock(td_void)
{
ai_mutex_lock(&g_ai_mutex);
}
static td_void ai_unlock(td_void)
{
ai_mutex_unlock(&g_ai_mutex);
}
static td_void ai_ch_api_lock(td_handle ai)
{
td_handle ai_lock = ai & AI_CHNID_MASK;
if (ai_lock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_lock(&g_ai_api_mutex[ai_lock]);
}
static td_void ai_ch_api_unlock(td_handle ai)
{
td_handle ai_unlock = ai & AI_CHNID_MASK;
if (ai_unlock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_unlock(&g_ai_api_mutex[ai_unlock]);
}
static td_void ai_ch_data_lock(td_handle ai)
{
td_handle ai_lock = ai & AI_CHNID_MASK;
if (ai_lock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_lock(&g_ai_data_mutex[ai_lock]);
}
static td_void ai_ch_data_unlock(td_handle ai)
{
td_handle ai_unlock = ai & AI_CHNID_MASK;
if (ai_unlock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_unlock(&g_ai_data_mutex[ai_unlock]);
}
static td_void ai_ch_io_lock(td_handle ai)
{
td_handle ai_lock = ai & AI_CHNID_MASK;
if (ai_lock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_lock(&g_ai_io_mutex[ai_lock]);
}
static td_void ai_ch_io_unlock(td_handle ai)
{
td_handle ai_unlock = ai & AI_CHNID_MASK;
if (ai_unlock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_unlock(&g_ai_io_mutex[ai_unlock]);
}
static td_void ai_ch_cmd_lock(td_handle ai)
{
td_handle ai_lock = ai & AI_CHNID_MASK;
if (ai_lock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_lock(&g_ai_cmd_mutex[ai_lock]);
}
static td_void ai_ch_cmd_unlock(td_handle ai)
{
td_handle ai_unlock = ai & AI_CHNID_MASK;
if (ai_unlock >= AI_MAX_TOTAL_NUM) {
return;
}
ai_mutex_unlock(&g_ai_cmd_mutex[ai_unlock]);
}
#define check_ai_id(handle) do { \
if (((handle) >= AI_MAX_HANDLE_ID) || ((handle) < AI_MIN_HANDLE_ID)) { \
soc_log_err("invalid ai id!\n"); \
soc_err_print_h32((handle)); \
return SOC_ERR_AI_INVALID_ID; \
} \
} while (0)
#define TIME_RATION 1000
static td_void ai_sleep(td_slong ms)
{
struct timespec ts;
if (ms == 0) {
return;
}
ts.tv_sec = ms / TIME_RATION;
ts.tv_nsec = (ms % TIME_RATION) * TIME_RATION * TIME_RATION;
if (nanosleep(&ts, TD_NULL) != 0) {
soc_log_err("call nanosleep failed\n");
return;
}
}
static ai_mpi_state *ai_get_chan(td_handle h_ai)
{
td_u32 id;
ai_mpi_state *ai_state = TD_NULL;
id = h_ai & AI_CHNID_MASK;
if (id >= AI_MAX_TOTAL_NUM) {
soc_err_print_h32(h_ai);
return TD_NULL;
}
ai_state = g_ai_res.ai[id];
if (ai_state == TD_NULL) {
return TD_NULL;
}
if (ai_state->h_ai != h_ai) {
return TD_NULL;
}
return ai_state;
}
static td_s32 ai_get_cur_delay(td_handle h_ai, td_u32 *delay);
#define ai_array_size(array) (sizeof((array)) / sizeof((array)[0]))
static td_bool ai_port_identify(ext_ai_port ai_port,
const ext_ai_port support_port[], td_u32 support_port_cnt)
{
td_u32 i;
for (i = 0; i < support_port_cnt; i++) {
if (ai_port == support_port[i]) {
return TD_TRUE;
}
}
return TD_FALSE;
}
static td_bool ai_port_is_i2s(ext_ai_port ai_port)
{
const ext_ai_port port_group[] = {
EXT_AI_I2S0,
EXT_AI_I2S1,
EXT_AI_I2S2,
EXT_AI_I2S3,
EXT_AI_I2S4,
};
return ai_port_identify(ai_port, port_group,
ai_array_size(port_group));
}
static td_bool ai_port_is_adc(ext_ai_port ai_port)
{
const ext_ai_port port_group[] = {
EXT_AI_ADC0,
EXT_AI_ADC1,
EXT_AI_ADC2,
EXT_AI_ADC3,
EXT_AI_ADC4,
};
return ai_port_identify(ai_port, port_group,
ai_array_size(port_group));
}
static td_bool ai_port_is_hdmi(ext_ai_port ai_port)
{
const ext_ai_port port_group[] = {
EXT_AI_HDMI0,
EXT_AI_HDMI1,
EXT_AI_HDMI2,
EXT_AI_HDMI3,
};
return ai_port_identify(ai_port, port_group,
ai_array_size(port_group));
}
static td_bool ai_port_is_sif(ext_ai_port ai_port)
{
const ext_ai_port port_group[] = {
EXT_AI_SIF0,
};
return ai_port_identify(ai_port, port_group,
ai_array_size(port_group));
}
static td_void ai_chan_i2s_set_attr(ai_mpi_state *ai_state, const ext_ai_attr *ai_attr)
{
ai_state->channel = ai_attr->un_attr.i2s_attr.attr.channel;
ai_state->bit_depth = ai_attr->un_attr.i2s_attr.attr.bit_depth;
}
static td_void ai_chan_adc_set_attr(ai_mpi_state *ai_state, const ext_ai_attr *ai_attr)
{
TD_UNUSED(ai_attr);
ai_state->channel = EXT_AUDIO_CH_STEREO;
ai_state->bit_depth = EXT_BIT_DEPTH_16;
}
static td_void ai_chan_hdmi_set_attr(ai_mpi_state *ai_state, const ext_ai_attr *ai_attr)
{
const ext_ai_hdmi_attr *hdmi_attr = &ai_attr->un_attr.hdmi_attr;
ai_state->channel = hdmi_attr->channel;
ai_state->bit_depth = hdmi_attr->bit_depth;
if ((hdmi_attr->hdmi_audio_data_format == EXT_AI_HDMI_FORMAT_LBR) ||
(hdmi_attr->hdmi_audio_data_format == EXT_AI_HDMI_FORMAT_HBR)) {
ai_state->pass_through = TD_TRUE;
ai_state->ai_proc_info->data_type = EXT_AUDIO_FORMAT_STREAM;
} else {
ai_state->pass_through = TD_FALSE;
ai_state->ai_proc_info->data_type = EXT_AUDIO_FORMAT_PCM;
}
}
static td_void ai_chan_sif_set_attr(ai_mpi_state *ai_state, const ext_ai_attr *ai_attr)
{
ai_state->channel = ai_attr->un_attr.sif_attr.channels;
ai_state->bit_depth = ai_attr->un_attr.sif_attr.bit_depth;
}
static td_void ai_set_chn_attr(ext_ai_port ai_port, const ext_ai_attr *ai_attr, ai_mpi_state *ai_state)
{
td_s32 ret;
td_u32 i;
const struct {
td_bool (*identify)(ext_ai_port ai_port);
td_void (*set_attr)(ai_mpi_state *ai_state, const ext_ai_attr *ai_attr);
} ops[] = {
{ai_port_is_i2s, ai_chan_i2s_set_attr},
{ai_port_is_adc, ai_chan_adc_set_attr},
{ai_port_is_hdmi, ai_chan_hdmi_set_attr},
{ai_port_is_sif, ai_chan_sif_set_attr},
};
ret = memcpy_s(&ai_state->attr, sizeof(ext_ai_attr),
ai_attr, sizeof(*ai_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return;
}
ai_state->ai_port = ai_port;
for (i = 0; i < ai_array_size(ops); i++) {
if (ops[i].identify(ai_port) == TD_FALSE) {
continue;
}
ops[i].set_attr(ai_state, ai_attr);
return;
}
/* ai_port not support */
soc_fatal_print_h32(ai_port);
}
static td_u32 ai_calc_frame_size(td_u32 ch, td_u32 bit_depth)
{
td_u32 res = 0;
if (bit_depth == EXT_BIT_DEPTH_16) {
res = ch * sizeof(td_u16);
} else if (bit_depth == EXT_BIT_DEPTH_24) {
res = ch * sizeof(td_u32);
}
return res;
}
static td_u32 ai_chan_cal_acquire_size(const ai_mpi_state *ai_state)
{
if (ai_state->attr.pcm_samples_per_frame > BITS_BURSTLENGTH_MAT) {
return 0;
}
if (ai_state->channel > EXT_AUDIO_CH_16) {
return 0;
}
return ai_state->attr.pcm_samples_per_frame *
ai_calc_frame_size(ai_state->channel, ai_state->bit_depth);
}
static td_void ai_build_frame(const ai_mpi_state *ai_state, ext_ao_frame *iapi_frame)
{
td_u32 time_ms = 0;
td_u32 aidelay_ms = 0;
ext_mpi_sys_get_time_stamp_ms(&time_ms);
ai_get_cur_delay(ai_state->h_ai, &aidelay_ms);
iapi_frame->pts = (td_s64)(time_ms - aidelay_ms);
iapi_frame->bit_depth = ai_state->bit_depth;
iapi_frame->interleaved = TD_TRUE;
iapi_frame->sample_rate = ai_state->attr.sample_rate;
iapi_frame->channels = ai_state->channel;
iapi_frame->frame_index = 0;
iapi_frame->pcm_samples = ai_state->attr.pcm_samples_per_frame;
iapi_frame->bits_bytes = 0;
iapi_frame->iec_data_type = 0;
iapi_frame->bits_buffer = TD_NULL;
iapi_frame->pcm_buffer = TD_NULL;
}
static td_s32 ai_kernel_release_frame(td_handle h_ai)
{
td_s32 ret;
ret = ioctl(g_ai_fd, CMD_AI_RELEASEFRAME, &h_ai);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_RELEASEFRAME failed\n");
soc_err_print_err_code(ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_kernel_acquire_frame(const ai_mpi_state *ai_state, td_u32 request_size, ext_ao_frame *frame)
{
td_s32 ret;
ai_buf_param ai_buf_info = {
.ai = ai_state->h_ai,
.ai_buf = {0},
};
ai_frame_param ai_get_frame = {
.ai = ai_state->h_ai,
.need_bytes = request_size,
};
ret = ioctl(g_ai_fd, CMD_AI_GETBUFINFO, &ai_buf_info);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETBUFINFO failed\n");
soc_err_print_err_code(ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_ACQUIREFRAME, &ai_get_frame);
if (ret != TD_SUCCESS) {
soc_log_warn("ioctl CMD_AI_ACQUIREFRAME failed\n");
soc_warn_print_err_code(ret);
return ret;
}
ai_build_frame(ai_state, frame);
frame->pcm_buffer = (td_s32 *)((uintptr_t)ai_buf_info.ai_buf.user_vir_addr);
return TD_SUCCESS;
}
static td_s32 ai_parser_send_frame(const ai_mpi_state *ai_state)
{
td_s32 ret;
td_u32 need_bytes = 0;
ext_ao_frame ao_frame = { 0 };
iec61937_parser_stream_buf stream = {TD_NULL, 0};
ret = iec61937_parser_get_burst_len(ai_state->h_parser, &need_bytes);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(iec61937_parser_get_burst_len, ret);
return ret;
}
ret = iec61937_parser_get_buf(ai_state->h_parser, need_bytes, &stream);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(iec61937_parser_get_buf, ret);
return ret;
}
if (stream.size == 0) {
return TD_SUCCESS;
}
ret = ai_kernel_acquire_frame(ai_state, stream.size, &ao_frame);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_kernel_acquire_frame, ret);
return ret;
}
ret = memcpy_s(stream.data, stream.size,
ao_frame.pcm_buffer, stream.size * sizeof(td_u8));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ai_kernel_release_frame(ai_state->h_ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_kernel_release_frame, ret);
return ret;
}
ret = iec61937_parser_put_buf(ai_state->h_parser, &stream);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(iec61937_parser_put_buf, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_parser_acquire_frame(ai_mpi_state *ai_state, ext_ao_frame *frame)
{
td_s32 ret;
ret = ai_parser_send_frame(ai_state);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(ai_parser_send_frame, ret);
}
if (ret == SOC_ERR_AI_NOT_ENOUGH_DATA) {
soc_log_dbg("not enough data.\n");
return ret;
}
if (iec61937_check_is_empty(ai_state->h_parser) == TD_TRUE) {
soc_log_dbg("not enough data.\n");
return SOC_ERR_AI_NOT_ENOUGH_DATA;
}
ret = iec61937_parser_acquire_frame(ai_state->h_parser,
(td_void *)(&frame->pcm_buffer), &frame->bits_bytes);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(iec61937_parser_acquire_frame, ret);
}
return ret;
}
static inline td_s32 ai_parser_release_frame(ext_parser_handle h_parser, const ext_ao_frame *frame)
{
check_ai_null_ptr(h_parser);
check_ai_null_ptr(frame);
return iec61937_parser_release_frame(h_parser, frame->bits_bytes);
}
static td_s32 ai_dev_get_enable(td_handle ai, td_bool *enable)
{
td_s32 ret;
ai_enable_param ai_enable = {
.ai = ai,
.ai_enable = TD_FALSE,
};
check_ai_id(ai);
check_ai_null_ptr(enable);
ret = ioctl(g_ai_fd, CMD_AI_GETENABLE, &ai_enable);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETENABLE failed!\n");
soc_err_print_err_code(ret);
return ret;
}
*enable = ai_enable.ai_enable;
return TD_SUCCESS;
}
static inline td_void ai_thread_lock(td_handle ai)
{
ai_ch_io_lock(ai);
ai_ch_cmd_lock(ai);
}
static inline td_void ai_thread_unlock(td_handle ai)
{
ai_ch_cmd_unlock(ai);
ai_ch_io_unlock(ai);
}
static td_bool ai_thread_idle(td_handle ai)
{
td_s32 ret;
td_bool enable = TD_FALSE;
ret = ai_dev_get_enable(ai, &enable);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_dev_get_enable, ret);
return TD_TRUE;
}
return !enable;
}
static td_bool ai_adec_thread_run_parser(ai_mpi_state *ai_state)
{
td_s32 ret;
ext_ao_frame ao_frame;
if (ai_thread_idle(ai_state->h_ai) == TD_TRUE) {
return TD_FALSE;
}
ret = ai_parser_acquire_frame(ai_state, &ao_frame);
if (ret == SOC_ERR_AI_FIND_SYNC_FAILED) {
ai_state->real_pcm = TD_TRUE;
return TD_TRUE;
}
if (ret != TD_SUCCESS) {
return TD_FALSE;
}
ret = ai_parser_release_frame(ai_state->h_parser, &ao_frame);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_parser_release_frame, ret);
return TD_TRUE;
}
ai_state->pass_through = TD_TRUE;
return TD_TRUE;
}
static td_void *ai_adec_thread(td_void *arg)
{
td_handle ai;
ai_mpi_state *ai_state = (ai_mpi_state *)arg;
if (ai_state == TD_NULL) {
return TD_NULL;
}
prctl(PR_SET_NAME, "ai_adec");
ai = ai_state->h_ai;
/* if passthrough or attach aenc, do not need to check */
if (ai_state->pass_through == TD_TRUE) {
return TD_NULL;
}
/* dtscd format only can be 44.1K */
if ((ai_state->attr.sample_rate != EXT_SAMPLE_RATE_44K) ||
(ai_port_is_hdmi(ai_state->ai_port) != TD_TRUE)) {
ai_state->real_pcm = TD_TRUE;
return TD_NULL;
}
while (ai_state->ai_thread_run == TD_TRUE) {
if (ai_state->dst_num != 0 && (ai_state->h_track == TD_INVALID_HANDLE)) {
ai_sleep(AI_CHECK_DELAY_SLEEP_TIME_MS);
continue;
}
ai_thread_lock(ai);
if (ai_adec_thread_run_parser(ai_state) == TD_TRUE) {
soc_log_notice("check datafomat success, ai_state->real_pcm = %d, ai_state->pass_through = %d\n",
ai_state->real_pcm, ai_state->pass_through);
ai_thread_unlock(ai);
break;
}
ai_thread_unlock(ai);
ai_sleep(AI_CHECK_DELAY_SLEEP_TIME_MS); /* 1000 us */
continue;
}
return TD_NULL;
}
static td_s32 ext_mpi_ai_get_status(td_handle h_ai, td_u32 *status);
enum {
STATUS_IDLE,
STATUS_CHECK,
STATUS_START,
};
typedef struct {
td_u32 thread_status;
td_u32 track_delay;
td_u32 ai_delay;
ext_ai_delay ai_delay_compensation;
td_u32 start_time;
td_u32 cur_time;
td_u32 delta_time;
} ai_track_thread_state;
static td_s32 ai_track_thread_state_init(ai_track_thread_state *state)
{
td_s32 ret;
ret = memset_s(state, sizeof(*state),
0, sizeof(ai_track_thread_state));
if (ret != EOK) {
soc_err_print_call_fun_err(memset_s, ret);
return ret;
}
state->thread_status = STATUS_IDLE;
return TD_SUCCESS;
}
static td_void ai_chan_try_self_reset(td_handle ai)
{
td_s32 ret;
td_u32 status = AI_CHN_STATUS_MAX;
ext_ai_attr ai_attr;
ret = ext_mpi_ai_get_status(ai, &status);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_get_status, ret);
return;
}
if (status != AI_CHN_STATUS_RESET) {
return;
}
ret = memset_s(&ai_attr, sizeof(ai_attr),
0, sizeof(ext_ai_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memset_s, ret);
return;
}
ret = ext_mpi_ai_get_attr(ai, &ai_attr);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_get_attr, ret);
return;
}
ret = ext_mpi_ai_set_enable(ai, TD_FALSE);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_set_enable, ret);
return;
}
ret = ext_mpi_ai_set_attr(ai, &ai_attr);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_set_attr, ret);
return;
}
ret = ext_mpi_ai_set_enable(ai, TD_TRUE);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_set_enable, ret);
return;
}
}
static td_void ai_track_thread_idle(ai_track_thread_state *state, const ai_mpi_state *ai_state)
{
td_s32 ret;
td_bool enable = TD_FALSE;
if (ai_state->h_track == TD_INVALID_HANDLE) {
return;
}
if (ai_state->real_pcm != TD_TRUE) {
return;
}
ret = ai_dev_get_enable(ai_state->h_ai, &enable);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_dev_get_enable, ret);
return;
}
if (enable != TD_TRUE) {
return;
}
if (ai_state->need_start != TD_TRUE) {
ai_ch_cmd_unlock(ai_state->h_ai);
ai_chan_try_self_reset(ai_state->h_ai);
ai_ch_cmd_lock(ai_state->h_ai);
return;
}
if (state->thread_status == STATUS_IDLE) {
ret = ext_mpi_sys_get_time_stamp_ms(&state->start_time);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_sys_get_time_stamp_ms, ret);
return;
}
}
state->thread_status = STATUS_CHECK;
}
static td_s32 ai_track_thread_get_info(ai_track_thread_state *state, const ai_mpi_state *ai_state)
{
td_s32 ret;
ret = ext_mpi_sys_get_time_stamp_ms(&state->cur_time);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_sys_get_time_stamp_ms, ret);
return ret;
}
if (state->cur_time >= state->start_time) {
state->delta_time = state->cur_time - state->start_time;
} else {
state->delta_time = (U32_MAX_VALUE - state->start_time) + (state->cur_time + 1);
}
ret = ext_mpi_ao_track_get_delay(ai_state->h_track, &state->track_delay);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_track_get_delay, ret);
return ret;
}
ret = ext_mpi_ai_get_delay(ai_state->h_ai, &state->ai_delay_compensation);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ai_get_delay, ret);
return ret;
}
ret = ai_get_cur_delay(ai_state->h_ai, &state->ai_delay);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_get_cur_delay, ret);
return ret;
}
return TD_SUCCESS;
}
static td_void ai_track_thread_check(ai_track_thread_state *state, const ai_mpi_state *ai_state)
{
td_s32 ret;
td_u32 delay_compensation;
td_u32 sys_time_check;
td_u32 track_delay_check;
if (ai_state->h_track == TD_INVALID_HANDLE) {
return;
}
if (state->thread_status != STATUS_CHECK) {
return;
}
ret = ai_track_thread_get_info(state, ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_track_thread_get_info, ret);
return;
}
delay_compensation = state->track_delay + state->ai_delay + EXT_AI_CMD_DELAY;
if (delay_compensation < state->ai_delay_compensation.delay) {
return;
}
track_delay_check = state->track_delay + state->ai_delay_compensation.delay;
sys_time_check = track_delay_check + state->delta_time;
if ((sys_time_check >= AO_TRACK_DELAY_FOR_AI_TIME) ||
(track_delay_check >= AO_TRACK_DELAY_FOR_AI_TIME)) {
state->thread_status = STATUS_START;
}
}
static td_void ai_track_thread_start_track(ai_track_thread_state *state, ai_mpi_state *ai_state)
{
td_s32 ret;
if (ai_state->h_track == TD_INVALID_HANDLE) {
return;
}
if (state->thread_status != STATUS_START) {
return;
}
ret = ext_mpi_ao_track_start(ai_state->h_track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_track_start, ret);
return;
}
ai_state->track_start = TD_TRUE;
ai_state->need_start = TD_FALSE;
state->thread_status = STATUS_IDLE;
}
static td_void *ai_track_thread(td_void *arg)
{
td_s32 ret;
td_handle ai;
ai_track_thread_state thread_state;
ai_mpi_state *ai_state = (ai_mpi_state *)arg;
if (ai_state == TD_NULL) {
return TD_NULL;
}
prctl(PR_SET_NAME, "ai_track");
ai = ai_state->h_ai;
ret = ai_track_thread_state_init(&thread_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_track_thread_state_init, ret);
return TD_NULL;
}
while (ai_state->ai_thread_run == TD_TRUE) {
ai_ch_cmd_lock(ai);
ai_track_thread_idle(&thread_state, ai_state);
ai_track_thread_check(&thread_state, ai_state);
ai_track_thread_start_track(&thread_state, ai_state);
ai_ch_cmd_unlock(ai);
ai_sleep(AI_CHECK_DELAY_SLEEP_TIME_MS);
}
return TD_NULL;
}
static td_s32 ai_chan_send_frame(const ai_mpi_state *ai_state, const ext_ao_frame *ao_frame)
{
td_s32 ret;
td_u32 i;
td_handle handle;
td_s32 (*send_frame)(td_handle handle, const ext_ao_frame *frame) = TD_NULL;
for (i = 0; i < AI_MAX_DST; i++) {
handle = ai_state->h_dst[i];
if (check_ao_handle(handle)) {
send_frame = ext_mpi_ao_track_send_data;
} else if (check_aenc_id(handle)) {
send_frame = ext_mpi_aenc_send_buffer;
} else {
continue;
}
ret = send_frame(handle, ao_frame);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(send_frame, ret);
return ret;
}
}
return TD_SUCCESS;
}
enum {
STATUS_ACQUIRE_FRAME,
STATUS_SEND_FRAME,
STATUS_RELEASE_FRAME,
};
static td_void *ai_data_thread(td_void *arg)
{
td_s32 ret;
td_u32 status = STATUS_ACQUIRE_FRAME;
td_u32 sleep_ms = 0;
ai_mpi_state *ai_state = (ai_mpi_state *)arg;
ext_ao_frame ai_frame = { 0 };
const td_u32 request_size = ai_chan_cal_acquire_size(ai_state);
prctl(PR_SET_NAME, "ai_data");
while (ai_state->ai_thread_run == TD_TRUE) {
ai_thread_lock(ai_state->h_ai);
if ((ai_state->dst_num == 0) || (ai_thread_idle(ai_state->h_ai) == TD_TRUE) ||
((ai_state->h_track != TD_INVALID_HANDLE) && (ai_state->track_start != TD_TRUE))) {
ai_thread_unlock(ai_state->h_ai);
ai_sleep(AI_SLEEP_TIME_MS * 2); /* 2 is a number */
status = STATUS_ACQUIRE_FRAME;
continue;
}
if (status == STATUS_ACQUIRE_FRAME) {
ret = ai_kernel_acquire_frame(ai_state, request_size, &ai_frame);
if (ret != TD_SUCCESS) {
soc_log_warn("call ext_mpi_ai_acquire_frame failed!\n");
sleep_ms = AI_SLEEP_TIME_MS << 1; /* 1 is a number */
} else {
status = STATUS_SEND_FRAME;
}
} else if (status == STATUS_SEND_FRAME) {
ret = ai_chan_send_frame(ai_state, &ai_frame);
if (ret != TD_SUCCESS) {
sleep_ms = AI_SLEEP_TIME_MS;
} else {
status = STATUS_RELEASE_FRAME;
}
} else if (status == STATUS_RELEASE_FRAME) {
ret = ai_kernel_release_frame(ai_state->h_ai);
if (ret != TD_SUCCESS) {
ai_thread_unlock(ai_state->h_ai);
soc_err_print_call_fun_err(ai_kernel_release_frame, ret);
continue;
}
status = STATUS_ACQUIRE_FRAME;
}
ai_thread_unlock(ai_state->h_ai);
ai_sleep(sleep_ms);
}
return TD_NULL;
}
static td_s32 ai_detach_ao_track(ai_mpi_state *ai_state, td_handle track)
{
td_s32 ret;
if ((track & 0xff) >= AO_MAX_REAL_TRACK_NUM) {
return SOC_ERR_AI_INVALID_PARA;
}
if (ai_state->h_track != track) {
soc_log_err("this track is not attach ai, can not detach!\n");
return SOC_ERR_AI_INVALID_PARA;
}
ret = ext_mpi_ao_track_detach_ai(ai_state->h_ai, track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_track_detach_ai, ret);
return ret;
}
ai_state->h_track = TD_INVALID_HANDLE;
return TD_SUCCESS;
}
static td_s32 ai_attach_ao_track(ai_mpi_state *ai_state, td_handle track)
{
td_s32 ret;
if ((track & 0xff) >= AO_MAX_REAL_TRACK_NUM) {
return SOC_ERR_AI_INVALID_PARA;
}
if (ai_state->h_track == track) {
return TD_SUCCESS;
}
if (ai_state->h_track != TD_INVALID_HANDLE) {
soc_log_err("AI can not attach more than one slave/master track!\n");
return SOC_ERR_AI_NOTSUPPORT;
}
if (ai_state->dst_num != 0) {
soc_log_err("AI is already attached by virtual track or aenc!\n");
return SOC_ERR_AI_NOTSUPPORT;
}
ret = ext_mpi_ao_track_attach_ai(ai_state->h_ai, track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_track_attach_ai, ret);
return ret;
}
ai_state->h_track = track;
ai_state->need_start = TD_TRUE;
return TD_SUCCESS;
}
static td_bool ai_check_aenc_attach(const ai_mpi_state *ai_state, td_handle aenc)
{
td_u32 i;
for (i = 0; i < AI_MAX_DST; i++) {
if (ai_state->h_dst[i] == aenc) {
return TD_TRUE;
}
}
return TD_FALSE;
}
static td_s32 ai_save_aenc_handle(ai_mpi_state *ai_state, td_handle aenc)
{
td_u32 i;
for (i = 0; i < AI_MAX_DST; i++) {
if (ai_state->h_dst[i] == TD_INVALID_HANDLE) {
ai_state->h_dst[i] = aenc;
ai_state->dst_num++;
return TD_SUCCESS;
}
}
soc_log_err("AI has attached max dst.\n");
return SOC_ERR_AI_NOTSUPPORT;
}
static td_s32 ai_detach_aenc(ai_mpi_state *ai_state, td_handle aenc)
{
td_u32 i;
for (i = 0; i < AI_MAX_DST; i++) {
if (ai_state->h_dst[i] == aenc) {
ai_state->h_dst[i] = TD_INVALID_HANDLE;
ai_state->dst_num--;
return TD_SUCCESS;
}
}
soc_log_err("this source is not attached, can not detach.\n");
return SOC_ERR_AI_NOTSUPPORT;
}
static td_s32 ai_attach_aenc(ai_mpi_state *ai_state, td_handle aenc)
{
td_s32 ret;
if (ai_check_aenc_attach(ai_state, aenc) == TD_TRUE) {
return TD_SUCCESS;
}
ret = ai_save_aenc_handle(ai_state, aenc);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_save_aenc_handle, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_attach(td_handle ai, td_handle handle)
{
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
if (check_ao_handle(handle)) {
return ai_attach_ao_track(ai_state, handle);
} else if (check_aenc_id(handle)) {
return ai_attach_aenc(ai_state, handle);
} else {
soc_err_print_h32(handle);
return SOC_ERR_AI_INVALID_PARA;
}
}
static td_s32 ai_detach(td_handle ai, td_handle handle)
{
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
if (check_ao_handle(handle)) {
return ai_detach_ao_track(ai_state, handle);
} else if (check_aenc_id(handle)) {
return ai_detach_aenc(ai_state, handle);
} else {
soc_err_print_h32(handle);
return SOC_ERR_AI_INVALID_PARA;
}
}
static td_void ai_proc_deinit(ai_mpi_state *ai_state)
{
td_s32 ret;
if (ai_state->ai_proc_info != TD_NULL) {
(td_void)munmap(ai_state->ai_proc_info, sizeof(ai_proc_info));
ai_state->ai_proc_info = TD_NULL;
}
ret = ioctl(g_ai_fd, CMD_AI_PROCDEINIT, &ai_state->h_ai);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_PROCDEINIT failed!\n");
soc_err_print_err_code(ret);
return;
}
}
static inline errno_t ai_proc_reset(ai_proc_info *ai_proc)
{
return memset_s(ai_proc, sizeof(*ai_proc),
0, sizeof(ai_proc_info));
}
static td_s32 ai_proc_init(ai_mpi_state *ai_state)
{
td_s32 ret;
ai_proc_init_param param = {
.ai = ai_state->h_ai,
.proc_phy_addr = 0,
.proc_buf_size = 0,
.proc_buf_map_fd = -1,
};
ret = ioctl(g_ai_fd, CMD_AI_PROCINIT, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_PROCINIT failed!\n");
soc_err_print_err_code(ret);
return ret;
}
ai_state->ai_proc_info = (ai_proc_info *)mmap((td_void *)0, sizeof(ai_proc_info),
PROT_READ | PROT_WRITE, MAP_SHARED, param.proc_buf_map_fd, 0);
if (ai_state->ai_proc_info == MAP_FAILED) {
soc_log_err("mmap ai_proc_info failed!\n");
ret = TD_FAILURE;
goto out;
}
ret = ai_proc_reset(ai_state->ai_proc_info);
if (ret != EOK) {
soc_err_print_call_fun_err(ai_proc_reset, ret);
goto out;
}
return TD_SUCCESS;
out:
ai_proc_deinit(ai_state);
return ret;
}
static td_void mpi_ai_unregister_extern_func(td_void)
{
td_s32 ret;
ret = ext_mpi_ao_unregister_extern_func(SOC_ID_AI);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_unregister_extern_func, ret);
}
}
static td_void mpi_ai_register_extern_func(td_void)
{
td_s32 ret;
const ext_ao_ext_module_fn ao_func = {
ext_mpi_ai_attach,
ext_mpi_ai_detach,
};
ret = ext_mpi_ao_register_extern_func(SOC_ID_AI, &ao_func);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_register_extern_func, ret);
}
}
td_s32 ext_mpi_ai_init(td_void)
{
ai_lock();
if (g_ai_fd < 0) {
g_ai_fd = open(AI_DEV_NAME, O_RDWR, 0);
if (g_ai_fd < 0) {
soc_log_fatal("open_ai_device err\n");
g_ai_fd = -1;
ai_unlock();
return SOC_ERR_AI_NOT_INIT;
}
}
ai_unlock();
mpi_ai_register_extern_func();
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_deinit(td_void)
{
td_s32 ret;
mpi_ai_unregister_extern_func();
ai_lock();
if (g_ai_fd >= 0) {
ret = close(g_ai_fd);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(close, ret);
soc_err_print_s32(g_ai_fd);
}
g_ai_fd = -1;
}
ai_unlock();
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_get_default_attr(ext_ai_port ai_port, ext_ai_attr *attr)
{
td_s32 ret;
ai_get_df_attr_param ai_get_df_attr = { 0 };
check_ai_port(ai_port);
check_ai_null_ptr(attr);
ai_get_df_attr.ai_port = ai_port;
ret = ioctl(g_ai_fd, CMD_AI_GETDEFAULTATTR, &ai_get_df_attr);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETDEFAULTATTR failed!\n");
soc_err_print_err_code(ret);
return ret;
}
ret = memcpy_s(attr, sizeof(*attr),
&ai_get_df_attr.attr, sizeof(ext_ai_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_dev_set_attr(td_handle ai, const ext_ai_attr *attr)
{
td_s32 ret;
ai_attr_param ai_set_attr = {
.ai = ai,
};
ret = memcpy_s(&ai_set_attr.attr, sizeof(ext_ai_attr),
attr, sizeof(*attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_SETATTR, &ai_set_attr);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETATTR failed\n");
soc_err_print_err_code(ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_chan_reattach_track(td_handle ai)
{
td_s32 ret;
td_handle track;
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
track = ai_state->h_track;
if (track == TD_INVALID_HANDLE) {
return TD_SUCCESS;
}
ret = ai_detach(ai, track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_detach, ret);
return ret;
}
ret = ext_mpi_ao_track_stop(track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ext_mpi_ao_track_stop, ret);
return ret;
}
ret = ai_attach(ai, track);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_attach, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 mpi_ai_set_attr(td_handle h_ai, const ext_ai_attr *attr)
{
td_s32 ret;
ret = ai_dev_set_attr(h_ai, attr);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_dev_set_attr, ret);
return ret;
}
ret = ai_chan_reattach_track(h_ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_chan_reattach_track, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_set_attr(td_handle ai, const ext_ai_attr *attr)
{
td_s32 ret;
check_ai_id(ai);
check_ai_null_ptr(attr);
ai_ch_api_lock(ai);
ai_ch_cmd_lock(ai);
ret = mpi_ai_set_attr(ai, attr);
ai_ch_cmd_unlock(ai);
ai_ch_api_unlock(ai);
return ret;
}
td_s32 ext_mpi_ai_get_attr(td_handle ai, ext_ai_attr *attr)
{
td_s32 ret;
ai_attr_param ai_get_attr = { 0 };
check_ai_id(ai);
check_ai_null_ptr(attr);
ai_get_attr.ai = ai;
ret = ioctl(g_ai_fd, CMD_AI_GETATTR, &ai_get_attr);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETATTR failed\n");
soc_err_print_err_code(ret);
return ret;
}
ret = memcpy_s(attr, sizeof(*attr),
&ai_get_attr.attr, sizeof(ext_ai_attr));
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 dev_ai_unmap_stream_buffer(td_handle ai)
{
td_s32 ret;
td_u8 *addr = TD_NULL;
ai_buf_param ai_buf_info = {
.ai = ai,
.ai_buf.user_vir_addr = 0,
.ai_buf.size = 0,
};
ret = ioctl(g_ai_fd, CMD_AI_GETBUFINFO, &ai_buf_info);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETBUFINFO failed\n");
soc_err_print_err_code(ret);
return ret;
}
addr = (td_u8 *)((uintptr_t)ai_buf_info.ai_buf.user_vir_addr);
ret = munmap(addr, ai_buf_info.ai_buf.size);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(munmap, ret);
return ret;
}
ai_buf_info.ai_buf.map_fd = TD_INVALID_HANDLE;
return TD_SUCCESS;
}
static td_s32 dev_ai_map_stream_buffer(td_handle handle)
{
td_s32 ret;
td_void *addr = TD_NULL;
ai_buf_param ai_buf_info = {
.ai = handle,
.ai_buf.size = 0,
.ai_buf.map_fd = -1,
};
ret = ioctl(g_ai_fd, CMD_AI_GETBUFINFO, &ai_buf_info);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETBUFINFO failed\n");
soc_err_print_err_code(ret);
return ret;
}
addr = mmap((td_void *)0, ai_buf_info.ai_buf.size,
PROT_READ | PROT_WRITE, MAP_SHARED, ai_buf_info.ai_buf.map_fd, 0);
if (addr == MAP_FAILED) {
soc_log_err("mmap failed\n");
return SOC_ERR_AI_INSUFFICIENT_RESOURCES;
}
ai_buf_info.ai_buf.user_vir_addr = (td_u8 *)addr - (td_u8 *)TD_NULL;
ret = ioctl(g_ai_fd, CMD_AI_SETBUFINFO, &ai_buf_info);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETBUFINFO failed\n");
soc_err_print_err_code(ret);
goto out;
}
return TD_SUCCESS;
out:
(td_void)munmap(addr, ai_buf_info.ai_buf.size);
return ret;
}
static td_s32 dev_ai_destroy(td_handle h_ai)
{
td_s32 ret;
ret = dev_ai_unmap_stream_buffer(h_ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dev_ai_unmap_stream_buffer, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_DESTROY, &h_ai);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_DESTROY failed!\n");
soc_err_print_err_code(ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 dev_ai_create(ext_ai_port ai_port, const ext_ai_attr *attr, td_handle *handle)
{
td_s32 ret;
ai_create_param ai_param = {
.ai_port = ai_port,
.ai = TD_INVALID_HANDLE,
};
ret = memcpy_s(&ai_param.attr, sizeof(ext_ai_attr), attr, sizeof(*attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_CREATE, &ai_param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_CREATE failed!\n");
soc_err_print_err_code(ret);
return ret;
}
*handle = ai_param.ai;
ret = dev_ai_map_stream_buffer(*handle);
if (ret != EOK) {
soc_err_print_call_fun_err(dev_ai_map_stream_buffer, ret);
goto out;
}
return TD_SUCCESS;
out:
if (ioctl(g_ai_fd, CMD_AI_DESTROY, handle) != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_DESTROY failed!\n");
}
*handle = TD_INVALID_HANDLE;
return ret;
}
static td_s32 ai_state_init(ai_mpi_state *ai_state, td_handle ai)
{
td_s32 ret;
td_u32 i;
ret = memset_s(ai_state, sizeof(*ai_state), 0, sizeof(ai_mpi_state));
if (ret != EOK) {
soc_err_print_call_fun_err(memset_s, ret);
return ret;
}
ai_state->h_ai = ai;
ai_state->h_track = TD_INVALID_HANDLE;
for (i = 0; i < AI_MAX_DST; i++) {
ai_state->h_dst[i] = TD_INVALID_HANDLE;
}
return TD_SUCCESS;
}
static td_void ai_state_free(ai_mpi_state *ai_state)
{
td_u32 id;
if (ai_state == TD_NULL) {
return;
}
id = ai_state->h_ai & AI_CHNID_MASK;
if (id >= AI_MAX_TOTAL_NUM) {
soc_err_print_h32(ai_state->h_ai);
return;
}
g_ai_res.ai[id] = TD_NULL;
free(ai_state);
}
static ai_mpi_state *ai_chan_alloc(td_handle ai)
{
td_s32 ret;
ai_mpi_state *ai_state = TD_NULL;
if ((ai & AI_CHNID_MASK) >= AI_MAX_TOTAL_NUM) {
return TD_NULL;
}
ai_state = (ai_mpi_state *)malloc(sizeof(ai_mpi_state));
if (ai_state == TD_NULL) {
soc_log_err("malloc( ai_mpi_state failed\n");
return TD_NULL;
}
ret = ai_state_init(ai_state, ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_state_init, ret);
free(ai_state);
return TD_NULL;
}
g_ai_res.ai[ai & AI_CHNID_MASK] = ai_state;
return ai_state;
}
static td_void ai_chan_stop_thread(ai_mpi_state *ai_state)
{
td_s32 ret;
ai_state->ai_thread_run = TD_FALSE;
if (ai_state->ai_adec_thd_inst != 0) {
ret = pthread_join(ai_state->ai_adec_thd_inst, TD_NULL);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_join, ret);
return;
}
ai_state->ai_adec_thd_inst = 0;
}
if (ai_state->ai_track_thd_inst != 0) {
ret = pthread_join(ai_state->ai_track_thd_inst, TD_NULL);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_join, ret);
return;
}
ai_state->ai_track_thd_inst = 0;
}
if (ai_state->ai_data_thd_inst != 0) {
ret = pthread_join(ai_state->ai_data_thd_inst, TD_NULL);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_join, ret);
return;
}
ai_state->ai_data_thd_inst = 0;
}
}
static td_s32 ai_chan_start_thread(ai_mpi_state *ai_state)
{
td_s32 ret;
ai_state->ai_thread_run = TD_TRUE;
ret = pthread_create(&ai_state->ai_data_thd_inst, TD_NULL,
ai_data_thread, (td_void *)ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_create, ret);
return ret;
}
ret = pthread_create(&ai_state->ai_track_thd_inst, TD_NULL,
ai_track_thread, (td_void *)ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_create, ret);
goto out;
}
ret = pthread_create(&ai_state->ai_adec_thd_inst, TD_NULL,
ai_adec_thread, (td_void *)ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(pthread_create, ret);
goto out;
}
return TD_SUCCESS;
out:
ai_chan_stop_thread(ai_state);
return ret;
}
static td_void ai_chan_parser_destroy(ai_mpi_state *ai_state)
{
if (ai_state->h_parser == TD_NULL) {
return;
}
iec61937_parser_destroy(ai_state->h_parser);
ai_state->h_parser = TD_NULL;
}
static td_s32 ai_chan_parser_create(ai_mpi_state *ai_state)
{
td_s32 ret;
iec61937_parser_state *parser_state = TD_NULL;
ret = iec61937_parser_create(&ai_state->h_parser);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(iec61937_parser_create, ret);
return ret;
}
parser_state = (iec61937_parser_state *)ai_state->h_parser;
parser_state->channel = ai_state->channel;
parser_state->bit_depth = ai_state->bit_depth;
return TD_SUCCESS;
}
static td_void ai_chan_destroy(ai_mpi_state *ai_state)
{
ai_chan_stop_thread(ai_state);
ai_chan_parser_destroy(ai_state);
ai_proc_deinit(ai_state);
ai_state_free(ai_state);
}
static td_s32 ai_chan_create(ext_ai_port ai_port, const ext_ai_attr *attr, td_handle ai)
{
td_s32 ret;
ai_mpi_state *ai_state = TD_NULL;
ai_state = ai_chan_alloc(ai);
if (ai_state == TD_NULL) {
soc_log_err("call ai_chan_alloc failed\n");
return TD_FAILURE;
}
ret = ai_proc_init(ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_proc_init, ret);
goto out0;
}
ai_set_chn_attr(ai_port, attr, ai_state);
ret = ai_chan_parser_create(ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_chan_parser_create, ret);
goto out1;
}
ret = ai_chan_start_thread(ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_chan_start_thread, ret);
goto out2;
}
return TD_SUCCESS;
out2:
ai_chan_parser_destroy(ai_state);
out1:
ai_proc_deinit(ai_state);
out0:
ai_state_free(ai_state);
return ret;
}
static td_s32 mpi_ai_create(ext_ai_port ai_port, const ext_ai_attr *attr, td_handle *handle)
{
td_s32 ret;
td_handle ai = TD_INVALID_HANDLE;
ret = dev_ai_create(ai_port, attr, &ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dev_ai_create, ret);
return ret;
}
ai_ch_api_lock(ai);
ai_ch_data_lock(ai);
ret = ai_chan_create(ai_port, attr, ai);
ai_ch_data_unlock(ai);
ai_ch_api_unlock(ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_chan_create, ret);
goto out;
}
*handle = ai;
return TD_SUCCESS;
out:
if (dev_ai_destroy(ai) != TD_SUCCESS) {
soc_log_err("call dev_ai_destroy failed\n");
}
*handle = TD_INVALID_HANDLE;
return ret;
}
static td_s32 mpi_ai_destroy(td_handle ai)
{
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
ai_ch_api_lock(ai);
ai_ch_data_lock(ai);
ai_chan_destroy(ai_state);
ai_ch_data_unlock(ai);
ai_ch_api_unlock(ai);
return dev_ai_destroy(ai);
}
td_s32 ext_mpi_ai_create(ext_ai_port ai_port, const ext_ai_attr *attr, td_handle *phandle)
{
td_s32 ret;
check_ai_port(ai_port);
check_ai_null_ptr(attr);
check_ai_null_ptr(phandle);
ai_lock();
ret = mpi_ai_create(ai_port, attr, phandle);
ai_unlock();
return ret;
}
td_s32 ext_mpi_ai_destroy(td_handle ai)
{
td_s32 ret;
ai_lock();
ret = mpi_ai_destroy(ai);
ai_unlock();
return ret;
}
static td_s32 ai_dev_set_enable(td_handle h_ai, td_bool enable)
{
td_s32 ret;
ai_enable_param ai_enable = {
.ai = h_ai,
.ai_enable = enable,
};
ret = ioctl(g_ai_fd, CMD_AI_SETENABLE, &ai_enable);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETENABLE failed\n");
soc_err_print_err_code(ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 mpi_ai_set_enable(td_handle ai, td_bool enable)
{
td_s32 ret;
ai_mpi_state *ai_state = TD_NULL;
ret = ai_dev_set_enable(ai, enable);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_dev_set_enable, ret);
return ret;
}
ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
if (enable == TD_TRUE) {
ai_state->need_start = TD_TRUE;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_set_enable(td_handle ai, td_bool enable)
{
td_s32 ret;
check_ai_id(ai);
ai_ch_api_lock(ai);
ai_ch_cmd_lock(ai);
ret = mpi_ai_set_enable(ai, enable);
ai_ch_cmd_unlock(ai);
ai_ch_api_unlock(ai);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(mpi_ai_set_enable, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_get_enable(td_handle ai, td_bool *enable)
{
td_s32 ret;
check_ai_id(ai);
check_ai_null_ptr(enable);
ai_ch_api_lock(ai);
ai_ch_cmd_lock(ai);
ret = ai_dev_get_enable(ai, enable);
ai_ch_cmd_unlock(ai);
ai_ch_api_unlock(ai);
return ret;
}
static ai_mpi_state *ai_get_acquire_frame_chan(td_handle ai)
{
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return TD_NULL;
}
if ((ai_state->h_dst[0] != TD_INVALID_HANDLE) ||
(ai_state->h_track != TD_INVALID_HANDLE)) {
soc_log_err("aenc or track attach this ai chan, can not acquire frame!\n");
return TD_NULL;
}
return ai_state;
}
static td_s32 ai_chan_acquire_frame(td_handle ai, ext_ao_frame *frame)
{
td_s32 ret;
ai_mpi_state *ai_state = ai_get_acquire_frame_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_acquire_frame_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
if (ai_state->pass_through == TD_TRUE) {
ret = ai_parser_acquire_frame(ai_state, frame);
if (ret != TD_SUCCESS) {
soc_log_err("ai_parser_acquire_frame failed\n");
soc_err_print_err_code(ret);
return ret;
}
} else {
const td_u32 request_size = ai_chan_cal_acquire_size(ai_state);
ret = ai_kernel_acquire_frame(ai_state, request_size, frame);
if (ret != TD_SUCCESS) {
soc_warn_print_call_fun_err(ai_kernel_acquire_frame, ret);
return ret;
}
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_acquire_frame(td_handle ai, ext_ao_frame *frame)
{
td_s32 ret;
check_ai_id(ai);
check_ai_null_ptr(frame);
ai_ch_data_lock(ai);
ai_ch_io_lock(ai);
ret = ai_chan_acquire_frame(ai, frame);
ai_ch_io_unlock(ai);
ai_ch_data_unlock(ai);
return ret;
}
static td_s32 ai_chan_release_frame(td_handle ai, const ext_ao_frame *ao_frame)
{
td_s32 ret;
ai_mpi_state *ai_state = ai_get_acquire_frame_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_acquire_frame_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
check_ai_null_ptr(ao_frame);
if (ai_state->pass_through == TD_TRUE) {
check_ai_null_ptr(ai_state->h_parser);
ret = ai_parser_release_frame(ai_state->h_parser, ao_frame);
if (ret != TD_SUCCESS) {
soc_log_err("ai_parser_release_frame failed\n");
soc_err_print_err_code(ret);
return ret;
}
} else {
ret = ai_kernel_release_frame(ai);
if (ret != TD_SUCCESS) {
soc_log_err("ai_kernel_release_frame failed\n");
soc_err_print_err_code(ret);
return ret;
}
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_release_frame(td_handle ai, const ext_ao_frame *frame)
{
td_s32 ret;
check_ai_id(ai);
check_ai_null_ptr(frame);
ai_ch_data_lock(ai);
ai_ch_io_lock(ai);
ret = ai_chan_release_frame(ai, frame);
ai_ch_io_unlock(ai);
ai_ch_data_unlock(ai);
return ret;
}
td_s32 ext_mpi_ai_attach(td_handle ai, td_handle dst)
{
td_s32 ret;
check_ai_id(ai);
ai_ch_api_lock(ai);
ai_ch_cmd_lock(ai);
ret = ai_attach(ai, dst);
ai_ch_cmd_unlock(ai);
ai_ch_api_unlock(ai);
return ret;
}
td_s32 ext_mpi_ai_detach(td_handle ai, td_handle dst)
{
td_s32 ret;
check_ai_id(ai);
ai_ch_api_lock(ai);
ai_ch_cmd_lock(ai);
ret = ai_detach(ai, dst);
ai_ch_cmd_unlock(ai);
ai_ch_api_unlock(ai);
return ret;
}
td_s32 ext_mpi_ai_set_delay(td_handle ai, const ext_ai_delay *delay)
{
td_s32 ret;
ai_delay_comps_param param = {
.ai = ai,
};
check_ai_id(ai);
check_ai_null_ptr(delay);
ret = memcpy_s(&param.delay_comps, sizeof(ext_ai_delay),
delay, sizeof(*delay));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_SETDELAYCOMPS, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETDELAYCOMPS failed!\n");
soc_err_print_err_code(ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_get_delay(td_handle ai, ext_ai_delay *delay)
{
td_s32 ret;
ai_delay_comps_param param = {
.ai = ai,
};
check_ai_id(ai);
check_ai_null_ptr(delay);
ret = ioctl(g_ai_fd, CMD_AI_GETDELAYCOMPS, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETDELAYCOMPS failed!\n");
soc_err_print_err_code(ret);
return ret;
}
ret = memcpy_s(delay, sizeof(*delay),
&param.delay_comps, sizeof(ext_ai_delay));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
static td_s32 ai_get_cur_delay(td_handle h_ai, td_u32 *delay)
{
td_s32 ret;
ai_port_delay_param param = {
.ai = h_ai,
.delay = 0,
};
check_ai_id(h_ai);
check_ai_null_ptr(delay);
ret = ioctl(g_ai_fd, CMD_AI_GETPORTDELAY, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETDELAYCOMPS failed!\n");
soc_err_print_err_code(ret);
return ret;
}
*delay = param.delay;
return TD_SUCCESS;
}
#define AI_PARSER_MAX_TIMES 100
static td_s32 ai_chan_get_stream_type(td_handle ai, ext_audio_format *stream_type)
{
td_s32 ret;
td_u32 i = 0;
iec61937_parser_stream_type iapi_audio_format;
ai_mpi_state *ai_state = ai_get_chan(ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return SOC_ERR_AI_INVALID_ID;
}
if (ai_state->h_dst[0] != TD_INVALID_HANDLE) {
soc_log_err("aenc attach this ai chan!\n");
return SOC_ERR_AI_NOTSUPPORT;
}
if (ai_state->pass_through == TD_FALSE) {
*stream_type = EXT_AUDIO_FORMAT_PCM;
return TD_SUCCESS;
}
check_ai_null_ptr(ai_state->h_parser);
check_ai_null_ptr(ai_state->ai_proc_info);
/* some devices send long time invalid data before playing, and the caller's
* thread interval is tens of milliseconds, the do-while code avoids the
* caller consuming multiple times and getting stream type too long.
*/
do {
if ((ai_state->ai_proc_info->data_type == EXT_AUDIO_FORMAT_STREAM) ||
(ai_state->ai_proc_info->data_type == EXT_AUDIO_FORMAT_CXT)) {
ret = ai_parser_send_frame(ai_state);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(ai_parser_send_frame, ret);
}
}
/* zero data no need to parser */
if (iec61937_check_is_empty(ai_state->h_parser) == TD_TRUE) {
soc_log_dbg("not enough data.\n");
return SOC_ERR_AI_NOT_ENOUGH_DATA;
}
ret = trans_audio_format_from_mpi_to_iapi_iec61937(&iapi_audio_format, stream_type);
if (ret != TD_SUCCESS) {
return ret;
}
ret = iec61937_parser_get_stream_type(ai_state->h_parser, &iapi_audio_format);
trans_audio_format_from_iapi_iec61937_to_mpi(&iapi_audio_format, stream_type);
i++;
if (i > AI_PARSER_MAX_TIMES) {
break;
}
} while ((ret == SOC_ERR_AI_FIND_SYNC_FAILED) || (ret == SOC_ERR_AI_BITS_INVALID_DATA));
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(iec61937_parser_get_stream_type, ret);
return ret;
}
ai_state->ai_proc_info->data_type = *stream_type;
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_get_stream_type(td_handle ai, ext_audio_format *stream_type)
{
td_s32 ret;
check_ai_id(ai);
check_ai_null_ptr(stream_type);
ai_ch_api_lock(ai);
ai_ch_io_lock(ai);
ret = ai_chan_get_stream_type(ai, stream_type);
ai_ch_io_unlock(ai);
ai_ch_api_unlock(ai);
return ret;
}
td_s32 ext_mpi_ai_set_nr_attr(td_handle ai, const ext_ai_nr_attr *nr_attr)
{
td_s32 ret;
ai_nr_param nr_param = {
.ai = ai,
};
check_ai_id(ai);
check_ai_null_ptr(nr_attr);
ret = memcpy_s(&nr_param.nr, sizeof(ext_ai_nr_attr),
nr_attr, sizeof(*nr_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_SETNRATTRS, &nr_param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETNRATTRS failed\n");
soc_err_print_err_code(ret);
}
return ret;
}
td_s32 ext_mpi_ai_get_nr_attr(td_handle ai, ext_ai_nr_attr *nr_attr)
{
td_s32 ret;
ai_nr_param nr_param = {
.ai = ai,
};
check_ai_id(ai);
check_ai_null_ptr(nr_attr);
ret = ioctl(g_ai_fd, CMD_AI_GETNRATTRS, &nr_param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETNRATTRS failed\n");
soc_err_print_err_code(ret);
return ret;
}
ret = memcpy_s(nr_attr, sizeof(*nr_attr),
&nr_param.nr, sizeof(ext_ai_nr_attr));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_set_line_in_volume(td_handle ai, const ext_ai_line_in_gain *line_in_gain)
{
td_s32 ret;
ai_line_in_gain_param param = {
.ai = ai,
};
check_ai_id(ai);
check_ai_null_ptr(line_in_gain);
if (line_in_gain->line_in_gain > LINE_IN_VOLUME_MAX) {
soc_log_err("line_in_gain is invalid\n");
return SOC_ERR_AI_INVALID_PARA;
}
ret = memcpy_s(&param.line_in_gain, sizeof(ext_ai_line_in_gain),
line_in_gain, sizeof(*line_in_gain));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
ret = ioctl(g_ai_fd, CMD_AI_SETLINEINVOLUME, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETLINEINVOLUME failed\n");
soc_err_print_err_code(ret);
}
return ret;
}
td_s32 ext_mpi_ai_get_line_in_volume(td_handle ai, ext_ai_line_in_gain *line_in_gain)
{
td_s32 ret;
ai_line_in_gain_param param = {
.ai = ai,
.line_in_gain.line_in_gain = 0,
};
check_ai_id(ai);
check_ai_null_ptr(line_in_gain);
ret = ioctl(g_ai_fd, CMD_AI_GETLINEINVOLUME, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETLINEINVOLUME failed\n");
soc_err_print_err_code(ret);
return ret;
}
ret = memcpy_s(line_in_gain, sizeof(*line_in_gain),
&param.line_in_gain, sizeof(ext_ai_line_in_gain));
if (ret != EOK) {
soc_err_print_call_fun_err(memcpy_s, ret);
return ret;
}
return TD_SUCCESS;
}
td_s32 ext_mpi_ai_set_line_in_mute(td_handle ai, td_bool mute)
{
td_s32 ret;
ai_line_in_mute_param param = {
.ai = ai,
.mute = mute,
};
check_ai_id(ai);
ret = ioctl(g_ai_fd, CMD_AI_SETLINEINMUTE, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_SETLINEINMUTE failed\n");
soc_err_print_err_code(ret);
}
return ret;
}
td_s32 ext_mpi_ai_get_line_in_mute(td_handle ai, td_bool *mute)
{
td_s32 ret;
ai_line_in_mute_param param = {
.ai = ai,
.mute = TD_FALSE,
};
check_ai_id(ai);
check_ai_null_ptr(mute);
ret = ioctl(g_ai_fd, CMD_AI_GETLINEINMUTE, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETLINEINMUTE failed\n");
soc_err_print_err_code(ret);
return ret;
}
*mute = param.mute;
return TD_SUCCESS;
}
static td_s32 ext_mpi_ai_get_status(td_handle h_ai, td_u32 *status)
{
td_s32 ret;
ai_status_param param = {
.ai = h_ai,
.status = AI_CHN_STATUS_MAX,
};
check_ai_id(h_ai);
check_ai_null_ptr(status);
ret = ioctl(g_ai_fd, CMD_AI_GETSTATUS, &param);
if (ret != TD_SUCCESS) {
soc_log_err("ioctl CMD_AI_GETSTATUS failed\n");
soc_err_print_err_code(ret);
return ret;
}
*status = param.status;
if (param.status == AI_CHN_STATUS_RESET) {
soc_log_warn("detect ai need reset\n");
}
return ret;
}
ext_ai_port ext_mpi_get_ai_port_by_handle(td_handle h_ai)
{
ai_mpi_state *ai_state = ai_get_chan(h_ai);
if (ai_state == TD_NULL) {
soc_log_err("ai_get_chan failed\n");
return EXT_AI_MAX;
}
return ai_state->ai_port;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */