diff --git a/tools/ctrace/docs/architecture.md b/tools/ctrace/docs/architecture.md index 3609119c0..57a5b03cd 100644 --- a/tools/ctrace/docs/architecture.md +++ b/tools/ctrace/docs/architecture.md @@ -13,10 +13,11 @@ backend-independent semantic events. Output backends consume these events to cre The first release profile supports SWO data containing ITM and DWT packets. The command line accepts the stable type names `itm`, `dwt`, `event`, `pmu`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. Output semantics are -currently implemented for `itm`, `dwt`, `exception`, `global_ts`, `overflow`, and `error`. DWT event-counter and PMU -packets are retained internally but are not mapped to their selectors yet; periodic PC samples remain disabled. Trace -Bus input is discovered so that a complete trace directory can be inspected, but `*.TB.raw` files are reported and -skipped until a decoder is implemented. +currently implemented for `itm`, `dwt`, `exception`, `pcsample`, `global_ts`, `overflow`, and `error`. DWT event-counter +and PMU packets are retained internally but are not mapped to their selectors yet. Periodic PC samples reach CSV and +CTF as semantic events; the CTF event distinguishes a sampled PC from a processor-sleep indication, and Trace Compass +shows processor-sleep intervals as a timeline. Trace Bus input is discovered so that a complete trace directory can be +inspected, but `*.TB.raw` files are reported and skipped until a decoder is implemented. The architecture separates protocol decoding, semantic interpretation, and output generation. This keeps output formats independent of OpenCSD and allows another raw trace channel to reuse the event model and output backends. diff --git a/tools/ctrace/docs/todo.md b/tools/ctrace/docs/todo.md index d736ac26c..f4a82d41c 100644 --- a/tools/ctrace/docs/todo.md +++ b/tools/ctrace/docs/todo.md @@ -12,7 +12,6 @@ - [ ] Add Armv8-M and Armv8.1-M DWT decoding. - [ ] Add `event` output in its own PR. - [ ] Add `pmu` output in its own PR. -- [ ] Add PC Sampling in its own PR after agreeing the sleep/`PC_SAMPLE` CTF contract. ## Multiple streams diff --git a/tools/ctrace/src/decode/DwtPacketDecoder.cpp b/tools/ctrace/src/decode/DwtPacketDecoder.cpp index ce3d12ffe..c59b85811 100644 --- a/tools/ctrace/src/decode/DwtPacketDecoder.cpp +++ b/tools/ctrace/src/decode/DwtPacketDecoder.cpp @@ -102,10 +102,32 @@ std::vector DwtPacketDecoder::decode(const DwtPayloadPacket& payload } if (source == DwtPacketSource::PeriodicPcSample) { - // PC samples need a dedicated output event. Until that event is - // defined, flush preceding data trace but do not expose the sample as - // an address event. output = flush(payload.quality, payload.tcyc); + const auto isPc = payload.size == 4U; + const auto isSleeping = payload.size == 1U && payload.value == 0U; + if (!isPc && !isSleeping) { + TraceEvent error{TraceIssueEvent{ + TraceIssueCode::UnsupportedDwtPcSamplePayload, + TraceIssueSeverity::Error, + "unsupported DWT PC-sample payload: size " + std::to_string(payload.size) + + ", value " + std::to_string(payload.value) + + "; expected a 4-byte PC or a 1-byte zero sleep indication", + std::nullopt, + std::nullopt, + }}; + error.index = payload.index; + error.traceBusId = payload.traceBusId; + error.tcyc = payload.tcyc; + error.quality = payload.quality; + output.push_back(std::move(error)); + return output; + } + TraceEvent packet{PcSampleTraceEvent{payload.value, isSleeping}}; + packet.index = payload.index; + packet.traceBusId = payload.traceBusId; + packet.tcyc = payload.tcyc; + packet.quality = payload.quality; + output.push_back(std::move(packet)); return output; } diff --git a/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp b/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp index 851f23a8e..4c4424c82 100644 --- a/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp +++ b/tools/ctrace/src/diagnostics/TraceIssueReporter.cpp @@ -45,6 +45,7 @@ static std::string displayErrorMessage(const TraceEvent& event, const TraceIssue case TraceIssueCode::DecodeError: case TraceIssueCode::InvalidExceptionAction: case TraceIssueCode::UnsupportedDwtAddressPayload: + case TraceIssueCode::UnsupportedDwtPcSamplePayload: case TraceIssueCode::OpenCsdDecodeError: return atRawOffset("trace decode error", event); } diff --git a/tools/ctrace/src/model/TraceEvent.h b/tools/ctrace/src/model/TraceEvent.h index de3867174..ad3a75f7a 100644 --- a/tools/ctrace/src/model/TraceEvent.h +++ b/tools/ctrace/src/model/TraceEvent.h @@ -40,6 +40,7 @@ enum class TraceIssueCode { DataLoss, InvalidExceptionAction, UnsupportedDwtAddressPayload, + UnsupportedDwtPcSamplePayload, OpenCsdDecodeError, OpenCsdBadPacketSequence, OpenCsdInvalidPacketHeader, @@ -138,6 +139,12 @@ struct PmuTraceEvent { std::uint32_t value = 0; }; +/** @brief Contains a periodic DWT PC sample or its processor-sleep indication. */ +struct PcSampleTraceEvent { + std::uint32_t pc = 0; + bool sleeping = false; +}; + /** @brief Marks a decoded local timestamp packet. */ struct LocalTimestampTraceEvent {}; @@ -166,7 +173,7 @@ struct TraceIssueEvent { /** @brief Stores the semantic payload of a decoded trace event. */ using TraceEventPayload = std::variant; /** @brief Describes timestamp and data-loss quality at an event. */ diff --git a/tools/ctrace/src/model/TraceSelection.cpp b/tools/ctrace/src/model/TraceSelection.cpp index bcacde28f..c766a5afc 100644 --- a/tools/ctrace/src/model/TraceSelection.cpp +++ b/tools/ctrace/src/model/TraceSelection.cpp @@ -54,6 +54,12 @@ static std::optional typeFor(const PmuTraceEvent&) return std::nullopt; } +/** @brief Exposes periodic DWT PC samples through their public output selector. */ +static std::optional typeFor(const PcSampleTraceEvent&) +{ + return TraceEventType::PcSample; +} + /** @brief Excludes local timestamp control packets from type selection. */ static std::optional typeFor(const LocalTimestampTraceEvent&) { diff --git a/tools/ctrace/src/output/csv/CsvRowMapper.cpp b/tools/ctrace/src/output/csv/CsvRowMapper.cpp index 65381c9c7..e4f9b6fd2 100644 --- a/tools/ctrace/src/output/csv/CsvRowMapper.cpp +++ b/tools/ctrace/src/output/csv/CsvRowMapper.cpp @@ -159,6 +159,10 @@ static CsvRow eventToCsvRow(const TraceEvent& event) } else if (const auto* exception = traceEventPayload(event)) { row[column(CsvColumn::Source)] = std::to_string(exception->number); row[column(CsvColumn::Value)] = exceptionActionCsvValue(exception->action); + } else if (const auto* sample = traceEventPayload(event)) { + if (!sample->sleeping) { + row[column(CsvColumn::Pc)] = hexValue(sample->pc, 4); + } } else if (const auto* timestamp = traceEventPayload(event)) { row[column(CsvColumn::Cycles)] = std::to_string(timestamp->value); } else if (const auto* overflow = traceEventPayload(event)) { diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.cpp b/tools/ctrace/src/output/ctf/CtfEncoder.cpp index 9809e14a3..817d4eedc 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.cpp +++ b/tools/ctrace/src/output/ctf/CtfEncoder.cpp @@ -200,6 +200,10 @@ void CtfEncoder::writeEvent(const TraceEvent& event) if (selected) { writeDwtAddrEvent(event, *address); } + } else if (const auto* sample = traceEventPayload(event)) { + if (selected) { + writePcSampleEvent(event, *sample); + } } else if (isTraceEvent(event)) { auto& streamState = m_streamStates[event.traceBusId]; if (event.quality.has_value()) { @@ -231,6 +235,25 @@ void CtfEncoder::writeEvent(const TraceEvent& event) } } +void CtfEncoder::writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample) +{ + const auto pcSize = sample.sleeping ? 0U : 4U; + const auto payloadSize = 1U + pcSize + 1U + 4U; + const auto eventTimestamp = allocateEventTimestamp(event.traceBusId); + const auto quality = computeSampleQuality(event); + const auto state = CtfSchema::value(sample.sleeping ? CtfSchema::PcSampleState::Sleep + : CtfSchema::PcSampleState::Pc); + m_stream.writeRecord(CtfSchema::value(CtfSchema::EventId::PcSample), eventTimestamp, event.traceBusId, payloadSize, + [&](CtfStreamWriter::Record& record) { + record.writeU8(state); + if (!sample.sleeping) { + record.writeU32(sample.pc); + } + record.writeU8(quality.first); + record.writeU32(quality.second); + }); +} + std::uint64_t CtfEncoder::allocateEventTimestamp(std::uint8_t traceBusId) { // CtfStreamWriter applies the final monotonic clamp across the multiplexed diff --git a/tools/ctrace/src/output/ctf/CtfEncoder.h b/tools/ctrace/src/output/ctf/CtfEncoder.h index bb5c65954..6034dcf20 100644 --- a/tools/ctrace/src/output/ctf/CtfEncoder.h +++ b/tools/ctrace/src/output/ctf/CtfEncoder.h @@ -75,6 +75,8 @@ class CtfEncoder final { void reportDwtSizeMismatch(const TraceEvent& event, const DwtDataTraceEvent& data, const ResolvedTraceSource* source); /** @brief Encodes one DWT address event. */ void writeDwtAddrEvent(const TraceEvent& event, const DwtAddressTraceEvent& address); + /** @brief Encodes one periodic PC-sample or processor-sleep event. */ + void writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample); /** @brief Encodes one reconstructed global timestamp event. */ void writeGlobalTimestampEvent(const TraceEvent& event, const GlobalTimestampTraceEvent& timestamp); /** @brief Applies one exception transition to its CTF lane state. */ diff --git a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp index b3bd845b3..0dfdca39e 100644 --- a/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp +++ b/tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp @@ -426,6 +426,27 @@ event { )"; } +/** @brief Writes the periodic PC-sample event declaration. */ +static void writePcSampleEvent(std::ostream& out) +{ + out << R"( +event { + id = )" + << CtfSchema::value(CtfSchema::EventId::PcSample) << R"(; + name = ")" + << CtfSchema::eventName(CtfSchema::EventId::PcSample) << R"("; + stream_id = )" + << CtfSchema::SwoStreamId << R"(; + fields := struct { + uint8_t cmsis_pc_sample_state; + uint32_t cmsis_pc[cmsis_pc_sample_state]; + uint8_t cmsis_sample_flags; + uint32_t cmsis_overflow_count; + }; +}; +)"; +} + /** @brief Writes status, exception, and global timestamp declarations. */ static void writeStatusEvents(std::ostream& out) { @@ -491,6 +512,7 @@ void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const std: writeDwtValueEvent(out); writeDwtAddressEvent(out); writeStatusEvents(out); + writePcSampleEvent(out); out.close(); if (!out) { throw std::runtime_error("Failed to write CTF metadata " + metadataPath.string()); diff --git a/tools/ctrace/src/output/ctf/CtfSchema.h b/tools/ctrace/src/output/ctf/CtfSchema.h index 26f943ba0..ad5a6f307 100644 --- a/tools/ctrace/src/output/ctf/CtfSchema.h +++ b/tools/ctrace/src/output/ctf/CtfSchema.h @@ -26,6 +26,7 @@ enum class EventId : std::uint32_t { TraceStatus = 3U, Exception = 4U, GlobalTimestamp = 5U, + PcSample = 6U, }; /** @brief Classifies CTF trace-status records. */ @@ -56,6 +57,12 @@ enum class ExceptionOrigin : std::uint8_t { Synthetic = 1U, }; +/** @brief Identifies whether a periodic PC sample carries a PC or reports processor sleep. */ +enum class PcSampleState : std::uint8_t { + Sleep = 0U, + Pc = 1U, +}; + /** @brief Identifies the supported CTF sample value encodings. */ enum class ValueTag : std::uint8_t { Signed8 = 0U, @@ -154,6 +161,12 @@ constexpr std::uint8_t value(ExceptionAction action) return static_cast(action); } +/** @brief Returns the integer representation of a PC-sample state. */ +constexpr std::uint8_t value(PcSampleState state) +{ + return static_cast(state); +} + /** @brief Returns the integer representation of an exception record origin. */ constexpr std::uint8_t value(ExceptionOrigin origin) { @@ -176,6 +189,8 @@ constexpr std::string_view eventName(EventId id) return "EXCEPTION"; case EventId::GlobalTimestamp: return "GLOBAL_TIMESTAMP"; + case EventId::PcSample: + return "PC_SAMPLE"; } return "UNKNOWN"; } diff --git a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp index bad1d145c..d9e839aaf 100644 --- a/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp +++ b/tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp @@ -79,6 +79,8 @@ static std::string valueHandlers(CtfSchema::EventId eventId, const char* prefix, /** @brief Generates the Trace Compass state-provider definition. */ static std::string stateProviderXml() { + // Numeric time-graph states are exposed as TSP style keys; string states are + // serialized without a style and appear as gaps in compatible clients. std::ostringstream xml; xml << R"(