/*! * \file trc_pkt_decode_etmv3.cpp * \brief OpenCSD : ETMv3 trace packet decode. * * \copyright Copyright (c) 2015, 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 "opencsd/etmv3/trc_pkt_decode_etmv3.h" #define DCD_NAME "DCD_ETMV3" TrcPktDecodeEtmV3::TrcPktDecodeEtmV3() : TrcPktDecodeBase(DCD_NAME) { initDecoder(); } TrcPktDecodeEtmV3::TrcPktDecodeEtmV3(int instIDNum) : TrcPktDecodeBase(DCD_NAME, instIDNum) { initDecoder(); } TrcPktDecodeEtmV3::~TrcPktDecodeEtmV3() { } /* implementation packet decoding interface */ ocsd_datapath_resp_t TrcPktDecodeEtmV3::processPacket() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; bool bPktDone = false; if(!m_config) return OCSD_RESP_FATAL_NOT_INIT; // iterate round the state machine, waiting for sync, then decoding packets. while(!bPktDone) { switch(m_curr_state) { case NO_SYNC: // output the initial not synced packet to the sink resp = sendUnsyncPacket(); m_curr_state = WAIT_ASYNC; // immediate wait for ASync and actually check out the packet break; case WAIT_ASYNC: // if async, wait for ISync, but this packet done. if(m_curr_packet_in->getType() == ETM3_PKT_A_SYNC) m_curr_state = WAIT_ISYNC; bPktDone = true; break; case WAIT_ISYNC: m_bWaitISync = true; // we are waiting for ISync if((m_curr_packet_in->getType() == ETM3_PKT_I_SYNC) || (m_curr_packet_in->getType() == ETM3_PKT_I_SYNC_CYCLE)) { // process the ISync immediately as the first ISync seen. resp = processISync((m_curr_packet_in->getType() == ETM3_PKT_I_SYNC_CYCLE),true); m_curr_state = SEND_PKTS; m_bWaitISync = false; } // something like TS, CC, PHDR+CC, which after ASYNC may be valid prior to ISync else if(preISyncValid(m_curr_packet_in->getType())) { // decode anything that might be valid - send will be set automatically resp = decodePacket(bPktDone); } else bPktDone = true; break; case DECODE_PKTS: resp = decodePacket(bPktDone); break; case SEND_PKTS: resp = m_outputElemList.sendElements(); if(OCSD_DATA_RESP_IS_CONT(resp)) m_curr_state = m_bWaitISync ? WAIT_ISYNC : DECODE_PKTS; bPktDone = true; break; default: bPktDone = true; LogError(ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_FAIL,m_index_curr_pkt,"Unknown Decoder State")); resetDecoder(); // mark decoder as unsynced - dump any current state. resp = OCSD_RESP_FATAL_SYS_ERR; break; } } return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::onEOT() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; OcsdTraceElement *pElem = 0; try { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_EO_TRACE); pElem->setUnSyncEOTReason(UNSYNC_EOT); m_outputElemList.commitAllPendElem(); m_curr_state = SEND_PKTS; resp = m_outputElemList.sendElements(); if(OCSD_DATA_RESP_IS_CONT(resp)) m_curr_state = DECODE_PKTS; } catch(ocsdError &err) { LogError(err); resetDecoder(); // mark decoder as unsynced - dump any current state. } return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::onReset() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; m_unsync_info = UNSYNC_RESET_DECODER; resetDecoder(); return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::onFlush() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; if(m_curr_state == SEND_PKTS) { resp = m_outputElemList.sendElements(); if(OCSD_DATA_RESP_IS_CONT(resp)) m_curr_state = m_bWaitISync ? WAIT_ISYNC : DECODE_PKTS; } return resp; } ocsd_err_t TrcPktDecodeEtmV3::onProtocolConfig() { ocsd_err_t err = OCSD_OK; if(m_config) { // set some static config elements m_CSID = m_config->getTraceID(); // check config compatible with current decoder support level. // at present no data trace; if(m_config->GetTraceMode() != EtmV3Config::TM_INSTR_ONLY) { err = OCSD_ERR_HW_CFG_UNSUPP; LogError(ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_HW_CFG_UNSUPP,"ETMv3 trace decoder : data trace decode not yet supported")); } // need to set up core profile info in follower ocsd_arch_profile_t arch_profile; arch_profile.arch = m_config->getArchVersion(); arch_profile.profile = m_config->getCoreProfile(); m_code_follower.setArchProfile(arch_profile); m_code_follower.setMemSpaceCSID(m_CSID); m_outputElemList.initCSID(m_CSID); } else err = OCSD_ERR_NOT_INIT; return err; } /* local decode methods */ // initialise on creation void TrcPktDecodeEtmV3::initDecoder() { m_CSID = 0; resetDecoder(); m_unsync_info = UNSYNC_INIT_DECODER; m_code_follower.initInterfaces(getMemoryAccessAttachPt(),getInstrDecodeAttachPt()); m_outputElemList.initSendIf(getTraceElemOutAttachPt()); } // reset for first use / re-use. void TrcPktDecodeEtmV3::resetDecoder() { m_curr_state = NO_SYNC; // mark as not synced m_bNeedAddr = true; m_bSentUnknown = false; m_bWaitISync = false; m_outputElemList.reset(); } OcsdTraceElement *TrcPktDecodeEtmV3::GetNextOpElem(ocsd_datapath_resp_t &resp) { OcsdTraceElement *pElem = m_outputElemList.getNextElem(m_index_curr_pkt); if(pElem == 0) { resp = OCSD_RESP_FATAL_NOT_INIT; throw ocsdError(OCSD_ERR_SEV_ERROR, OCSD_ERR_MEM,m_index_curr_pkt,m_CSID,"Memory Allocation Error - fatal"); } return pElem; } bool TrcPktDecodeEtmV3::preISyncValid(ocsd_etmv3_pkt_type pkt_type) { bool bValid = false; // its a timestamp if((pkt_type == ETM3_PKT_TIMESTAMP) || // or we are cycleacc and its a packet that can have CC in it (m_config->isCycleAcc() && ((pkt_type == ETM3_PKT_CYCLE_COUNT) || (pkt_type == ETM3_PKT_P_HDR))) ) bValid = true; return bValid; } // simple packet transforms handled here, more complex processing passed on to specific routines. ocsd_datapath_resp_t TrcPktDecodeEtmV3::decodePacket(bool &pktDone) { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; bool bISyncHasCC = false; OcsdTraceElement *pElem = 0; pktDone = false; // there may be pended packets that can now be committed. // only the branch address with exception and cancel element can cancel // if not one of those, commit immediately, otherwise defer to branch address handler. if(m_curr_packet_in->getType() != ETM3_PKT_BRANCH_ADDRESS) m_outputElemList.commitAllPendElem(); try { switch(m_curr_packet_in->getType()) { case ETM3_PKT_NOTSYNC: // mark as not synced - must have lost sync in the packet processor somehow throw ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_BAD_PACKET_SEQ,m_index_curr_pkt,m_CSID,"Trace Packet Synchronisation Lost"); break; // no action for these packets - ignore and continue case ETM3_PKT_INCOMPLETE_EOT: case ETM3_PKT_A_SYNC: case ETM3_PKT_IGNORE: break; // markers for valid packets case ETM3_PKT_CYCLE_COUNT: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_CYCLE_COUNT); pElem->setCycleCount(m_curr_packet_in->getCycleCount()); break; case ETM3_PKT_TRIGGER: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_EVENT); pElem->setEvent(EVENT_TRIGGER,0); break; case ETM3_PKT_BRANCH_ADDRESS: resp = processBranchAddr(); break; case ETM3_PKT_I_SYNC_CYCLE: bISyncHasCC = true; case ETM3_PKT_I_SYNC: resp = processISync(bISyncHasCC); break; case ETM3_PKT_P_HDR: resp = processPHdr(); break; case ETM3_PKT_CONTEXT_ID: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); m_PeContext.setCtxtID(m_curr_packet_in->getCtxtID()); pElem->setContext(m_PeContext); break; case ETM3_PKT_VMID: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); m_PeContext.setVMID(m_curr_packet_in->getVMID()); pElem->setContext(m_PeContext); break; case ETM3_PKT_EXCEPTION_ENTRY: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_EXCEPTION); pElem->setExcepMarker(); // exception entries are always v7M data markers in ETMv3 trace. break; case ETM3_PKT_EXCEPTION_EXIT: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_EXCEPTION_RET); pendExceptionReturn(); break; case ETM3_PKT_TIMESTAMP: pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_TIMESTAMP); pElem->setTS(m_curr_packet_in->getTS()); break; // data packets - data trace not supported at present case ETM3_PKT_STORE_FAIL: case ETM3_PKT_OOO_DATA: case ETM3_PKT_OOO_ADDR_PLC: case ETM3_PKT_NORM_DATA: case ETM3_PKT_DATA_SUPPRESSED: case ETM3_PKT_VAL_NOT_TRACED: case ETM3_PKT_BAD_TRACEMODE: resp = OCSD_RESP_FATAL_INVALID_DATA; throw ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_HW_CFG_UNSUPP,m_index_curr_pkt,m_CSID,"Invalid packet type : Data Tracing decode not supported."); break; // packet errors case ETM3_PKT_BAD_SEQUENCE: resp = OCSD_RESP_FATAL_INVALID_DATA; throw ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_BAD_PACKET_SEQ,m_index_curr_pkt,m_CSID,"Bad Packet sequence."); break; default: case ETM3_PKT_RESERVED: resp = OCSD_RESP_FATAL_INVALID_DATA; throw ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_BAD_PACKET_SEQ,m_index_curr_pkt,m_CSID,"Reserved or unknown packet ID."); break; } m_curr_state = m_outputElemList.elemToSend() ? SEND_PKTS : DECODE_PKTS; pktDone = !m_outputElemList.elemToSend(); } catch(ocsdError &err) { LogError(err); m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. pktDone = true; } catch(...) { LogError(ocsdError(OCSD_ERR_SEV_ERROR,OCSD_ERR_FAIL,m_index_curr_pkt,m_CSID,"Bad Packet sequence.")); resp = OCSD_RESP_FATAL_SYS_ERR; m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. pktDone = true; } return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::sendUnsyncPacket() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; OcsdTraceElement *pElem = 0; try { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_NO_SYNC); pElem->setUnSyncEOTReason(m_unsync_info); resp = m_outputElemList.sendElements(); } catch(ocsdError &err) { LogError(err); m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. } return resp; } void TrcPktDecodeEtmV3::setNeedAddr(bool bNeedAddr) { m_bNeedAddr = bNeedAddr; m_bSentUnknown = false; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::processISync(const bool withCC, const bool firstSync /* = false */) { // map ISync reason to generic reason codes. static trace_on_reason_t on_map[] = { TRACE_ON_NORMAL, TRACE_ON_NORMAL, TRACE_ON_OVERFLOW, TRACE_ON_EX_DEBUG }; ocsd_datapath_resp_t resp = OCSD_RESP_CONT; bool ctxtUpdate = m_curr_packet_in->isCtxtUpdated(); OcsdTraceElement *pElem = 0; try { pElem = GetNextOpElem(resp); if(firstSync || (m_curr_packet_in->getISyncReason() != iSync_Periodic)) { pElem->setType(OCSD_GEN_TRC_ELEM_TRACE_ON); pElem->setTraceOnReason(on_map[(int)m_curr_packet_in->getISyncReason()]); pElem = GetNextOpElem(resp); } // look for context changes.... if(ctxtUpdate || firstSync) { // if not first time out, read existing context in output element, // otherwise we are setting it new. if(firstSync) m_PeContext.resetCtxt(); if(m_curr_packet_in->isCtxtIDUpdated()) m_PeContext.setCtxtID(m_curr_packet_in->getCtxtID()); if(m_curr_packet_in->isVMIDUpdated()) m_PeContext.setVMID(m_curr_packet_in->getVMID()); if(m_curr_packet_in->isCtxtFlagsUpdated()) { m_PeContext.setEL(m_curr_packet_in->isHyp() ? ocsd_EL2 : ocsd_EL_unknown); m_PeContext.setSecLevel(m_curr_packet_in->isNS() ? ocsd_sec_nonsecure : ocsd_sec_secure); } // prepare the context packet pElem->setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); pElem->setContext(m_PeContext); pElem->setISA(m_curr_packet_in->ISA()); // with cycle count... if(m_curr_packet_in->getISyncHasCC()) pElem->setCycleCount(m_curr_packet_in->getCycleCount()); } // set ISync address - if it is a valid I address if(!m_curr_packet_in->getISyncNoAddr()) { if(m_curr_packet_in->getISyncIsLSiPAddr()) { // TBD: handle extra data processing instruction for data trace // need to output E atom relating to the data instruction // rare - on start-up case. // main instruction address saved in data address for this packet type. m_IAddr = m_curr_packet_in->getDataAddr(); } else { m_IAddr = m_curr_packet_in->getAddr(); } setNeedAddr(false); // ready to process atoms. } m_curr_state = m_outputElemList.elemToSend() ? SEND_PKTS : DECODE_PKTS; } catch(ocsdError &err) { LogError(err); m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. } return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::processBranchAddr() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; OcsdTraceElement *pElem = 0; bool bUpdatePEContext = false; // might need to cancel something ... if the last output was an instruction range or excep return if(m_curr_packet_in->isExcepCancel()) m_outputElemList.cancelPendElem(); else m_outputElemList.commitAllPendElem(); // otherwise commit any pending elements. // record the address m_IAddr = m_curr_packet_in->getAddr(); setNeedAddr(false); // no longer need an address. // exception packet - may need additional output if(m_curr_packet_in->isExcepPkt()) { // exeception packet may have exception, context change, or both. // check for context change if(m_curr_packet_in->isCtxtUpdated()) { ocsd_sec_level sec = m_curr_packet_in->isNS() ? ocsd_sec_nonsecure : ocsd_sec_secure; if(sec != m_PeContext.getSecLevel()) { m_PeContext.setSecLevel(sec); bUpdatePEContext = true; } ocsd_ex_level pkt_el = m_curr_packet_in->isHyp() ? ocsd_EL2 : ocsd_EL_unknown; if(pkt_el != m_PeContext.getEL()) { m_PeContext.setEL(pkt_el); bUpdatePEContext = true; } } // now decide if we need to send any packets out. try { if(bUpdatePEContext) { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_PE_CONTEXT); pElem->setContext(m_PeContext); } // check for exception if(m_curr_packet_in->excepNum() != 0) { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_EXCEPTION); pElem->setExceptionNum(m_curr_packet_in->excepNum()); } // finally - do we have anything to send yet? m_curr_state = m_outputElemList.elemToSend() ? SEND_PKTS : DECODE_PKTS; } catch(ocsdError &err) { LogError(err); m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. } } return resp; } ocsd_datapath_resp_t TrcPktDecodeEtmV3::processPHdr() { ocsd_datapath_resp_t resp = OCSD_RESP_CONT; OcsdTraceElement *pElem = 0; ocsd_isa isa; Etmv3Atoms atoms(m_config->isCycleAcc()); atoms.initAtomPkt(m_curr_packet_in,m_index_curr_pkt); isa = m_curr_packet_in->ISA(); m_code_follower.setMemSpaceAccess((m_PeContext.getSecLevel() == ocsd_sec_secure) ? OCSD_MEM_SPACE_S : OCSD_MEM_SPACE_N); try { do { // if we do not have a valid address then send any cycle count elements // and stop processing if(m_bNeedAddr) { // output unknown address packet or a cycle count packet if(!m_bSentUnknown || m_config->isCycleAcc()) { pElem = GetNextOpElem(resp); if(m_bSentUnknown || !atoms.numAtoms()) pElem->setType(OCSD_GEN_TRC_ELEM_CYCLE_COUNT); else pElem->setType(OCSD_GEN_TRC_ELEM_ADDR_UNKNOWN); if(m_config->isCycleAcc()) pElem->setCycleCount(atoms.getRemainCC()); m_bSentUnknown = true; } atoms.clearAll(); // skip remaining atoms } else // have an address, can process atoms { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_INSTR_RANGE); // cycle accurate may have a cycle count to use if(m_config->isCycleAcc()) { // note: it is possible to have a CC only atom packet. if(!atoms.numAtoms()) // override type if CC only pElem->setType(OCSD_GEN_TRC_ELEM_CYCLE_COUNT); // set cycle count pElem->setCycleCount(atoms.getAtomCC()); } // now process the atom if(atoms.numAtoms()) { m_code_follower.setISA(isa); m_code_follower.followSingleAtom(m_IAddr,atoms.getCurrAtomVal()); // valid code range if(m_code_follower.hasRange()) { pElem->setAddrRange(m_IAddr,m_code_follower.getRangeEn()); pElem->setLastInstrInfo(atoms.getCurrAtomVal() == ATOM_E, m_code_follower.getInstrType(), m_code_follower.getInstrSubType(),m_code_follower.getInstrSize()); pElem->setLastInstrCond(m_code_follower.isCondInstr()); pElem->setISA(isa); if(m_code_follower.hasNextAddr()) m_IAddr = m_code_follower.getNextAddr(); else setNeedAddr(true); } // next address has new ISA? if(m_code_follower.ISAChanged()) isa = m_code_follower.nextISA(); // there is a nacc if(m_code_follower.isNacc()) { if(m_code_follower.hasRange()) { pElem = GetNextOpElem(resp); pElem->setType(OCSD_GEN_TRC_ELEM_ADDR_NACC); } else pElem->updateType(OCSD_GEN_TRC_ELEM_ADDR_NACC); pElem->setAddrStart(m_code_follower.getNaccAddr()); setNeedAddr(true); m_code_follower.clearNacc(); // we have generated some code for the nacc. } } atoms.clearAtom(); // next atom } } while(atoms.numAtoms()); // is tha last element an atom? int numElem = m_outputElemList.getNumElem(); if(numElem >= 1) { // if the last thing is an instruction range, pend it - could be cancelled later. if(m_outputElemList.getElemType(numElem-1) == OCSD_GEN_TRC_ELEM_INSTR_RANGE) m_outputElemList.pendLastNElem(1); } // finally - do we have anything to send yet? m_curr_state = m_outputElemList.elemToSend() ? SEND_PKTS : DECODE_PKTS; } catch(ocsdError &err) { LogError(err); m_unsync_info = UNSYNC_BAD_PACKET; resetDecoder(); // mark decoder as unsynced - dump any current state. } return resp; } // if v7M -> pend only ERET, if V7A/R pend ERET and prev instr. void TrcPktDecodeEtmV3::pendExceptionReturn() { int pendElem = 1; if(m_config->getCoreProfile() != profile_CortexM) { int nElem = m_outputElemList.getNumElem(); if(nElem > 1) { if(m_outputElemList.getElemType(nElem - 2) == OCSD_GEN_TRC_ELEM_INSTR_RANGE) pendElem = 2; // need to pend instr+eret for A/R } } m_outputElemList.pendLastNElem(pendElem); } /* End of File trc_pkt_decode_etmv3.cpp */