/* * \file trc_pkt_decode_ptm.cpp * \brief OpenCSD : PTM packet decoder. * * \copyright Copyright (c) 2016, ARM Limited. All Rights Reserved. */ /* * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. 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. * * 3. Neither the name of the copyright holder 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. */ #include #include "opencsd/ptm/trc_pkt_decode_ptm.h" #define DCD_NAME "DCD_PTM" TrcPktDecodePtm::TrcPktDecodePtm() : TrcPktDecodeBase(DCD_NAME) { initDecoder(); } TrcPktDecodePtm::TrcPktDecodePtm(int instIDNum) : TrcPktDecodeBase(DCD_NAME,instIDNum) { initDecoder(); } TrcPktDecodePtm::~TrcPktDecodePtm() { } /*********************** implementation packet decoding interface */ ocsd_datapath_resp_t TrcPktDecodePtm::processPacket() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; bool bPktDone = false; while(!bPktDone) { switch(m_curr_state) { case NO_SYNC: // no sync - output a no sync packet then transition to wait sync. m_output_elem.elem_type = OCSD_GEN_TRC_ELEM_NO_SYNC; m_output_elem.unsync_eot_info = m_unsync_info; resp = outputTraceElement(m_output_elem); m_curr_state = (m_curr_packet_in->getType() == PTM_PKT_A_SYNC) ? WAIT_ISYNC : WAIT_SYNC; bPktDone = true; break; case WAIT_SYNC: if(m_curr_packet_in->getType() == PTM_PKT_A_SYNC) m_curr_state = WAIT_ISYNC; bPktDone = true; break; case WAIT_ISYNC: if(m_curr_packet_in->getType() == PTM_PKT_I_SYNC) m_curr_state = DECODE_PKTS; else bPktDone = true; break; case DECODE_PKTS: resp = decodePacket(); bPktDone = true; break; default: // should only see these after a _WAIT resp - in flush handler case CONT_ISYNC: case CONT_ATOM: bPktDone = true; // throw a decoder error break; } } return resp; } ocsd_datapath_resp_t TrcPktDecodePtm::onEOT() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; // shouldn't be any packets left to be processed - flush shoudl have done this. // just output the end of trace marker m_output_elem.setType(OCSD_GEN_TRC_ELEM_EO_TRACE); m_output_elem.setUnSyncEOTReason(UNSYNC_EOT); resp = outputTraceElement(m_output_elem); return resp; } ocsd_datapath_resp_t TrcPktDecodePtm::onReset() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; m_unsync_info = UNSYNC_RESET_DECODER; resetDecoder(); return resp; } ocsd_datapath_resp_t TrcPktDecodePtm::onFlush() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; resp = contProcess(); return resp; } // atom and isync packets can have multiple ouput packets that can be _WAITed mid stream. ocsd_datapath_resp_t TrcPktDecodePtm::contProcess() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; switch(m_curr_state) { case CONT_ISYNC: resp = processIsync(); break; case CONT_ATOM: resp = processAtom(); break; case CONT_WPUP: resp = processWPUpdate(); break; case CONT_BRANCH: resp = processBranch(); break; default: break; // not a state that requires further processing } if(OCSD_DATA_RESP_IS_CONT(resp) && processStateIsCont()) m_curr_state = DECODE_PKTS; // continue packet processing - assuming we have not degraded into an unsynced state. return resp; } ocsd_err_t TrcPktDecodePtm::onProtocolConfig() { ocsd_err_t err = OCSD_OK; if(m_config == 0) return OCSD_ERR_NOT_INIT; // static config - copy of CSID for easy reference m_CSID = m_config->getTraceID(); // handle return stack implementation if (m_config->hasRetStack()) { m_return_stack.set_active(m_config->enaRetStack()); #ifdef TRC_RET_STACK_DEBUG m_return_stack.set_dbg_logger(this); #endif } // config options affecting decode m_instr_info.pe_type.profile = m_config->coreProfile(); m_instr_info.pe_type.arch = m_config->archVersion(); m_instr_info.dsb_dmb_waypoints = m_config->dmsbWayPt() ? 1 : 0; m_instr_info.wfi_wfe_branch = 0; return err; } /****************** local decoder routines */ void TrcPktDecodePtm::initDecoder() { m_CSID = 0; m_instr_info.pe_type.profile = profile_Unknown; m_instr_info.pe_type.arch = ARCH_UNKNOWN; m_instr_info.dsb_dmb_waypoints = 0; m_unsync_info = UNSYNC_INIT_DECODER; resetDecoder(); } void TrcPktDecodePtm::resetDecoder() { m_curr_state = NO_SYNC; m_need_isync = true; // need context to start. m_instr_info.isa = ocsd_isa_unknown; m_mem_nacc_pending = false; m_pe_context.ctxt_id_valid = 0; m_pe_context.bits64 = 0; m_pe_context.vmid_valid = 0; m_pe_context.exception_level = ocsd_EL_unknown; m_pe_context.security_level = ocsd_sec_secure; m_pe_context.el_valid = 0; m_curr_pe_state.instr_addr = 0x0; m_curr_pe_state.isa = ocsd_isa_unknown; m_curr_pe_state.valid = false; m_atoms.clearAll(); m_output_elem.init(); } ocsd_datapath_resp_t TrcPktDecodePtm::decodePacket() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; switch(m_curr_packet_in->getType()) { // ignore these from trace o/p point of veiw case PTM_PKT_NOTSYNC: case PTM_PKT_INCOMPLETE_EOT: case PTM_PKT_NOERROR: break; // bad / reserved packet - need to wait for next sync point case PTM_PKT_BAD_SEQUENCE: case PTM_PKT_RESERVED: m_curr_state = WAIT_SYNC; m_need_isync = true; // need context to re-start. m_output_elem.setType(OCSD_GEN_TRC_ELEM_NO_SYNC); resp = outputTraceElement(m_output_elem); break; // packets we can ignore if in sync case PTM_PKT_A_SYNC: case PTM_PKT_IGNORE: break; // case PTM_PKT_I_SYNC: resp = processIsync(); break; case PTM_PKT_BRANCH_ADDRESS: resp = processBranch(); break; case PTM_PKT_TRIGGER: m_output_elem.setType(OCSD_GEN_TRC_ELEM_EVENT); m_output_elem.setEvent(EVENT_TRIGGER, 0); resp = outputTraceElement(m_output_elem); break; case PTM_PKT_WPOINT_UPDATE: resp = processWPUpdate(); break; case PTM_PKT_CONTEXT_ID: { bool bUpdate = true; // see if this is a change if((m_pe_context.ctxt_id_valid) && (m_pe_context.context_id == m_curr_packet_in->context.ctxtID)) bUpdate = false; if(bUpdate) { m_pe_context.context_id = m_curr_packet_in->context.ctxtID; m_pe_context.ctxt_id_valid = 1; m_output_elem.setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); m_output_elem.setContext(m_pe_context); resp = outputTraceElement(m_output_elem); } } break; case PTM_PKT_VMID: { bool bUpdate = true; // see if this is a change if((m_pe_context.vmid_valid) && (m_pe_context.vmid == m_curr_packet_in->context.VMID)) bUpdate = false; if(bUpdate) { m_pe_context.vmid = m_curr_packet_in->context.VMID; m_pe_context.vmid_valid = 1; m_output_elem.setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); m_output_elem.setContext(m_pe_context); resp = outputTraceElement(m_output_elem); } } break; case PTM_PKT_ATOM: if(m_curr_pe_state.valid) { m_atoms.initAtomPkt(m_curr_packet_in->getAtom(),m_index_curr_pkt); resp = processAtom(); } break; case PTM_PKT_TIMESTAMP: m_output_elem.setType(OCSD_GEN_TRC_ELEM_TIMESTAMP); m_output_elem.timestamp = m_curr_packet_in->timestamp; if(m_curr_packet_in->cc_valid) m_output_elem.setCycleCount(m_curr_packet_in->cycle_count); resp = outputTraceElement(m_output_elem); break; case PTM_PKT_EXCEPTION_RET: m_output_elem.setType(OCSD_GEN_TRC_ELEM_EXCEPTION_RET); resp = outputTraceElement(m_output_elem); break; } return resp; } ocsd_datapath_resp_t TrcPktDecodePtm::processIsync() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; // extract the I-Sync data if not re-entering after a _WAIT if(m_curr_state == DECODE_PKTS) { m_curr_pe_state.instr_addr = m_curr_packet_in->getAddrVal(); m_curr_pe_state.isa = m_curr_packet_in->getISA(); m_curr_pe_state.valid = true; m_i_sync_pe_ctxt = m_curr_packet_in->ISAChanged(); if(m_curr_packet_in->CtxtIDUpdated()) { m_pe_context.context_id = m_curr_packet_in->getCtxtID(); m_pe_context.ctxt_id_valid = 1; m_i_sync_pe_ctxt = true; } if(m_curr_packet_in->VMIDUpdated()) { m_pe_context.vmid = m_curr_packet_in->getVMID(); m_pe_context.vmid_valid = 1; m_i_sync_pe_ctxt = true; } m_pe_context.security_level = m_curr_packet_in->getNS() ? ocsd_sec_nonsecure : ocsd_sec_secure; if(m_need_isync || (m_curr_packet_in->iSyncReason() != iSync_Periodic)) { m_output_elem.setType(OCSD_GEN_TRC_ELEM_TRACE_ON); m_output_elem.trace_on_reason = TRACE_ON_NORMAL; if(m_curr_packet_in->iSyncReason() == iSync_TraceRestartAfterOverflow) m_output_elem.trace_on_reason = TRACE_ON_OVERFLOW; else if(m_curr_packet_in->iSyncReason() == iSync_DebugExit) m_output_elem.trace_on_reason = TRACE_ON_EX_DEBUG; if(m_curr_packet_in->hasCC()) m_output_elem.setCycleCount(m_curr_packet_in->getCCVal()); resp = outputTraceElement(m_output_elem); } else { // periodic - no output m_i_sync_pe_ctxt = false; } m_need_isync = false; // got 1st Isync - can continue to process data. m_return_stack.flush(); } if(m_i_sync_pe_ctxt && OCSD_DATA_RESP_IS_CONT(resp)) { m_output_elem.setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); m_output_elem.setContext(m_pe_context); m_output_elem.setISA(m_curr_pe_state.isa); resp = outputTraceElement(m_output_elem); m_i_sync_pe_ctxt = false; } // if wait and still stuff to process.... if(OCSD_DATA_RESP_IS_WAIT(resp) && ( m_i_sync_pe_ctxt)) m_curr_state = CONT_ISYNC; return resp; } // change of address and/or exception in program flow. // implies E atom before the branch if none exception. ocsd_datapath_resp_t TrcPktDecodePtm::processBranch() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; // initial pass - decoding packet. if(m_curr_state == DECODE_PKTS) { // specific behviour if this is an exception packet. if(m_curr_packet_in->isBranchExcepPacket()) { // exception - record address and output exception packet. m_output_elem.setType(OCSD_GEN_TRC_ELEM_EXCEPTION); m_output_elem.exception_number = m_curr_packet_in->excepNum(); m_output_elem.excep_ret_addr = 0; if(m_curr_pe_state.valid) { m_output_elem.excep_ret_addr = 1; m_output_elem.en_addr = m_curr_pe_state.instr_addr; } // could be an associated cycle count if(m_curr_packet_in->hasCC()) m_output_elem.setCycleCount(m_curr_packet_in->getCCVal()); // output the element resp = outputTraceElement(m_output_elem); } else { // branch address only - implies E atom - need to output a range element based on the atom. if(m_curr_pe_state.valid) resp = processAtomRange(ATOM_E,"BranchAddr"); } // now set the branch address for the next time. m_curr_pe_state.isa = m_curr_packet_in->getISA(); m_curr_pe_state.instr_addr = m_curr_packet_in->getAddrVal(); m_curr_pe_state.valid = true; } // atom range may return with NACC pending checkPendingNacc(resp); // if wait and still stuff to process.... if(OCSD_DATA_RESP_IS_WAIT(resp) && ( m_mem_nacc_pending)) m_curr_state = CONT_BRANCH; return resp; } // effectively completes a range prior to exception or after many bytes of trace (>4096) // ocsd_datapath_resp_t TrcPktDecodePtm::processWPUpdate() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; // if we need an address to run from then the WPUpdate will not form a range as // we do not have a start point - still waiting for branch or other address packet if(m_curr_pe_state.valid) { // WP update implies atom - use E, we cannot be sure if the instruction passed its condition codes // - though it doesn't really matter as it is not a branch so cannot change flow. resp = processAtomRange(ATOM_E,"WP update",TRACE_TO_ADDR_INCL,m_curr_packet_in->getAddrVal()); } // atom range may return with NACC pending checkPendingNacc(resp); // if wait and still stuff to process.... if(OCSD_DATA_RESP_IS_WAIT(resp) && ( m_mem_nacc_pending)) m_curr_state = CONT_WPUP; return resp; } // a single atom packet can result in multiple range outputs...need to be re-entrant in case we get a wait response. // also need to handle nacc response from instruction walking routine // ocsd_datapath_resp_t TrcPktDecodePtm::processAtom() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; // loop to process all the atoms in the packet while(m_atoms.numAtoms() && m_curr_pe_state.valid && OCSD_DATA_RESP_IS_CONT(resp)) { resp = processAtomRange(m_atoms.getCurrAtomVal(),"atom"); if(!m_curr_pe_state.valid) m_atoms.clearAll(); else m_atoms.clearAtom(); } // bad address may mean a nacc needs sending checkPendingNacc(resp); // if wait and still stuff to process.... if(OCSD_DATA_RESP_IS_WAIT(resp) && ( m_mem_nacc_pending || m_atoms.numAtoms())) m_curr_state = CONT_ATOM; return resp; } void TrcPktDecodePtm::checkPendingNacc(ocsd_datapath_resp_t &resp) { if(m_mem_nacc_pending && OCSD_DATA_RESP_IS_CONT(resp)) { m_output_elem.setType(OCSD_GEN_TRC_ELEM_ADDR_NACC); m_output_elem.st_addr = m_nacc_addr; resp = outputTraceElementIdx(m_index_curr_pkt,m_output_elem); m_mem_nacc_pending = false; } } // given an atom element - walk the code and output a range or mark nacc. ocsd_datapath_resp_t TrcPktDecodePtm::processAtomRange(const ocsd_atm_val A, const char *pkt_msg, const waypoint_trace_t traceWPOp /*= TRACE_WAYPOINT*/, const ocsd_vaddr_t nextAddrMatch /*= 0*/) { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; bool bWPFound = false; std::ostringstream oss; ocsd_err_t err = OCSD_OK; m_instr_info.instr_addr = m_curr_pe_state.instr_addr; m_instr_info.isa = m_curr_pe_state.isa; // set type (which resets out-elem) before traceInstrToWP modifies out-elem values m_output_elem.setType(OCSD_GEN_TRC_ELEM_INSTR_RANGE); err = traceInstrToWP(bWPFound,traceWPOp,nextAddrMatch); if(err != OCSD_OK) { if(err == OCSD_ERR_UNSUPPORTED_ISA) { m_curr_pe_state.valid = false; // need a new address packet oss << "Warning: unsupported instruction set processing " << pkt_msg << " packet."; LogError(ocsdError(OCSD_ERR_SEV_WARN,err,m_index_curr_pkt,m_CSID,oss.str())); // wait for next address return OCSD_RESP_WARN_CONT; } else { resp = OCSD_RESP_FATAL_INVALID_DATA; oss << "Error processing " << pkt_msg << " packet."; LogError(ocsdError(OCSD_ERR_SEV_ERROR,err,m_index_curr_pkt,m_CSID,oss.str())); return resp; } } if(bWPFound) { // save recorded next instuction address ocsd_vaddr_t nextAddr = m_instr_info.instr_addr; // action according to waypoint type and atom value switch(m_instr_info.type) { case OCSD_INSTR_BR: if (A == ATOM_E) { m_instr_info.instr_addr = m_instr_info.branch_addr; if (m_instr_info.is_link) m_return_stack.push(nextAddr,m_instr_info.isa); } break; // For PTM -> branch addresses imply E atom, N atom does not need address (return stack will require this) case OCSD_INSTR_BR_INDIRECT: if (A == ATOM_E) { // atom on indirect branch - either implied E from a branch address packet, or return stack if active. // indirect branch taken - need new address -if the current packet is a branch address packet this will be sorted. m_curr_pe_state.valid = false; // if return stack and the incoming packet is an atom. if (m_return_stack.is_active() && (m_curr_packet_in->getType() == PTM_PKT_ATOM)) { // we have an E atom packet and return stack value - set address from return stack m_instr_info.instr_addr = m_return_stack.pop(m_instr_info.next_isa); if (m_return_stack.overflow()) { resp = OCSD_RESP_FATAL_INVALID_DATA; oss << "Return stack error processing " << pkt_msg << " packet."; LogError(ocsdError(OCSD_ERR_SEV_ERROR, OCSD_ERR_RET_STACK_OVERFLOW, m_index_curr_pkt, m_CSID, oss.str())); return resp; } else m_curr_pe_state.valid = true; } if(m_instr_info.is_link) m_return_stack.push(nextAddr, m_instr_info.isa); } break; } m_output_elem.setLastInstrInfo((A == ATOM_E),m_instr_info.type, m_instr_info.sub_type,m_instr_info.instr_size); m_output_elem.setISA(m_curr_pe_state.isa); if(m_curr_packet_in->hasCC()) m_output_elem.setCycleCount(m_curr_packet_in->getCCVal()); m_output_elem.setLastInstrCond(m_instr_info.is_conditional); resp = outputTraceElementIdx(m_index_curr_pkt,m_output_elem); m_curr_pe_state.instr_addr = m_instr_info.instr_addr; m_curr_pe_state.isa = m_instr_info.next_isa; } else { // no waypoint - likely inaccessible memory range. m_curr_pe_state.valid = false; // need an address update if(m_output_elem.st_addr != m_output_elem.en_addr) { // some trace before we were out of memory access range m_output_elem.setLastInstrInfo(true,m_instr_info.type, m_instr_info.sub_type,m_instr_info.instr_size); m_output_elem.setISA(m_curr_pe_state.isa); m_output_elem.setLastInstrCond(m_instr_info.is_conditional); resp = outputTraceElementIdx(m_index_curr_pkt,m_output_elem); } } return resp; } ocsd_err_t TrcPktDecodePtm::traceInstrToWP(bool &bWPFound, const waypoint_trace_t traceWPOp /*= TRACE_WAYPOINT*/, const ocsd_vaddr_t nextAddrMatch /*= 0*/) { uint32_t opcode; uint32_t bytesReq; ocsd_err_t err = OCSD_OK; ocsd_vaddr_t curr_op_address; ocsd_mem_space_acc_t mem_space = (m_pe_context.security_level == ocsd_sec_secure) ? OCSD_MEM_SPACE_S : OCSD_MEM_SPACE_N; m_output_elem.st_addr = m_output_elem.en_addr = m_instr_info.instr_addr; m_output_elem.num_instr_range = 0; bWPFound = false; while(!bWPFound && !m_mem_nacc_pending) { // start off by reading next opcode; bytesReq = 4; curr_op_address = m_instr_info.instr_addr; // save the start address for the current opcode err = accessMemory(m_instr_info.instr_addr,mem_space,&bytesReq,(uint8_t *)&opcode); if(err != OCSD_OK) break; if(bytesReq == 4) // got data back { m_instr_info.opcode = opcode; err = instrDecode(&m_instr_info); if(err != OCSD_OK) break; // increment address - may be adjusted by direct branch value later m_instr_info.instr_addr += m_instr_info.instr_size; // update the range decoded address in the output packet. m_output_elem.en_addr = m_instr_info.instr_addr; m_output_elem.num_instr_range++; m_output_elem.last_i_type = m_instr_info.type; // either walking to match the next instruction address or a real waypoint if(traceWPOp != TRACE_WAYPOINT) { if(traceWPOp == TRACE_TO_ADDR_EXCL) bWPFound = (m_output_elem.en_addr == nextAddrMatch); else bWPFound = (curr_op_address == nextAddrMatch); } else bWPFound = (m_instr_info.type != OCSD_INSTR_OTHER); } else { // not enough memory accessible. m_mem_nacc_pending = true; m_nacc_addr = m_instr_info.instr_addr; } } return err; } /* End of File trc_pkt_decode_ptm.cpp */