#include "fw_pvt.h" #include "viddec_fw_parser_ipclib_config.h" #include "viddec_fw_common_defs.h" #include "viddec_fw_parser.h" #include "viddec_fw_debug.h" /* This define makes sure that the structure is stored in Local memory. This is shared memory between host and FW.*/ volatile dmem_t _dmem __attribute__ ((section (".exchange"))); /* Debug index should be disbaled for Production FW */ uint32_t dump_ptr=0; uint32_t timer=0; /* Auto Api definitions */ ismd_api_group viddec_fw_api_array[2]; extern void viddec_fw_parser_register_callbacks(void); /*------------------------------------------------------------------------------ * Function: initialize firmware SVEN TX Output *------------------------------------------------------------------------------ */ int SMDEXPORT viddec_fw_parser_sven_init(struct SVEN_FW_Globals *sven_fw_globals ) { extern int sven_fw_set_globals(struct SVEN_FW_Globals *fw_globals ); return(sven_fw_set_globals(sven_fw_globals)); } /*------------------------------------------------------------------------------ * Function: viddec_fw_check_watermark_boundary * This function figures out if we crossesd watermark boundary on input data. * before represents the ES Queue data when we started and current represents ES Queue data * when we are ready to swap.Threshold is the amount of data specified by the driver to trigger an * interrupt. * We return true if threshold is between before and current. *------------------------------------------------------------------------------ */ static inline uint32_t viddec_fw_check_watermark_boundary(uint32_t before, uint32_t current, uint32_t threshold) { return ((before >= threshold) && (current < threshold)); } /*------------------------------------------------------------------------------ * Function: viddec_fw_get_total_input_Q_data * This function figures out how much data is available in input queue of the FW *------------------------------------------------------------------------------ */ static uint32_t viddec_fw_get_total_input_Q_data(uint32_t indx) { FW_IPC_Handle *fwipc = GET_IPC_HANDLE(_dmem); uint32_t ret; int32_t pos=0; FW_IPC_ReceiveQue *rcv_q; rcv_q = &fwipc->rcv_q[indx]; /* count the cubby buffer which we already read if present */ ret = (_dmem.stream_info[indx].buffered_data) ? CONFIG_IPC_MESSAGE_MAX_SIZE:0; ret += ipc_mq_read_avail(&rcv_q->mq, (int32_t *)&pos); return ret; } /*------------------------------------------------------------------------------ * Function: mfd_round_robin * Params: * [in] pri: Priority of the stream * [in] indx: stream id number of the last stream that was scheduled. * [out] qnum: Stream id of priority(pri) which has data. * This function is responsible for figuring out which stream needs to be scheduled next. * It starts after the last scheduled stream and walks through all streams until it finds * a stream which is of required priority, in start state, has space on output and data in * input. * If no such stream is found qnum is not updated and return value is 0. * If a stream is found then qnum is updated with that id and function returns 1. *------------------------------------------------------------------------------ */ uint32_t mfd_round_robin(uint32_t pri, int32_t *qnum, int32_t indx) { FW_IPC_Handle *fwipc = GET_IPC_HANDLE(_dmem); int32_t i = CONFIG_IPC_FW_MAX_RX_QUEUES; uint32_t ret = 0; /* Go through all queues until we find a valid queue of reqd priority */ while(i>0) { indx++; if(indx >= CONFIG_IPC_FW_MAX_RX_QUEUES) indx = 0; /* We should look only at queues which match priority and in running state */ if( (_dmem.stream_info[indx].state == 1) && (_dmem.stream_info[indx].priority == pri)) { uint32_t inpt_avail=0, output_avail=0, wklds_avail =0 , pos; FW_IPC_ReceiveQue *rcv_q; rcv_q = &fwipc->rcv_q[indx]; inpt_avail = (_dmem.stream_info[indx].buffered_data > 0) || (ipc_mq_read_avail(&rcv_q->mq, (int32_t *)&pos) > 0); /* we have to check for two workloads to protect against error cases where we might have to push both current and next workloads */ output_avail = FwIPC_SpaceAvailForMessage(fwipc, &fwipc->snd_q[indx], CONFIG_IPC_MESSAGE_MAX_SIZE, &pos) >= 2; pos = 0; /* Need at least current and next to proceed */ wklds_avail = (ipc_mq_read_avail(&fwipc->wkld_q[indx].mq, (int32_t *)&pos) >= (CONFIG_IPC_MESSAGE_MAX_SIZE << 1)); if(inpt_avail && output_avail && wklds_avail) {/* Success condition: we have some data on input and enough space on output queue */ *qnum = indx; ret =1; break; } } i--; } return ret; } static inline void mfd_setup_emitter(FW_IPC_Handle *fwipc, FW_IPC_ReceiveQue *rcv_q, mfd_pk_strm_cxt *cxt) { int32_t ret1=0,ret=0; /* We don't check return values for the peek as round robin guarantee's that we have required free workloads */ ret = FwIPC_PeekReadMessage(fwipc, rcv_q, (char *)&(cxt->wkld1), sizeof(ipc_msg_data), 0); ret1 = FwIPC_PeekReadMessage(fwipc, rcv_q, (char *)&(cxt->wkld2), sizeof(ipc_msg_data), 1); viddec_emit_update(&(cxt->pm.emitter), cxt->wkld1.phys, cxt->wkld2.phys, cxt->wkld1.len, cxt->wkld2.len); } static inline void mfd_init_swap_memory(viddec_pm_cxt_t *pm, uint32_t codec_type, uint32_t start_addr, uint32_t clean) { uint32_t *persist_mem; persist_mem = (uint32_t *)(start_addr | GV_DDR_MEM_MASK); viddec_pm_init_context(pm,codec_type, persist_mem, clean); pm->sc_prefix_info.first_sc_detect = 1; viddec_emit_init(&(pm->emitter)); } void output_omar_wires( unsigned int value ) { #ifdef RTL_SIMULATION reg_write(CONFIG_IPC_ROFF_HOST_DOORBELL, value ); #endif } /*------------------------------------------------------------------------------ * Function: viddec_fw_init_swap_memory * This function is responsible for seeting the swap memory to a good state for current stream. * The swap parameter tells us whether we need to dma the context to local memory. * We call init on emitter and parser manager which inturn calls init of the codec we are opening the stream for. *------------------------------------------------------------------------------ */ void viddec_fw_init_swap_memory(unsigned int stream_id, unsigned int swap, unsigned int clean) { mfd_pk_strm_cxt *cxt; mfd_stream_info *cxt_swap; cxt = (mfd_pk_strm_cxt *)&(_dmem.srm_cxt); cxt_swap = (mfd_stream_info *)&(_dmem.stream_info[stream_id]); if(swap) {/* Swap context into local memory */ cp_using_dma(cxt_swap->ddr_cxt, (uint32_t) &(cxt->pm), sizeof(viddec_pm_cxt_t), false, false); } { mfd_init_swap_memory(&(cxt->pm), cxt_swap->strm_type, cxt_swap->ddr_cxt+cxt_swap->cxt_size, clean); cxt_swap->wl_time = 0; cxt_swap->es_time = 0; } if(swap) {/* Swap context into DDR */ cp_using_dma(cxt_swap->ddr_cxt, (uint32_t) &(cxt->pm), sizeof(viddec_pm_cxt_t), true, false); } } /*------------------------------------------------------------------------------ * Function: viddec_fw_push_current_frame_to_output * This is a helper function to read a workload from input queue and push to output queue. * This is called when are done with a frame. *------------------------------------------------------------------------------ */ static inline void viddec_fw_push_current_frame_to_output(FW_IPC_Handle *fwipc, uint32_t cur) { ipc_msg_data wkld_to_push; FwIPC_ReadMessage(fwipc, &fwipc->wkld_q[cur], (char *)&(wkld_to_push), sizeof(ipc_msg_data)); FwIPC_SendMessage(fwipc, cur, (char *)&(wkld_to_push), sizeof(ipc_msg_data)); } /*------------------------------------------------------------------------------ * Function: viddec_fw_get_next_stream_to_schedule * This is a helper function to figure out which active stream needs to be scheduled next. * If none of the streams are active it returns -1. *------------------------------------------------------------------------------ */ static inline int viddec_fw_get_next_stream_to_schedule(void) { int32_t cur = -1; if(mfd_round_robin(viddec_stream_priority_REALTIME, &cur, _dmem.g_pk_data.high_id)) { /* On success store the stream id */ _dmem.g_pk_data.high_id = cur; } else { /* Check Low priority Queues, Since we couldn't find a valid realtime stream */ if(mfd_round_robin(viddec_stream_priority_BACKGROUND, &cur, _dmem.g_pk_data.low_id)) { _dmem.g_pk_data.low_id = cur; } } return cur; } /*------------------------------------------------------------------------------ * Function: viddec_fw_update_pending_interrupt_flag * This is a helper function to figure out if we need to mark an interrupt pending for this stream. * We update status value here if we find any of the interrupt conditions are true. * If this stream has a interrupt pending which we could not send to host, we don't overwrite past status info. *------------------------------------------------------------------------------ */ static inline void viddec_fw_update_pending_interrupt_flag(int32_t cur, mfd_stream_info *cxt_swap, uint8_t pushed_a_workload, uint32_t es_Q_data_at_start) { if(_dmem.int_status[cur].mask) { if(!cxt_swap->pending_interrupt) { uint32_t es_Q_data_now; uint8_t wmark_boundary_reached=false; es_Q_data_now = viddec_fw_get_total_input_Q_data((uint32_t)cur); wmark_boundary_reached = viddec_fw_check_watermark_boundary(es_Q_data_at_start, es_Q_data_now, cxt_swap->low_watermark); _dmem.int_status[cur].status = 0; if(pushed_a_workload) { _dmem.int_status[cur].status |= VIDDEC_FW_WKLD_DATA_AVAIL; } if(wmark_boundary_reached) { _dmem.int_status[cur].status |= VIDDEC_FW_INPUT_WATERMARK_REACHED; } cxt_swap->pending_interrupt = ( _dmem.int_status[cur].status != 0); } } else { cxt_swap->pending_interrupt = false; } } static inline void viddec_fw_handle_error_and_inband_messages(int32_t cur, uint32_t pm_ret) { FW_IPC_Handle *fwipc = GET_IPC_HANDLE(_dmem); viddec_fw_push_current_frame_to_output(fwipc, cur); switch(pm_ret) { case PM_EOS: case PM_OVERFLOW: { viddec_fw_init_swap_memory(cur, false, true); } break; case PM_DISCONTINUITY: { viddec_fw_init_swap_memory(cur, false, false); } break; default: break; } } void viddec_fw_debug_scheduled_stream_state(int32_t indx, int32_t start) { FW_IPC_Handle *fwipc = GET_IPC_HANDLE(_dmem); uint32_t inpt_avail=0, output_avail=0, wklds_avail =0 , pos; FW_IPC_ReceiveQue *rcv_q; uint32_t message; message = (start) ? SVEN_MODULE_EVENT_GV_FW_PK_SCHDL_STRM_START: SVEN_MODULE_EVENT_GV_FW_PK_SCHDL_STRM_END; rcv_q = &fwipc->rcv_q[indx]; inpt_avail = ipc_mq_read_avail(&rcv_q->mq, (int32_t *)&pos); inpt_avail += ((_dmem.stream_info[indx].buffered_data > 0) ? CONFIG_IPC_MESSAGE_MAX_SIZE: 0); inpt_avail = inpt_avail >> 4; pos = 0; output_avail = ipc_mq_read_avail(&fwipc->snd_q[indx].mq, (int32_t *)&pos); output_avail = output_avail >> 4; pos = 0; wklds_avail = ipc_mq_read_avail(&fwipc->wkld_q[indx].mq, (int32_t *)&pos); wklds_avail = wklds_avail >> 4; WRITE_SVEN(message, (int)indx, (int)inpt_avail, (int)output_avail, (int)wklds_avail, 0, 0); } /*------------------------------------------------------------------------------ * Function: viddec_fw_process_async_queues(A.K.A -> Parser Kernel) * This function is responsible for handling the asynchronous queues. * * The first step is to figure out which stream to run. The current algorithm * will go through all high priority queues for a valid stream, if not found we * go through lower priority queues. * * If a valid stream is found we swap the required context from DDR to DMEM and do all necessary * things to setup the stream. * Once a stream is setup we call the parser manager and wait until a wrkld is created or no more input * data left. * Once we find a wkld we push it to host and save the current context to DDR. *------------------------------------------------------------------------------ */ static inline int32_t viddec_fw_process_async_queues() { int32_t cur = -1; cur = viddec_fw_get_next_stream_to_schedule(); if(cur != -1) { FW_IPC_Handle *fwipc = GET_IPC_HANDLE(_dmem); FW_IPC_ReceiveQue *rcv_q; /* bits captured by OMAR */ output_omar_wires( 0x0 ); rcv_q = &fwipc->rcv_q[cur]; { mfd_pk_strm_cxt *cxt; mfd_stream_info *cxt_swap; cxt = (mfd_pk_strm_cxt *)&(_dmem.srm_cxt); cxt_swap = (mfd_stream_info *)&(_dmem.stream_info[cur]); /* Step 1: Swap rodata to local memory. Not doing this currently as all the rodata fits in local memory. */ {/* Step 2: Swap context into local memory */ cp_using_dma(cxt_swap->ddr_cxt, (uint32_t) &(cxt->pm), sizeof(viddec_pm_cxt_t), false, false); } /* Step 3:setup emitter by reading input data and workloads and initialising it */ mfd_setup_emitter(fwipc, &fwipc->wkld_q[cur], cxt); viddec_fw_debug_scheduled_stream_state(cur, true); /* Step 4: Call Parser Manager until workload done or No more ES buffers */ { ipc_msg_data *data = 0; uint8_t stream_active = true, pushed_a_workload=false; uint32_t pm_ret = PM_SUCCESS, es_Q_data_at_start; uint32_t start_time, time=0; start_time = set_wdog(VIDDEC_WATCHDOG_COUNTER_MAX); timer=0; es_Q_data_at_start = viddec_fw_get_total_input_Q_data((uint32_t)cur); do { output_omar_wires( 0x1 ); { uint32_t es_t0,es_t1; get_wdog(&es_t0); pm_ret = viddec_pm_parse_es_buffer(&(cxt->pm), cxt_swap->strm_type, data); get_wdog(&es_t1); cxt_swap->es_time += get_total_ticks(es_t0, es_t1); } switch(pm_ret) { case PM_EOS: case PM_WKLD_DONE: case PM_OVERFLOW: case PM_DISCONTINUITY: {/* Finished a frame worth of data or encountered fatal error*/ stream_active = false; } break; case PM_NO_DATA: { uint32_t next_ret=0; if ( (NULL != data) && (0 != cxt_swap->es_time) ) { /* print performance info for this buffer */ WRITE_SVEN(SVEN_MODULE_EVENT_GV_FW_PK_ES_DONE, (int)cur, (int)cxt_swap->es_time, (int)cxt->input.phys, (int)cxt->input.len, (int)cxt->input.id, (int)cxt->input.flags ); cxt_swap->es_time = 0; } next_ret = FwIPC_ReadMessage(fwipc, rcv_q, (char *)&(cxt->input), sizeof(ipc_msg_data)); if(next_ret != 0) { data = &(cxt->input); WRITE_SVEN(SVEN_MODULE_EVENT_GV_FW_PK_ES_START, (int)cur, (int)cxt_swap->wl_time, (int)cxt->input.phys, (int)cxt->input.len, (int)cxt->input.id, (int)cxt->input.flags ); } else {/* No data on input queue */ cxt_swap->buffered_data = 0; stream_active = false; } } break; default: {/* Not done with current buffer */ data = NULL; } break; } }while(stream_active); get_wdog(&time); cxt_swap->wl_time += get_total_ticks(start_time, time); /* Step 5: If workload done push workload out */ switch(pm_ret) { case PM_EOS: case PM_WKLD_DONE: case PM_OVERFLOW: case PM_DISCONTINUITY: {/* Push current workload as we are done with the frame */ cxt_swap->buffered_data = (PM_WKLD_DONE == pm_ret) ? true: false; viddec_pm_update_time(&(cxt->pm), cxt_swap->wl_time); /* xmit performance info for this workload output */ WRITE_SVEN( SVEN_MODULE_EVENT_GV_FW_PK_WL_DONE, (int)cur, (int)cxt_swap->wl_time, (int)cxt->wkld1.phys, (int)cxt->wkld1.len, (int)cxt->wkld1.id, (int)cxt->wkld1.flags ); cxt_swap->wl_time = 0; viddec_fw_push_current_frame_to_output(fwipc, cur); if(pm_ret != PM_WKLD_DONE) { viddec_fw_handle_error_and_inband_messages(cur, pm_ret); } pushed_a_workload = true; } break; default: break; } /* Update information on whether we have active interrupt for this stream */ viddec_fw_update_pending_interrupt_flag(cur, cxt_swap, pushed_a_workload, es_Q_data_at_start); } viddec_fw_debug_scheduled_stream_state(cur, false); /* Step 6: swap context into DDR */ { cp_using_dma(cxt_swap->ddr_cxt, (uint32_t) &(cxt->pm), sizeof(viddec_pm_cxt_t), true, false); } } } return cur; } /*------------------------------------------------------------------------------ * Function: process_command * This magic function figures out which function to excute based on autoapi. *------------------------------------------------------------------------------ */ static inline void process_command(uint32_t cmd_id, unsigned char *command) { int32_t groupid = ((cmd_id >> 24) - 13) & 0xff; int32_t funcid = cmd_id & 0xffffff; /* writing func pointer to hsot doorbell */ output_omar_wires( (int) viddec_fw_api_array[groupid].unmarshal[funcid] ); WRITE_SVEN( SVEN_MODULE_EVENT_GV_FW_AUTOAPI_CMD,(int) cmd_id, (int) command, ((int *)command)[0], ((int *)command)[1], ((int *)command)[2], ((int *)command)[3] ); viddec_fw_api_array[groupid].unmarshal[funcid](0, command); } /*------------------------------------------------------------------------------ * Function: viddec_fw_process_sync_queues(A.K.A auto api) * Params: * [in] msg: common sync structure where all required parameters are present for autoapi. * * This function is responsible for handling synchronous messages. All synchronous messages * are handled through auto api. * what are synchronous messages? Anything releated to teardown or opening a stream Ex: open, close, flush etc. * * Only once synchronous message at a time. When a synchronous message its id is usually in cp doorbell. Once * we are done handling synchronous message through auto api we release doorbell to let the host write next * message. *------------------------------------------------------------------------------ */ static inline int32_t viddec_fw_process_sync_queues(unsigned char *msg) { int32_t ret = -1; if(0 == reg_read(CONFIG_IPC_ROFF_RISC_DOORBELL_STATUS)) { uint32_t command1=0; command1 = reg_read(CONFIG_IPC_ROFF_RISC_RX_DOORBELL); process_command(command1, msg); reg_write(CONFIG_IPC_ROFF_RISC_DOORBELL_STATUS, 0x2); /* Inform Host we are done with this message */ ret = 0; } return ret; } /*------------------------------------------------------------------------------ * Function: viddec_fw_check_for_pending_int * This function walks through all active streams to see if atleast one stream has a pending interrupt * and returns true if it finds one. *------------------------------------------------------------------------------ */ static inline uint32_t viddec_fw_check_for_pending_int(void) { uint32_t i=0, ret=false; /* start from 0 to max streams that fw can handle*/ while(i < FW_SUPPORTED_STREAMS) { if(_dmem.stream_info[i].state == 1) { if((_dmem.stream_info[i].pending_interrupt) && _dmem.int_status[i].mask) { ret = true; } else {/* If this is not in INT state clear the status before sending it to host */ _dmem.int_status[i].status = 0; } } i++; } return ret; } /*------------------------------------------------------------------------------ * Function: viddec_fw_clear_processed_int * This function walks through all active streams to clear pending interrupt state.This is * called after a INT was issued. *------------------------------------------------------------------------------ */ static inline void viddec_fw_clear_processed_int(void) { uint32_t i=0; /* start from 0 to max streams that fw can handle*/ while(i < FW_SUPPORTED_STREAMS) { //if(_dmem.stream_info[i].state == 1) _dmem.stream_info[i].pending_interrupt = false; i++; } return; } /*------------------------------------------------------------------------------ * Function: viddec_fw_int_host * This function interrupts host if data is available for host or any other status * is valid which the host configures the FW to. * There is only one interrupt line so this is a shared Int for all streams, Host should * look at status of all streams when it receives a Int. * The FW will interrupt the host only if host doorbell is free, in other words the host * should always make the doorbell free at the End of its ISR. *------------------------------------------------------------------------------ */ static inline int32_t viddec_fw_int_host() { /* We Interrupt the host only if host is ready to receive an interrupt */ if((reg_read(CONFIG_IPC_ROFF_HOST_DOORBELL_STATUS) & GV_DOORBELL_STATS) == GV_DOORBELL_STATS) { if(viddec_fw_check_for_pending_int()) { /* If a pending interrupt is found trigger INT */ reg_write(CONFIG_IPC_ROFF_HOST_DOORBELL, VIDDEC_FW_PARSER_IPC_HOST_INT); /* Clear all stream's pending Interrupt info since we use a global INT for all streams */ viddec_fw_clear_processed_int(); } } return 1; } volatile unsigned int stack_corrupted __attribute__ ((section (".stckovrflwchk"))); /*------------------------------------------------------------------------------ * Function: main * This function is the main firmware function. Its a infinite loop where it polls * for messages and processes them if they are available. Currently we ping pong between * synchronous and asynchronous messages one at a time. If we have multiple aysnchronous * queues we always process only one between synchronous messages. * * For multiple asynchronous queues we round robin through the high priorities first and pick * the first one available. Next time when we come around for asynchronous message we start * from the next stream onwards so this guarantees that we give equal time slices for same * priority queues. If no high priority queues are active we go to low priority queues and repeat * the same process. *------------------------------------------------------------------------------ */ int main(void) { unsigned char *msg = (uint8_t *)&(_dmem.buf.data[0]); /* We wait until host reads sync message */ reg_write(CONFIG_IPC_ROFF_HOST_RX_DOORBELL, GV_FW_IPC_HOST_SYNC); while ( GV_DOORBELL_STATS != reg_read(CONFIG_IPC_ROFF_HOST_DOORBELL_STATUS) ) { /*poll register until done bit is set */ /* Host re-writes Vsparc DRAM (BSS) in this loop and will hit the DONE bit when complete */ } enable_intr(); /* Initialize State for queues */ viddec_fw_parser_register_callbacks(); FwIPC_Initialize(GET_IPC_HANDLE(_dmem), (volatile char *)msg); _dmem.g_pk_data.high_id = _dmem.g_pk_data.low_id = -1; viddec_pm_init_ops(); stack_corrupted = 0xDEADBEEF; while(1) { viddec_fw_process_sync_queues(msg); viddec_fw_process_async_queues(); viddec_fw_int_host(); #if 0 if(stack_corrupted != 0xDEADBEEF) { WRITE_SVEN(SVEN_MODULE_EVENT_GV_FW_FATAL_STACK_CORRPON, 0, 0, 0, 0, 0, 0); while(1); } #endif } return 1; }