LCOV - code coverage report
Current view: top level - mu2e-pcie-utils/dtcInterfaceLib - DTC.cpp (source / functions) Coverage Total Hit
Test: mu2edaq.info.cleaned Lines: 0.0 % 2350 0
Test Date: 2026-10-29 01:38:59 Functions: 0.0 % 359 0

            Line data    Source code
       1              : 
       2              : #include "TRACE/tracemf.h"
       3              : #define TRACE_NAME "DTC.cpp"
       4              : 
       5              : #include "DTC.h"
       6              : #include "EVBErrorStatus.h"
       7              : #define TLVL_GetData TLVL_DEBUG + 5
       8              : #define TLVL_GetJSONData TLVL_DEBUG + 6
       9              : #define TLVL_ReadBuffer TLVL_DEBUG + 7
      10              : #define TLVL_ReadNextDAQPacket TLVL_DEBUG + 8
      11              : #define TLVL_ReadNextDCSPacket TLVL_DEBUG + 9
      12              : #define TLVL_SendDCSRequestPacket TLVL_DEBUG + 10
      13              : #define TLVL_SendHeartbeatPacket TLVL_DEBUG + 11
      14              : #define TLVL_VerifySimFileInDTC TLVL_DEBUG + 12
      15              : #define TLVL_VerifySimFileInDTC2 TLVL_DEBUG + 13
      16              : #define TLVL_VerifySimFileInDTC3 TLVL_DEBUG + 14
      17              : #define TLVL_WriteSimFileToDTC TLVL_DEBUG + 15
      18              : #define TLVL_WriteSimFileToDTC2 TLVL_DEBUG + 16
      19              : #define TLVL_WriteSimFileToDTC3 TLVL_DEBUG + 17
      20              : #define TLVL_WriteDetectorEmulatorData TLVL_DEBUG + 18
      21              : #define TLVL_WriteDataPacket TLVL_DEBUG + 19
      22              : #define TLVL_ReleaseBuffers TLVL_DEBUG + 20
      23              : #define TLVL_GetCurrentBuffer TLVL_DEBUG + 21
      24              : #define TLVL_GetData_3 TLVL_DEBUG + 22
      25              : #define TLVL_GetData_2 TLVL_DEBUG + 23
      26              : #define TLVL_NextSubEventCout 24  // TTEST adds TLVL_DEBUG
      27              : #define TLVL_Crossover TLVL_DEBUG + 30
      28              : #define TLVL_Crossover1 TLVL_DEBUG + 31
      29              : 
      30              : #include "dtcInterfaceLib/otsStyleCoutMacros.h"
      31              : 
      32              : #define DTC_TLOG(lvl) TLOG(lvl) << "DTC " << device_.getDeviceUID() << ": "
      33              : #undef __COUT_HDR__
      34              : #define __COUT_HDR__ "DTC " << device_.getDeviceUID() << ": "
      35              : 
      36              : #include <unistd.h>
      37              : #include <fstream>
      38              : #include <iostream>
      39              : #include <sstream>    // Convert uint to hex string
      40              : #include <iomanip>    // std::setw / std::setfill for record hex dumps
      41              : #include <algorithm>  // std::min
      42              : 
      43            0 : DTCLib::DTC::DTC(DTC_SimMode mode, int dtc, unsigned rocMask, std::string expectedDesignVersion, bool skipInit, std::string simMemoryFile, const std::string& uid)
      44            0 :         : DTC_Registers(mode, dtc, simMemoryFile, rocMask, expectedDesignVersion, skipInit, uid), daqDMAInfo_(), dcsDMAInfo_()
      45              : {
      46            0 :         __COUT__ << "CONSTRUCTOR";
      47            0 : }
      48              : 
      49            0 : DTCLib::DTC::~DTC()
      50              : {
      51            0 :         __COUT__ << "DESTRUCTOR";
      52              :         // TLOG_ENTEX(-6);
      53            0 :         TRACE_EXIT
      54              :         {
      55            0 :                 __COUT__ << "DESTRUCTOR exit";
      56            0 :         };
      57              : 
      58              :         // do not want to release all from destructor, in case this instance of the device is not the 'owner' of a DMA-channel
      59              :         //      so do not call ReleaseAllBuffers(DTC_DMA_Engine_DAQ) or ReleaseAllBuffers(DTC_DMA_Engine_DCS);
      60              : 
      61              :         // assume the destructor is destructive (could be in response to exceptions), so force ending of dcs lock
      62              :         try
      63              :         {
      64            0 :                 device_.end_dcs_transaction();
      65              :         }
      66            0 :         catch (...)
      67              :         {
      68            0 :                 __COUT_WARN__ << "Ignoring exception caught in DESTRUCTOR while ending of dcs lock";
      69            0 :         }
      70            0 : }
      71              : 
      72              : //
      73              : // DMA Functions -- This if for HW event building
      74              : //
      75            0 : std::vector<std::unique_ptr<DTCLib::DTC_Event>> DTCLib::DTC::GetData(DTC_EventWindowTag when, bool matchEventWindowTag)
      76              : {
      77            0 :         DTC_TLOG(TLVL_GetData) << "GetData begin EventWindowTag=" << when.GetEventWindowTag(true) << ", matching=" << (matchEventWindowTag ? "true" : "false");
      78            0 :         std::vector<std::unique_ptr<DTC_Event>> output;
      79            0 :         std::unique_ptr<DTC_Event> packet = nullptr;
      80              : 
      81              :         // Release read buffers here "I am done with everything I read before" (because the return is pointers to the raw data, not copies)
      82            0 :         ReleaseBuffers(DTC_DMA_Engine_DAQ);  // Currently race condition because GetCurrentBuffer(info) is used inside to decide how many buffers to release.
      83              : 
      84              :         try
      85              :         {
      86              :                 // Read the next DTC_Event
      87            0 :                 auto tries = 0;
      88            0 :                 while (packet == nullptr && tries < 3)
      89              :                 {
      90            0 :                         DTC_TLOG(TLVL_GetData) << "GetData before ReadNextDAQPacket, tries=" << tries;
      91            0 :                         packet = ReadNextDAQDMA(100);
      92            0 :                         if (packet != nullptr)
      93              :                         {
      94            0 :                                 DTC_TLOG(TLVL_GetData) << "GetData after ReadDMADAQPacket, ts=0x" << std::hex
      95            0 :                                                                            << packet->GetEventWindowTag().GetEventWindowTag(true);
      96              :                         }
      97            0 :                         tries++;
      98              :                         // if (packet == nullptr) usleep(5000);
      99              :                 }
     100            0 :                 if (packet == nullptr)
     101              :                 {
     102            0 :                         DTC_TLOG(TLVL_GetData) << "GetData: Timeout Occurred! (DTC_Event is nullptr after retries)";
     103            0 :                         return output;
     104              :                 }
     105              : 
     106            0 :                 if (packet->GetEventWindowTag() != when && matchEventWindowTag)
     107              :                 {
     108            0 :                         DTC_TLOG(TLVL_ERROR) << "GetData: Error: DTC_Event has wrong Event Window Tag! 0x" << std::hex << when.GetEventWindowTag(true)
     109            0 :                                                                  << "(expected) != 0x" << std::hex << packet->GetEventWindowTag().GetEventWindowTag(true);
     110            0 :                         packet.reset(nullptr);
     111            0 :                         daqDMAInfo_.currentReadPtr = daqDMAInfo_.lastReadPtr;
     112            0 :                         return output;
     113              :                 }
     114              : 
     115            0 :                 when = packet->GetEventWindowTag();
     116              : 
     117            0 :                 DTC_TLOG(TLVL_GetData) << "GetData: Adding DTC_Event " << (void*)daqDMAInfo_.lastReadPtr << " to the list (first)";
     118            0 :                 output.push_back(std::move(packet));
     119              : 
     120            0 :                 auto done = false;
     121            0 :                 while (!done)
     122              :                 {
     123            0 :                         DTC_TLOG(TLVL_GetData) << "GetData: Reading next DAQ Packet";
     124            0 :                         packet = ReadNextDAQDMA(0);
     125            0 :                         if (packet == nullptr)  // End of Data
     126              :                         {
     127            0 :                                 DTC_TLOG(TLVL_GetData) << "GetData: Next packet is nullptr; we're done";
     128            0 :                                 done = true;
     129            0 :                                 daqDMAInfo_.currentReadPtr = nullptr;
     130              :                         }
     131            0 :                         else if (packet->GetEventWindowTag() != when)
     132              :                         {
     133            0 :                                 DTC_TLOG(TLVL_GetData) << "GetData: Next packet has ts=0x" << std::hex << packet->GetEventWindowTag().GetEventWindowTag(true)
     134            0 :                                                                            << ", not 0x" << std::hex << when.GetEventWindowTag(true) << "; we're done";
     135            0 :                                 done = true;
     136            0 :                                 daqDMAInfo_.currentReadPtr = daqDMAInfo_.lastReadPtr;
     137              :                         }
     138              :                         else
     139              :                         {
     140            0 :                                 DTC_TLOG(TLVL_GetData) << "GetData: Next packet has same ts=0x" << std::hex
     141            0 :                                                                            << packet->GetEventWindowTag().GetEventWindowTag(true) << ", continuing (bc=0x" << std::hex
     142            0 :                                                                            << packet->GetEventByteCount() << ")";
     143              :                         }
     144              : 
     145            0 :                         if (!done)
     146              :                         {
     147            0 :                                 DTC_TLOG(TLVL_GetData) << "GetData: Adding pointer " << (void*)daqDMAInfo_.lastReadPtr << " to the list";
     148            0 :                                 output.push_back(std::move(packet));
     149              :                         }
     150              :                 }
     151              :         }
     152            0 :         catch (DTC_WrongPacketTypeException& ex)
     153              :         {
     154            0 :                 DTC_TLOG(TLVL_WARNING) << "GetData: Bad omen: Wrong packet type at the current read position";
     155            0 :                 daqDMAInfo_.currentReadPtr = nullptr;
     156            0 :         }
     157            0 :         catch (DTC_IOErrorException& ex)
     158              :         {
     159            0 :                 daqDMAInfo_.currentReadPtr = nullptr;
     160            0 :                 DTC_TLOG(TLVL_WARNING) << "GetData: IO Exception Occurred!";
     161            0 :         }
     162            0 :         catch (DTC_DataCorruptionException& ex)
     163              :         {
     164            0 :                 daqDMAInfo_.currentReadPtr = nullptr;
     165            0 :                 DTC_TLOG(TLVL_WARNING) << "GetData: Data Corruption Exception Occurred!";
     166            0 :         }
     167              : 
     168            0 :         DTC_TLOG(TLVL_GetData) << "GetData RETURN";
     169            0 :         return output;
     170            0 : }  // GetData
     171              : 
     172              : //
     173              : // GetSubEventData v3 -- extract subevents into Events from the current data stream
     174            0 : std::vector<std::shared_ptr<DTCLib::DTC_Event>> DTCLib::DTC::GetSubEventDataAsEvents(
     175              :         DTC_EventWindowTag when, bool matchEventWindowTag,
     176              :         const size_t vectorBundleTarget /* = 1 */, const size_t retries /* = 3 */)
     177              : {
     178              :         (void)when;                 // not yet used; filtering can be added once basic flow works
     179              :         (void)matchEventWindowTag;  // not yet used; filtering can be added once basic flow works
     180              : 
     181              :         // Save up to 8 trailing QWs of the current DMA buffer for diagnostic dumps on later
     182              :         // exceptions. Takes its inputs as parameters so it doesn't capture variables that
     183              :         // aren't in scope yet at the lambda's definition site.
     184            0 :         auto saveBufferTail = [this](const uint8_t* dmaBufferStartPtr, size_t dmaBytes) {
     185            0 :                 const size_t tailStart = (dmaBytes >= 64) ? dmaBytes - 64 : 0;
     186            0 :                 lastBufferTailCount_ = 0;
     187            0 :                 for (size_t i = tailStart; i + 8 <= dmaBytes && lastBufferTailCount_ < 8; i += 8)
     188            0 :                         lastBufferTailQwords_[lastBufferTailCount_++] =
     189            0 :                                 *reinterpret_cast<const uint64_t*>(dmaBufferStartPtr + i);
     190            0 :         };
     191              : 
     192              :         // Returns the last good event window tag as a string, or "-1" if unavailable.
     193            0 :         auto ewtTag = [&]() -> std::string {
     194            0 :                 if (!hasLastGoodSubEventHeader_) return "-1";
     195            0 :                 return std::to_string(lastGoodSubEventHeader_.event_tag_low |
     196            0 :                                                           (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32));
     197            0 :         };
     198              : 
     199              :         // Returns the ring-buffer slot index of a DMA buffer pointer as a 2-digit string
     200              :         // (e.g. "07", "42"), or "??" when the pointer is outside the mmap'd region.
     201            0 :         auto bufferIndexStr = [this](const void* ptr) -> std::string {
     202            0 :                 const int idx = GetDevice()->GetBufferIndex(DTC_DMA_Engine_DAQ, ptr);
     203            0 :                 if (idx < 0) return "??";
     204            0 :                 std::ostringstream oss;
     205            0 :                 oss << std::setw(2) << std::setfill('0') << idx;
     206            0 :                 return oss.str();
     207            0 :         };
     208              : 
     209            0 :         if (needToFinishEvent_)
     210              :         {
     211            0 :                 auto inProgress = std::move(extractedEvents_.back());
     212            0 :                 extractedEvents_.clear();
     213            0 :                 extractedEvents_.push_back(std::move(inProgress));
     214            0 :         }
     215              :         else
     216              :         {
     217            0 :                 extractedEvents_.clear();
     218            0 :                 currentEventSize_ = 0;
     219            0 :                 subEventByteCount_ = 0;
     220            0 :                 extractedSubeventBytes_ = 0;
     221              :         }
     222            0 :         extractedEvents_.reserve(vectorBundleTarget + 1);
     223            0 :         size_t eventsParsedThisTime = 0;
     224              : 
     225              :         // Release the previous buffer NOW — we are done with it.
     226            0 :         ReleaseBuffers(DTC_DMA_Engine_DAQ);
     227              : 
     228              :         // ------------------------------------------------------------------
     229              :         do  // primary loop to read buffers until target event count is reached or timeout
     230              :         {
     231            0 :                 DTC_TLOG(TLVL_GetCurrentBuffer) << "GetSubEventData BEGIN [last EWT=" << ewtTag()
     232            0 :                                                                                 << "] eventsParsedThisTime=" << eventsParsedThisTime
     233            0 :                                                                                 << " totalEventsParsed_=" << totalEventsParsed_;
     234              : 
     235              :                 // Read next buffer
     236              :                 int sts;
     237              :                 {
     238            0 :                         int retry = retries;
     239              :                         do {
     240            0 :                                 sts = ReadBuffer(DTC_DMA_Engine_DAQ, 100 /*ms*/);
     241            0 :                         } while (sts <= 0 && --retry > 0);
     242              :                 }
     243            0 :                 if (sts <= 0)
     244              :                 {
     245            0 :                         DTC_TLOG(TLVL_GetCurrentBuffer) << "GetSubEventData: ReadBuffer returned " << sts << ", no data";
     246              :                         {
     247            0 :                                 std::vector<std::shared_ptr<DTC_Event>> result;
     248            0 :                                 size_t cc = extractedEvents_.size() - (needToFinishEvent_ ? 1 : 0);
     249            0 :                                 result.reserve(cc);
     250            0 :                                 for (size_t i = 0; i < cc; ++i)
     251            0 :                                         result.push_back(extractedEvents_[i]);
     252            0 :                                 return result;
     253            0 :                         }
     254              :                 }
     255              : 
     256            0 :                 if (lastBufferTailCount_ == 8)
     257              :                 {
     258            0 :                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: Last dmaBufferIndex #" << lastDMABufferIndex_ << " tail qwords: "
     259            0 :                                                                          << std::hex
     260            0 :                                                                          << lastBufferTailQwords_[0] << " "
     261            0 :                                                                          << lastBufferTailQwords_[1] << " "
     262            0 :                                                                          << lastBufferTailQwords_[2] << " "
     263            0 :                                                                          << lastBufferTailQwords_[3] << " | "
     264            0 :                                                                          << lastBufferTailQwords_[4] << " "
     265            0 :                                                                          << lastBufferTailQwords_[5] << " "
     266            0 :                                                                          << lastBufferTailQwords_[6] << " "
     267            0 :                                                                          << lastBufferTailQwords_[7];
     268              :                 }
     269              : 
     270              :                 // sts = number of bytes the DMA engine transferred into this buffer
     271            0 :                 const size_t dmaBytes = static_cast<size_t>(sts);
     272              : 
     273            0 :                 const bool prevBufferWasFull = lastDMABufferWasFull_;
     274            0 :                 const bool prevBufferWasMax = lastDMABufferWasMax_;
     275            0 :                 const size_t MAX_TRANSFER_SIZE = 0xffe9;
     276            0 :                 lastDMABufferWasFull_ = (dmaBytes == MAX_TRANSFER_SIZE);
     277            0 :                 lastDMABufferWasMax_ = (dmaBytes == sizeof(mu2e_databuff_t));
     278              : 
     279            0 :                 const uint8_t* dmaBufferStartPtr = reinterpret_cast<const uint8_t*>(&daqDMAInfo_.buffer.back()[0]);
     280              :                 {
     281            0 :                         const uint64_t* qw = reinterpret_cast<const uint64_t*>(dmaBufferStartPtr);
     282            0 :                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: first 8 QWs of dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << ": " << std::hex
     283            0 :                                                                          << qw[0] << " " << qw[1] << " " << qw[2] << " " << qw[3] << " | "
     284            0 :                                                                          << qw[4] << " " << qw[5] << " " << qw[6] << " " << qw[7] << std::dec;
     285              :                 }
     286            0 :                 saveBufferTail(dmaBufferStartPtr, dmaBytes);
     287            0 :                 lastDMABufferIndex_ = bufferIndexStr(dmaBufferStartPtr);
     288            0 :                 if (lastBufferTailCount_ == 8)
     289              :                 {
     290            0 :                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: This dmaBufferIndex #" << lastDMABufferIndex_ << " tail qwords: "
     291            0 :                                                                          << std::hex
     292            0 :                                                                          << lastBufferTailQwords_[0] << " "
     293            0 :                                                                          << lastBufferTailQwords_[1] << " "
     294            0 :                                                                          << lastBufferTailQwords_[2] << " "
     295            0 :                                                                          << lastBufferTailQwords_[3] << " | "
     296            0 :                                                                          << lastBufferTailQwords_[4] << " "
     297            0 :                                                                          << lastBufferTailQwords_[5] << " "
     298            0 :                                                                          << lastBufferTailQwords_[6] << " "
     299            0 :                                                                          << lastBufferTailQwords_[7];
     300              :                 }
     301              :                 {
     302            0 :                         const uint64_t* qw = reinterpret_cast<const uint64_t*>(dmaBufferStartPtr);
     303            0 :                         if (qw[0] == 0xdeadbeef)
     304              :                         {
     305            0 :                                 __SS__ << "GetSubEventData: Detected 0xdeadbeef at start of dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " -- this buffer is likely not filled by DMA yet! Is kernel driver and DTC out of sync? Try sending a finite number (e.g. 2) of events through the system TWICE. If that doesn't work, try resetting the PCIe.\nSpy output follows:\n"
     306            0 :                                            << __E__;
     307            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */ | (1 << 28) /* to force spy */, ss);
     308            0 :                                 __SS_THROW__;
     309              :                                 // continue;  // skip processing this buffer, try to read the next one
     310            0 :                         }
     311              :                 }
     312              : 
     313              :                 // The DMA descriptor byte count (sts) is the number of bytes actually filled.
     314              :                 // The last byte is the AXI tlast flag byte when the buffer is not completely full.
     315              :                 // When it IS completely full (dmaBytes == sizeof(mu2e_databuff_t)) there is no tlast byte.
     316            0 :                 const size_t payloadBytes = (dmaBytes < sizeof(mu2e_databuff_t)) ? dmaBytes - 1 : dmaBytes;
     317              : 
     318            0 :                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: new buffer dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     319            0 :                                                                  << " dmaBytes=" << dmaBytes << "(0x" << std::hex << dmaBytes << std::dec << ")"
     320            0 :                                                                  << " bufferTotalBytes=" << payloadBytes << "(0x" << std::hex << payloadBytes << std::dec << ")"
     321            0 :                                                                  << " maxBufferBytes=" << sizeof(mu2e_databuff_t) << "(0x" << std::hex << sizeof(mu2e_databuff_t) << std::dec << ")"
     322            0 :                                                                  << " prevBufferWasFull=" << (prevBufferWasFull ? "true" : "false")
     323            0 :                                                                  << " prevBufferWasMax=" << (prevBufferWasMax ? "true" : "false")
     324            0 :                                                                  << " thisBufferIsFull=" << (lastDMABufferWasFull_ ? "true" : "false")
     325            0 :                                                                  << " thisBufferIsMax=" << (lastDMABufferWasMax_ ? "true" : "false");
     326              : 
     327            0 :                 if (dmaBytes > sizeof(mu2e_databuff_t))
     328              :                 {
     329            0 :                         __SS__ << "GetSubEventData: Detected overflow transfer size at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     330            0 :                                    << " [0x" << std::hex << dmaBytes << " > sizeof(mu2e_databuff_t)=0x" << sizeof(mu2e_databuff_t) << std::dec
     331            0 :                                    << "]\nSpy output follows:\n"
     332            0 :                                    << __E__;
     333            0 :                         device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | (1 << 28), ss);
     334            0 :                         __SS_THROW__;
     335            0 :                 }
     336              : 
     337            0 :                 size_t dmaBufferOffset = 0;
     338              : 
     339            0 :                 if (needToFinishEvent_ && !prevBufferWasMax && payloadBytes >= sizeof(uint64_t))
     340              :                 {
     341            0 :                         const size_t remainingBytes = subEventByteCount_ - extractedSubeventBytes_;
     342            0 :                         const size_t continuationPrefix = static_cast<size_t>(*reinterpret_cast<const uint64_t*>(dmaBufferStartPtr) & 0xFFFFFFULL);
     343            0 :                         if (continuationPrefix == remainingBytes + 8)
     344              :                         {
     345            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: continuation prefix validated (final) at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     346            0 :                                                                                  << " prefixVal=0x" << std::hex << continuationPrefix << std::dec
     347            0 :                                                                                  << " == remainingBytes(" << remainingBytes << ") + 8"
     348            0 :                                                                                  << "; skipping 8-byte prefix";
     349            0 :                                 dmaBufferOffset = sizeof(uint64_t);
     350              :                         }
     351            0 :                         else if (continuationPrefix == MAX_TRANSFER_SIZE - 1)
     352              :                         {
     353            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: continuation prefix validated (intermediate, another max sub-transfer) at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     354            0 :                                                                                  << " prefixVal=0x" << std::hex << continuationPrefix << std::dec
     355            0 :                                                                                  << " == MAX_TRANSFER_SIZE-1=0x" << (MAX_TRANSFER_SIZE - 1) << std::dec
     356            0 :                                                                                  << " remainingBytes=" << remainingBytes
     357            0 :                                                                                  << "; skipping 8-byte prefix";
     358            0 :                                 dmaBufferOffset = sizeof(uint64_t);
     359              :                         }
     360              :                         else
     361              :                         {
     362            0 :                                 __SS__ << "GetSubEventData: continuation prefix mismatch at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     363            0 :                                            << " prefixVal=" << continuationPrefix << "(0x" << std::hex << continuationPrefix << std::dec << ")"
     364            0 :                                            << " expected " << (remainingBytes + 8) << "(0x" << std::hex << (remainingBytes + 8) << std::dec << ")"
     365            0 :                                            << " or MAX_TRANSFER_SIZE-1=0x" << std::hex << (MAX_TRANSFER_SIZE - 1) << std::dec
     366            0 :                                            << " remainingBytes=" << remainingBytes
     367            0 :                                            << " extractedSubeventBytes_=" << extractedSubeventBytes_ << "/" << subEventByteCount_
     368              :                                            << " prevBufferWasFull=" << (prevBufferWasFull ? "true" : "false")
     369            0 :                                            << " prevBufferWasMax=" << (prevBufferWasMax ? "true" : "false");
     370            0 :                                 __SS_THROW__;
     371            0 :                         }
     372            0 :                 }
     373            0 :                 else if (needToFinishEvent_ && prevBufferWasMax)
     374              :                 {
     375            0 :                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: raw continuation (prevBufferWasMax) at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr)
     376            0 :                                                                          << " -- no prefix, starting at offset 0";
     377              :                 }
     378              : 
     379            0 :                 while (dmaBufferOffset < payloadBytes)
     380              :                 {
     381            0 :                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: top of buffer processing loop, dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " dmaBufferOffset= " << dmaBufferOffset
     382            0 :                                                                          << "/" << payloadBytes
     383            0 :                                                                          << " eventsParsedThisTime=" << eventsParsedThisTime
     384            0 :                                                                          << " totalEventsParsed_=" << totalEventsParsed_
     385            0 :                                                                          << " needToFinishEvent_=" << (needToFinishEvent_ ? "true" : "false")
     386            0 :                                                                          << " subEventHeaderQwsFilled_=" << subEventHeaderQwsFilled_
     387            0 :                                                                          << " extractedSubeventBytes_=" << extractedSubeventBytes_ << "/" << subEventByteCount_;
     388              : 
     389              :                         // ---------------------------------------------------------------
     390              :                         // Assemble the DTC_SubEventHeader one QW at a time into the standing
     391              :                         // buffer (subEventHeaderBuf_). The header is 6 QWs (48 B); it may
     392              :                         // span DMA buffer boundaries, so state persists in DTC class members.
     393              :                         // As soon as the first 2 QWs are in, we know the event size and can
     394              :                         // emplace_back the DTC_Event. Once all 6 QWs are in, the assembled
     395              :                         // header is memcpy'd into the Event's owned buffer.
     396              :                         // ---------------------------------------------------------------
     397            0 :                         if (currentEventSize_ == 0)  // haven't yet determined the event size from the first 2 header QWs
     398              :                         {
     399              :                                 // Fill QWs from the current buffer until we have the first 2 (or the buffer runs out)
     400            0 :                                 while (subEventHeaderQwsFilled_ < 2 && (payloadBytes - dmaBufferOffset) >= sizeof(uint64_t))
     401              :                                 {
     402            0 :                                         subEventHeaderBuf_[subEventHeaderQwsFilled_++] =
     403            0 :                                                 *reinterpret_cast<const uint64_t*>(dmaBufferStartPtr + dmaBufferOffset);
     404            0 :                                         dmaBufferOffset += sizeof(uint64_t);
     405              :                                 }
     406            0 :                                 if (subEventHeaderQwsFilled_ < 2)
     407              :                                 {
     408              :                                         // Header straddles DMA buffer boundary; resume on next ReadBuffer
     409            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: only " << subEventHeaderQwsFilled_
     410            0 :                                                                                          << " header QW(s) filled, need 2 before sizing; breaking to fetch next buffer";
     411            0 :                                         break;
     412              :                                 }
     413              : 
     414              :                                 // QW0: per-sub-transfer framing prefix -> bits[0:23] = transfer byte count (24 bits)
     415              :                                 //      When == MAX_TRANSFER_SIZE - 1, the event is larger than one sub-transfer (multi-buffer event).
     416              :                                 // QW1: DTC_SubEventHeader -> bits[0:24] = inclusive_subevent_byte_count (25 bits)
     417            0 :                                 const size_t potentialEventTransferPrefix = static_cast<size_t>(subEventHeaderBuf_[0] & 0xFFFFFFULL);
     418            0 :                                 subEventByteCount_ = static_cast<size_t>(subEventHeaderBuf_[1] & 0x1FFFFFFULL);
     419              : 
     420            0 :                                 if (potentialEventTransferPrefix == MAX_TRANSFER_SIZE - 1)
     421              :                                 {
     422            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: multi-buffer event detected"
     423            0 :                                                                                          << " potentialEventTransferPrefix=0x" << std::hex << potentialEventTransferPrefix
     424            0 :                                                                                          << " == MAX_TRANSFER_SIZE-1=0x" << (MAX_TRANSFER_SIZE - 1) << std::dec
     425            0 :                                                                                          << " subEventByteCount_=" << subEventByteCount_
     426            0 :                                                                                          << " (event spans multiple DMA buffers)";
     427              :                                 }
     428            0 :                                 else if (potentialEventTransferPrefix != subEventByteCount_ + 8)
     429              :                                 {
     430            0 :                                         __SS__ << "GetSubEventData: Header sanity check FAILED: potentialEventTransferPrefix=" << potentialEventTransferPrefix << "(0x" << std::hex << potentialEventTransferPrefix << std::dec << ")"
     431            0 :                                                    << " subEventByteCount_=" << subEventByteCount_ << "(0x" << std::hex << subEventByteCount_ << std::dec << ")"
     432            0 :                                                    << " [expected potentialEventTransferPrefix == 0x" << std::hex << (MAX_TRANSFER_SIZE - 1) << std::dec << " (multi-buffer) or potentialEventTransferPrefix - 8 == subEventByteCount_ (single-buffer), diff="
     433            0 :                                                    << std::dec << (static_cast<int64_t>(potentialEventTransferPrefix) - 8 - static_cast<int64_t>(subEventByteCount_))
     434            0 :                                                    << "] at dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " dmaBufferOffset=" << dmaBufferOffset
     435            0 :                                                    << " payloadBytes=" << payloadBytes
     436            0 :                                                    << " last EWT=" << ewtTag() << " eventsParsedThisTime=" << eventsParsedThisTime
     437            0 :                                                    << " totalEventsParsed_=" << totalEventsParsed_;
     438              : 
     439            0 :                                         __SS_THROW__;
     440            0 :                                 }
     441              : 
     442              :                                 // currentEventSize_ = destination buffer size: struct EventHeader + full subevent
     443            0 :                                 currentEventSize_ = sizeof(DTC_EventHeader) + subEventByteCount_;
     444            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: sized event, potentialEventTransferPrefix=" << potentialEventTransferPrefix << "(0x" << std::hex << potentialEventTransferPrefix << std::dec << ")"
     445            0 :                                                                                  << " subEventByteCount_=" << subEventByteCount_ << "(0x" << std::hex << subEventByteCount_ << std::dec << ")"
     446            0 :                                                                                  << " currentEventSize_=" << currentEventSize_ << "(0x" << std::hex << currentEventSize_ << std::dec << ")"
     447            0 :                                                                                  << " at dmaBufferOffset=" << dmaBufferOffset;
     448              :                         }
     449              : 
     450              :                         // fill subevent header cache
     451            0 :                         if (!needToFinishEvent_ && currentEventSize_ > 0 && subEventHeaderQwsFilled_ < kSubEventHeaderQws)
     452              :                         {
     453              :                                 // We have a partially filled header from the previous buffer(s); try to fill the rest before doing anything else
     454            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: Continuing to fill subevent header, currently have "
     455            0 :                                                                                  << subEventHeaderQwsFilled_ << "/" << kSubEventHeaderQws << " QWs filled";
     456            0 :                                 while (subEventHeaderQwsFilled_ < kSubEventHeaderQws && (payloadBytes - dmaBufferOffset) >= sizeof(uint64_t))
     457              :                                 {
     458            0 :                                         subEventHeaderBuf_[subEventHeaderQwsFilled_++] =
     459            0 :                                                 *reinterpret_cast<const uint64_t*>(dmaBufferStartPtr + dmaBufferOffset);
     460            0 :                                         dmaBufferOffset += sizeof(uint64_t);
     461            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: filled QW " << subEventHeaderQwsFilled_ << "/" << kSubEventHeaderQws;
     462              :                                 }
     463            0 :                                 if (subEventHeaderQwsFilled_ < kSubEventHeaderQws)
     464              :                                 {
     465              :                                         // Still don't have the full header; wait for the next buffer
     466            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: still only have " << subEventHeaderQwsFilled_
     467            0 :                                                                                          << " header QW(s) filled, need " << kSubEventHeaderQws
     468            0 :                                                                                          << "; breaking to fetch next buffer";
     469            0 :                                         break;
     470              :                                 }
     471              : 
     472            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: assembled subevent header: "
     473            0 :                                                                                  << reinterpret_cast<const DTC_SubEventHeader*>(subEventHeaderBuf_.data() + 1)->toJson();
     474              :                         }  // end fill subevent header cache
     475              : 
     476              :                         // start a new event and finish copying its subevent header
     477            0 :                         if (!needToFinishEvent_ && currentEventSize_ > 0 && subEventHeaderQwsFilled_ == kSubEventHeaderQws)
     478              :                         {
     479            0 :                                 extractedEvents_.push_back(std::make_shared<DTC_Event>(currentEventSize_));
     480              : 
     481            0 :                                 DTCLib::DTC_EventHeader evtHdr;
     482            0 :                                 evtHdr.inclusive_event_byte_count = currentEventSize_;
     483            0 :                                 evtHdr.num_dtcs = 1;
     484            0 :                                 evtHdr.event_tag_low = reinterpret_cast<const DTC_SubEventHeader*>(subEventHeaderBuf_.data() + 1)->event_tag_low;
     485            0 :                                 evtHdr.event_tag_high = reinterpret_cast<const DTC_SubEventHeader*>(subEventHeaderBuf_.data() + 1)->event_tag_high;
     486            0 :                                 uint8_t* eventBuf = static_cast<uint8_t*>(const_cast<void*>(extractedEvents_.back()->GetRawBufferPointer()));
     487            0 :                                 memcpy(eventBuf, &evtHdr, sizeof(DTCLib::DTC_EventHeader));
     488            0 :                                 *extractedEvents_.back()->GetHeader() = evtHdr;
     489            0 :                                 extractedSubeventBytes_ = 0;
     490              : 
     491            0 :                                 memcpy(eventBuf + sizeof(DTCLib::DTC_EventHeader) + extractedSubeventBytes_, subEventHeaderBuf_.data() + 1, sizeof(DTC_SubEventHeader));
     492            0 :                                 extractedSubeventBytes_ += sizeof(DTC_SubEventHeader);
     493              : 
     494            0 :                                 subEventHeaderQwsFilled_ = 0;
     495            0 :                                 needToFinishEvent_ = true;
     496              : 
     497            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: copied " << sizeof(DTC_SubEventHeader)
     498            0 :                                                                                  << "-byte subevent header from dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " into Event vector position #" << extractedEvents_.size()
     499            0 :                                                                                  << " EWT=" << extractedEvents_.back()->GetEventWindowTag() << "(0x" << std::hex << extractedEvents_.back()->GetEventWindowTag() << std::dec << ")"
     500            0 :                                                                                  << " (extractedSubeventBytes_=" << extractedSubeventBytes_
     501            0 :                                                                                  << "/" << subEventByteCount_ << ")";
     502              :                         }
     503              : 
     504            0 :                         if (needToFinishEvent_)
     505              :                         {
     506            0 :                                 size_t bytesToCopy = std::min(payloadBytes - dmaBufferOffset, subEventByteCount_ - extractedSubeventBytes_);
     507              : 
     508            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: copying " << bytesToCopy << " bytes from dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " into Event vector position #" << extractedEvents_.size()
     509            0 :                                                                                  << " EWT=" << extractedEvents_.back()->GetEventWindowTag() << "(0x" << std::hex << extractedEvents_.back()->GetEventWindowTag() << std::dec << ")"
     510            0 :                                                                                  << " (before extractedSubeventBytes_=" << extractedSubeventBytes_
     511            0 :                                                                                  << "/" << subEventByteCount_ << ")";
     512              : 
     513            0 :                                 uint8_t* destPtr = static_cast<uint8_t*>(const_cast<void*>(extractedEvents_.back()->GetRawBufferPointer())) + sizeof(DTC_EventHeader) + extractedSubeventBytes_;
     514            0 :                                 memcpy(destPtr, dmaBufferStartPtr + dmaBufferOffset, bytesToCopy);
     515            0 :                                 dmaBufferOffset += bytesToCopy;
     516            0 :                                 extractedSubeventBytes_ += bytesToCopy;
     517              : 
     518            0 :                                 DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: copied " << bytesToCopy
     519            0 :                                                                                  << "-bytes of subevent payload from dmaBufferIndex #" << bufferIndexStr(dmaBufferStartPtr) << " into Event vector position #" << extractedEvents_.size()
     520            0 :                                                                                  << " EWT=" << extractedEvents_.back()->GetEventWindowTag() << "(0x" << std::hex << extractedEvents_.back()->GetEventWindowTag() << std::dec << ")"
     521            0 :                                                                                  << " (extractedSubeventBytes_=" << extractedSubeventBytes_
     522            0 :                                                                                  << "/" << subEventByteCount_ << ")";
     523              : 
     524            0 :                                 if (extractedSubeventBytes_ == subEventByteCount_)
     525              :                                 {
     526            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: finished assembling Event vector position #" << extractedEvents_.size()
     527            0 :                                                                                          << " EWT=" << extractedEvents_.back()->GetEventWindowTag() << "(0x" << std::hex << extractedEvents_.back()->GetEventWindowTag() << std::dec << ")"
     528            0 :                                                                                          << " (total extractedSubeventBytes_=" << extractedSubeventBytes_
     529            0 :                                                                                          << "/" << subEventByteCount_ << ", eventBytes=" << currentEventSize_ << "); resetting for next event"
     530            0 :                                                                                          << " eventsParsedThisTime=" << eventsParsedThisTime
     531            0 :                                                                                          << " totalEventsParsed_=" << totalEventsParsed_;
     532            0 :                                         currentEventSize_ = 0;
     533            0 :                                         subEventByteCount_ = 0;
     534            0 :                                         extractedSubeventBytes_ = 0;
     535            0 :                                         needToFinishEvent_ = false;
     536            0 :                                         eventsParsedThisTime++;
     537            0 :                                         totalEventsParsed_++;
     538            0 :                                         subEventHeaderQwsFilled_ = 0;
     539              : 
     540            0 :                                         extractedEvents_.back()->SetupEvent();
     541              :                                 }
     542              :                                 else
     543              :                                 {
     544            0 :                                         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData: still need to copy "
     545            0 :                                                                                          << (subEventByteCount_ - extractedSubeventBytes_)
     546            0 :                                                                                          << " bytes to finish Event vector position #" << extractedEvents_.size()
     547            0 :                                                                                          << " EWT=" << extractedEvents_.back()->GetEventWindowTag() << "(0x" << std::hex << extractedEvents_.back()->GetEventWindowTag() << std::dec << ")"
     548            0 :                                                                                          << "; breaking to fetch next buffer";
     549            0 :                                         break;
     550              :                                 }
     551              :                         }
     552              :                 }  // end buffer processing loop
     553              : 
     554            0 :                 ReleaseBuffers(DTC_DMA_Engine_DAQ);
     555              : 
     556            0 :         } while (eventsParsedThisTime < vectorBundleTarget);
     557              :         // ------------------------------------------------------------------
     558              : 
     559            0 :         size_t completedCount = extractedEvents_.size() - (needToFinishEvent_ ? 1 : 0);
     560            0 :         DTC_TLOG(TLVL_GetData_3) << "GetSubEventData END [last EWT=" << ewtTag()
     561            0 :                                                          << "] eventsParsedThisTime=" << eventsParsedThisTime
     562            0 :                                                          << " totalEventsParsed_=" << totalEventsParsed_
     563            0 :                                                          << " completedCount=" << completedCount
     564            0 :                                                          << " needToFinishEvent_=" << (needToFinishEvent_ ? "true" : "false");
     565              : 
     566            0 :         std::vector<std::shared_ptr<DTC_Event>> result;
     567            0 :         result.reserve(completedCount);
     568            0 :         for (size_t i = 0; i < completedCount; ++i)
     569            0 :                 result.push_back(extractedEvents_[i]);
     570            0 :         return result;
     571            0 : }  // end GetSubEventDataAsEvents()
     572              : 
     573              : // ---------------------------------------------------------------------------
     574              : // GetEVBDataAsEvents -- FAFA chunk parser for EVB3 DMA stream
     575              : // ---------------------------------------------------------------------------
     576              : // Reads one DMA buffer per call, parses FAFA chunk framing, accumulates
     577              : // per-source reassembly buffers, and extracts complete subevents.
     578              : // Each complete subevent is wrapped in a DTC_Event (with a synthetic
     579              : // DTC_EventHeader) and returned.  Incomplete subevents (remote chunk
     580              : // fragmentation) remain in evbPerSourceReassembly_ for the next call.
     581              : // ---------------------------------------------------------------------------
     582            0 : std::vector<std::shared_ptr<DTCLib::DTC_Event>> DTCLib::DTC::GetEVBDataAsEvents(
     583              :         DTC_EventWindowTag when, bool matchEventWindowTag, const size_t retries)
     584              : {
     585              :         (void)when;  // reserved for future EWT filtering
     586              :         (void)matchEventWindowTag;
     587            0 :         std::vector<std::shared_ptr<DTC_Event>> output;
     588              : 
     589              :         // Step profile: one steady_clock read per step boundary; totals are reported by FormatEVBReadProfile().
     590              :         using ProfileClock = std::chrono::steady_clock;
     591            0 :         const ProfileClock::time_point callStart = ProfileClock::now();
     592            0 :         ProfileClock::time_point lapStart = callStart;
     593            0 :         auto lap = [&lapStart](uint64_t& accumulator) {
     594            0 :                 const ProfileClock::time_point now = ProfileClock::now();
     595            0 :                 accumulator += static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(now - lapStart).count());
     596            0 :                 lapStart = now;
     597            0 :         };
     598            0 :         ++evbReadProfile_.calls;
     599              :         struct ProfileTotal
     600              :         {
     601              :                 uint64_t& total;
     602              :                 ProfileClock::time_point start;
     603            0 :                 ~ProfileTotal() { total += static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(ProfileClock::now() - start).count()); }
     604            0 :         } profileTotal{evbReadProfile_.totalNs, callStart};
     605              : 
     606              :         static const size_t RECORD_HEADER_SIZE = sizeof(uint64_t);  // 8 bytes: firmware record header word
     607              : 
     608              :         // Hex-dump a record / subevent as 64-bit words in DMA order, 4 per line
     609            0 :         auto dumpRecord = [](std::ostream& os, const char* label, uint8_t src, const uint8_t* data, size_t bytes) {
     610            0 :                 os << label << " src=0x" << std::hex << static_cast<int>(src) << std::dec
     611            0 :                    << " (" << bytes << " bytes, " << bytes / sizeof(uint64_t) << " words):\n";
     612            0 :                 const uint64_t* w = reinterpret_cast<const uint64_t*>(data);
     613            0 :                 size_t nw = bytes / sizeof(uint64_t);
     614            0 :                 for (size_t i = 0; i < nw; ++i)
     615              :                 {
     616            0 :                         if (i % 4 == 0) os << "  +" << std::dec << std::setw(3) << (i * 8) << ":";
     617            0 :                         os << " " << std::hex << std::setw(16) << std::setfill('0') << w[i] << std::setfill(' ');
     618            0 :                         if (i % 4 == 3 || i + 1 == nw) os << "\n";
     619              :                 }
     620            0 :                 os << std::dec;
     621            0 :         };
     622              : 
     623              :         // Describe a pending (incomplete) tag: which sources are present, dump each staged subevent
     624            0 :         auto describePending = [&](std::ostream& os, uint64_t tag, const EVBPendingTag& pend) {
     625            0 :                 os << "  EWT=" << tag << ": " << pend.subevents.size() << " of " << static_cast<int>(evbNumSources_)
     626            0 :                    << " sources present, age " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - pend.firstArrival).count()
     627            0 :                    << " ms. Present source_dtc_id:";
     628            0 :                 for (auto& [sid, bytes] : pend.subevents) os << " 0x" << std::hex << static_cast<int>(sid) << std::dec;
     629            0 :                 os << ". Sources seen this run (any tag):";
     630            0 :                 for (auto& [sid, buf] : evbPerSourceReassembly_) os << " 0x" << std::hex << static_cast<int>(sid) << std::dec;
     631            0 :                 os << "\n";
     632            0 :                 for (auto& [sid, bytes] : pend.subevents)
     633            0 :                         dumpRecord(os, "  staged subevent (no record header)", sid, bytes.data(), bytes.size());
     634            0 :         };
     635              : 
     636            0 :         auto dumpEVBErrorStatus = [&](std::ostream& os) {
     637            0 :                 uint32_t evbErr = ReadRegister_(DTC_Register_EventBuilderErrorFlags);
     638            0 :                 os << "EVB Error/Status (0x9370) at abort: 0x" << std::hex << std::setw(8) << std::setfill('0') << evbErr << std::dec << std::setfill(' ') << "\n";
     639            0 :                 for (const auto& line : DecodeEVBErrorStatus(evbErr))
     640            0 :                         os << "  " << line << "\n";
     641            0 :         };
     642              : 
     643              :         // Step 0: event-completion timeout / overflow scan.  Runs on EVERY call, including calls
     644              :         // where no DMA data arrives, so a tag whose remaining subevents never come is reported.
     645            0 :         if (!evbPendingTags_.empty())
     646              :         {
     647            0 :                 auto now = std::chrono::steady_clock::now();
     648            0 :                 bool overflow = evbPendingTags_.size() > evbMaxOpenTags_;
     649            0 :                 for (auto& [tag, pend] : evbPendingTags_)
     650              :                 {
     651            0 :                         bool timedOut = (now - pend.firstArrival) > evbEventTimeout_;
     652            0 :                         if (!timedOut && !overflow) continue;
     653            0 :                         std::stringstream ss;
     654              :                         ss << "GetEVBDataAsEvents: event assembly " << (timedOut ? "TIMEOUT" : "OVERFLOW")
     655            0 :                            << " -- EWT=" << tag << " has waited " << std::chrono::duration_cast<std::chrono::milliseconds>(now - pend.firstArrival).count()
     656            0 :                            << " ms since its first subevent (limit " << evbEventTimeout_.count() << " ms), "
     657            0 :                            << pend.subevents.size() << " of " << static_cast<int>(evbNumSources_) << " sources present; open tags=" << evbPendingTags_.size()
     658            0 :                            << " (max " << evbMaxOpenTags_ << "). Stopping EVB DMA parsing.\n";
     659            0 :                         describePending(ss, tag, pend);
     660            0 :                         dumpEVBErrorStatus(ss);
     661            0 :                         DTC_TLOG(TLVL_ERROR) << ss.str();
     662            0 :                         ++evbFramingErrors_;
     663            0 :                         throw std::runtime_error(ss.str());
     664            0 :                 }
     665              :         }
     666              : 
     667            0 :         lap(evbReadProfile_.timeoutScanNs);
     668              : 
     669              :         // Step 1: read one DMA buffer directly from hardware
     670            0 :         void* buffer = nullptr;
     671            0 :         int dmaBytes = device_.read_data(DTC_DMA_Engine_DAQ, &buffer, 1 /*tmo_ms*/);
     672            0 :         lap(evbReadProfile_.readDataNs);
     673              : 
     674            0 :         if (dmaBytes <= 0 || buffer == nullptr)
     675              :         {
     676            0 :                 if (dmaBytes < 0)
     677            0 :                         DTC_TLOG(TLVL_ERROR) << "GetEVBDataAsEvents: read_data error " << dmaBytes;
     678            0 :                 return output;
     679              :         }
     680            0 :         ++evbReadProfile_.callsWithData;
     681              : 
     682              :         // DMA buffer lifetime: this one buffer is released when the function exits by ANY path
     683              :         // (normal return or a throw from any check below).  Step 3 copies every chunk payload out
     684              :         // of the buffer into host memory; nothing after that holds a pointer into the DMA ring.
     685              :         // Event completion therefore never depends on holding DMA buffers -- an event whose
     686              :         // subevents span hundreds of DMA transfers costs host memory only.
     687              :         struct DmaRelease
     688              :         {
     689              :                 decltype(device_)& d;
     690              :                 uint64_t& releaseNs;
     691            0 :                 ~DmaRelease()
     692              :                 {
     693            0 :                         const ProfileClock::time_point releaseStart = ProfileClock::now();
     694              :                         try
     695              :                         {
     696            0 :                                 d.read_release(DTC_DMA_Engine_DAQ, 1);
     697              :                         }
     698            0 :                         catch (...)
     699            0 :                         {}
     700            0 :                         releaseNs += static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(ProfileClock::now() - releaseStart).count());
     701            0 :                 }
     702            0 :         } dmaRelease{device_, evbReadProfile_.releaseBufferNs};
     703              : 
     704              :         // Step 2: compute usable payload (strip tlast byte if short DMA)
     705            0 :         size_t payloadBytes = static_cast<size_t>(dmaBytes);
     706            0 :         if (payloadBytes < sizeof(mu2e_databuff_t))
     707            0 :                 payloadBytes -= 1;  // tlast marker byte
     708            0 :         size_t payloadWords = payloadBytes / sizeof(uint64_t);
     709            0 :         const uint64_t* words = static_cast<const uint64_t*>(buffer);
     710            0 :         ++evbDMABuffersRead_;
     711              : 
     712              :         // Step 2b: expected number of source DTCs per tag (= EVB destination-node count) and this DTC's
     713              :         // MAC, read once per run (first data buffer after ResetEVBAssembly); both are constant for a run
     714              :         // and the two register reads per buffer they used to cost are now gone from the data path.
     715            0 :         if (!evbSourcesKnown_)
     716              :         {
     717            0 :                 uint8_t n = ReadEVBNumberOfDestinationNodes();
     718            0 :                 evbNumSources_ = (n == 0) ? 1 : n;
     719            0 :                 evbLocalMac_ = ReadEVBLocalMACAddress();
     720            0 :                 evbSourcesKnown_ = true;
     721              :         }
     722            0 :         lap(evbReadProfile_.registerReadNs);
     723              : 
     724              :         // Step 3: FAFA chunk walk
     725            0 :         size_t ptr = 0;
     726            0 :         std::vector<std::tuple<size_t, uint8_t, uint16_t>> chunksSeenInThisBuffer;
     727            0 :         while (ptr < payloadWords)
     728              :         {
     729            0 :                 uint64_t w = words[ptr];
     730            0 :                 if ((w >> 48) == 0xFAFA)
     731              :                 {
     732            0 :                         uint16_t chunk_wc = static_cast<uint16_t>((w >> 8) & 0xFFFF);
     733            0 :                         uint8_t chunk_src = static_cast<uint8_t>(w & 0xFF);
     734              : 
     735            0 :                         if (ptr + 1 + chunk_wc > payloadWords)
     736              :                         {
     737              :                                 // A chunk that does not fit its DMA buffer never happens in normal running (hw
     738              :                                 // agent 2026-10-05: a chunk never straddles a DMA close); it is the aftermath of
     739              :                                 // a TX disable or reset mid-run, or a firmware fault.  Report everything that
     740              :                                 // locates it and stop, rather than silently dropping the chunk and continuing.
     741            0 :                                 std::stringstream ss;
     742            0 :                                 ss << "GetEVBDataAsEvents: FAFA framing error at word " << ptr << " of " << payloadWords
     743            0 :                                    << " (DMA " << dmaBytes << " bytes): chunk header 0x" << std::hex << std::setw(16) << std::setfill('0') << w << std::setfill(' ') << std::dec
     744            0 :                                    << " = src 0x" << std::hex << static_cast<int>(chunk_src) << std::dec << ", chunk_wc=" << chunk_wc
     745            0 :                                    << " exceeds remaining " << (payloadWords - ptr - 1) << " words (short by " << (ptr + 1 + chunk_wc - payloadWords)
     746            0 :                                    << "). Chunks already taken from this buffer (word:src/wc):";
     747            0 :                                 if (chunksSeenInThisBuffer.empty())
     748            0 :                                         ss << " none";
     749              :                                 else
     750            0 :                                         for (const auto& [chunkWord, chunkSrc, chunkWordCount] : chunksSeenInThisBuffer)
     751            0 :                                                 ss << " " << chunkWord << ":0x" << std::hex << static_cast<int>(chunkSrc) << std::dec << "/" << chunkWordCount;
     752              :                                 ss
     753            0 :                                         << ". Last released EWT=" << (evbHaveReleasedTag_ ? std::to_string(evbLastReleasedTag_) : std::string("none")) << " eventsReleased=" << evbEventsReleased_ << " openTags=" << evbPendingTags_.size()
     754            0 :                                         << "; reassembly bytes pending per source:";
     755            0 :                                 for (const auto& [sourceId, sourceBuffer] : evbPerSourceReassembly_)
     756            0 :                                         ss << " 0x" << std::hex << static_cast<int>(sourceId) << std::dec << "=" << sourceBuffer.size();
     757            0 :                                 ss << ".\n";
     758            0 :                                 const size_t dumpFromWord = ptr >= 16 ? ptr - 16 : 0;
     759            0 :                                 const size_t dumpBytes = std::min(static_cast<size_t>(payloadWords - dumpFromWord) * sizeof(uint64_t), static_cast<size_t>(1024));
     760            0 :                                 dumpRecord(ss, "DMA buffer from 16 words before the bad chunk header (offsets relative to that point)", chunk_src,
     761            0 :                                                    reinterpret_cast<const uint8_t*>(&words[dumpFromWord]), dumpBytes);
     762            0 :                                 auto lg = evbLastGoodRecord_.find(chunk_src);
     763            0 :                                 if (lg != evbLastGoodRecord_.end())
     764            0 :                                         dumpRecord(ss, "Last GOOD record (same src, for comparison)", chunk_src, lg->second.data(), lg->second.size());
     765              :                                 else
     766            0 :                                         ss << "(no previous good record from src=0x" << std::hex << static_cast<int>(chunk_src) << std::dec << ")\n";
     767            0 :                                 dumpEVBErrorStatus(ss);
     768            0 :                                 device_.resetSpyHasOccurred();
     769            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | 16, ss);
     770            0 :                                 DTC_TLOG(TLVL_ERROR) << ss.str();
     771            0 :                                 ++evbFramingErrors_;
     772            0 :                                 throw std::runtime_error(ss.str());
     773            0 :                         }
     774            0 :                         chunksSeenInThisBuffer.emplace_back(ptr, chunk_src, chunk_wc);
     775              : 
     776            0 :                         size_t chunkBytes = static_cast<size_t>(chunk_wc) * sizeof(uint64_t);
     777            0 :                         evbDrainedBytes_ += chunkBytes;
     778            0 :                         if (!evbDrainOnly_)
     779              :                         {
     780            0 :                                 auto& srcBuf = evbPerSourceReassembly_[chunk_src];
     781            0 :                                 const uint8_t* chunkData = reinterpret_cast<const uint8_t*>(&words[ptr + 1]);
     782            0 :                                 srcBuf.insert(srcBuf.end(), chunkData, chunkData + chunkBytes);
     783              :                         }
     784              : 
     785            0 :                         ++evbChunksParsed_;
     786            0 :                         if (chunk_src != evbLocalMac_ && chunk_wc < evbRemoteChunkSizeHistogram_.size())
     787            0 :                                 ++evbRemoteChunkSizeHistogram_[chunk_wc];
     788            0 :                         DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: FAFA chunk src=0x" << std::hex << static_cast<int>(chunk_src)
     789            0 :                                                                          << " wc=" << std::dec << chunk_wc << " words";
     790            0 :                         ptr += 1 + chunk_wc;
     791              :                 }
     792              :                 else
     793              :                 {
     794              :                         // close filler or end of data
     795            0 :                         break;
     796              :                 }
     797              :         }
     798              : 
     799            0 :         lap(evbReadProfile_.chunkWalkNs);
     800              : 
     801              :         // Drain-only: the DMA buffer is released on return; nothing else is done with the data
     802            0 :         if (evbDrainOnly_)
     803            0 :                 return output;
     804              : 
     805              :         // Step 4: extract complete subevents from each source's reassembly buffer
     806              :         // Each subevent (local and remote) is preceded by an 8-byte firmware
     807              :         // record header word (bits [15:0] = total record byte count including
     808              :         // this word).  The AXI demux routes the full ROC transfer to both the
     809              :         // local buffer manager and the DDR->10GbE path, so the record header
     810              :         // is present in both.  Skip it to reach the DTC_SubEventHeader.
     811            0 :         for (auto& [src, srcBuf] : evbPerSourceReassembly_)
     812              :         {
     813            0 :                 while (srcBuf.size() >= RECORD_HEADER_SIZE + sizeof(DTC_SubEventHeader))
     814              :                 {
     815            0 :                         const auto* subHdr = reinterpret_cast<const DTC_SubEventHeader*>(srcBuf.data() + RECORD_HEADER_SIZE);
     816            0 :                         size_t subEvtByteCount = subHdr->inclusive_subevent_byte_count;
     817              : 
     818            0 :                         if (subEvtByteCount < sizeof(DTC_SubEventHeader))
     819              :                         {
     820            0 :                                 DTC_TLOG(TLVL_ERROR) << "GetEVBDataAsEvents: invalid inclusive_subevent_byte_count=" << subEvtByteCount
     821            0 :                                                                          << " from source " << static_cast<int>(src) << "; clearing source buffer";
     822            0 :                                 ++evbFramingErrors_;
     823            0 :                                 srcBuf.clear();
     824            0 :                                 break;
     825              :                         }
     826              : 
     827            0 :                         size_t totalRecordBytes = RECORD_HEADER_SIZE + subEvtByteCount;
     828              : 
     829              :                         // Split records (EVB3_DMA_FAFA_protocol.md "Split-record layout", hw agent 2026-10-05):
     830              :                         // a subevent larger than one AXIMux DMA transfer (~64 KB) arrives as several records.
     831              :                         // The first carries the subevent header; each continuation record is only its own
     832              :                         // 8-byte count word (bytes incl. itself, low nibble 8) followed by the next raw words.
     833              :                         // Payloads (record bytes - 8) must add up exactly to the inclusive count.  Wait until
     834              :                         // every piece has arrived, then remove the continuation count words so the subevent
     835              :                         // is contiguous behind the first count word.
     836            0 :                         bool splitRecordJoined = false;
     837              :                         {
     838            0 :                                 const uint64_t firstCountWord = *reinterpret_cast<const uint64_t*>(srcBuf.data());
     839            0 :                                 const size_t firstRecordBytes = static_cast<size_t>(firstCountWord & 0xFFFF);
     840            0 :                                 if ((firstRecordBytes & 0xF) == 0x8 && firstRecordBytes > RECORD_HEADER_SIZE + sizeof(DTC_SubEventHeader) &&
     841            0 :                                         firstRecordBytes < totalRecordBytes)
     842              :                                 {
     843            0 :                                         std::vector<size_t> continuationCountWordOffsets;
     844            0 :                                         size_t payloadBytesCollected = firstRecordBytes - RECORD_HEADER_SIZE;
     845            0 :                                         size_t nextRecordOffset = firstRecordBytes;
     846            0 :                                         bool allPiecesPresent = true;
     847            0 :                                         std::string splitError;
     848            0 :                                         while (payloadBytesCollected < subEvtByteCount)
     849              :                                         {
     850            0 :                                                 if (srcBuf.size() < nextRecordOffset + RECORD_HEADER_SIZE)
     851              :                                                 {
     852            0 :                                                         allPiecesPresent = false;
     853            0 :                                                         break;
     854              :                                                 }
     855            0 :                                                 const uint64_t continuationCountWord = *reinterpret_cast<const uint64_t*>(srcBuf.data() + nextRecordOffset);
     856            0 :                                                 const size_t continuationRecordBytes = static_cast<size_t>(continuationCountWord & 0xFFFF);
     857            0 :                                                 const size_t continuationPayloadBytes = continuationRecordBytes - RECORD_HEADER_SIZE;
     858            0 :                                                 if ((continuationRecordBytes & 0xF) != 0x8 || continuationRecordBytes <= RECORD_HEADER_SIZE)
     859              :                                                 {
     860            0 :                                                         std::stringstream reason;
     861            0 :                                                         reason << "continuation count word at byte " << nextRecordOffset << " = 0x" << std::hex << std::setw(16) << std::setfill('0')
     862            0 :                                                                    << continuationCountWord << std::setfill(' ') << std::dec << " is not a count quadword (low nibble must be 8)";
     863            0 :                                                         splitError = reason.str();
     864            0 :                                                         break;
     865            0 :                                                 }
     866            0 :                                                 if (payloadBytesCollected + continuationPayloadBytes > subEvtByteCount)
     867              :                                                 {
     868            0 :                                                         std::stringstream reason;
     869            0 :                                                         reason << "continuation record at byte " << nextRecordOffset << " (" << continuationRecordBytes << " bytes) overshoots: "
     870            0 :                                                                    << payloadBytesCollected << " + " << continuationPayloadBytes << " > " << subEvtByteCount;
     871            0 :                                                         splitError = reason.str();
     872            0 :                                                         break;
     873            0 :                                                 }
     874            0 :                                                 if (srcBuf.size() < nextRecordOffset + continuationRecordBytes)
     875              :                                                 {
     876            0 :                                                         allPiecesPresent = false;
     877            0 :                                                         break;
     878              :                                                 }
     879            0 :                                                 continuationCountWordOffsets.push_back(nextRecordOffset);
     880            0 :                                                 payloadBytesCollected += continuationPayloadBytes;
     881            0 :                                                 nextRecordOffset += continuationRecordBytes;
     882              :                                         }
     883              : 
     884            0 :                                         if (!splitError.empty())
     885              :                                         {
     886            0 :                                                 std::stringstream ss;
     887            0 :                                                 ss << "GetEVBDataAsEvents: split subevent from src=0x" << std::hex << static_cast<int>(src) << std::dec
     888            0 :                                                    << " cannot be reassembled: first record " << firstRecordBytes << " bytes, subevent inclusive count " << subEvtByteCount
     889            0 :                                                    << "; " << splitError << "; tag_low=" << subHdr->event_tag_low << ".\n";
     890            0 :                                                 dumpRecord(ss, "BAD split record (reassembly buffer head)", src, srcBuf.data(), std::min(srcBuf.size(), static_cast<size_t>(1024)));
     891            0 :                                                 if (nextRecordOffset < srcBuf.size())
     892            0 :                                                         dumpRecord(ss, "at the bad continuation", src, srcBuf.data() + nextRecordOffset,
     893            0 :                                                                            std::min(srcBuf.size() - nextRecordOffset, static_cast<size_t>(256)));
     894            0 :                                                 dumpEVBErrorStatus(ss);
     895            0 :                                                 DTC_TLOG(TLVL_ERROR) << ss.str();
     896            0 :                                                 ++evbFramingErrors_;
     897            0 :                                                 srcBuf.clear();
     898            0 :                                                 throw std::runtime_error(ss.str());
     899            0 :                                         }
     900            0 :                                         if (!allPiecesPresent)
     901              :                                         {
     902            0 :                                                 DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: src=0x" << std::hex << static_cast<int>(src) << std::dec
     903            0 :                                                                                                  << " split subevent incomplete: have " << payloadBytesCollected << " of " << subEvtByteCount
     904            0 :                                                                                                  << " payload bytes in " << (continuationCountWordOffsets.size() + 1) << " record(s); waiting for more chunks";
     905            0 :                                                 break;
     906              :                                         }
     907              :                                         // remove the continuation count words, last first so earlier offsets stay valid
     908            0 :                                         for (auto offsetIterator = continuationCountWordOffsets.rbegin(); offsetIterator != continuationCountWordOffsets.rend(); ++offsetIterator)
     909            0 :                                                 srcBuf.erase(srcBuf.begin() + *offsetIterator, srcBuf.begin() + *offsetIterator + RECORD_HEADER_SIZE);
     910            0 :                                         splitRecordJoined = true;
     911            0 :                                         ++evbSplitSubeventsJoined_;
     912            0 :                                         DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: src=0x" << std::hex << static_cast<int>(src) << std::dec << " joined split subevent tag_low="
     913            0 :                                                                                          << subHdr->event_tag_low << " from " << (continuationCountWordOffsets.size() + 1) << " records, " << subEvtByteCount << " bytes";
     914            0 :                                 }
     915              :                         }
     916              : 
     917              :                         // Header consistency BEFORE waiting for more data: the observed firmware record
     918              :                         // header word bits[15:0] = total record bytes = subevent inclusive count + 8.  If a
     919              :                         // word-replacement corruption lands on either size field, the two disagree; without
     920              :                         // this check a too-large count would silently "wait for more chunks" forever.
     921              :                         {
     922            0 :                                 uint64_t recHdrWord = *reinterpret_cast<const uint64_t*>(srcBuf.data());
     923            0 :                                 size_t recHdrBytes = static_cast<size_t>(recHdrWord & 0xFFFF);
     924            0 :                                 bool recHdrOK = splitRecordJoined || (recHdrBytes == totalRecordBytes);
     925            0 :                                 bool numRocsOK = (subHdr->num_rocs >= 1 && subHdr->num_rocs <= 6);
     926            0 :                                 if (!recHdrOK || !numRocsOK)
     927              :                                 {
     928            0 :                                         std::stringstream ss;
     929            0 :                                         ss << "GetEVBDataAsEvents: record header inconsistent from src=0x" << std::hex << static_cast<int>(src) << std::dec
     930            0 :                                            << ": record header word=0x" << std::hex << std::setw(16) << std::setfill('0') << recHdrWord << std::setfill(' ') << std::dec
     931            0 :                                            << " -> " << recHdrBytes << " bytes, but subevent inclusive count=" << subEvtByteCount
     932            0 :                                            << " (+8 = " << totalRecordBytes << ")" << (recHdrOK ? "" : " MISMATCH")
     933            0 :                                            << "; tag_low=" << subHdr->event_tag_low << " tag_high=0x" << std::hex << subHdr->event_tag_high << std::dec
     934            0 :                                            << " num_rocs=" << subHdr->num_rocs << (numRocsOK ? "" : " (INVALID, expect 1..6)")
     935            0 :                                            << ". Aborting rather than waiting forever for a corrupt-sized record.\n";
     936            0 :                                         size_t dumpBytes = std::min(srcBuf.size(), static_cast<size_t>(1024));
     937            0 :                                         dumpRecord(ss, "BAD record (reassembly buffer head)", src, srcBuf.data(), dumpBytes);
     938            0 :                                         auto lg = evbLastGoodRecord_.find(src);
     939            0 :                                         if (lg != evbLastGoodRecord_.end())
     940            0 :                                                 dumpRecord(ss, "Last GOOD record (same src, for comparison)", src, lg->second.data(), lg->second.size());
     941              :                                         else
     942            0 :                                                 ss << "(no previous good record from src=0x" << std::hex << static_cast<int>(src) << std::dec << ")\n";
     943            0 :                                         dumpEVBErrorStatus(ss);
     944            0 :                                         device_.resetSpyHasOccurred();
     945            0 :                                         device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | 16, ss);
     946            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str();
     947            0 :                                         ++evbFramingErrors_;
     948            0 :                                         srcBuf.clear();
     949            0 :                                         throw std::runtime_error(ss.str());
     950            0 :                                 }
     951              :                         }
     952              : 
     953            0 :                         if (srcBuf.size() < totalRecordBytes)
     954              :                         {
     955            0 :                                 DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: src=0x" << std::hex << static_cast<int>(src) << std::dec
     956            0 :                                                                                  << " record incomplete: have " << srcBuf.size() << " of " << totalRecordBytes << " bytes; waiting for more chunks";
     957            0 :                                 break;  // incomplete subevent, wait for more chunks
     958              :                         }
     959            0 :                         lap(evbReadProfile_.recordScanNs);
     960              : 
     961            0 :                         size_t eventSize = sizeof(DTC_EventHeader) + subEvtByteCount;
     962            0 :                         auto event = std::make_shared<DTC_Event>(eventSize);
     963              : 
     964            0 :                         DTC_EventHeader evtHdr{};
     965            0 :                         evtHdr.inclusive_event_byte_count = eventSize;
     966            0 :                         evtHdr.num_dtcs = 1;
     967            0 :                         evtHdr.event_tag_low = subHdr->event_tag_low;
     968            0 :                         evtHdr.event_tag_high = subHdr->event_tag_high;
     969              : 
     970            0 :                         if (subHdr->subevent_format_version != CURRENT_SUBEVENT_FORMAT_VERSION)
     971              :                         {
     972            0 :                                 std::stringstream ss;
     973            0 :                                 ss << "GetEVBDataAsEvents: subevent_format_version mismatch: 0x"
     974            0 :                                    << std::hex << subHdr->subevent_format_version
     975            0 :                                    << " != expected 0x" << static_cast<uint16_t>(CURRENT_SUBEVENT_FORMAT_VERSION)
     976            0 :                                    << ". Check that your DTC FPGA version matches the software expectation.\n";
     977            0 :                                 dumpRecord(ss, "BAD record", src, srcBuf.data(), totalRecordBytes);
     978            0 :                                 dumpEVBErrorStatus(ss);
     979            0 :                                 device_.resetSpyHasOccurred();
     980            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | 16, ss);
     981            0 :                                 DTC_TLOG(TLVL_ERROR) << ss.str();
     982            0 :                                 ++evbFramingErrors_;
     983            0 :                                 srcBuf.erase(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
     984            0 :                                 throw std::runtime_error(ss.str());
     985            0 :                         }
     986              : 
     987              :                         // Guard: DTC_SubEvent::SetupSubEvent() never returns if the FIRST ROC data header
     988              :                         // (subevent offset 48) is not a DataHeader packet -- its while-loop catches the
     989              :                         // constructor exception without advancing byte_count and spins forever, flooding
     990              :                         // the log.  Pre-check with the same test SetupSubEvent uses, and throw with a dump.
     991            0 :                         if (subEvtByteCount >= sizeof(DTC_SubEventHeader) + 16)
     992              :                         {
     993            0 :                                 const uint8_t* roc0Hdr = srcBuf.data() + RECORD_HEADER_SIZE + sizeof(DTC_SubEventHeader);
     994            0 :                                 DTC_EventWindowTag tag(static_cast<uint32_t>(subHdr->event_tag_low), static_cast<uint16_t>(subHdr->event_tag_high));
     995            0 :                                 bool hdrOK = DTC_DataHeaderPacket::IsDataHeaderPacket(roc0Hdr, tag);
     996            0 :                                 if (!hdrOK)
     997              :                                 {
     998            0 :                                         std::stringstream ss;
     999            0 :                                         ss << "GetEVBDataAsEvents: record failed pre-parse validation from src=0x" << std::hex << static_cast<int>(src) << std::dec
    1000            0 :                                            << ": subevent header tag_low=" << subHdr->event_tag_low
    1001            0 :                                            << " tag_high=0x" << std::hex << subHdr->event_tag_high << std::dec
    1002            0 :                                            << " num_rocs=" << subHdr->num_rocs
    1003            0 :                                            << "; first ROC data header word (offset 48) = 0x" << std::hex << std::setw(16) << std::setfill('0')
    1004            0 :                                            << *reinterpret_cast<const uint64_t*>(roc0Hdr) << std::setfill(' ') << std::dec
    1005              :                                            << (hdrOK ? " (valid)" : " (FAILED IsDataHeaderPacket check against the header's tag)")
    1006            0 :                                            << ". A corrupt header makes SetupSubEvent loop forever; aborting.\n";
    1007            0 :                                         dumpRecord(ss, "BAD record", src, srcBuf.data(), totalRecordBytes);
    1008            0 :                                         auto lg = evbLastGoodRecord_.find(src);
    1009            0 :                                         if (lg != evbLastGoodRecord_.end())
    1010            0 :                                                 dumpRecord(ss, "Last GOOD record (same src, for comparison)", src, lg->second.data(), lg->second.size());
    1011              :                                         else
    1012            0 :                                                 ss << "(no previous good record from src=0x" << std::hex << static_cast<int>(src) << std::dec << ")\n";
    1013            0 :                                         dumpEVBErrorStatus(ss);
    1014            0 :                                         device_.resetSpyHasOccurred();
    1015            0 :                                         device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | 16, ss);
    1016            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1017            0 :                                         ++evbFramingErrors_;
    1018            0 :                                         srcBuf.erase(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
    1019            0 :                                         throw std::runtime_error(ss.str());
    1020            0 :                                 }
    1021            0 :                         }
    1022              : 
    1023            0 :                         DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: parsing subevent src=0x" << std::hex << static_cast<int>(src)
    1024            0 :                                                                          << " EWT=" << std::dec << ((static_cast<uint64_t>(subHdr->event_tag_high) << 32) | subHdr->event_tag_low)
    1025            0 :                                                                          << " bytes=" << subEvtByteCount << " num_rocs=" << subHdr->num_rocs
    1026            0 :                                                                          << " dtc_mac=0x" << std::hex << subHdr->dtc_mac
    1027            0 :                                                                          << " fmt=0x" << subHdr->subevent_format_version << std::dec;
    1028              : 
    1029            0 :                         uint8_t* eventBuf = static_cast<uint8_t*>(const_cast<void*>(event->GetRawBufferPointer()));
    1030            0 :                         memcpy(eventBuf, &evtHdr, sizeof(DTC_EventHeader));
    1031            0 :                         *event->GetHeader() = evtHdr;
    1032              : 
    1033            0 :                         memcpy(eventBuf + sizeof(DTC_EventHeader), srcBuf.data() + RECORD_HEADER_SIZE, subEvtByteCount);
    1034              : 
    1035            0 :                         event->SetupEvent();
    1036              : 
    1037            0 :                         if (event->IsCorrupt())
    1038              :                         {
    1039            0 :                                 std::stringstream ss;
    1040            0 :                                 ss << "GetEVBDataAsEvents: event corruption detected at EWT="
    1041            0 :                                    << event->GetEventWindowTag().GetEventWindowTag(true)
    1042            0 :                                    << " from src=0x" << std::hex << static_cast<int>(src) << std::dec << "\n";
    1043            0 :                                 dumpRecord(ss, "BAD record", src, srcBuf.data(), totalRecordBytes);
    1044            0 :                                 auto lg = evbLastGoodRecord_.find(src);
    1045            0 :                                 if (lg != evbLastGoodRecord_.end())
    1046            0 :                                         dumpRecord(ss, "Last GOOD record (same src, for comparison)", src, lg->second.data(), lg->second.size());
    1047              :                                 else
    1048            0 :                                         ss << "(no previous good record from src=0x" << std::hex << static_cast<int>(src) << std::dec << ")\n";
    1049            0 :                                 dumpEVBErrorStatus(ss);
    1050            0 :                                 device_.resetSpyHasOccurred();
    1051            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 | 8 | 16, ss);
    1052            0 :                                 DTC_TLOG(TLVL_ERROR) << ss.str();
    1053            0 :                                 srcBuf.erase(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
    1054            0 :                                 throw std::runtime_error(ss.str());
    1055            0 :                         }
    1056              : 
    1057            0 :                         lap(evbReadProfile_.subeventSetupNs);
    1058            0 :                         evbLastGoodRecord_[src].assign(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
    1059              : 
    1060              :                         // Stage the validated subevent (record header stripped) into the per-tag assembly
    1061              :                         // table.  Subevents for one tag arrive once from each of the N source DTCs, in no
    1062              :                         // fixed order and intermixed with other tags; the event is released only when all
    1063              :                         // N are present (Step 5).  A second delivery from the same source is an error.
    1064              :                         {
    1065            0 :                                 uint64_t tag = (static_cast<uint64_t>(subHdr->event_tag_high) << 32) | subHdr->event_tag_low;
    1066            0 :                                 uint8_t srcId = static_cast<uint8_t>(subHdr->source_dtc_id);
    1067            0 :                                 auto& pend = evbPendingTags_[tag];
    1068            0 :                                 if (pend.subevents.empty())
    1069            0 :                                         pend.firstArrival = std::chrono::steady_clock::now();
    1070            0 :                                 if (pend.subevents.count(srcId))
    1071              :                                 {
    1072            0 :                                         std::stringstream ss;
    1073            0 :                                         ss << "GetEVBDataAsEvents: DUPLICATE subevent for EWT=" << tag << " from source_dtc_id=0x" << std::hex << static_cast<int>(srcId)
    1074            0 :                                            << " (chunk src=0x" << static_cast<int>(src) << std::dec << "); this source already delivered this tag.\n";
    1075            0 :                                         dumpRecord(ss, "DUPLICATE record", src, srcBuf.data(), totalRecordBytes);
    1076            0 :                                         describePending(ss, tag, pend);
    1077            0 :                                         dumpEVBErrorStatus(ss);
    1078            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1079            0 :                                         ++evbFramingErrors_;
    1080            0 :                                         srcBuf.erase(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
    1081            0 :                                         throw std::runtime_error(ss.str());
    1082            0 :                                 }
    1083            0 :                                 pend.subevents[srcId].assign(srcBuf.begin() + RECORD_HEADER_SIZE, srcBuf.begin() + totalRecordBytes);
    1084            0 :                                 DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: staged EWT=" << tag << " source_dtc_id=0x" << std::hex << static_cast<int>(srcId) << std::dec
    1085            0 :                                                                                  << " (" << pend.subevents.size() << "/" << static_cast<int>(evbNumSources_) << " sources); open tags=" << evbPendingTags_.size();
    1086              :                         }
    1087              : 
    1088            0 :                         srcBuf.erase(srcBuf.begin(), srcBuf.begin() + totalRecordBytes);
    1089            0 :                         ++evbReadProfile_.recordsExtracted;
    1090            0 :                         lap(evbReadProfile_.stagingNs);
    1091            0 :                 }
    1092              :         }
    1093            0 :         lap(evbReadProfile_.recordScanNs);  // loop exits (incomplete record / empty source) land here
    1094              : 
    1095              :         // Step 5: release complete events, strictly in increasing tag order.  Because each source
    1096              :         // delivers its tags in increasing order and every source contributes to every tag owned by
    1097              :         // this DTC, the lowest pending tag is always the next to complete; a later tag completing
    1098              :         // first means a record was lost for the lower tag, which Step 0's timeout will report.
    1099            0 :         while (!evbPendingTags_.empty())
    1100              :         {
    1101            0 :                 auto it = evbPendingTags_.begin();
    1102            0 :                 auto& pend = it->second;
    1103            0 :                 if (pend.subevents.size() < evbNumSources_)
    1104            0 :                         break;  // oldest tag still incomplete: wait (Step 0 guards against waiting forever)
    1105              : 
    1106            0 :                 uint64_t tag = it->first;
    1107            0 :                 if (evbHaveReleasedTag_ && tag <= evbLastReleasedTag_)
    1108              :                 {
    1109            0 :                         std::stringstream ss;
    1110            0 :                         ss << "GetEVBDataAsEvents: ORDERING violation -- completed EWT=" << tag << " is not greater than last released EWT=" << evbLastReleasedTag_ << ".\n";
    1111            0 :                         describePending(ss, tag, pend);
    1112            0 :                         dumpEVBErrorStatus(ss);
    1113            0 :                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1114            0 :                         ++evbFramingErrors_;
    1115            0 :                         throw std::runtime_error(ss.str());
    1116            0 :                 }
    1117              : 
    1118            0 :                 size_t total = 0;
    1119            0 :                 for (auto& [sid, bytes] : pend.subevents) total += bytes.size();
    1120            0 :                 size_t eventSize = sizeof(DTC_EventHeader) + total;
    1121            0 :                 auto event = std::make_shared<DTC_Event>(eventSize);
    1122              : 
    1123            0 :                 const auto* firstSub = reinterpret_cast<const DTC_SubEventHeader*>(pend.subevents.begin()->second.data());
    1124            0 :                 DTC_EventHeader evtHdr{};
    1125            0 :                 evtHdr.inclusive_event_byte_count = eventSize;
    1126            0 :                 evtHdr.num_dtcs = pend.subevents.size();
    1127            0 :                 evtHdr.event_tag_low = static_cast<uint32_t>(tag & 0xFFFFFFFF);
    1128            0 :                 evtHdr.event_tag_high = static_cast<uint16_t>(tag >> 32);
    1129            0 :                 evtHdr.event_mode = firstSub->event_mode;
    1130            0 :                 evtHdr.dtc_mac = evbLocalMac_;
    1131            0 :                 evtHdr.partition_id = firstSub->partition_id;
    1132            0 :                 evtHdr.evb_mode = firstSub->evb_mode;
    1133              : 
    1134            0 :                 uint8_t* buf = static_cast<uint8_t*>(const_cast<void*>(event->GetRawBufferPointer()));
    1135            0 :                 memcpy(buf, &evtHdr, sizeof(DTC_EventHeader));
    1136            0 :                 *event->GetHeader() = evtHdr;
    1137            0 :                 size_t off = sizeof(DTC_EventHeader);
    1138            0 :                 for (auto& [sid, bytes] : pend.subevents)  // std::map: ascending source_dtc_id
    1139              :                 {
    1140            0 :                         memcpy(buf + off, bytes.data(), bytes.size());
    1141            0 :                         off += bytes.size();
    1142              :                 }
    1143              : 
    1144            0 :                 event->SetupEvent();
    1145            0 :                 if (event->IsCorrupt())
    1146              :                 {
    1147            0 :                         std::stringstream ss;
    1148            0 :                         ss << "GetEVBDataAsEvents: assembled event EWT=" << tag << " (" << pend.subevents.size() << " subevents, " << eventSize
    1149            0 :                            << " bytes) failed SetupEvent although each subevent validated individually.\n";
    1150            0 :                         describePending(ss, tag, pend);
    1151            0 :                         dumpEVBErrorStatus(ss);
    1152            0 :                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1153            0 :                         ++evbFramingErrors_;
    1154            0 :                         throw std::runtime_error(ss.str());
    1155            0 :                 }
    1156              : 
    1157            0 :                 evbLastReleasedTag_ = tag;
    1158            0 :                 evbHaveReleasedTag_ = true;
    1159            0 :                 ++evbEventsReleased_;
    1160            0 :                 DTC_TLOG(TLVL_DEBUG + 1) << "GetEVBDataAsEvents: released complete event EWT=" << tag << " with " << pend.subevents.size()
    1161            0 :                                                                  << " subevents, " << eventSize << " bytes; open tags=" << (evbPendingTags_.size() - 1);
    1162            0 :                 output.push_back(std::move(event));
    1163            0 :                 evbPendingTags_.erase(it);
    1164            0 :         }
    1165            0 :         lap(evbReadProfile_.eventReleaseNs);
    1166              : 
    1167              :         // Step 6: the DMA buffer is released by dmaRelease (RAII, declared after read_data) on return.
    1168            0 :         return output;
    1169            0 : }  // end GetEVBDataAsEvents()
    1170              : 
    1171            0 : std::string DTCLib::DTC::FormatEVBReadProfile() const
    1172              : {
    1173            0 :         const EVBReadProfile& profile = evbReadProfile_;
    1174            0 :         std::ostringstream o;
    1175            0 :         if (profile.calls == 0)
    1176              :         {
    1177            0 :                 o << "no GetEVBDataAsEvents calls since reset";
    1178            0 :                 return o.str();
    1179              :         }
    1180            0 :         const double totalMs = profile.totalNs / 1e6;
    1181            0 :         o << "calls " << profile.calls << " (" << profile.callsWithData << " with data, " << (profile.calls - profile.callsWithData) << " idle), records "
    1182            0 :           << profile.recordsExtracted << ", events released " << evbEventsReleased_ << ", time in GetEVBDataAsEvents " << std::fixed << std::setprecision(1)
    1183            0 :           << totalMs << " ms\n";
    1184            0 :         auto row = [&](const char* label, uint64_t ns, uint64_t perCount, const char* perLabel) {
    1185            0 :                 o << "    " << std::left << std::setw(36) << label << std::right << std::setw(10) << std::fixed << std::setprecision(1) << (ns / 1e6) << " ms "
    1186            0 :                   << std::setw(6) << std::setprecision(1) << (profile.totalNs ? 100.0 * ns / profile.totalNs : 0.0) << " %";
    1187            0 :                 if (perCount)
    1188            0 :                         o << "   " << std::setw(8) << std::setprecision(2) << (ns / 1e3 / perCount) << " us/" << perLabel;
    1189            0 :                 o << "\n";
    1190            0 :         };
    1191            0 :         const uint64_t dataCalls = profile.callsWithData ? profile.callsWithData : 1;
    1192            0 :         row("Step 0 open-tag timeout scan", profile.timeoutScanNs, profile.calls, "call");
    1193            0 :         row("Step 1 read_data (incl. idle wait)", profile.readDataNs, profile.calls, "call");
    1194            0 :         row("Step 3 FAFA chunk walk + copy", profile.chunkWalkNs, dataCalls, "buffer");
    1195            0 :         row("Step 2b register reads (N, MAC, once)", profile.registerReadNs, dataCalls, "buffer");
    1196            0 :         row("Step 4b record scan + checks", profile.recordScanNs, dataCalls, "buffer");
    1197            0 :         row("Step 4c DTC_Event alloc+copy+Setup", profile.subeventSetupNs, profile.recordsExtracted, "record");
    1198            0 :         row("Step 4d staging copies + erase", profile.stagingNs, profile.recordsExtracted, "record");
    1199            0 :         row("Step 5 event assembly + release", profile.eventReleaseNs, evbEventsReleased_, "event");
    1200            0 :         row("DMA read_release", profile.releaseBufferNs, dataCalls, "buffer");
    1201            0 :         const uint64_t accounted = profile.timeoutScanNs + profile.readDataNs + profile.chunkWalkNs + profile.registerReadNs + profile.recordScanNs +
    1202            0 :                                                            profile.subeventSetupNs + profile.stagingNs + profile.eventReleaseNs + profile.releaseBufferNs;
    1203            0 :         row("(unaccounted: clock reads, returns)", profile.totalNs > accounted ? profile.totalNs - accounted : 0, 0, "");
    1204            0 :         return o.str();
    1205            0 : }  // end FormatEVBReadProfile()
    1206              : 
    1207            0 : std::string DTCLib::DTC::FormatEVBRemoteChunkSizes() const
    1208              : {
    1209            0 :         uint64_t total = 0, under64 = 0, weightedWords = 0;
    1210            0 :         for (size_t words = 0; words < evbRemoteChunkSizeHistogram_.size(); ++words)
    1211              :         {
    1212            0 :                 total += evbRemoteChunkSizeHistogram_[words];
    1213            0 :                 weightedWords += evbRemoteChunkSizeHistogram_[words] * words;
    1214            0 :                 if (words < 64) under64 += evbRemoteChunkSizeHistogram_[words];
    1215              :         }
    1216            0 :         std::ostringstream o;
    1217            0 :         if (total == 0)
    1218              :         {
    1219            0 :                 o << "no remote chunks seen";
    1220            0 :                 return o.str();
    1221              :         }
    1222            0 :         uint64_t running = 0;
    1223            0 :         size_t median = 0;
    1224            0 :         for (size_t words = 0; words < evbRemoteChunkSizeHistogram_.size(); ++words)
    1225              :         {
    1226            0 :                 running += evbRemoteChunkSizeHistogram_[words];
    1227            0 :                 if (running * 2 >= total)
    1228              :                 {
    1229            0 :                         median = words;
    1230            0 :                         break;
    1231              :                 }
    1232              :         }
    1233            0 :         std::vector<std::pair<uint64_t, size_t>> topSizes;
    1234            0 :         for (size_t words = 0; words < evbRemoteChunkSizeHistogram_.size(); ++words)
    1235            0 :                 if (evbRemoteChunkSizeHistogram_[words]) topSizes.emplace_back(evbRemoteChunkSizeHistogram_[words], words);
    1236            0 :         std::sort(topSizes.rbegin(), topSizes.rend());
    1237            0 :         o << total << " remote chunks, median " << median << " words, mean " << std::fixed << std::setprecision(1)
    1238            0 :           << (static_cast<double>(weightedWords) / total) << " words, under 64 words: " << under64 << " (" << std::setprecision(1)
    1239            0 :           << (100.0 * under64 / total) << " %); most common sizes (words:count):";
    1240            0 :         for (size_t index = 0; index < topSizes.size() && index < 8; ++index)
    1241            0 :                 o << " " << topSizes[index].second << ":" << topSizes[index].first;
    1242            0 :         return o.str();
    1243            0 : }  // end FormatEVBRemoteChunkSizes()
    1244              : 
    1245              : // ---------------------------------------------------------------------------
    1246              : // GetSubEventData v2 -- simplified, one-buffer-per-call subevent extractor
    1247              : // ---------------------------------------------------------------------------
    1248              : // Design rules:
    1249              : //   • Each call reads exactly ONE new DMA buffer from hardware (or completes a
    1250              : //     pending cross-buffer subevent from the previous call) then returns.
    1251              : //   • All complete subevents found inside that buffer are returned immediately.
    1252              : //   • If a subevent straddles the end of the buffer, we save the partial bytes
    1253              : //     into pendingSubEventBytes_ and return.  On the next call we finish it
    1254              : //     before touching the new buffer.
    1255              : //   • We never hold more than 2 DMA buffers at once (the previous one, still
    1256              : //     referenced by pending data, plus the new one).  As soon as we are done
    1257              : //     with a buffer we release it via read_release.
    1258              : // ---------------------------------------------------------------------------
    1259            0 : std::vector<std::unique_ptr<DTCLib::DTC_SubEvent>> DTCLib::DTC::GetSubEventData(
    1260              :         DTC_EventWindowTag when, bool matchEventWindowTag)
    1261              : {
    1262              :         (void)matchEventWindowTag;  // not yet used; filtering can be added once basic flow works
    1263            0 :         std::vector<std::unique_ptr<DTC_SubEvent>> output;
    1264              : 
    1265              :         // ------------------------------------------------------------------
    1266              :         // Step 1: get the next DMA buffer from hardware
    1267              :         // ------------------------------------------------------------------
    1268            0 :         DTC_TLOG(TLVL_GetData_2) << "GetSubEventData BEGIN EWT=" << when.GetEventWindowTag(true)
    1269            0 :                                                          << " pendingBytes=" << pendingSubEventBytes_.size()
    1270            0 :                                                          << " pendingTotal=" << pendingSubEventTotalBytes_
    1271            0 :                                                          << " pendingPrefixConsumed=" << pendingPrefixConsumed_
    1272            0 :                                                          << " daqBufs=" << daqDMAInfo_.buffer.size();
    1273              : 
    1274              :         // Release the previous buffer NOW — we are done with it.
    1275              :         // (Pending bytes have already been copied out of it into pendingSubEventBytes_.)
    1276            0 :         if (daqDMAInfo_.buffer.size() > 0)
    1277              :         {
    1278            0 :                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: releasing " << daqDMAInfo_.buffer.size() << " previous buffer(s)";
    1279            0 :                 ReleaseBuffers(DTC_DMA_Engine_DAQ);
    1280              :         }
    1281              : 
    1282              :         // Read next buffer
    1283              :         int sts;
    1284              :         {
    1285            0 :                 int retry = 3;
    1286              :                 do {
    1287            0 :                         sts = ReadBuffer(DTC_DMA_Engine_DAQ, 100 /*ms*/);
    1288            0 :                 } while (sts <= 0 && --retry > 0);
    1289              :         }
    1290            0 :         if (sts <= 0)
    1291              :         {
    1292            0 :                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: ReadBuffer returned " << sts << ", no data";
    1293            0 :                 return output;
    1294              :         }
    1295              : 
    1296              :         // sts = number of bytes the DMA engine transferred into this buffer
    1297            0 :         const size_t dmaBytes = static_cast<size_t>(sts);
    1298              : 
    1299              :         // Track whether PREVIOUS buffer was full (for diagnostics) and update for this buffer.
    1300            0 :         const bool prevBufferWasFull = lastDMABufferWasFull_;
    1301            0 :         lastDMABufferWasFull_ = (dmaBytes == sizeof(mu2e_databuff_t));
    1302              : 
    1303            0 :         const uint8_t* bufStart = reinterpret_cast<const uint8_t*>(&daqDMAInfo_.buffer.back()[0]);
    1304              :         // The DMA descriptor byte count (sts) is the number of bytes actually filled.
    1305              :         // The last byte is the AXI tlast flag byte when the buffer is not completely full.
    1306              :         // When it IS completely full (dmaBytes == sizeof(mu2e_databuff_t)) there is no tlast byte.
    1307            0 :         const size_t payloadBytes = (dmaBytes < sizeof(mu2e_databuff_t)) ? dmaBytes - 1 : dmaBytes;
    1308              : 
    1309            0 :         DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: new buffer bufStart=" << (void*)bufStart
    1310            0 :                                                          << " dmaBytes=" << dmaBytes
    1311            0 :                                                          << " payloadBytes=" << payloadBytes
    1312            0 :                                                          << " sizeof(mu2e_databuff_t)=" << sizeof(mu2e_databuff_t);
    1313              : 
    1314              :         // Print first and last 8 quad-words of buffer for orientation / continuity checking
    1315            0 :         if (TTEST(TLVL_GetData_2 - TLVL_DEBUG))
    1316              :         {
    1317              :                 {
    1318            0 :                         std::stringstream ss;
    1319              :                         // TLOG_SCOPED(TLVL_GetData_2) <<
    1320            0 :                         ss << "GetSubEventData: buffer first 8 qwords: ";
    1321            0 :                         for (int i = 0; i < 8 && (size_t)(i * 8) < payloadBytes; ++i)
    1322              :                                 // TLOG_ADD
    1323            0 :                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1324            0 :                                    << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1325            0 :                         DTC_TLOG(TLVL_GetData_2) << ss.str();
    1326            0 :                 }
    1327              : 
    1328              :                 {
    1329            0 :                         std::stringstream ss;
    1330              :                         // TLOG_SCOPED(TLVL_GetData_2) <<
    1331            0 :                         ss << "GetSubEventData: buffer last  8 qwords: ";
    1332            0 :                         const size_t lastStart = (payloadBytes >= 64) ? payloadBytes - 64 : 0;
    1333            0 :                         for (size_t i = lastStart; i + 8 <= payloadBytes; i += 8)
    1334              :                                 // TLOG_ADD
    1335            0 :                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1336            0 :                                    << *reinterpret_cast<const uint64_t*>(bufStart + i) << " ";
    1337            0 :                         DTC_TLOG(TLVL_GetData_2) << ss.str();
    1338            0 :                 }
    1339              :         }
    1340              : 
    1341              :         // Capture this buffer's tail (last up to 8 qwords) for the next call's diagnostic printout.
    1342              :         // Called at every return path so lastBufferTailQwords_ always reflects the most recently
    1343              :         // processed buffer by the time the next call enters Step 2.
    1344            0 :         auto saveBufferTail = [&]() {
    1345            0 :                 const size_t tailStart = (payloadBytes >= 64) ? payloadBytes - 64 : 0;
    1346            0 :                 lastBufferTailCount_ = 0;
    1347            0 :                 for (size_t i = tailStart; i + 8 <= payloadBytes && lastBufferTailCount_ < 8; i += 8)
    1348            0 :                         lastBufferTailQwords_[lastBufferTailCount_++] =
    1349            0 :                                 *reinterpret_cast<const uint64_t*>(bufStart + i);
    1350            0 :         };
    1351              : 
    1352              :         // Returns the last good event window tag as a string, or "-1" if unavailable.
    1353            0 :         auto ewtTag = [&]() -> std::string {
    1354            0 :                 if (!hasLastGoodSubEventHeader_) return "-1";
    1355            0 :                 return std::to_string(lastGoodSubEventHeader_.event_tag_low |
    1356            0 :                                                           (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32));
    1357            0 :         };
    1358              : 
    1359              :         // ------------------------------------------------------------------
    1360              :         // Step 2: if we have a pending (cross-buffer) subevent, finish it
    1361              :         //         using data from the new buffer before parsing the rest
    1362              :         // ------------------------------------------------------------------
    1363            0 :         size_t bufOffset = 0;  // our walk position within this buffer
    1364              : 
    1365            0 :         if (!pendingSubEventBytes_.empty() || pendingPrefixConsumed_)
    1366              :         {
    1367              :                 // Prefix handling for continuation buffers:
    1368              :                 //
    1369              :                 // When the previous buffer was completely full (prevBufferWasFull == true),
    1370              :                 // the hardware split mid-sub-transfer.  This buffer is a raw continuation —
    1371              :                 // NO framing prefix at offset 0.  bufOffset = 0.
    1372              :                 //
    1373              :                 // When the previous buffer was NOT full (had tlast, prevBufferWasFull == false),
    1374              :                 // the sub-transfer completed.  This buffer starts a NEW sub-transfer with an
    1375              :                 // 8-byte per-sub-transfer byte count prefix.  bufOffset = 8.
    1376              :                 //
    1377              :                 // When pendingPrefixConsumed_ is true, the prefix was already consumed in the
    1378              :                 // previous buffer's Step 3.  This buffer starts at offset 0.
    1379              : 
    1380              :                 // Use TLVL_Crossover only for the interesting cases (header/prefix crossover).
    1381            0 :                 const bool isHeaderOrPrefixCrossover = (pendingSubEventTotalBytes_ == 0) || pendingPrefixConsumed_;
    1382            0 :                 const auto crossoverLogLevel = isHeaderOrPrefixCrossover ? TLVL_Crossover : TLVL_GetData_2;
    1383              : 
    1384            0 :                 if (pendingPrefixConsumed_)
    1385              :                 {
    1386              :                         // Prefix was already consumed in previous buffer's Step 3
    1387            0 :                         bufOffset = 0;
    1388              :                 }
    1389            0 :                 else if (prevBufferWasFull)
    1390              :                 {
    1391              :                         // Previous buffer was full — raw continuation, no prefix
    1392            0 :                         bufOffset = 0;
    1393              :                 }
    1394              :                 else
    1395              :                 {
    1396              :                         // Previous buffer was NOT full — new sub-transfer with 8-byte prefix
    1397            0 :                         if (payloadBytes < sizeof(uint64_t))
    1398              :                         {
    1399            0 :                                 DTC_TLOG(TLVL_ERROR) << "GetSubEventData: continuation buffer too small ("
    1400            0 :                                                                          << payloadBytes << " bytes) to hold prefix -- dropping"
    1401            0 :                                                                          << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1402            0 :                                 pendingSubEventBytes_.clear();
    1403            0 :                                 pendingSubEventTotalBytes_ = 0;
    1404            0 :                                 pendingPrefixConsumed_ = false;
    1405            0 :                                 saveBufferTail();
    1406            0 :                                 ReleaseBuffers(DTC_DMA_Engine_DAQ);
    1407            0 :                                 return output;
    1408              :                         }
    1409            0 :                         bufOffset = sizeof(uint64_t);
    1410              :                 }
    1411              : 
    1412            0 :                 DTC_TLOG(crossoverLogLevel) << "===== SUBEVENT CROSSOVER: continuation buffer ====="
    1413            0 :                                                                         << " pendingBytes=" << pendingSubEventBytes_.size()
    1414            0 :                                                                         << " pendingTotal=" << pendingSubEventTotalBytes_
    1415            0 :                                                                         << " pendingPrefixConsumed=" << pendingPrefixConsumed_
    1416            0 :                                                                         << " prevBufferWasFull=" << prevBufferWasFull
    1417            0 :                                                                         << " bufOffset=" << bufOffset
    1418            0 :                                                                         << " payloadBytes=" << payloadBytes
    1419            0 :                                                                         << " firstQword=0x" << std::hex
    1420            0 :                                                                         << *reinterpret_cast<const uint64_t*>(bufStart) << std::dec;
    1421              :                 // Dump first 8 qwords of continuation buffer so we can verify what's there
    1422              :                 {
    1423            0 :                         std::stringstream ss;
    1424            0 :                         ss << "===== CROSSOVER: continuation buffer first 8 qwords: ";
    1425            0 :                         for (int i = 0; i < 8 && (size_t)(i * 8) < payloadBytes; ++i)
    1426            0 :                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1427            0 :                                    << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1428            0 :                         DTC_TLOG(crossoverLogLevel) << ss.str();
    1429            0 :                 }
    1430              : 
    1431            0 :                 pendingPrefixConsumed_ = false;  // consumed
    1432              : 
    1433            0 :                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: completing pending subevent"
    1434            0 :                                                                  << " have=" << pendingSubEventBytes_.size()
    1435            0 :                                                                  << " pendingTotal=" << pendingSubEventTotalBytes_;
    1436              : 
    1437              :                 // If we stored a partial header last time (pendingSubEventTotalBytes_==0),
    1438              :                 // try to complete the header first using bytes from the new buffer.
    1439            0 :                 if (pendingSubEventTotalBytes_ == 0)
    1440              :                 {
    1441            0 :                         const size_t have = pendingSubEventBytes_.size();
    1442            0 :                         const size_t headerBytesNeeded = sizeof(DTC_SubEventHeader) - have;
    1443            0 :                         const size_t avail = payloadBytes - bufOffset;
    1444            0 :                         if (avail < headerBytesNeeded)
    1445              :                         {
    1446              :                                 // Still not enough for header — absorb and wait
    1447            0 :                                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: still can't complete header,"
    1448            0 :                                                                                  << " have=" << have << " need " << headerBytesNeeded
    1449            0 :                                                                                  << " more but only " << avail << " available";
    1450            0 :                                 pendingSubEventBytes_.insert(pendingSubEventBytes_.end(),
    1451            0 :                                                                                          bufStart + bufOffset, bufStart + payloadBytes);
    1452            0 :                                 saveBufferTail();
    1453            0 :                                 ReleaseBuffers(DTC_DMA_Engine_DAQ);
    1454            0 :                                 return output;
    1455              :                         }
    1456              :                         // Complete the header, extract total byte count
    1457            0 :                         pendingSubEventBytes_.insert(pendingSubEventBytes_.end(),
    1458            0 :                                                                                  bufStart + bufOffset, bufStart + bufOffset + headerBytesNeeded);
    1459            0 :                         pendingSubEventTotalBytes_ = static_cast<size_t>(
    1460            0 :                                 *reinterpret_cast<const uint32_t*>(pendingSubEventBytes_.data()) & 0x1FFFFFF);
    1461            0 :                         bufOffset += headerBytesNeeded;
    1462            0 :                         DTC_TLOG(TLVL_Crossover) << "===== CROSSOVER: header assembled from partial bytes ====="
    1463            0 :                                                                          << " subEventByteCount=" << pendingSubEventTotalBytes_
    1464            0 :                                                                          << " (0x" << std::hex << pendingSubEventTotalBytes_ << std::dec << ")"
    1465            0 :                                                                          << " bufOffset=" << bufOffset;
    1466              :                         // Dump assembled header for verification
    1467              :                         {
    1468            0 :                                 std::stringstream ss;
    1469            0 :                                 ss << "===== CROSSOVER: assembled header (" << pendingSubEventBytes_.size() << " bytes): ";
    1470            0 :                                 for (size_t i = 0; i < pendingSubEventBytes_.size() && i < sizeof(DTC_SubEventHeader); i += 4)
    1471            0 :                                         ss << std::hex << std::setw(8) << std::setfill('0')
    1472            0 :                                            << *reinterpret_cast<const uint32_t*>(&pendingSubEventBytes_[i]) << " ";
    1473              :                                 // Show format version field location for quick visual check
    1474            0 :                                 if (pendingSubEventBytes_.size() >= sizeof(DTC_SubEventHeader))
    1475              :                                 {
    1476            0 :                                         auto* hdr = reinterpret_cast<const DTC_SubEventHeader*>(pendingSubEventBytes_.data());
    1477            0 :                                         ss << " | fmt_ver=" << hdr->subevent_format_version
    1478            0 :                                            << " num_rocs=" << hdr->num_rocs
    1479            0 :                                            << " tag=" << (hdr->event_tag_low | (static_cast<uint64_t>(hdr->event_tag_high) << 32));
    1480              :                                 }
    1481            0 :                                 DTC_TLOG(TLVL_Crossover) << ss.str();
    1482            0 :                         }
    1483              : 
    1484              :                         // Sanity check: the inclusive byte count must be at least the header size
    1485            0 :                         if (pendingSubEventTotalBytes_ < sizeof(DTC_SubEventHeader))
    1486              :                         {
    1487            0 :                                 DTC_TLOG(TLVL_ERROR) << "GetSubEventData: completed pending header has invalid"
    1488            0 :                                                                          << " subEventByteCount=" << pendingSubEventTotalBytes_
    1489            0 :                                                                          << " < sizeof(DTC_SubEventHeader)=" << sizeof(DTC_SubEventHeader)
    1490            0 :                                                                          << " -- discarding corrupt pending data"
    1491            0 :                                                                          << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1492            0 :                                 pendingSubEventBytes_.clear();
    1493            0 :                                 pendingSubEventTotalBytes_ = 0;
    1494              :                                 // Fall through to Step 3 to continue parsing the rest of this buffer
    1495              :                         }
    1496              :                 }
    1497              : 
    1498              :                 // If the sanity check above cleared pendingSubEventBytes_, skip assembly
    1499            0 :                 if (!pendingSubEventBytes_.empty())
    1500              :                 {
    1501            0 :                         const size_t have = pendingSubEventBytes_.size();
    1502            0 :                         const size_t need = pendingSubEventTotalBytes_;
    1503            0 :                         const size_t still = (need > have) ? need - have : 0;
    1504              : 
    1505            0 :                         if (still > (payloadBytes - bufOffset))
    1506              :                         {
    1507              :                                 // Still not enough — absorb rest of new buffer and keep pending
    1508            0 :                                 const size_t absorb = payloadBytes - bufOffset;
    1509            0 :                                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: pending subevent still incomplete after new buffer"
    1510            0 :                                                                                  << " still=" << still << " absorbing=" << absorb;
    1511            0 :                                 pendingSubEventBytes_.insert(pendingSubEventBytes_.end(),
    1512            0 :                                                                                          bufStart + bufOffset, bufStart + bufOffset + absorb);
    1513              :                                 // Release this buffer immediately — data is copied
    1514            0 :                                 saveBufferTail();
    1515            0 :                                 ReleaseBuffers(DTC_DMA_Engine_DAQ);
    1516            0 :                                 return output;  // come back next call
    1517              :                         }
    1518              : 
    1519              :                         // We have enough — copy the remainder from this buffer
    1520            0 :                         pendingSubEventBytes_.insert(pendingSubEventBytes_.end(),
    1521            0 :                                                                                  bufStart + bufOffset, bufStart + bufOffset + still);
    1522            0 :                         bufOffset += still;
    1523              : 
    1524            0 :                         DTC_TLOG(crossoverLogLevel) << "===== CROSSOVER: pending subevent fully assembled ====="
    1525            0 :                                                                                 << " totalBytes=" << need
    1526            0 :                                                                                 << " (0x" << std::hex << need << std::dec << ")"
    1527            0 :                                                                                 << " bufOffset now=" << bufOffset;
    1528              :                         // Dump assembled subevent header for final verification before construction
    1529              :                         {
    1530            0 :                                 std::stringstream ss;
    1531            0 :                                 ss << "===== CROSSOVER: assembled subevent header: ";
    1532            0 :                                 size_t dumpLen = std::min(need, sizeof(DTC_SubEventHeader));
    1533            0 :                                 for (size_t i = 0; i < dumpLen; i += 4)
    1534            0 :                                         ss << std::hex << std::setw(8) << std::setfill('0')
    1535            0 :                                            << *reinterpret_cast<const uint32_t*>(&pendingSubEventBytes_[i]) << " ";
    1536            0 :                                 if (need >= sizeof(DTC_SubEventHeader))
    1537              :                                 {
    1538            0 :                                         auto* hdr = reinterpret_cast<const DTC_SubEventHeader*>(pendingSubEventBytes_.data());
    1539            0 :                                         ss << " | fmt_ver=" << hdr->subevent_format_version
    1540            0 :                                            << " num_rocs=" << hdr->num_rocs
    1541            0 :                                            << " tag=" << (hdr->event_tag_low | (static_cast<uint64_t>(hdr->event_tag_high) << 32));
    1542              :                                 }
    1543            0 :                                 DTC_TLOG(crossoverLogLevel) << ss.str();
    1544            0 :                         }
    1545              : 
    1546              :                         // Snapshot assembled header for diagnostics on failure
    1547              :                         std::vector<uint8_t> assembledSnapshot(pendingSubEventBytes_.begin(),
    1548            0 :                                                                                                    pendingSubEventBytes_.begin() + std::min(pendingSubEventBytes_.size(), sizeof(DTC_SubEventHeader)));
    1549              : 
    1550              :                         // Construct the completed subevent from the assembled bytes
    1551            0 :                         auto inmem = std::make_unique<DTC_SubEvent>(need);
    1552            0 :                         memcpy(const_cast<void*>(inmem->GetRawBufferPointer()),
    1553            0 :                                    pendingSubEventBytes_.data(), need);
    1554            0 :                         pendingSubEventBytes_.clear();
    1555            0 :                         pendingSubEventTotalBytes_ = 0;
    1556              : 
    1557              :                         try
    1558              :                         {
    1559            0 :                                 std::string errs;
    1560            0 :                                 if (!inmem->SetupSubEvent(errs))
    1561              :                                 {
    1562            0 :                                         DTC_TLOG(TLVL_ERROR) << "GetSubEventData: corrupt pending subevent: " << errs << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1563              :                                         // don't throw — log and continue parsing rest of buffer
    1564              :                                 }
    1565              :                                 else
    1566              :                                 {
    1567              :                                         // Track last good subevent header for diagnostics
    1568            0 :                                         lastGoodSubEventHeader_ = *inmem->GetHeader();
    1569            0 :                                         hasLastGoodSubEventHeader_ = true;
    1570            0 :                                         ++totalSubEventsParsed_;
    1571              : 
    1572            0 :                                         DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: completed pending subevent tag="
    1573            0 :                                                                                          << inmem->GetEventWindowTag().GetEventWindowTag(true);
    1574            0 :                                         output.push_back(std::move(inmem));
    1575              :                                 }
    1576            0 :                         }
    1577            0 :                         catch (...)
    1578              :                         {
    1579              :                                 // Dump full diagnostic context before re-throwing
    1580            0 :                                 DTC_TLOG(TLVL_ERROR) << "GetSubEventData: EXCEPTION setting up pending (cross-buffer) subevent!"
    1581            0 :                                                                          << " assembled totalBytes=" << need << " (0x" << std::hex << need << std::dec << ")"
    1582            0 :                                                                          << " prevBufferWasFull=" << prevBufferWasFull
    1583            0 :                                                                          << " pendingPrefixConsumed(was)=" << pendingPrefixConsumed_
    1584            0 :                                                                          << " bufOffset=" << bufOffset
    1585            0 :                                                                          << " payloadBytes=" << payloadBytes
    1586            0 :                                                                          << " firstQword=0x" << std::hex
    1587            0 :                                                                          << *reinterpret_cast<const uint64_t*>(bufStart) << std::dec << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1588              :                                 // Dump assembled header bytes
    1589              :                                 {
    1590            0 :                                         std::stringstream ss;
    1591            0 :                                         ss << "  Assembled header bytes: ";
    1592            0 :                                         for (size_t i = 0; i < assembledSnapshot.size(); i += 4)
    1593            0 :                                                 ss << std::hex << std::setw(8) << std::setfill('0')
    1594            0 :                                                    << *reinterpret_cast<const uint32_t*>(&assembledSnapshot[i]) << " ";
    1595            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1596            0 :                                 }
    1597              :                                 // Dump last good subevent header if available
    1598            0 :                                 if (hasLastGoodSubEventHeader_)
    1599              :                                 {
    1600            0 :                                         std::stringstream ss;
    1601            0 :                                         ss << "  Last GOOD subevent header: ";
    1602            0 :                                         auto* p = reinterpret_cast<const uint32_t*>(&lastGoodSubEventHeader_);
    1603            0 :                                         for (size_t i = 0; i < sizeof(DTC_SubEventHeader) / 4; ++i)
    1604            0 :                                                 ss << std::hex << std::setw(8) << std::setfill('0') << p[i] << " ";
    1605            0 :                                         ss << " | fmt_ver=" << lastGoodSubEventHeader_.subevent_format_version
    1606            0 :                                            << " num_rocs=" << lastGoodSubEventHeader_.num_rocs
    1607            0 :                                            << " tag=" << (lastGoodSubEventHeader_.event_tag_low | (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32));
    1608            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1609            0 :                                 }
    1610              :                                 else
    1611            0 :                                         DTC_TLOG(TLVL_ERROR) << "  No last good subevent header available";
    1612              :                                 // Dump previous buffer tail (last up to 8 qwords before the crossover boundary)
    1613            0 :                                 if (lastBufferTailCount_ > 0)
    1614              :                                 {
    1615            0 :                                         std::stringstream ss;
    1616            0 :                                         ss << "  Previous buffer last " << lastBufferTailCount_ << " qwords: ";
    1617            0 :                                         for (size_t i = 0; i < lastBufferTailCount_; ++i)
    1618            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0') << lastBufferTailQwords_[i] << " ";
    1619            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1620            0 :                                 }
    1621              :                                 else
    1622            0 :                                         DTC_TLOG(TLVL_ERROR) << "  No previous buffer tail available";
    1623              :                                 // Dump continuation buffer context around the boundary
    1624              :                                 {
    1625            0 :                                         std::stringstream ss;
    1626            0 :                                         ss << "  Continuation buffer first 16 qwords (raw): ";
    1627            0 :                                         for (int i = 0; i < 16 && (size_t)(i * 8) < payloadBytes; ++i)
    1628            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1629            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1630            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1631            0 :                                 }
    1632              :                                 // Dump DMA buffers via spy for full hardware state
    1633            0 :                                 DTC_TLOG(TLVL_ERROR) << "  Calling spy() for DMA buffer dump..."
    1634            0 :                                                                          << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1635            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */ | 16 /* stack trace */);
    1636              :                                 // Re-throw; if it's a DTC_WrongPacketTypeException, augment with last good event tag
    1637              :                                 try
    1638              :                                 {
    1639            0 :                                         throw;
    1640              :                                 }
    1641            0 :                                 catch (const DTC_WrongPacketTypeException& e)
    1642              :                                 {
    1643            0 :                                         std::string augMsg = std::string(e.what());
    1644            0 :                                         if (hasLastGoodSubEventHeader_)
    1645            0 :                                                 augMsg += " [last good EWT=" + std::to_string(lastGoodSubEventHeader_.event_tag_low | (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32)) + "]";
    1646            0 :                                         throw std::runtime_error(augMsg);
    1647            0 :                                 }
    1648            0 :                                 catch (...)
    1649              :                                 {
    1650            0 :                                         throw;
    1651            0 :                                 }  // all other exception types: propagate unchanged
    1652            0 :                         }
    1653            0 :                 }  // end if pendingSubEventBytes_ not empty after header validation
    1654              :         }
    1655              : 
    1656              :         // ------------------------------------------------------------------
    1657              :         // Step 3: walk the rest of this buffer extracting complete subevents
    1658              :         // ------------------------------------------------------------------
    1659              :         // Buffer layout (repeating):
    1660              :         //   [8 bytes]  DTC per-sub-transfer framing prefix  -- skipped, value NOT used
    1661              :         //   [N bytes]  subevent: header (48 bytes) + ROC payload
    1662              :         // Traversal is driven by two things only:
    1663              :         //   1. payloadBytes (from sts) -- authoritative outer bound
    1664              :         //   2. subevent header byte count (bits 24:0 of header word 0) -- advance within buffer
    1665              : 
    1666            0 :         while (bufOffset + sizeof(uint64_t) <= payloadBytes)
    1667              :         {
    1668              :                 // Skip the 8-byte DTC framing prefix — do not use its value
    1669            0 :                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: skipping 8-byte prefix @ bufOffset=" << bufOffset
    1670            0 :                                                                  << " prefixVal=" << *reinterpret_cast<const uint64_t*>(bufStart + bufOffset)
    1671            0 :                                                                  << " remaining=(" << payloadBytes - bufOffset << ")";
    1672            0 :                 bufOffset += sizeof(uint64_t);
    1673              : 
    1674              :                 // The subevent inclusive byte count lives in the first 4 bytes of the header
    1675              :                 // (bits 24:0 = inclusive byte count incl. the header itself)
    1676            0 :                 const uint8_t* sePtr = bufStart + bufOffset;
    1677            0 :                 const size_t seAvail = payloadBytes - bufOffset;  // bytes remaining in this buffer
    1678              : 
    1679              :                 // Print first 8 qwords at this subevent boundary for diagnostics
    1680            0 :                 if (TTEST(TLVL_GetData_2 - TLVL_DEBUG))
    1681              :                 {
    1682            0 :                         std::stringstream ss;
    1683            0 :                         ss << "GetSubEventData: subevent boundary @ bufOffset=" << bufOffset
    1684            0 :                            << " seAvail(dist to payload end)=" << seAvail
    1685            0 :                            << " distToMaxBufEnd=" << (sizeof(mu2e_databuff_t) - bufOffset)
    1686            0 :                            << " first 8 qwords: ";
    1687            0 :                         for (int i = 0; i < 8 && (size_t)(i * 8) < seAvail; ++i)
    1688            0 :                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1689            0 :                                    << *reinterpret_cast<const uint64_t*>(sePtr + i * 8) << " ";
    1690            0 :                         DTC_TLOG(TLVL_GetData_2) << ss.str();
    1691            0 :                 }
    1692              : 
    1693            0 :                 size_t subEventByteCount = 0;
    1694            0 :                 if (seAvail >= sizeof(DTC_SubEventHeader))
    1695              :                 {
    1696            0 :                         subEventByteCount = static_cast<size_t>(
    1697            0 :                                 *reinterpret_cast<const uint32_t*>(sePtr) & 0x1FFFFFF);
    1698              : 
    1699              :                         // Consistency check: the 8-byte framing prefix we just skipped is an
    1700              :                         // inclusive byte count for this sub-transfer (prefix + header + payload),
    1701              :                         // while the first 4 bytes of the SubEvent header are the subevent inclusive
    1702              :                         // byte count (header + payload, NO prefix).  Therefore
    1703              :                         //     prefix_low_32 - subEventByteCount == 8
    1704              :                         // for any well-formed pair.  If this fails the parser is desynced and
    1705              :                         // continuing would absorb hundreds of KB of unrelated data into a bogus
    1706              :                         // "pending" subevent (see the 0x230023 incident on 2026-05-18).  Throw
    1707              :                         // immediately so the caller learns at first sight of corruption.
    1708            0 :                         const uint32_t prefixLow32 =
    1709            0 :                                 *reinterpret_cast<const uint32_t*>(bufStart + bufOffset - sizeof(uint64_t));
    1710            0 :                         if (prefixLow32 < 8 || (prefixLow32 - 8) != subEventByteCount)
    1711              :                         {
    1712            0 :                                 std::ostringstream oss;
    1713              :                                 oss << "GetSubEventData: subevent header consistency check FAILED"
    1714            0 :                                         << " -- prefix_low32=0x" << std::hex << prefixLow32
    1715            0 :                                         << " subEventByteCount=0x" << subEventByteCount << std::dec
    1716              :                                         << " (expected prefix - byteCount == 8)"
    1717            0 :                                         << " at bufOffset=" << bufOffset
    1718            0 :                                         << " payloadBytes=" << payloadBytes
    1719            0 :                                         << " [last EWT=" << ewtTag()
    1720            0 :                                         << " totalParsed=" << totalSubEventsParsed_ << "]";
    1721            0 :                                 DTC_TLOG(TLVL_ERROR) << oss.str();
    1722              :                                 // Dump 16 qwords of context around the boundary so post-mortem is possible
    1723              :                                 {
    1724            0 :                                         std::stringstream ss;
    1725            0 :                                         ss << "  Context (prefix-8 .. +16 qwords): ";
    1726            0 :                                         const size_t ctxStart =
    1727            0 :                                                 (bufOffset >= sizeof(uint64_t)) ? bufOffset - sizeof(uint64_t) : 0;
    1728            0 :                                         for (size_t i = ctxStart;
    1729            0 :                                                  i + sizeof(uint64_t) <= payloadBytes && i < ctxStart + 17 * sizeof(uint64_t);
    1730            0 :                                                  i += sizeof(uint64_t))
    1731            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1732            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i) << " ";
    1733            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1734            0 :                                 }
    1735              :                                 // Dump buffer start (first 16 qwords) to check for the 0xdeadbeef
    1736              :                                 // release_all marker at qword[0].
    1737              :                                 {
    1738            0 :                                         std::stringstream ss;
    1739            0 :                                         ss << "  Buffer FIRST 16 qwords (from bufStart, check qword[0] for 0xdeadbeef): ";
    1740            0 :                                         for (int i = 0; i < 16 && (size_t)(i * 8) < payloadBytes; ++i)
    1741            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1742            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1743            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str();
    1744            0 :                                 }
    1745            0 :                                 device_.spy(DTC_DMA_Engine_DAQ,
    1746              :                                                         3 /* once */ | 8 /* wide */ | 16 /* stack trace */);
    1747            0 :                                 throw std::runtime_error(oss.str());
    1748            0 :                         }
    1749              :                 }
    1750            0 :                 else if (seAvail > 0)
    1751              :                 {
    1752              :                         // Partial header in this buffer — need to read from next buffer
    1753              :                         // We have fewer than 48 header bytes.  Store what we have and return.
    1754            0 :                         DTC_TLOG(TLVL_Crossover) << "===== SUBEVENT HEADER CROSSOVER DETECTED ====="
    1755            0 :                                                                          << " partial header at bufOffset=" << bufOffset
    1756            0 :                                                                          << " seAvail=" << seAvail << " < sizeof(DTC_SubEventHeader)=" << sizeof(DTC_SubEventHeader)
    1757            0 :                                                                          << " -- saving " << seAvail << " partial bytes, will complete on next buffer";
    1758              :                         // Dump the partial header bytes being cached
    1759              :                         {
    1760            0 :                                 std::stringstream ss;
    1761            0 :                                 ss << "===== CROSSOVER: caching partial header bytes: ";
    1762            0 :                                 for (size_t i = 0; i < seAvail; i += 4)
    1763              :                                 {
    1764            0 :                                         if (i + 4 <= seAvail)
    1765            0 :                                                 ss << std::hex << std::setw(8) << std::setfill('0')
    1766            0 :                                                    << *reinterpret_cast<const uint32_t*>(sePtr + i) << " ";
    1767              :                                         else
    1768            0 :                                                 for (size_t j = i; j < seAvail; ++j)
    1769            0 :                                                         ss << std::hex << std::setw(2) << std::setfill('0')
    1770            0 :                                                            << static_cast<unsigned>(sePtr[j]) << " ";
    1771              :                                 }
    1772            0 :                                 DTC_TLOG(TLVL_Crossover) << ss.str();
    1773            0 :                         }
    1774            0 :                         pendingSubEventBytes_.assign(sePtr, sePtr + seAvail);
    1775              : 
    1776              :                         // If we have at least 4 bytes, we can extract and validate the byte count early
    1777            0 :                         if (seAvail >= sizeof(uint32_t))
    1778              :                         {
    1779            0 :                                 pendingSubEventTotalBytes_ = static_cast<size_t>(
    1780            0 :                                         *reinterpret_cast<const uint32_t*>(sePtr) & 0x1FFFFFF);
    1781            0 :                                 if (pendingSubEventTotalBytes_ < sizeof(DTC_SubEventHeader))
    1782              :                                 {
    1783            0 :                                         DTC_TLOG(TLVL_ERROR) << "GetSubEventData: partial header has invalid"
    1784            0 :                                                                                  << " subEventByteCount=" << pendingSubEventTotalBytes_
    1785            0 :                                                                                  << " < sizeof(DTC_SubEventHeader)=" << sizeof(DTC_SubEventHeader)
    1786            0 :                                                                                  << " -- discarding corrupt partial header"
    1787            0 :                                                                                  << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1788            0 :                                         pendingSubEventBytes_.clear();
    1789            0 :                                         pendingSubEventTotalBytes_ = 0;
    1790            0 :                                         break;
    1791              :                                 }
    1792              : 
    1793              :                                 // Same prefix/byteCount consistency check as the full-header path:
    1794              :                                 // prefix_low_32 - subEventByteCount must equal 8.  This is the only place
    1795              :                                 // we get a chance to validate before storing pendingSubEventTotalBytes_
    1796              :                                 // and letting Step 2 absorb potentially many KB of unrelated data on the
    1797              :                                 // next calls.  Throw immediately if it fails.
    1798            0 :                                 const uint32_t prefixLow32 =
    1799            0 :                                         *reinterpret_cast<const uint32_t*>(bufStart + bufOffset - sizeof(uint64_t));
    1800            0 :                                 if (prefixLow32 < 8 || (prefixLow32 - 8) != pendingSubEventTotalBytes_)
    1801              :                                 {
    1802            0 :                                         std::ostringstream oss;
    1803              :                                         oss << "GetSubEventData: partial-header consistency check FAILED"
    1804            0 :                                                 << " -- prefix_low32=0x" << std::hex << prefixLow32
    1805            0 :                                                 << " pendingSubEventTotalBytes=0x" << pendingSubEventTotalBytes_ << std::dec
    1806              :                                                 << " (expected prefix - byteCount == 8)"
    1807            0 :                                                 << " at bufOffset=" << bufOffset
    1808            0 :                                                 << " seAvail=" << seAvail
    1809            0 :                                                 << " payloadBytes=" << payloadBytes
    1810            0 :                                                 << " [last EWT=" << ewtTag()
    1811            0 :                                                 << " totalParsed=" << totalSubEventsParsed_ << "]";
    1812            0 :                                         DTC_TLOG(TLVL_ERROR) << oss.str();
    1813              :                                         // Dump context around the boundary (prefix + whatever partial bytes we have)
    1814              :                                         {
    1815            0 :                                                 std::stringstream ss;
    1816            0 :                                                 ss << "  Context (prefix-8 .. end-of-buffer, up to 17 qwords): ";
    1817            0 :                                                 const size_t ctxStart =
    1818            0 :                                                         (bufOffset >= sizeof(uint64_t)) ? bufOffset - sizeof(uint64_t) : 0;
    1819            0 :                                                 for (size_t i = ctxStart;
    1820            0 :                                                          i + sizeof(uint64_t) <= payloadBytes && i < ctxStart + 17 * sizeof(uint64_t);
    1821            0 :                                                          i += sizeof(uint64_t))
    1822            0 :                                                         ss << std::hex << std::setw(16) << std::setfill('0')
    1823            0 :                                                            << *reinterpret_cast<const uint64_t*>(bufStart + i) << " ";
    1824            0 :                                                 DTC_TLOG(TLVL_ERROR) << ss.str();
    1825            0 :                                         }
    1826              :                                         // Dump buffer start (first 16 qwords) to check for the 0xdeadbeef
    1827              :                                         // release_all marker at qword[0].
    1828              :                                         {
    1829            0 :                                                 std::stringstream ss;
    1830            0 :                                                 ss << "  Buffer FIRST 16 qwords (from bufStart, check qword[0] for 0xdeadbeef): ";
    1831            0 :                                                 for (int i = 0; i < 16 && (size_t)(i * 8) < payloadBytes; ++i)
    1832            0 :                                                         ss << std::hex << std::setw(16) << std::setfill('0')
    1833            0 :                                                            << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1834            0 :                                                 DTC_TLOG(TLVL_ERROR) << ss.str();
    1835            0 :                                         }
    1836            0 :                                         device_.spy(DTC_DMA_Engine_DAQ,
    1837              :                                                                 3 /* once */ | 8 /* wide */ | 16 /* stack trace */);
    1838              :                                         // Clear the pending state so we do not absorb anything on the next call
    1839            0 :                                         pendingSubEventBytes_.clear();
    1840            0 :                                         pendingSubEventTotalBytes_ = 0;
    1841            0 :                                         throw std::runtime_error(oss.str());
    1842            0 :                                 }
    1843              : 
    1844            0 :                                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: partial header early byteCount="
    1845            0 :                                                                                  << pendingSubEventTotalBytes_;
    1846              :                                 // Note: pendingSubEventTotalBytes_ is set but header is still incomplete
    1847              :                                 // (pendingSubEventBytes_.size() < sizeof(DTC_SubEventHeader)).
    1848              :                                 // Step 2 on next call will complete the header before assembling payload.
    1849              :                         }
    1850              :                         else
    1851              :                         {
    1852              :                                 // Fewer than 4 bytes — can't extract byte count yet
    1853            0 :                                 pendingSubEventTotalBytes_ = 0;
    1854              :                         }
    1855            0 :                         break;  // done with this buffer
    1856              :                 }
    1857              :                 else
    1858              :                 {
    1859              :                         // Exactly 0 bytes remain after the prefix — subevent fully in next buffer.
    1860              :                         // The prefix was consumed here, so the next buffer is a continuation buffer
    1861              :                         // that starts with raw subevent header data at offset 0 (no prefix).
    1862              :                         // Mark this so the next call's Step 2 knows to treat it as a continuation.
    1863            0 :                         DTC_TLOG(TLVL_Crossover) << "===== SUBEVENT PREFIX-ONLY CROSSOVER DETECTED ====="
    1864            0 :                                                                          << " prefix at end of buffer, 0 subevent bytes here"
    1865            0 :                                                                          << " -- next buffer is a continuation with no prefix"
    1866            0 :                                                                          << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1867            0 :                         pendingSubEventBytes_.resize(0);  // empty but see note below
    1868            0 :                         pendingSubEventTotalBytes_ = 0;
    1869              :                         // Set pendingPrefixConsumed_ so the next call enters Step 2
    1870              :                         // (continuation path) instead of Step 3 (which would incorrectly skip a prefix).
    1871            0 :                         pendingPrefixConsumed_ = true;
    1872            0 :                         break;
    1873              :                 }
    1874              : 
    1875            0 :                 if (subEventByteCount < sizeof(DTC_SubEventHeader))
    1876              :                 {
    1877            0 :                         DTC_TLOG(TLVL_ERROR) << "GetSubEventData: subEventByteCount=" << subEventByteCount
    1878            0 :                                                                  << " < sizeof(DTC_SubEventHeader)=" << sizeof(DTC_SubEventHeader)
    1879            0 :                                                                  << " at bufOffset=" << bufOffset << " -- stopping parse"
    1880            0 :                                                                  << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1881            0 :                         break;
    1882              :                 }
    1883              : 
    1884            0 :                 DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: subevent @ bufOffset=" << bufOffset
    1885            0 :                                                                  << " subEventByteCount=" << subEventByteCount
    1886            0 :                                                                  << " seAvail=" << seAvail;
    1887              : 
    1888            0 :                 if (subEventByteCount <= seAvail)
    1889              :                 {
    1890              :                         // Entire subevent fits in this buffer — copy it out so the DMA buffer
    1891              :                         // can be safely released without use-after-free.  (The pointer-based
    1892              :                         // DTC_SubEvent(sePtr) constructor stores a raw pointer into the DMA
    1893              :                         // buffer, which becomes dangling after ReleaseBuffers.)
    1894              :                         try
    1895              :                         {
    1896            0 :                                 auto res = std::make_unique<DTC_SubEvent>(subEventByteCount);
    1897            0 :                                 memcpy(const_cast<void*>(res->GetRawBufferPointer()), sePtr, subEventByteCount);
    1898            0 :                                 std::string errs;
    1899            0 :                                 if (!res->SetupSubEvent(errs))
    1900              :                                 {
    1901            0 :                                         DTC_TLOG(TLVL_ERROR) << "GetSubEventData: corrupt subevent at bufOffset=" << bufOffset
    1902            0 :                                                                                  << " errs=" << errs << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1903              :                                 }
    1904              :                                 else
    1905              :                                 {
    1906              :                                         // Track last good subevent header for diagnostics
    1907            0 :                                         lastGoodSubEventHeader_ = *res->GetHeader();
    1908            0 :                                         hasLastGoodSubEventHeader_ = true;
    1909            0 :                                         ++totalSubEventsParsed_;
    1910              : 
    1911            0 :                                         DTC_TLOG(TLVL_GetData_2) << "GetSubEventData: subevent OK tag="
    1912            0 :                                                                                          << res->GetEventWindowTag().GetEventWindowTag(true)
    1913            0 :                                                                                          << " bytes=" << subEventByteCount;
    1914            0 :                                         output.push_back(std::move(res));
    1915              :                                 }
    1916            0 :                         }
    1917            0 :                         catch (...)
    1918              :                         {
    1919            0 :                                 DTC_TLOG(TLVL_ERROR) << "GetSubEventData: EXCEPTION constructing/setting up in-place subevent!"
    1920            0 :                                                                          << " bufOffset=" << bufOffset
    1921            0 :                                                                          << " subEventByteCount=" << subEventByteCount
    1922            0 :                                                                          << " (0x" << std::hex << subEventByteCount << std::dec << ")"
    1923            0 :                                                                          << " seAvail=" << seAvail
    1924            0 :                                                                          << " prevBufferWasFull=" << prevBufferWasFull
    1925            0 :                                                                          << " payloadBytes=" << payloadBytes
    1926            0 :                                                                          << " firstQword=0x" << std::hex
    1927            0 :                                                                          << *reinterpret_cast<const uint64_t*>(bufStart) << std::dec << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1928              :                                 // Dump the subevent header bytes at this location
    1929              :                                 {
    1930            0 :                                         std::stringstream ss;
    1931            0 :                                         ss << "  Subevent header bytes at bufOffset=" << bufOffset << ": ";
    1932            0 :                                         size_t dumpLen = std::min(seAvail, sizeof(DTC_SubEventHeader));
    1933            0 :                                         for (size_t i = 0; i < dumpLen; i += 4)
    1934            0 :                                                 ss << std::hex << std::setw(8) << std::setfill('0')
    1935            0 :                                                    << *reinterpret_cast<const uint32_t*>(sePtr + i) << " ";
    1936            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1937            0 :                                 }
    1938              :                                 // Dump context: 8 qwords before and 8 qwords after the subevent start
    1939              :                                 {
    1940            0 :                                         std::stringstream ss;
    1941            0 :                                         ss << "  Context around bufOffset=" << bufOffset << ": BEFORE: ";
    1942            0 :                                         size_t beforeStart = (bufOffset >= 64) ? bufOffset - 64 : 0;
    1943            0 :                                         for (size_t i = beforeStart; i + 8 <= bufOffset; i += 8)
    1944            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1945            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i) << " ";
    1946            0 :                                         ss << " | AFTER: ";
    1947            0 :                                         for (size_t i = bufOffset; i + 8 <= payloadBytes && i < bufOffset + 64; i += 8)
    1948            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1949            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i) << " ";
    1950            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1951            0 :                                 }
    1952              :                                 // Dump buffer start (first 16 qwords) so we can check for the 0xdeadbeef
    1953              :                                 // release_all marker at qword[0] -- if it's still there, hardware never
    1954              :                                 // wrote this buffer since the last ReleaseAllBuffers.
    1955              :                                 {
    1956            0 :                                         std::stringstream ss;
    1957            0 :                                         ss << "  Buffer FIRST 16 qwords (from bufStart, check qword[0] for 0xdeadbeef): ";
    1958            0 :                                         for (int i = 0; i < 16 && (size_t)(i * 8) < payloadBytes; ++i)
    1959            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0')
    1960            0 :                                                    << *reinterpret_cast<const uint64_t*>(bufStart + i * 8) << " ";
    1961            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1962            0 :                                 }
    1963              :                                 // Dump last good subevent header if available
    1964            0 :                                 if (hasLastGoodSubEventHeader_)
    1965              :                                 {
    1966            0 :                                         std::stringstream ss;
    1967            0 :                                         ss << "  Last GOOD subevent header: ";
    1968            0 :                                         auto* p = reinterpret_cast<const uint32_t*>(&lastGoodSubEventHeader_);
    1969            0 :                                         for (size_t i = 0; i < sizeof(DTC_SubEventHeader) / 4; ++i)
    1970            0 :                                                 ss << std::hex << std::setw(8) << std::setfill('0') << p[i] << " ";
    1971            0 :                                         ss << " | fmt_ver=" << lastGoodSubEventHeader_.subevent_format_version
    1972            0 :                                            << " num_rocs=" << lastGoodSubEventHeader_.num_rocs
    1973            0 :                                            << " tag=" << (lastGoodSubEventHeader_.event_tag_low | (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32));
    1974            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1975            0 :                                 }
    1976              :                                 else
    1977            0 :                                         DTC_TLOG(TLVL_ERROR) << "  No last good subevent header available";
    1978              :                                 // Dump previous buffer tail (last up to 8 qwords from the buffer before this one)
    1979            0 :                                 if (lastBufferTailCount_ > 0)
    1980              :                                 {
    1981            0 :                                         std::stringstream ss;
    1982            0 :                                         ss << "  Previous buffer last " << lastBufferTailCount_ << " qwords: ";
    1983            0 :                                         for (size_t i = 0; i < lastBufferTailCount_; ++i)
    1984            0 :                                                 ss << std::hex << std::setw(16) << std::setfill('0') << lastBufferTailQwords_[i] << " ";
    1985            0 :                                         DTC_TLOG(TLVL_ERROR) << ss.str() << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1986            0 :                                 }
    1987              :                                 else
    1988            0 :                                         DTC_TLOG(TLVL_ERROR) << "  No previous buffer tail available";
    1989              :                                 // Dump DMA buffers via spy for full hardware state
    1990            0 :                                 DTC_TLOG(TLVL_ERROR) << "  Calling spy() for DMA buffer dump..."
    1991            0 :                                                                          << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    1992            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */ | 16 /* stack trace */);
    1993              :                                 // Re-throw; if it's a DTC_WrongPacketTypeException, augment with last good event tag
    1994              :                                 try
    1995              :                                 {
    1996            0 :                                         throw;
    1997              :                                 }
    1998            0 :                                 catch (const DTC_WrongPacketTypeException& e)
    1999              :                                 {
    2000            0 :                                         std::string augMsg = std::string(e.what());
    2001            0 :                                         if (hasLastGoodSubEventHeader_)
    2002            0 :                                                 augMsg += " [last good EWT=" + std::to_string(lastGoodSubEventHeader_.event_tag_low | (static_cast<uint64_t>(lastGoodSubEventHeader_.event_tag_high) << 32)) + "]";
    2003            0 :                                         throw std::runtime_error(augMsg);
    2004            0 :                                 }
    2005            0 :                                 catch (...)
    2006              :                                 {
    2007            0 :                                         throw;
    2008            0 :                                 }  // all other exception types: propagate unchanged
    2009            0 :                         }
    2010            0 :                         bufOffset += subEventByteCount;
    2011              :                 }
    2012              :                 else
    2013              :                 {
    2014              :                         // Subevent crosses buffer boundary — save what we have and return
    2015            0 :                         DTC_TLOG(TLVL_GetData_2) << "===== SUBEVENT BODY CROSSOVER DETECTED ====="
    2016            0 :                                                                          << " have=" << seAvail
    2017            0 :                                                                          << " need=" << subEventByteCount
    2018            0 :                                                                          << " (0x" << std::hex << subEventByteCount << std::dec << ")"
    2019            0 :                                                                          << " missing=" << (subEventByteCount - seAvail)
    2020            0 :                                                                          << " -- saving partial subevent, will continue on next buffer";
    2021            0 :                         pendingSubEventBytes_.assign(sePtr, sePtr + seAvail);
    2022            0 :                         pendingSubEventTotalBytes_ = subEventByteCount;
    2023            0 :                         break;  // done with this buffer
    2024              :                 }
    2025              :         }
    2026              : 
    2027              :         // ------------------------------------------------------------------
    2028              :         // Step 3b: check for leftover bytes that didn't fit in the while-loop
    2029              :         // ------------------------------------------------------------------
    2030              :         // The while-loop requires at least 8 bytes (sizeof(uint64_t)) to enter,
    2031              :         // since every subevent is preceded by an 8-byte framing prefix.  If fewer
    2032              :         // than 8 bytes remain after the last complete subevent (or partial-header
    2033              :         // caching), they would otherwise be silently dropped.
    2034            0 :         if (pendingSubEventBytes_.empty() && !pendingPrefixConsumed_ && bufOffset < payloadBytes)
    2035              :         {
    2036            0 :                 const size_t leftover = payloadBytes - bufOffset;
    2037              :                 // For non-full buffers, leftover bytes < 8 are most likely a partial
    2038              :                 // framing prefix or alignment padding — the next buffer starts fresh.
    2039              :                 // Log at WARNING level so this is visible in diagnostics.
    2040            0 :                 DTC_TLOG(TLVL_WARNING) << "GetSubEventData: " << leftover
    2041            0 :                                                            << " leftover byte(s) at end of buffer (bufOffset=" << bufOffset
    2042            0 :                                                            << " payloadBytes=" << payloadBytes
    2043            0 :                                                            << " bufferFull=" << lastDMABufferWasFull_ << ")"
    2044            0 :                                                            << " [last EWT=" << ewtTag() << " totalParsed=" << totalSubEventsParsed_ << "]";
    2045              :                 // Dump the first 8 qwords of the leftover region so we can see what stopped the parse
    2046              :                 {
    2047            0 :                         std::stringstream ss;
    2048            0 :                         ss << "GetSubEventData: leftover region first 8 qwords @ bufOffset=" << bufOffset << ": ";
    2049            0 :                         for (int i = 0; i < 8 && bufOffset + (size_t)(i * 8) + 8 <= payloadBytes; ++i)
    2050            0 :                                 ss << std::hex << std::setw(16) << std::setfill('0')
    2051            0 :                                    << *reinterpret_cast<const uint64_t*>(bufStart + bufOffset + i * 8) << " ";
    2052              :                         // Also show the raw 4-byte value that was (or would be) parsed as subEventByteCount
    2053            0 :                         if (leftover >= sizeof(uint32_t))
    2054            0 :                                 ss << " | raw_byteCount_field=0x" << std::hex
    2055            0 :                                    << (*reinterpret_cast<const uint32_t*>(bufStart + bufOffset) & 0x1FFFFFF)
    2056            0 :                                    << std::dec;
    2057            0 :                         DTC_TLOG(TLVL_WARNING) << ss.str();
    2058            0 :                 }
    2059              :         }
    2060              : 
    2061              :         // ------------------------------------------------------------------
    2062              :         // Step 4: report and return
    2063              :         // ------------------------------------------------------------------
    2064            0 :         DTC_TLOG(TLVL_GetData) << "GetSubEventData RETURN output.size()=" << output.size()
    2065            0 :                                                    << " pendingBytes=" << pendingSubEventBytes_.size()
    2066            0 :                                                    << " pendingTotal=" << pendingSubEventTotalBytes_
    2067            0 :                                                    << " pendingPrefixConsumed=" << pendingPrefixConsumed_;
    2068              :         // Always refresh lastGoodSubEventHeader_ from the last successfully returned subevent
    2069              :         // so the member is current even if the inline saves were skipped (e.g. early-exit paths).
    2070            0 :         if (!output.empty() && output.back() && output.back()->GetHeader())
    2071              :         {
    2072            0 :                 lastGoodSubEventHeader_ = *output.back()->GetHeader();
    2073            0 :                 hasLastGoodSubEventHeader_ = true;
    2074              :         }
    2075            0 :         saveBufferTail();
    2076            0 :         return output;
    2077            0 : }  // endGetSubEventData() v2
    2078              : 
    2079            0 : void DTCLib::DTC::WriteSimFileToDTC(std::string file, bool /*goForever*/, bool overwriteEnvironment,
    2080              :                                                                         std::string outputFileName, bool skipVerify)
    2081              : {
    2082            0 :         bool success = false;
    2083            0 :         int retryCount = 0;
    2084            0 :         while (!success && retryCount < 5)
    2085              :         {
    2086            0 :                 DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC BEGIN";
    2087            0 :                 auto writeOutput = outputFileName != "";
    2088            0 :                 std::ofstream outputStream;
    2089            0 :                 if (writeOutput)
    2090              :                 {
    2091            0 :                         outputStream.open(outputFileName, std::ios::out | std::ios::binary);
    2092              :                 }
    2093            0 :                 auto sim = getenv("DTCLIB_SIM_FILE");
    2094            0 :                 if (!overwriteEnvironment && sim != nullptr)
    2095              :                 {
    2096            0 :                         file = std::string(sim);
    2097              :                 }
    2098              : 
    2099            0 :                 DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC file is " << file << ", Setting up DTC";
    2100            0 :                 DisableDetectorEmulator();
    2101            0 :                 DisableDetectorEmulatorMode();
    2102              :                 // ResetDDR();  // this can take about a second
    2103            0 :                 ResetDDRWriteAddress();
    2104            0 :                 ResetDDRReadAddress();
    2105            0 :                 SetDDRDataLocalStartAddress(0x0);
    2106            0 :                 SetDDRDataLocalEndAddress(0xFFFFFFFF);
    2107            0 :                 EnableDetectorEmulatorMode();
    2108            0 :                 SetDetectorEmulationDMACount(1);
    2109            0 :                 SetDetectorEmulationDMADelayCount(250);  // 1 microseconds
    2110            0 :                 uint64_t totalSize = 0;
    2111            0 :                 auto n = 0;
    2112              : 
    2113            0 :                 auto sizeCheck = true;
    2114            0 :                 DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC Opening file";
    2115            0 :                 std::ifstream is(file, std::ifstream::binary);
    2116            0 :                 DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC Reading file";
    2117            0 :                 while (is && is.good() && sizeCheck)
    2118              :                 {
    2119            0 :                         DTC_TLOG(TLVL_WriteSimFileToDTC2) << "WriteSimFileToDTC Reading a DMA from file..." << file;
    2120            0 :                         auto buf = reinterpret_cast<mu2e_databuff_t*>(new char[0x10000]);
    2121            0 :                         is.read(reinterpret_cast<char*>(buf), sizeof(uint64_t));
    2122            0 :                         if (is.eof())
    2123              :                         {
    2124            0 :                                 DTC_TLOG(TLVL_WriteSimFileToDTC2) << "WriteSimFileToDTC End of file reached.";
    2125            0 :                                 delete[] buf;
    2126            0 :                                 break;
    2127              :                         }
    2128            0 :                         auto sz = *reinterpret_cast<uint64_t*>(buf);
    2129            0 :                         is.read(reinterpret_cast<char*>(buf) + 8, sz - sizeof(uint64_t));
    2130            0 :                         if (sz < 80 && sz > 0)
    2131              :                         {
    2132            0 :                                 auto oldSize = sz;
    2133            0 :                                 sz = 80;
    2134            0 :                                 memcpy(buf, &sz, sizeof(uint64_t));
    2135            0 :                                 uint64_t sixtyFour = 64;
    2136            0 :                                 memcpy(reinterpret_cast<uint64_t*>(buf) + 1, &sixtyFour, sizeof(uint64_t));
    2137            0 :                                 bzero(reinterpret_cast<uint64_t*>(buf) + 2, sz - oldSize);
    2138              :                         }
    2139              :                         // is.read((char*)buf + 8, sz - sizeof(uint64_t));
    2140            0 :                         if (sz > 0 && (sz + totalSize < 0xFFFFFFFF || simMode_ == DTC_SimMode_LargeFile))
    2141              :                         {
    2142            0 :                                 DTC_TLOG(TLVL_WriteSimFileToDTC2) << "WriteSimFileToDTC Size is " << sz << ", writing to device";
    2143            0 :                                 if (writeOutput)
    2144              :                                 {
    2145            0 :                                         DTC_TLOG(TLVL_WriteSimFileToDTC3)
    2146            0 :                                                 << "WriteSimFileToDTC: Stripping off DMA header words and writing to binary file";
    2147            0 :                                         outputStream.write(reinterpret_cast<char*>(buf) + 16, sz - 16);
    2148              :                                 }
    2149              : 
    2150            0 :                                 auto dmaByteCount = *(reinterpret_cast<uint64_t*>(buf) + 1);
    2151            0 :                                 DTC_TLOG(TLVL_WriteSimFileToDTC3) << "WriteSimFileToDTC: Inclusive write byte count: " << sz
    2152            0 :                                                                                                   << ", DMA Byte count: " << dmaByteCount;
    2153            0 :                                 if (sz - 8 != dmaByteCount)
    2154              :                                 {
    2155            0 :                                         DTC_TLOG(TLVL_ERROR) << "WriteSimFileToDTC: ERROR: Inclusive write Byte count " << sz
    2156            0 :                                                                                  << " is inconsistent with DMA byte count " << dmaByteCount << " for DMA at 0x"
    2157            0 :                                                                                  << std::hex << totalSize << " (0x" << sz - 16 << " != 0x" << dmaByteCount << ")";
    2158            0 :                                         sizeCheck = false;
    2159              :                                 }
    2160              : 
    2161            0 :                                 totalSize += sz - 8;
    2162            0 :                                 n++;
    2163            0 :                                 DTC_TLOG(TLVL_WriteSimFileToDTC3) << "WriteSimFileToDTC: totalSize is now " << totalSize << ", n is now " << n;
    2164            0 :                                 WriteDetectorEmulatorData(buf, static_cast<size_t>(sz));
    2165            0 :                         }
    2166            0 :                         else if (sz > 0)
    2167              :                         {
    2168            0 :                                 DTC_TLOG(TLVL_WriteSimFileToDTC2) << "WriteSimFileToDTC DTC memory is now full. Closing file.";
    2169            0 :                                 sizeCheck = false;
    2170              :                         }
    2171            0 :                         delete[] buf;
    2172              :                 }
    2173              : 
    2174            0 :                 DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC Closing file. sizecheck=" << sizeCheck << ", eof=" << is.eof()
    2175            0 :                                                                                  << ", fail=" << is.fail() << ", bad=" << is.bad();
    2176            0 :                 is.close();
    2177            0 :                 if (writeOutput) outputStream.close();
    2178            0 :                 SetDDRDataLocalEndAddress(static_cast<uint32_t>(totalSize - 1));
    2179            0 :                 success = skipVerify || VerifySimFileInDTC(file, outputFileName);
    2180            0 :                 retryCount++;
    2181            0 :         }
    2182              : 
    2183            0 :         if (retryCount == 5)
    2184              :         {
    2185            0 :                 DTC_TLOG(TLVL_ERROR) << "WriteSimFileToDTC FAILED after 5 attempts! ABORTING!";
    2186            0 :                 exit(4);
    2187              :         }
    2188              :         else
    2189              :         {
    2190            0 :                 __COUT_INFO__ << "WriteSimFileToDTC Took " << retryCount << " attempts to write file";
    2191              :         }
    2192              : 
    2193            0 :         SetDetectorEmulatorInUse();
    2194            0 :         DTC_TLOG(TLVL_WriteSimFileToDTC) << "WriteSimFileToDTC END";
    2195            0 : }
    2196              : 
    2197            0 : bool DTCLib::DTC::VerifySimFileInDTC(std::string file, std::string rawOutputFilename)
    2198              : {
    2199            0 :         uint64_t totalSize = 0;
    2200            0 :         auto n = 0;
    2201            0 :         auto sizeCheck = true;
    2202              : 
    2203            0 :         auto writeOutput = rawOutputFilename != "";
    2204            0 :         std::ofstream outputStream;
    2205            0 :         if (writeOutput)
    2206              :         {
    2207            0 :                 outputStream.open(rawOutputFilename + ".verify", std::ios::out | std::ios::binary);
    2208              :         }
    2209              : 
    2210            0 :         auto sim = getenv("DTCLIB_SIM_FILE");
    2211            0 :         if (file.size() == 0 && sim != nullptr)
    2212              :         {
    2213            0 :                 file = std::string(sim);
    2214              :         }
    2215              : 
    2216            0 :         ResetDDRReadAddress();
    2217            0 :         DTC_TLOG(TLVL_VerifySimFileInDTC) << "VerifySimFileInDTC Opening file";
    2218            0 :         std::ifstream is(file, std::ifstream::binary);
    2219            0 :         if (!is || !is.good())
    2220              :         {
    2221            0 :                 DTC_TLOG(TLVL_ERROR) << "VerifySimFileInDTC Failed to open file " << file << "!";
    2222              :         }
    2223              : 
    2224            0 :         DTC_TLOG(TLVL_VerifySimFileInDTC) << "VerifySimFileInDTC Reading file";
    2225            0 :         while (is && is.good() && sizeCheck)
    2226              :         {
    2227            0 :                 DTC_TLOG(TLVL_VerifySimFileInDTC2) << "VerifySimFileInDTC Reading a DMA from file..." << file;
    2228              :                 uint64_t file_buffer_size;
    2229            0 :                 auto buffer_from_file = reinterpret_cast<mu2e_databuff_t*>(new char[0x10000]);
    2230            0 :                 is.read(reinterpret_cast<char*>(&file_buffer_size), sizeof(uint64_t));
    2231            0 :                 if (is.eof())
    2232              :                 {
    2233            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC2) << "VerifySimFileInDTC End of file reached.";
    2234            0 :                         delete[] buffer_from_file;
    2235            0 :                         break;
    2236              :                 }
    2237            0 :                 is.read(reinterpret_cast<char*>(buffer_from_file), file_buffer_size - sizeof(uint64_t));
    2238            0 :                 if (file_buffer_size < 80 && file_buffer_size > 0)
    2239              :                 {
    2240              :                         // auto oldSize = file_buffer_size;
    2241            0 :                         file_buffer_size = 80;
    2242            0 :                         uint64_t sixtyFour = 64;
    2243            0 :                         memcpy(reinterpret_cast<uint64_t*>(buffer_from_file), &sixtyFour, sizeof(uint64_t));
    2244              :                         // bzero(reinterpret_cast<uint64_t*>(buffer_from_file) + 2, sz - oldSize);
    2245              :                 }
    2246              : 
    2247            0 :                 if (file_buffer_size > 0 && (file_buffer_size + totalSize < 0xFFFFFFFF || simMode_ == DTC_SimMode_LargeFile))
    2248              :                 {
    2249            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC2) << "VerifySimFileInDTC Expected Size is " << file_buffer_size - sizeof(uint64_t) << ", reading from device";
    2250            0 :                         auto inclusiveByteCount = *(reinterpret_cast<uint64_t*>(buffer_from_file));
    2251            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC3) << "VerifySimFileInDTC: DMA Write size: " << file_buffer_size
    2252            0 :                                                                                            << ", Inclusive byte count: " << inclusiveByteCount;
    2253            0 :                         if (file_buffer_size - 8 != inclusiveByteCount)
    2254              :                         {
    2255            0 :                                 DTC_TLOG(TLVL_ERROR) << "VerifySimFileInDTC: ERROR: DMA Write size " << file_buffer_size
    2256            0 :                                                                          << " is inconsistent with DMA byte count " << inclusiveByteCount << " for DMA at 0x"
    2257            0 :                                                                          << std::hex << totalSize << " (0x" << file_buffer_size - 8 << " != 0x" << inclusiveByteCount << ")";
    2258            0 :                                 sizeCheck = false;
    2259              :                         }
    2260              : 
    2261            0 :                         totalSize += file_buffer_size;
    2262            0 :                         n++;
    2263            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC3) << "VerifySimFileInDTC: totalSize is now " << totalSize << ", n is now " << n;
    2264              :                         // WriteDetectorEmulatorData(buffer_from_file, static_cast<size_t>(sz));
    2265            0 :                         DisableDetectorEmulator();
    2266            0 :                         SetDetectorEmulationDMACount(1);
    2267            0 :                         EnableDetectorEmulator();
    2268              : 
    2269              :                         mu2e_databuff_t* buffer_from_device;
    2270            0 :                         auto tmo_ms = 1500;
    2271            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC) << "VerifySimFileInDTC - before read for DAQ ";
    2272            0 :                         auto sts = device_.read_data(DTC_DMA_Engine_DAQ, reinterpret_cast<void**>(&buffer_from_device), tmo_ms);
    2273            0 :                         if (writeOutput && sts > 8)
    2274              :                         {
    2275            0 :                                 DTC_TLOG(TLVL_VerifySimFileInDTC3) << "VerifySimFileInDTC: Writing to binary file";
    2276            0 :                                 outputStream.write(reinterpret_cast<char*>(*buffer_from_device), sts);
    2277              :                         }
    2278              : 
    2279            0 :                         if (sts == 0)
    2280              :                         {
    2281            0 :                                 DTC_TLOG(TLVL_ERROR) << "VerifySimFileInDTC Error reading buffer " << n << ", aborting!";
    2282            0 :                                 delete[] buffer_from_file;
    2283            0 :                                 is.close();
    2284            0 :                                 if (writeOutput) outputStream.close();
    2285            0 :                                 return false;
    2286              :                         }
    2287            0 :                         size_t readSz = *(reinterpret_cast<uint64_t*>(buffer_from_device));
    2288            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC) << "VerifySimFileInDTC - after read, bc=" << inclusiveByteCount << " sts=" << sts
    2289            0 :                                                                                           << " rdSz=" << readSz;
    2290              : 
    2291              :                         // DMA engine strips off leading 64-bit word
    2292            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC3) << "VerifySimFileInDTC - Checking buffer size";
    2293            0 :                         if (static_cast<size_t>(sts) != inclusiveByteCount)
    2294              :                         {
    2295            0 :                                 DTC_TLOG(TLVL_ERROR) << "VerifySimFileInDTC Buffer " << n << " has size 0x" << std::hex << sts
    2296            0 :                                                                          << " but the input file has size 0x" << std::hex << inclusiveByteCount
    2297            0 :                                                                          << " for that buffer!";
    2298              : 
    2299            0 :                                 device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2300            0 :                                 delete[] buffer_from_file;
    2301            0 :                                 is.close();
    2302            0 :                                 if (writeOutput) outputStream.close();
    2303            0 :                                 return false;
    2304              :                         }
    2305              : 
    2306            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC2) << "VerifySimFileInDTC - Checking buffer contents";
    2307            0 :                         size_t cnt = sts % sizeof(uint64_t) == 0 ? sts / sizeof(uint64_t) : 1 + (sts / sizeof(uint64_t));
    2308              : 
    2309            0 :                         for (size_t ii = 0; ii < cnt; ++ii)
    2310              :                         {
    2311            0 :                                 auto l = *(reinterpret_cast<uint64_t*>(*buffer_from_device) + ii);
    2312            0 :                                 auto r = *(reinterpret_cast<uint64_t*>(*buffer_from_file) + ii);
    2313            0 :                                 if (l != r)
    2314              :                                 {
    2315            0 :                                         size_t address = totalSize - file_buffer_size + ((ii + 1) * sizeof(uint64_t));
    2316            0 :                                         DTC_TLOG(TLVL_ERROR) << "VerifySimFileInDTC Buffer " << n << " word " << ii << " (Address in file 0x" << std::hex
    2317            0 :                                                                                  << address << "):"
    2318            0 :                                                                                  << " Expected 0x" << std::hex << r << ", but got 0x" << std::hex << l
    2319            0 :                                                                                  << ". Returning False!";
    2320            0 :                                         DTC_TLOG(TLVL_VerifySimFileInDTC3) << "VerifySimFileInDTC Next words: "
    2321            0 :                                                                                                                   "Expected 0x"
    2322            0 :                                                                                                            << std::hex << *(reinterpret_cast<uint64_t*>(*buffer_from_file) + ii + 1) << ", "
    2323            0 :                                                                                                                                                                                                                                                                         "DTC: 0x"
    2324            0 :                                                                                                            << std::hex << *(reinterpret_cast<uint64_t*>(*buffer_from_device) + ii + 1);
    2325            0 :                                         delete[] buffer_from_file;
    2326            0 :                                         is.close();
    2327            0 :                                         if (writeOutput) outputStream.close();
    2328            0 :                                         device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2329            0 :                                         return false;
    2330              :                                 }
    2331              :                         }
    2332            0 :                         device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2333            0 :                 }
    2334            0 :                 else if (file_buffer_size > 0)
    2335              :                 {
    2336            0 :                         DTC_TLOG(TLVL_VerifySimFileInDTC2) << "VerifySimFileInDTC DTC memory is now full. Closing file.";
    2337            0 :                         sizeCheck = false;
    2338              :                 }
    2339            0 :                 delete[] buffer_from_file;
    2340              :         }
    2341              : 
    2342            0 :         DTC_TLOG(TLVL_VerifySimFileInDTC) << "VerifySimFileInDTC Closing file. sizecheck=" << sizeCheck << ", eof=" << is.eof()
    2343            0 :                                                                           << ", fail=" << is.fail() << ", bad=" << is.bad();
    2344            0 :         __COUT_INFO__ << "VerifySimFileInDTC: The Detector Emulation file was written correctly";
    2345            0 :         is.close();
    2346            0 :         if (writeOutput) outputStream.close();
    2347            0 :         return true;
    2348            0 : }
    2349              : 
    2350              : // ROC Register Functions
    2351            0 : uint16_t DTCLib::DTC::ReadROCRegister(const DTC_Link_ID& link, const uint16_t address, int tmo_ms)
    2352              : {
    2353            0 :         uint16_t retries = 0;  // change to 1 to attempt reinitializing
    2354              :         do
    2355              :         {
    2356            0 :                 dcsDMAInfo_.currentReadPtr = nullptr;
    2357              : 
    2358            0 :                 device_.begin_dcs_transaction();
    2359            0 :                 ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2360            0 :                 SendDCSRequestPacket(link, DTC_DCSOperationType_Read, address,
    2361              :                                                          0x0 /*data*/, 0x0 /*address2*/, 0x0 /*data2*/,
    2362              :                                                          false /*quiet*/);
    2363              : 
    2364            0 :                 uint16_t data = 0xFFFF;
    2365              : 
    2366              :                 try
    2367              :                 {
    2368            0 :                         auto reply = ReadNextDCSPacket(tmo_ms);
    2369            0 :                         device_.end_dcs_transaction();
    2370              : 
    2371            0 :                         if (reply != nullptr)  // have data!
    2372              :                         {
    2373            0 :                                 auto replytmp = reply->GetReply(false);
    2374            0 :                                 auto linktmp = reply->GetLinkID();
    2375            0 :                                 data = replytmp.second;
    2376              : 
    2377            0 :                                 DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2378            0 :                                                                          << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2379            0 :                                                                          << "address=" << static_cast<int>(replytmp.first) << " (expected " << static_cast<int>(address)
    2380            0 :                                                                          << "), "
    2381            0 :                                                                          << "data=" << data;
    2382            0 :                                 if (linktmp == link && replytmp.first == address)
    2383            0 :                                         return data;
    2384              :                                 else
    2385              :                                 {
    2386            0 :                                         __SS__ << "Mismatch identified in link=" << linktmp << " != " << link << " or "
    2387            0 :                                                    << "address=" << replytmp.first << " != " << address << ". Corrupt ROC response?" << __E__;
    2388              :                                         ss << "\n\nIf interpreting as a DTC_DCSReplyPacket, here is the data: \n"
    2389            0 :                                            << reply->toJSON();
    2390            0 :                                         __SS_THROW__;
    2391            0 :                                 }
    2392              :                         }
    2393            0 :                 }
    2394            0 :                 catch (const std::exception& e)
    2395              :                 {
    2396            0 :                         __SS__ << "Failure attempting to read a ROC register at link " << static_cast<int>(link) << " address 0x" << std::hex << static_cast<int>(address) << ". Exception caught: " << e.what() << __E__;
    2397            0 :                         __SS_THROW__;
    2398            0 :                 }
    2399              : 
    2400              :                 // if here then software received no response from DTC, try a software re-init to realign DMA pointers
    2401            0 :                 if (retries)  // do not reinit on last try
    2402              :                 {
    2403            0 :                         __COUT__ << "Software received no response to the DCS request from the DTC, trying a DMA re-init. retries = " << retries << __E__;
    2404            0 :                         device_.initDMAEngine();
    2405              :                 }
    2406              : 
    2407            0 :         } while (retries--);
    2408              : 
    2409              :         // throw exception for no data after retries
    2410            0 :         __SS__ << "A timeout occurred attempting to read a ROC register at link " << static_cast<int>(link) << " address 0x" << std::hex << static_cast<int>(address) << ". No DCS reply packet received after " << std::dec << tmo_ms << " ms! "
    2411            0 :                    << "Check the clocks and that the ROC link is enabled and locked. Restarting the DTC software instance may fix the problem and realign DMA pointers." << std::endl;
    2412              : 
    2413            0 :         if (TTEST(20))
    2414              :         {
    2415            0 :                 __COUT_ERR__ << "\n"
    2416            0 :                                          << ss.str();
    2417            0 :                 device_.spy(DTC_DMA_Engine_DCS, 3 /* for once */ | 8 /* for wide view */);
    2418            0 :                 __COUT_ERR__ << otsStyleStackTrace();
    2419              :         }
    2420              : 
    2421            0 :         __SS_THROW__;
    2422            0 : }  // end ReadROCRegister()
    2423              : 
    2424            0 : bool DTCLib::DTC::WriteROCRegister(const DTC_Link_ID& link, const uint16_t address, const uint16_t data, bool requestAck, int ack_tmo_ms)
    2425              : {
    2426            0 :         device_.begin_dcs_transaction();
    2427            0 :         if (requestAck)
    2428              :         {
    2429            0 :                 dcsDMAInfo_.currentReadPtr = nullptr;
    2430            0 :                 ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2431              :         }
    2432            0 :         SendDCSRequestPacket(link, DTC_DCSOperationType_Write, address, data,
    2433              :                                                  0x0 /*address2*/, 0x0 /*data2*/,
    2434              :                                                  false /*quiet*/, requestAck);
    2435              : 
    2436            0 :         bool ackReceived = false;
    2437            0 :         if (requestAck)
    2438              :         {
    2439            0 :                 DTC_TLOG(TLVL_TRACE) << "WriteROCRegister: Checking for ack";
    2440            0 :                 auto reply = ReadNextDCSPacket(ack_tmo_ms);
    2441            0 :                 while (reply != nullptr)
    2442              :                 {
    2443            0 :                         auto reply1tmp = reply->GetReply(false);
    2444            0 :                         auto linktmp = reply->GetLinkID();
    2445            0 :                         DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2446            0 :                                                                  << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2447            0 :                                                                  << "address1=" << static_cast<int>(reply1tmp.first) << " (expected "
    2448            0 :                                                                  << static_cast<int>(address) << "), "
    2449            0 :                                                                  << "data1=" << static_cast<int>(reply1tmp.second);
    2450              : 
    2451            0 :                         reply.reset(nullptr);
    2452            0 :                         if (reply1tmp.first != address || linktmp != link || !reply->IsAckRequested())
    2453              :                         {
    2454            0 :                                 DTC_TLOG(TLVL_TRACE) << "Address or link did not match, or ack bit was not set, reading next packet!";
    2455            0 :                                 reply = ReadNextDCSPacket(ack_tmo_ms);  // Read the next packet
    2456              :                         }
    2457              :                         else
    2458              :                         {
    2459            0 :                                 ackReceived = true;
    2460              :                         }
    2461              :                 }
    2462            0 :         }
    2463            0 :         device_.end_dcs_transaction();
    2464            0 :         return !requestAck || ackReceived;
    2465              : }
    2466              : 
    2467            0 : std::pair<uint16_t, uint16_t> DTCLib::DTC::ReadROCRegisters(const DTC_Link_ID& link, const uint16_t address1,
    2468              :                                                                                                                         const uint16_t address2, int tmo_ms)
    2469              : {
    2470            0 :         dcsDMAInfo_.currentReadPtr = nullptr;
    2471              : 
    2472            0 :         device_.begin_dcs_transaction();
    2473            0 :         ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2474            0 :         SendDCSRequestPacket(link, DTC_DCSOperationType_Read, address1, 0, address2);
    2475            0 :         usleep(2500);
    2476            0 :         uint16_t data1 = 0xFFFF;
    2477            0 :         uint16_t data2 = 0xFFFF;
    2478              : 
    2479            0 :         auto reply = ReadNextDCSPacket(tmo_ms);
    2480              : 
    2481            0 :         while (reply != nullptr)
    2482              :         {
    2483            0 :                 auto reply1tmp = reply->GetReply(false);
    2484            0 :                 auto reply2tmp = reply->GetReply(true);
    2485            0 :                 auto linktmp = reply->GetLinkID();
    2486            0 :                 DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2487            0 :                                                          << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2488            0 :                                                          << "address1=" << static_cast<int>(reply1tmp.first) << " (expected "
    2489            0 :                                                          << static_cast<int>(address1) << "), "
    2490            0 :                                                          << "data1=" << static_cast<int>(reply1tmp.second)
    2491            0 :                                                          << "address2=" << static_cast<int>(reply2tmp.first) << " (expected "
    2492            0 :                                                          << static_cast<int>(address2) << "), "
    2493            0 :                                                          << "data2=" << static_cast<int>(reply2tmp.second);
    2494              : 
    2495            0 :                 reply.reset(nullptr);
    2496            0 :                 if (reply1tmp.first != address1 || reply2tmp.first != address2 || linktmp != link)
    2497              :                 {
    2498            0 :                         DTC_TLOG(TLVL_TRACE) << "Address or link did not match, reading next packet!";
    2499            0 :                         reply = ReadNextDCSPacket(tmo_ms);  // Read the next packet
    2500            0 :                         continue;
    2501            0 :                 }
    2502              :                 else
    2503              :                 {
    2504            0 :                         data1 = reply1tmp.second;
    2505            0 :                         data2 = reply2tmp.second;
    2506              :                 }
    2507              :         }
    2508            0 :         device_.end_dcs_transaction();
    2509            0 :         DTC_TLOG(TLVL_TRACE) << "ReadROCRegisters returning " << static_cast<int>(data1) << " for link " << static_cast<int>(link)
    2510            0 :                                                  << ", address " << static_cast<int>(address1) << ", " << static_cast<int>(data2) << ", address "
    2511            0 :                                                  << static_cast<int>(address2);
    2512            0 :         return std::make_pair(data1, data2);
    2513            0 : }
    2514              : 
    2515            0 : bool DTCLib::DTC::WriteROCRegisters(const DTC_Link_ID& link, const uint16_t address1, const uint16_t data1,
    2516              :                                                                         const uint16_t address2, const uint16_t data2, bool requestAck, int ack_tmo_ms)
    2517              : {
    2518            0 :         device_.begin_dcs_transaction();
    2519            0 :         if (requestAck)
    2520              :         {
    2521            0 :                 dcsDMAInfo_.currentReadPtr = nullptr;
    2522            0 :                 ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2523              :         }
    2524            0 :         SendDCSRequestPacket(link, DTC_DCSOperationType_Write, address1, data1, address2, data2, false /*quiet*/, requestAck);
    2525              : 
    2526            0 :         bool ackReceived = false;
    2527            0 :         if (requestAck)
    2528              :         {
    2529            0 :                 DTC_TLOG(TLVL_TRACE) << "WriteROCRegisters: Checking for ack";
    2530            0 :                 auto reply = ReadNextDCSPacket(ack_tmo_ms);
    2531            0 :                 while (reply != nullptr)
    2532              :                 {
    2533            0 :                         auto reply1tmp = reply->GetReply(false);
    2534            0 :                         auto reply2tmp = reply->GetReply(true);
    2535            0 :                         auto linktmp = reply->GetLinkID();
    2536            0 :                         DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2537            0 :                                                                  << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2538            0 :                                                                  << "address1=" << static_cast<int>(reply1tmp.first) << " (expected "
    2539            0 :                                                                  << static_cast<int>(address1) << "), "
    2540            0 :                                                                  << "data1=" << static_cast<int>(reply1tmp.second)
    2541            0 :                                                                  << "address2=" << static_cast<int>(reply2tmp.first) << " (expected "
    2542            0 :                                                                  << static_cast<int>(address2) << "), "
    2543            0 :                                                                  << "data2=" << static_cast<int>(reply2tmp.second);
    2544              : 
    2545            0 :                         reply.reset(nullptr);
    2546            0 :                         if (reply1tmp.first != address1 || reply2tmp.first != address2 || linktmp != link || !reply->IsAckRequested())
    2547              :                         {
    2548            0 :                                 DTC_TLOG(TLVL_TRACE) << "Address or link did not match, or ack bit was not set, reading next packet!";
    2549            0 :                                 reply = ReadNextDCSPacket(ack_tmo_ms);  // Read the next packet
    2550              :                         }
    2551              :                         else
    2552              :                         {
    2553            0 :                                 ackReceived = true;
    2554              :                         }
    2555              :                 }
    2556            0 :         }
    2557            0 :         device_.end_dcs_transaction();
    2558            0 :         return !requestAck || ackReceived;
    2559              : }
    2560              : 
    2561            0 : void DTCLib::DTC::ReadROCBlock(
    2562              :         std::vector<uint16_t>& data,
    2563              :         const DTC_Link_ID& link, const uint16_t address,
    2564              :         const uint16_t wordCount, bool incrementAddress, int tmo_ms)
    2565              : {
    2566            0 :         DTC_DCSRequestPacket req(link, DTC_DCSOperationType_BlockRead, false, incrementAddress, address, wordCount);
    2567              : 
    2568            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "ReadROCBlock before WriteDMADCSPacket - DTC_DCSRequestPacket";
    2569              : 
    2570            0 :         dcsDMAInfo_.currentReadPtr = nullptr;
    2571              : 
    2572            0 :         if (!ReadDCSReception()) EnableDCSReception();
    2573              : 
    2574            0 :         device_.begin_dcs_transaction();
    2575            0 :         ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2576            0 :         WriteDMAPacket(req);
    2577            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "ReadROCBlock after  WriteDMADCSPacket - DTC_DCSRequestPacket";
    2578              : 
    2579            0 :         usleep(2500);
    2580              : 
    2581            0 :         auto reply = ReadNextDCSPacket(tmo_ms);
    2582            0 :         while (reply != nullptr)
    2583              :         {
    2584            0 :                 auto replytmp = reply->GetReply(false);
    2585            0 :                 auto linktmp = reply->GetLinkID();
    2586            0 :                 DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2587            0 :                                                          << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2588            0 :                                                          << "address=" << static_cast<int>(replytmp.first) << " (expected " << static_cast<int>(address)
    2589            0 :                                                          << "), "
    2590            0 :                                                          << "wordCount=" << static_cast<int>(replytmp.second);
    2591              : 
    2592            0 :                 data = reply->GetBlockReadData();
    2593            0 :                 auto packetCount = reply->GetBlockPacketCount();
    2594            0 :                 reply.reset(nullptr);
    2595            0 :                 if (replytmp.first != address || linktmp != link)
    2596              :                 {
    2597            0 :                         DTC_TLOG(TLVL_TRACE) << "Address or link did not match, reading next packet!";
    2598            0 :                         reply = ReadNextDCSPacket(tmo_ms);  // Read the next packet
    2599            0 :                         continue;
    2600            0 :                 }
    2601              : 
    2602            0 :                 auto wordCount = replytmp.second;
    2603            0 :                 auto processedWords = 3;
    2604              : 
    2605            0 :                 while (packetCount > 0)
    2606              :                 {
    2607            0 :                         dcsDMAInfo_.lastReadPtr = reinterpret_cast<uint8_t*>(dcsDMAInfo_.lastReadPtr) + 16;
    2608            0 :                         auto dataPacket = new DTC_DataPacket(dcsDMAInfo_.lastReadPtr);
    2609            0 :                         if (dataPacket == nullptr) break;
    2610              : 
    2611            0 :                         DTC_TLOG(TLVL_TRACE) << "ReadROCBlock: next data packet: " << dataPacket->toJSON();
    2612            0 :                         auto byteInPacket = 0;
    2613              : 
    2614            0 :                         while (wordCount - processedWords > 0 && byteInPacket < 16)
    2615              :                         {
    2616            0 :                                 uint16_t thisWord = dataPacket->GetByte(byteInPacket) + (dataPacket->GetByte(byteInPacket + 1) << 8);
    2617            0 :                                 byteInPacket += 2;
    2618            0 :                                 data.push_back(thisWord);
    2619            0 :                                 processedWords++;
    2620              :                         }
    2621              : 
    2622            0 :                         packetCount--;
    2623              :                 }
    2624              :         }
    2625            0 :         device_.end_dcs_transaction();
    2626              : 
    2627            0 :         DTC_TLOG(TLVL_TRACE) << "ReadROCBlock returning " << static_cast<int>(data.size()) << " words for link " << static_cast<int>(link)
    2628            0 :                                                  << ", address " << static_cast<int>(address);
    2629            0 : }
    2630              : 
    2631            0 : bool DTCLib::DTC::WriteROCBlock(const DTC_Link_ID& link, const uint16_t address,
    2632              :                                                                 const std::vector<uint16_t>& blockData, bool requestAck, bool incrementAddress, int ack_tmo_ms)
    2633              : {
    2634            0 :         DTC_DCSRequestPacket req(link, DTC_DCSOperationType_BlockWrite, requestAck, incrementAddress, address);
    2635            0 :         req.SetBlockWriteData(blockData);
    2636              : 
    2637            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "WriteROCBlock before WriteDMADCSPacket - DTC_DCSRequestPacket";
    2638              : 
    2639            0 :         if (!ReadDCSReception()) EnableDCSReception();
    2640              : 
    2641            0 :         device_.begin_dcs_transaction();
    2642            0 :         if (requestAck)
    2643              :         {
    2644            0 :                 dcsDMAInfo_.currentReadPtr = nullptr;
    2645            0 :                 ReleaseAllBuffers(DTC_DMA_Engine_DCS);
    2646              :         }
    2647            0 :         WriteDMAPacket(req);
    2648            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "WriteROCBlock after  WriteDMADCSPacket - DTC_DCSRequestPacket";
    2649              : 
    2650            0 :         bool ackReceived = false;
    2651            0 :         if (requestAck)
    2652              :         {
    2653            0 :                 DTC_TLOG(TLVL_TRACE) << "WriteROCBlock: Checking for ack";
    2654            0 :                 auto reply = ReadNextDCSPacket(ack_tmo_ms);
    2655            0 :                 while (reply != nullptr)
    2656              :                 {
    2657            0 :                         auto reply1tmp = reply->GetReply(false);
    2658            0 :                         auto linktmp = reply->GetLinkID();
    2659            0 :                         DTC_TLOG(TLVL_TRACE) << "Got packet, "
    2660            0 :                                                                  << "link=" << static_cast<int>(linktmp) << " (expected " << static_cast<int>(link) << "), "
    2661            0 :                                                                  << "address1=" << static_cast<int>(reply1tmp.first) << " (expected "
    2662            0 :                                                                  << static_cast<int>(address) << "), "
    2663            0 :                                                                  << "data1=" << static_cast<int>(reply1tmp.second);
    2664              : 
    2665            0 :                         reply.reset(nullptr);
    2666            0 :                         if (reply1tmp.first != address || linktmp != link || !reply->IsAckRequested())
    2667              :                         {
    2668            0 :                                 DTC_TLOG(TLVL_TRACE) << "Address or link did not match, or ack bit was not set, reading next packet!";
    2669            0 :                                 reply = ReadNextDCSPacket(ack_tmo_ms);  // Read the next packet
    2670              :                         }
    2671              :                         else
    2672              :                         {
    2673            0 :                                 ackReceived = true;
    2674              :                         }
    2675              :                 }
    2676            0 :         }
    2677            0 :         device_.end_dcs_transaction();
    2678            0 :         return !requestAck || ackReceived;
    2679            0 : }
    2680              : 
    2681            0 : uint16_t DTCLib::DTC::ReadExtROCRegister(const DTC_Link_ID& link, const uint16_t block,
    2682              :                                                                                  const uint16_t address, int tmo_ms)
    2683              : {
    2684            0 :         uint16_t addressT = address & 0x7FFF;
    2685            0 :         WriteROCRegister(link, 12, block, false, tmo_ms);
    2686            0 :         WriteROCRegister(link, 13, addressT, false, tmo_ms);
    2687            0 :         WriteROCRegister(link, 13, addressT | 0x8000, false, tmo_ms);
    2688            0 :         return ReadROCRegister(link, 22, tmo_ms);
    2689              : }
    2690              : 
    2691            0 : bool DTCLib::DTC::WriteExtROCRegister(const DTC_Link_ID& link, const uint16_t block,
    2692              :                                                                           const uint16_t address,
    2693              :                                                                           const uint16_t data, bool requestAck, int ack_tmo_ms)
    2694              : {
    2695            0 :         uint16_t dataT = data & 0x7FFF;
    2696            0 :         bool success = true;
    2697            0 :         success &= WriteROCRegister(link, 12, block + (address << 8), requestAck, ack_tmo_ms);
    2698            0 :         success &= WriteROCRegister(link, 13, dataT, requestAck, ack_tmo_ms);
    2699            0 :         success &= WriteROCRegister(link, 13, dataT | 0x8000, requestAck, ack_tmo_ms);
    2700            0 :         return success;
    2701              : }
    2702              : 
    2703            0 : std::string DTCLib::DTC::ROCRegDump(const DTC_Link_ID& link)
    2704              : {
    2705            0 :         std::ostringstream o;
    2706            0 :         o.setf(std::ios_base::boolalpha);
    2707            0 :         o << "{";
    2708            0 :         o << "\"Forward Detector 0 Status\": " << ReadExtROCRegister(link, 8, 0) << ",\n";
    2709            0 :         o << "\"Forward Detector 1 Status\": " << ReadExtROCRegister(link, 9, 0) << ",\n";
    2710            0 :         o << "\"Command Handler Status\": " << ReadExtROCRegister(link, 10, 0) << ",\n";
    2711            0 :         o << "\"Packet Sender 0 Status\": " << ReadExtROCRegister(link, 11, 0) << ",\n";
    2712            0 :         o << "\"Packet Sender 1 Status\": " << ReadExtROCRegister(link, 12, 0) << ",\n";
    2713            0 :         o << "\"Forward Detector 0 Errors\": " << ReadExtROCRegister(link, 8, 1) << ",\n";
    2714            0 :         o << "\"Forward Detector 1 Errors\": " << ReadExtROCRegister(link, 9, 1) << ",\n";
    2715            0 :         o << "\"Command Handler Errors\": " << ReadExtROCRegister(link, 10, 1) << ",\n";
    2716            0 :         o << "\"Packet Sender 0 Errors\": " << ReadExtROCRegister(link, 11, 1) << ",\n";
    2717            0 :         o << "\"Packet Sender 1 Errors\": " << ReadExtROCRegister(link, 12, 1) << "\n";
    2718            0 :         o << "}";
    2719              : 
    2720            0 :         return o.str();
    2721            0 : }
    2722              : 
    2723            0 : void DTCLib::DTC::SendHeartbeatPacket(const DTC_Link_ID& link, const DTC_EventWindowTag& when, bool quiet)
    2724              : {
    2725            0 :         DTC_HeartbeatPacket req(link, when);
    2726            0 :         DTC_TLOG(TLVL_SendHeartbeatPacket) << "SendHeartbeatPacket before WriteDMADAQPacket - DTC_HeartbeatPacket";
    2727            0 :         if (!quiet) DTC_TLOG(TLVL_SendHeartbeatPacket) << req.toJSON();
    2728            0 :         WriteDMAPacket(req);
    2729            0 :         DTC_TLOG(TLVL_SendHeartbeatPacket) << "SendHeartbeatPacket after  WriteDMADAQPacket - DTC_HeartbeatPacket";
    2730            0 : }
    2731              : 
    2732              : // Note! Before calling this function SendDCSRequestPacket(), the device must be locked with device_.begin_dcs_transaction();
    2733            0 : void DTCLib::DTC::SendDCSRequestPacket(const DTC_Link_ID& link, const DTC_DCSOperationType type, const uint16_t address,
    2734              :                                                                            const uint16_t data, const uint16_t address2, const uint16_t data2, bool quiet, bool requestAck)
    2735              : {
    2736            0 :         DTC_DCSRequestPacket req(link, type, requestAck, false /*incrementAddress*/, address, data);
    2737              : 
    2738            0 :         if (!quiet) DTC_TLOG(TLVL_SendDCSRequestPacket) << "Init DCS Packet: \n"
    2739            0 :                                                                                                         << req.toJSON();
    2740              : 
    2741            0 :         if (type == DTC_DCSOperationType_DoubleRead ||
    2742              :                 type == DTC_DCSOperationType_DoubleWrite)
    2743              :         {
    2744            0 :                 DTC_TLOG(TLVL_SendDCSRequestPacket) << "Double operation enabled!";
    2745            0 :                 req.AddRequest(address2, data2);
    2746              :         }
    2747              : 
    2748            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "SendDCSRequestPacket before WriteDMADCSPacket - DTC_DCSRequestPacket";
    2749              : 
    2750            0 :         if (!quiet) DTC_TLOG(TLVL_SendDCSRequestPacket) << "Sending DCS Packet: \n"
    2751            0 :                                                                                                         << req.toJSON();
    2752              : 
    2753            0 :         if (!ReadDCSReception()) EnableDCSReception();
    2754              : 
    2755            0 :         WriteDMAPacket(req);
    2756            0 :         DTC_TLOG(TLVL_SendDCSRequestPacket) << "SendDCSRequestPacket after  WriteDMADCSPacket - DTC_DCSRequestPacket";
    2757            0 : }
    2758              : 
    2759            0 : std::unique_ptr<DTCLib::DTC_Event> DTCLib::DTC::ReadNextDAQDMA(int tmo_ms)
    2760              : {
    2761            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA BEGIN";
    2762              : 
    2763            0 :         if (daqDMAInfo_.currentReadPtr != nullptr)
    2764              :         {
    2765            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA BEFORE BUFFER CHECK daqDMAInfo_.currentReadPtr="
    2766            0 :                                                                                  << (void*)daqDMAInfo_.currentReadPtr << " *nextReadPtr_=0x" << std::hex
    2767            0 :                                                                                  << *(uint16_t*)daqDMAInfo_.currentReadPtr;
    2768              :         }
    2769              :         else
    2770              :         {
    2771            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA BEFORE BUFFER CHECK daqDMAInfo_.currentReadPtr=nullptr";
    2772              :         }
    2773              : 
    2774            0 :         auto index = CFOandDTC_DMAs::GetCurrentBuffer(&daqDMAInfo_);
    2775              : 
    2776              :         // Need new buffer if GetCurrentBuffer returns -1 (no buffers) or -2 (done with all held buffers)
    2777            0 :         if (index < 0)
    2778              :         {
    2779            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA Obtaining new DAQ Buffer";
    2780              : 
    2781            0 :                 void* oldBufferPtr = nullptr;
    2782            0 :                 if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    2783            0 :                 auto sts = ReadBuffer(DTC_DMA_Engine_DAQ, tmo_ms);  // does return code
    2784            0 :                 if (sts <= 0)
    2785              :                 {
    2786            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA: ReadBuffer returned " << sts << ", returning nullptr";
    2787            0 :                         return nullptr;
    2788              :                 }
    2789              :                 // MUST BE ABLE TO HANDLE daqbuffer_==nullptr OR retry forever?
    2790            0 :                 daqDMAInfo_.currentReadPtr = &daqDMAInfo_.buffer.back()[0];
    2791            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA daqDMAInfo_.currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr
    2792            0 :                                                                                  << " *daqDMAInfo_.currentReadPtr=0x" << std::hex << *(unsigned*)daqDMAInfo_.currentReadPtr
    2793            0 :                                                                                  << " lastReadPtr_=" << (void*)daqDMAInfo_.lastReadPtr;
    2794            0 :                 void* bufferIndexPointer = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2795            0 :                 if (daqDMAInfo_.currentReadPtr == oldBufferPtr && daqDMAInfo_.bufferIndex == *static_cast<uint32_t*>(bufferIndexPointer))
    2796              :                 {
    2797            0 :                         DTC_TLOG(TLVL_WARN) << "ReadNextDAQDMA: New buffer is the same as old. Releasing buffer and returning nullptr";
    2798            0 :                         daqDMAInfo_.currentReadPtr = nullptr;
    2799              :                         // We didn't actually get a new buffer...this probably means there's no more data
    2800              :                         // Try and see if we're merely stuck...hopefully, all the data is out of the buffers...
    2801            0 :                         device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2802            0 :                         return nullptr;
    2803              :                 }
    2804            0 :                 daqDMAInfo_.bufferIndex++;
    2805              : 
    2806            0 :                 daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2807            0 :                 *static_cast<uint32_t*>(daqDMAInfo_.currentReadPtr) = daqDMAInfo_.bufferIndex;
    2808            0 :                 daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2809              : 
    2810            0 :                 index = daqDMAInfo_.buffer.size() - 1;
    2811              :         }
    2812              : 
    2813            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "Creating DTC_Event from current DMA Buffer";
    2814              :         // Utilities::PrintBuffer(daqDMAInfo_.currentReadPtr, 128, TLVL_ReadNextDAQPacket);
    2815            0 :         auto res = std::make_unique<DTC_Event>(daqDMAInfo_.currentReadPtr);  // only does setup of Event Header
    2816              : 
    2817            0 :         auto eventByteCount = res->GetEventByteCount();
    2818            0 :         if (eventByteCount == 0)
    2819              :         {
    2820            0 :                 __SS__ << "Event inclusive byte count cannot be zero!" << __E__;
    2821            0 :                 __SS_THROW__;
    2822            0 :         }
    2823            0 :         size_t remainingBufferSize = CFOandDTC_DMAs::GetBufferByteCount(&daqDMAInfo_, index) - sizeof(uint64_t);
    2824            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "eventByteCount: " << eventByteCount << ", remainingBufferSize: " << remainingBufferSize;
    2825              :         // Check for continued DMA
    2826            0 :         if (eventByteCount > remainingBufferSize)
    2827              :         {
    2828              :                 // We're going to set lastReadPtr here, so that if this buffer isn't used by GetData, we start at the beginning of this event next time
    2829            0 :                 daqDMAInfo_.lastReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr);
    2830              : 
    2831            0 :                 auto inmem = std::make_unique<DTC_Event>(eventByteCount);
    2832            0 :                 memcpy(const_cast<void*>(inmem->GetRawBufferPointer()), res->GetRawBufferPointer(), remainingBufferSize);
    2833              : 
    2834            0 :                 auto bytes_read = remainingBufferSize;
    2835            0 :                 while (bytes_read < eventByteCount)
    2836              :                 {
    2837            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA Obtaining new DAQ Buffer, bytes_read=" << bytes_read << ", eventByteCount=" << eventByteCount;
    2838              : 
    2839            0 :                         void* oldBufferPtr = nullptr;
    2840            0 :                         if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    2841            0 :                         auto sts = ReadBuffer(DTC_DMA_Engine_DAQ, tmo_ms);  // does return code
    2842            0 :                         if (sts <= 0)
    2843              :                         {
    2844            0 :                                 DTC_TLOG(TLVL_WARN) << "ReadNextDAQDMA: ReadBuffer returned " << sts << ", returning nullptr";
    2845            0 :                                 return nullptr;
    2846              :                         }
    2847              :                         // MUST BE ABLE TO HANDLE daqbuffer_==nullptr OR retry forever?
    2848            0 :                         daqDMAInfo_.currentReadPtr = &daqDMAInfo_.buffer.back()[0];
    2849            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA daqDMAInfo_.currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr
    2850            0 :                                                                                          << " *daqDMAInfo_.currentReadPtr=0x" << std::hex << *(unsigned*)daqDMAInfo_.currentReadPtr
    2851            0 :                                                                                          << " lastReadPtr_=" << (void*)daqDMAInfo_.lastReadPtr;
    2852            0 :                         void* bufferIndexPointer = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2853            0 :                         if (daqDMAInfo_.currentReadPtr == oldBufferPtr && daqDMAInfo_.bufferIndex == *static_cast<uint32_t*>(bufferIndexPointer))
    2854              :                         {
    2855            0 :                                 DTC_TLOG(TLVL_WARN) << "ReadNextDAQDMA: New buffer is the same as old. Releasing buffer and returning nullptr";
    2856            0 :                                 daqDMAInfo_.currentReadPtr = nullptr;
    2857              :                                 // We didn't actually get a new buffer...this probably means there's no more data
    2858              :                                 // Try and see if we're merely stuck...hopefully, all the data is out of the buffers...
    2859            0 :                                 device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2860            0 :                                 return nullptr;
    2861              :                         }
    2862            0 :                         daqDMAInfo_.bufferIndex++;
    2863              : 
    2864            0 :                         size_t buffer_size = *static_cast<uint16_t*>(daqDMAInfo_.currentReadPtr);
    2865            0 :                         daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2866            0 :                         *static_cast<uint32_t*>(daqDMAInfo_.currentReadPtr) = daqDMAInfo_.bufferIndex;
    2867            0 :                         daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2868              : 
    2869            0 :                         size_t remainingEventSize = eventByteCount - bytes_read;
    2870            0 :                         size_t copySize = remainingEventSize < buffer_size - 8 ? remainingEventSize : buffer_size - 8;
    2871            0 :                         memcpy(const_cast<uint8_t*>(static_cast<const uint8_t*>(inmem->GetRawBufferPointer()) + bytes_read), daqDMAInfo_.currentReadPtr, copySize);
    2872            0 :                         bytes_read += buffer_size - 8;
    2873              : 
    2874              :                         // Increment by the size of the data block
    2875            0 :                         daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + copySize;
    2876              :                 }
    2877              : 
    2878            0 :                 res.swap(inmem);
    2879            0 :         }
    2880              :         else  // Event not split over multiple DMAs
    2881              :         {
    2882              :                 // Update the packet pointers
    2883              : 
    2884              :                 // lastReadPtr_ is easy...
    2885            0 :                 daqDMAInfo_.lastReadPtr = daqDMAInfo_.currentReadPtr;
    2886              : 
    2887              :                 // Increment by the size of the data block
    2888            0 :                 daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + res->GetEventByteCount();
    2889              :         }
    2890            0 :         res->SetupEvent();  // does setup of Event header + all payload
    2891              : 
    2892            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQDMA: RETURN";
    2893            0 :         return res;
    2894            0 : }  // end ReadNextDAQDMA()
    2895              : 
    2896            0 : bool DTCLib::DTC::ReadNextDAQSubEventDMA(std::vector<std::unique_ptr<DTC_SubEvent>>& output, int tmo_ms)
    2897              : {
    2898            0 :         TRACE_EXIT
    2899              :         {
    2900            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA EXIT"
    2901            0 :                                                                                  << " currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr
    2902            0 :                                                                                  << " lastReadPtr=" << (void*)daqDMAInfo_.lastReadPtr
    2903            0 :                                                                                  << " buffer.size()=" << daqDMAInfo_.buffer.size();
    2904            0 :         };
    2905              : 
    2906            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA BEGIN";
    2907              : 
    2908            0 :         if (daqDMAInfo_.currentReadPtr != nullptr)
    2909              :         {
    2910            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA BEFORE BUFFER CHECK daqDMAInfo_.currentReadPtr="
    2911            0 :                                                                                  << (void*)daqDMAInfo_.currentReadPtr << " currentBufferTransferSize=0x" << std::hex
    2912            0 :                                                                                  << *(uint16_t*)daqDMAInfo_.currentReadPtr;
    2913              :         }
    2914              :         else
    2915              :         {
    2916            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA BEFORE BUFFER CHECK daqDMAInfo_.currentReadPtr=nullptr";
    2917              :         }
    2918              : 
    2919            0 :         auto index = CFOandDTC_DMAs::GetCurrentBuffer(&daqDMAInfo_);  // if buffers onhand, returns daqDMAInfo_.buffer.size().. which is count used by ReleaseBuffers()
    2920              : 
    2921            0 :         size_t metaBufferSize = 0;
    2922              : 
    2923              :         // Need new starting subevent buffer if GetCurrentBuffer returns -1 (no buffers) or -2 (done with all held buffers)
    2924            0 :         if (index < 0)
    2925              :         {
    2926            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA Obtaining new DAQ Buffer";
    2927              : 
    2928            0 :                 void* oldBufferPtr = nullptr;
    2929            0 :                 if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    2930            0 :                 auto sts = ReadBuffer(DTC_DMA_Engine_DAQ, tmo_ms);  // does return code
    2931            0 :                 if (sts <= 0)
    2932              :                 {
    2933            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA: ReadBuffer returned " << sts << ", returning false";
    2934            0 :                         return false;
    2935              :                 }
    2936            0 :                 metaBufferSize = sts;
    2937              :                 // MUST BE ABLE TO HANDLE daqbuffer_==nullptr OR retry forever?
    2938            0 :                 daqDMAInfo_.currentReadPtr = &daqDMAInfo_.buffer.back()[0];
    2939            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA daqDMAInfo_.currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr
    2940            0 :                                                                                  << " currentBufferTransferSize=0x" << std::hex << *(unsigned*)daqDMAInfo_.currentReadPtr
    2941            0 :                                                                                  << " lastReadPtr=" << (void*)daqDMAInfo_.lastReadPtr;
    2942            0 :                 void* bufferIndexPointer = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2943            0 :                 if (daqDMAInfo_.currentReadPtr == oldBufferPtr && daqDMAInfo_.bufferIndex == *static_cast<uint32_t*>(bufferIndexPointer))
    2944              :                 {
    2945            0 :                         daqDMAInfo_.currentReadPtr = nullptr;
    2946              :                         // We didn't actually get a new buffer...this probably means there's no more data
    2947              :                         // Try and see if we're merely stuck...hopefully, all the data is out of the buffers...
    2948            0 :                         device_.read_release(DTC_DMA_Engine_DAQ, 1);
    2949            0 :                         DTC_TLOG(TLVL_WARN)
    2950            0 :                                 << "ReadNextDAQSubEventDMA: New buffer was the same as old. Released buffer and returning false";
    2951            0 :                         return false;
    2952              :                 }
    2953            0 :                 daqDMAInfo_.bufferIndex++;
    2954              : 
    2955            0 :                 daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2956            0 :                 *static_cast<uint32_t*>(daqDMAInfo_.currentReadPtr) = daqDMAInfo_.bufferIndex;
    2957            0 :                 daqDMAInfo_.currentReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) + 4;
    2958              : 
    2959            0 :                 index = daqDMAInfo_.buffer.size() - 1;
    2960            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "Creating DTC_SubEvent from new DMA Buffer, index=" << index << " in " << daqDMAInfo_.buffer.size() << " buffers.";
    2961              :         }
    2962              :         else  // buffer already onhand
    2963              :         {
    2964            0 :                 __SS__ << "Impossible buffer already onhand!" << __E__;
    2965            0 :                 __SS_THROW__;
    2966              : 
    2967              :                 // DTC_TLOG(TLVL_ReadNextDAQPacket) << "Creating DTC_SubEvent from current DMA Buffer, index=" << index <<
    2968              :                 //      " in " << daqDMAInfo_.buffer.size() << " buffers.";
    2969            0 :         }
    2970              : 
    2971            0 :         if (  // current read ptr sanity check
    2972            0 :                 (void*)(static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) - 8) !=
    2973            0 :                 (void*)(&daqDMAInfo_.buffer[index][0]))
    2974              :         {
    2975            0 :                 __SS__ << "Impossible current buffer pointer for index " << index << ".. " << (void*)(static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr) - 8) << " != " << (void*)&daqDMAInfo_.buffer[index - 1][0] << __E__;
    2976            0 :                 __SS_THROW__;
    2977            0 :         }
    2978              : 
    2979              :         // Utilities::PrintBuffer(daqDMAInfo_.currentReadPtr, 128, TLVL_ReadNextDAQPacket);
    2980              : 
    2981            0 :         size_t inBufferByteCount = CFOandDTC_DMAs::GetBufferByteCount(&daqDMAInfo_, index);
    2982              :         // Use DMA descriptor byte count from ReadBuffer (dont use the in-buffer DMA transfer size count because the DTC stacks sub-transfers!)
    2983              :         // Subtract 1 for tlast byte, but NOT when buffer is completely full (no room for tlast)
    2984            0 :         size_t remainingBufferSize = metaBufferSize < sizeof(mu2e_databuff_t) ? metaBufferSize - 1 : metaBufferSize;
    2985            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "sizeof(DTC_SubEventHeader) = " << sizeof(DTC_SubEventHeader) << " GetBufferByteCount=" << inBufferByteCount
    2986            0 :                                                                          << " metaBufferSize=" << metaBufferSize;
    2987              : 
    2988              :         // Extract multiple subevents from buffer
    2989              :         //  Note: the while loop condition allows entry when there is at least the
    2990              :         //  per-subevent DMA transfer size (8 bytes) remaining.  This covers three cases:
    2991              :         //    1) Only the 8-byte per-subevent prefix at the buffer tail (0 header bytes) -- entire subevent is in the next buffer
    2992              :         //    2) Partial subevent header straddles the buffer boundary (1..47 header bytes)
    2993              :         //    3) Full subevent header (and possibly payload) fits in the current buffer
    2994              :         //  Cases 1 and 2 are handled by reading continuation buffer(s) and assembling contiguously.
    2995            0 :         while (remainingBufferSize >= sizeof(uint64_t))
    2996              :         {
    2997            0 :                 remainingBufferSize -= sizeof(uint64_t);  // remove per-subevent DMA transfer size from remaining byte count
    2998              : 
    2999              :                 // Check if the subevent header is fully contained in the current buffer
    3000            0 :                 bool headerSplitAcrossBuffers = (remainingBufferSize < sizeof(DTC_SubEventHeader));
    3001              : 
    3002            0 :                 if (headerSplitAcrossBuffers)
    3003              :                 {
    3004              :                         // The subevent header straddles the DMA buffer boundary (or is entirely in the next buffer).
    3005              :                         // We must read the continuation buffer to assemble the full header before we can
    3006              :                         // determine the subevent byte count and proceed with normal processing.
    3007            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA: subevent header split across DMA buffer boundary!"
    3008            0 :                                                                                          << " partialBytes(remainingBufferSize)=" << remainingBufferSize << " < sizeof(DTC_SubEventHeader)=" << sizeof(DTC_SubEventHeader)
    3009            0 :                                                                                          << " at currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr;
    3010              : 
    3011            0 :                         daqDMAInfo_.lastReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr);
    3012              : 
    3013              :                         // Save partial data from end of current buffer (may be 0 if only the per-subevent prefix was here)
    3014            0 :                         size_t partialBytes = remainingBufferSize;
    3015              : 
    3016              :                         // Read continuation buffer to get the rest
    3017            0 :                         void* oldBufferPtr = nullptr;
    3018            0 :                         if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    3019              : 
    3020              :                         int sts;
    3021            0 :                         int retry = 10;
    3022            0 :                         while ((sts = ReadBuffer(DTC_DMA_Engine_DAQ, 10 /* retries */)) <= 0 && --retry > 0)
    3023            0 :                                 usleep(1000);
    3024              : 
    3025            0 :                         if (sts <= 0)
    3026              :                         {
    3027            0 :                                 __SS__ << "Timeout after receiving only partial subevent header (" << partialBytes << "/" << sizeof(DTC_SubEventHeader) << " bytes)!"
    3028            0 :                                            << " currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr;
    3029            0 :                                 __SS_THROW__;
    3030            0 :                         }
    3031              : 
    3032            0 :                         auto* contBufferStart = &daqDMAInfo_.buffer.back()[0];
    3033            0 :                         if (contBufferStart == oldBufferPtr)
    3034              :                         {
    3035            0 :                                 __SS__ << "Received same buffer twice, only received partial subevent header!! partialBytes=" << partialBytes;
    3036            0 :                                 __SS_THROW__;
    3037            0 :                         }
    3038            0 :                         daqDMAInfo_.bufferIndex++;
    3039              : 
    3040              :                         // Assemble a temporary buffer with at least the full header so we can read the subevent byte count.
    3041              :                         // NOTE: continuation buffers do NOT have a per-sub-transfer DMA byte count header at offset 0.
    3042            0 :                         size_t contBufferPayload = sts;
    3043            0 :                         size_t headerBytesNeeded = sizeof(DTC_SubEventHeader) - partialBytes;
    3044              : 
    3045            0 :                         if (contBufferPayload < headerBytesNeeded)
    3046              :                         {
    3047            0 :                                 __SS__ << "Continuation buffer too small to complete subevent header! contBufferPayload=" << contBufferPayload
    3048            0 :                                            << " headerBytesNeeded=" << headerBytesNeeded;
    3049            0 :                                 __SS_THROW__;
    3050            0 :                         }
    3051              : 
    3052              :                         // Assemble the complete header from the partial data in the old buffer
    3053              :                         // (if any) and the beginning of the continuation buffer.
    3054              :                         uint8_t headerBuf[sizeof(DTC_SubEventHeader)];
    3055            0 :                         if (partialBytes > 0)
    3056            0 :                                 memcpy(headerBuf, daqDMAInfo_.currentReadPtr, partialBytes);
    3057            0 :                         memcpy(headerBuf + partialBytes, contBufferStart, headerBytesNeeded);
    3058              : 
    3059              :                         // Extract subEventByteCount from the assembled header (first 25 bits of first 4 bytes)
    3060            0 :                         auto subEventByteCount = static_cast<size_t>(
    3061            0 :                                 *reinterpret_cast<uint32_t*>(headerBuf) & 0x1FFFFFF);
    3062              : 
    3063            0 :                         if (subEventByteCount < sizeof(DTC_SubEventHeader))
    3064              :                         {
    3065            0 :                                 __SS__ << "SubEvent inclusive byte count (" << subEventByteCount << ") cannot be less than the size of the subevent header ("
    3066            0 :                                            << sizeof(DTC_SubEventHeader) << "-bytes)! (detected during split-header handling)" << __E__;
    3067            0 :                                 __SS_THROW__;
    3068            0 :                         }
    3069              : 
    3070            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA split-header: assembled subEventByteCount=" << subEventByteCount
    3071            0 :                                                                                          << " (0x" << std::hex << subEventByteCount << ")"
    3072            0 :                                                                                          << " partialBytes=" << std::dec << partialBytes << " contBufferPayload=" << contBufferPayload;
    3073              : 
    3074              :                         // Allocate contiguous memory for the full subevent and copy data in
    3075            0 :                         auto inmem = std::make_unique<DTC_SubEvent>(subEventByteCount);
    3076              : 
    3077              :                         // Copy partial data from end of previous buffer (if any)
    3078            0 :                         if (partialBytes > 0)
    3079            0 :                                 memcpy(const_cast<void*>(inmem->GetRawBufferPointer()), daqDMAInfo_.currentReadPtr, partialBytes);
    3080              : 
    3081              :                         // Copy data from continuation buffer
    3082            0 :                         size_t contBytesForSubEvent = subEventByteCount - partialBytes;
    3083            0 :                         size_t contCopySize = contBytesForSubEvent < contBufferPayload ? contBytesForSubEvent : contBufferPayload;
    3084            0 :                         memcpy(const_cast<uint8_t*>(static_cast<const uint8_t*>(inmem->GetRawBufferPointer()) + partialBytes),
    3085              :                                    contBufferStart, contCopySize);
    3086              : 
    3087            0 :                         auto bytes_read = partialBytes + contCopySize;
    3088            0 :                         daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(contBufferStart) + contCopySize;
    3089              : 
    3090            0 :                         size_t lastBufferPayloadSize = contBufferPayload;
    3091            0 :                         size_t lastCopySize = contCopySize;
    3092              : 
    3093              :                         // If the subevent still needs more data from additional buffers, continue reading
    3094            0 :                         while (bytes_read < subEventByteCount)
    3095              :                         {
    3096            0 :                                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA split-header continuation: bytes_read=" << bytes_read
    3097            0 :                                                                                                  << " subEventByteCount=" << subEventByteCount;
    3098              : 
    3099            0 :                                 oldBufferPtr = nullptr;
    3100            0 :                                 if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    3101              : 
    3102            0 :                                 retry = 10;
    3103            0 :                                 while ((sts = ReadBuffer(DTC_DMA_Engine_DAQ, 10 /* retries */)) <= 0 && --retry > 0)
    3104            0 :                                         usleep(1000);
    3105              : 
    3106            0 :                                 if (sts <= 0)
    3107              :                                 {
    3108            0 :                                         __SS__ << "Timeout after receiving only partial subevent (split-header)! bytes_read=" << bytes_read
    3109            0 :                                                    << " subEventByteCount=" << subEventByteCount;
    3110            0 :                                         __SS_THROW__;
    3111            0 :                                 }
    3112              : 
    3113            0 :                                 daqDMAInfo_.currentReadPtr = &daqDMAInfo_.buffer.back()[0];
    3114            0 :                                 if (daqDMAInfo_.currentReadPtr == oldBufferPtr)
    3115              :                                 {
    3116            0 :                                         __SS__ << "Received same buffer twice during split-header subevent continuation! bytes_read=" << bytes_read;
    3117            0 :                                         __SS_THROW__;
    3118            0 :                                 }
    3119            0 :                                 daqDMAInfo_.bufferIndex++;
    3120              : 
    3121            0 :                                 size_t buffer_size = sts;
    3122            0 :                                 size_t remainingEventSize = subEventByteCount - bytes_read;
    3123            0 :                                 size_t copySize = remainingEventSize < buffer_size ? remainingEventSize : buffer_size;
    3124              : 
    3125            0 :                                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA split-header continuation buffer: buffer_size=" << buffer_size
    3126            0 :                                                                                                  << " copySize=" << copySize << " remainingEventSize=" << remainingEventSize;
    3127              : 
    3128            0 :                                 memcpy(const_cast<uint8_t*>(static_cast<const uint8_t*>(inmem->GetRawBufferPointer()) + bytes_read),
    3129            0 :                                            daqDMAInfo_.currentReadPtr, copySize);
    3130            0 :                                 bytes_read += buffer_size;
    3131              : 
    3132            0 :                                 daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + copySize;
    3133            0 :                                 lastBufferPayloadSize = buffer_size;
    3134            0 :                                 lastCopySize = copySize;
    3135              :                         }
    3136              : 
    3137              :                         // Now construct the SubEvent with the fully assembled data
    3138            0 :                         auto res = std::make_unique<DTC_SubEvent>(inmem->GetRawBufferPointer());
    3139              : 
    3140            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA split-header: tag=" << res->GetEventWindowTag().GetEventWindowTag(true)
    3141            0 :                                                                                          << std::hex << "(0x" << res->GetEventWindowTag().GetEventWindowTag(true) << ")"
    3142            0 :                                                                                          << " subEventByteCount=" << std::dec << subEventByteCount;
    3143              : 
    3144            0 :                         res.swap(inmem);
    3145              : 
    3146              :                         try
    3147              :                         {
    3148            0 :                                 std::string accumulatedErrors = "";
    3149            0 :                                 auto ok = res->SetupSubEvent(accumulatedErrors);
    3150            0 :                                 if (!ok)
    3151              :                                 {
    3152            0 :                                         __SS__ << "SubEvent is corrupt (split-header)! EWT=" << res->GetEventWindowTag() << ".\n\nAccumulated Errors: " << accumulatedErrors << __E__;
    3153            0 :                                         __SS_THROW__;
    3154            0 :                                 }
    3155            0 :                         }
    3156            0 :                         catch (...)
    3157              :                         {
    3158            0 :                                 device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */);
    3159            0 :                                 DTC_TLOG(TLVL_ERROR) << otsStyleStackTrace();
    3160            0 :                                 throw;
    3161            0 :                         }
    3162              : 
    3163            0 :                         output.push_back(std::move(res));
    3164              : 
    3165              :                         // Update remaining buffer size for the last-read buffer to check for more subevents
    3166            0 :                         remainingBufferSize = lastBufferPayloadSize - lastCopySize;
    3167              : 
    3168              :                         // Skip past the next sub-transfer DMA byte count header (same as single-buffer path)
    3169            0 :                         daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + sizeof(uint64_t);
    3170            0 :                 }
    3171              :                 else  // subevent header fully in current buffer
    3172              :                 {
    3173            0 :                         auto res = std::make_unique<DTC_SubEvent>(daqDMAInfo_.currentReadPtr);  // only does setup of SubEvent header!
    3174              : 
    3175            0 :                         auto subEventByteCount = res->GetSubEventByteCount();
    3176            0 :                         if (subEventByteCount < sizeof(DTC_SubEventHeader))
    3177              :                         {
    3178            0 :                                 __SS__ << "SubEvent inclusive byte count cannot be less than the size of the subevent header (" << sizeof(DTC_SubEventHeader) << "-bytes)!" << __E__;
    3179            0 :                                 __SS_THROW__;
    3180            0 :                         }
    3181              : 
    3182            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "subevent tag=" << res->GetEventWindowTag().GetEventWindowTag(true) << std::hex << "(0x" << res->GetEventWindowTag().GetEventWindowTag(true) << ")"
    3183            0 :                                                                                          << " inclusive byte count: 0x" << std::hex << subEventByteCount << " (" << std::dec << subEventByteCount << ") inclusive packets " << subEventByteCount / 16 << ", remaining buffer size: 0x" << std::hex << remainingBufferSize << " (" << std::dec << remainingBufferSize << ") this buffer in packets = " << (remainingBufferSize - sizeof(DTC_SubEventHeader)) / 16 << ". "
    3184            0 :                                                                                          << "Total subevent packet count: " << (subEventByteCount - sizeof(DTC_SubEventHeader)) / 16;
    3185              : 
    3186              :                         // Check for continued DMA
    3187            0 :                         if (subEventByteCount > remainingBufferSize)
    3188              :                         {
    3189            0 :                                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "subevent needs more data by bytes " << std::hex << subEventByteCount - remainingBufferSize << " (" << std::dec << subEventByteCount - remainingBufferSize << ") packets " << (subEventByteCount - remainingBufferSize) / 16 << ". subEventByteCount=" << subEventByteCount << " remainingBufferSize=" << remainingBufferSize;
    3190              : 
    3191              :                                 // We're going to set lastReadPtr here, so that if this buffer isn't used by GetData, we start at the beginning of this event next time
    3192            0 :                                 daqDMAInfo_.lastReadPtr = static_cast<uint8_t*>(daqDMAInfo_.currentReadPtr);
    3193              : 
    3194            0 :                                 auto inmem = std::make_unique<DTC_SubEvent>(subEventByteCount);
    3195              : 
    3196            0 :                                 if (TTEST(TLVL_NextSubEventCout))  // for debugging
    3197              :                                 {
    3198            0 :                                         std::cout << "1st DMA buffer res size=" << remainingBufferSize << "\n";
    3199            0 :                                         auto ptr = reinterpret_cast<const uint8_t*>(res->GetRawBufferPointer());
    3200            0 :                                         for (size_t i = 0; i < remainingBufferSize /* + 16 */; i += 4)
    3201            0 :                                                 std::cout << std::dec << "res#" << i << "/" << remainingBufferSize << "(" << i / 16 << "/" << remainingBufferSize / 16 << ")" << std::hex << std::setw(8) << std::setfill('0') << *((uint32_t*)(&(ptr[i]))) << std::endl;
    3202              :                                 }
    3203              : 
    3204            0 :                                 memcpy(const_cast<void*>(inmem->GetRawBufferPointer()), res->GetRawBufferPointer(), remainingBufferSize);
    3205              : 
    3206            0 :                                 if (TTEST(TLVL_NextSubEventCout))  // for debugging
    3207              :                                 {
    3208            0 :                                         std::cout << "1st DMA buffer inmem size=" << remainingBufferSize << "\n";
    3209            0 :                                         auto ptr = reinterpret_cast<const uint8_t*>(inmem->GetRawBufferPointer());
    3210            0 :                                         for (size_t i = 0; i < remainingBufferSize + 16; i += 4)
    3211            0 :                                                 std::cout << std::dec << "inmem#" << i << "(" << i / 16 << ")" << std::hex << std::setw(8) << std::setfill('0') << *((uint32_t*)(&(ptr[i]))) << std::endl;
    3212              :                                 }
    3213              : 
    3214            0 :                                 auto bytes_read = remainingBufferSize;
    3215            0 :                                 size_t lastBufferPayloadSize = 0;
    3216            0 :                                 size_t lastCopySize = 0;
    3217              : 
    3218            0 :                                 while (bytes_read < subEventByteCount)
    3219              :                                 {
    3220            0 :                                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA tag=" << inmem->GetEventWindowTag().GetEventWindowTag(true) << std::hex << "(0x" << inmem->GetEventWindowTag().GetEventWindowTag(true) << ")"
    3221            0 :                                                                                                          << " Obtaining new DAQ Buffer, bytes_read=" << bytes_read << ", subEventByteCount=" << subEventByteCount;
    3222              : 
    3223            0 :                                         void* oldBufferPtr = nullptr;
    3224            0 :                                         if (daqDMAInfo_.buffer.size() > 0) oldBufferPtr = &daqDMAInfo_.buffer.back()[0];
    3225              : 
    3226            0 :                                         if (TTEST(TLVL_NextSubEventCout))  // for debugging
    3227              :                                         {
    3228            0 :                                                 std::cout << "1st DMA buffer\n";
    3229            0 :                                                 auto ptr = reinterpret_cast<const uint8_t*>(&daqDMAInfo_.buffer.back()[0]);
    3230            0 :                                                 for (size_t i = 0; i < bytes_read + sizeof(DTCLib::DTC_SubEventHeader); i += 4)
    3231            0 :                                                         std::cout << std::dec << "#" << i << "(" << i / 16 << ")" << std::hex << std::setw(8) << std::setfill('0') << *((uint32_t*)(&(ptr[i]))) << std::endl;
    3232              :                                         }
    3233              : 
    3234              :                                         int sts;
    3235            0 :                                         int retry = 10;
    3236              :                                         // timeout is an exception at this point because no way to resolve partial subevent record!
    3237            0 :                                         while ((sts = ReadBuffer(DTC_DMA_Engine_DAQ, 10 /* retries */))  // does return code
    3238            0 :                                                            <= 0 &&
    3239            0 :                                                    --retry > 0) usleep(1000);
    3240              : 
    3241            0 :                                         if (sts <= 0)
    3242              :                                         {
    3243            0 :                                                 __SS__ << "Timeout of " << tmo_ms << " ms after receiving only partial subevent! Subevent tag=" << inmem->GetEventWindowTag().GetEventWindowTag(true) << std::hex << "(0x" << inmem->GetEventWindowTag().GetEventWindowTag(true) << ")";
    3244            0 :                                                 __SS_THROW__;
    3245            0 :                                         }
    3246              : 
    3247            0 :                                         daqDMAInfo_.currentReadPtr = &daqDMAInfo_.buffer.back()[0];
    3248            0 :                                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA daqDMAInfo_.currentReadPtr=" << (void*)daqDMAInfo_.currentReadPtr
    3249            0 :                                                                                                          << " *daqDMAInfo_.currentReadPtr=0x" << std::hex << *(unsigned*)daqDMAInfo_.currentReadPtr
    3250            0 :                                                                                                          << " lastReadPtr=" << (void*)daqDMAInfo_.lastReadPtr;
    3251              : 
    3252            0 :                                         if (TTEST(TLVL_NextSubEventCout))  // for debugging
    3253              :                                         {
    3254            0 :                                                 std::cout << "1st buffer\n";
    3255            0 :                                                 auto ptr = reinterpret_cast<const uint8_t*>(inmem->GetRawBufferPointer());
    3256            0 :                                                 for (size_t i = 0; i < bytes_read + 16; i += 4)
    3257            0 :                                                         std::cout << std::dec << "#" << i << "(" << i / 16 << ")" << std::hex << std::setw(8) << std::setfill('0') << *((uint32_t*)(&(ptr[i]))) << std::endl;
    3258              :                                         }
    3259              : 
    3260            0 :                                         if (daqDMAInfo_.currentReadPtr == oldBufferPtr)
    3261              :                                         {
    3262              :                                                 // We didn't actually get a new buffer...this probably means there's no more data
    3263              :                                                 // timeout is an exception at this point because no way to resolve partial subevent record!
    3264            0 :                                                 __SS__ << "Received same buffer twice, only received partial subevent!! Subevent tag=" << inmem->GetEventWindowTag().GetEventWindowTag(true) << std::hex << "(0x" << inmem->GetEventWindowTag().GetEventWindowTag(true) << ")";
    3265            0 :                                                 __SS_THROW__;
    3266            0 :                                         }
    3267            0 :                                         daqDMAInfo_.bufferIndex++;
    3268              : 
    3269              :                                         // NOTE: continuation buffers do NOT have a per-sub-transfer DMA byte count header at offset 0
    3270              :                                         //  (unlike the initial buffer). The raw continuation data starts at offset 0.
    3271              :                                         //  Use the full sts (DMA descriptor byte count) as payload size.
    3272            0 :                                         size_t buffer_size = sts;
    3273              : 
    3274            0 :                                         size_t remainingEventSize = subEventByteCount - bytes_read;
    3275            0 :                                         size_t copySize = remainingEventSize < buffer_size ? remainingEventSize : buffer_size;
    3276              : 
    3277              :                                         if (0)  // for debugging
    3278              :                                         {
    3279              :                                                 std::cout << "2nd buffer\n";
    3280              :                                                 auto ptr = reinterpret_cast<const uint8_t*>(daqDMAInfo_.currentReadPtr);
    3281              :                                                 for (size_t i = 0; i < copySize; i += 4)
    3282              :                                                         std::cout << std::dec << "#" << i << "(" << i / 16 << "/" << copySize / 16 << ")" << std::hex << std::setw(8) << std::setfill('0') << *((uint32_t*)(&(ptr[i]))) << std::endl;
    3283              :                                         }
    3284              : 
    3285            0 :                                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA got new buffer for subevent continuation, buffer_size=" << buffer_size << " copySize=" << copySize << " remainingEventSize=" << remainingEventSize;
    3286            0 :                                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA so far bytes_read = " << bytes_read << " packets = " << bytes_read / 16 - sizeof(DTC_SubEventHeader) / 16;
    3287            0 :                                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA copySize = " << copySize << " packets = " << copySize / 16;
    3288            0 :                                         memcpy(const_cast<uint8_t*>(static_cast<const uint8_t*>(inmem->GetRawBufferPointer()) + bytes_read), daqDMAInfo_.currentReadPtr, copySize);
    3289            0 :                                         bytes_read += buffer_size;
    3290              : 
    3291              :                                         // Increment by the size of the data block
    3292            0 :                                         daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + copySize;
    3293              : 
    3294            0 :                                         lastBufferPayloadSize = buffer_size;
    3295            0 :                                         lastCopySize = copySize;
    3296              :                                 }  // end primary continuation of multi-DMA subevent transfers
    3297              : 
    3298            0 :                                 res.swap(inmem);
    3299              : 
    3300              :                                 try
    3301              :                                 {
    3302            0 :                                         std::string accumulatedErrors = "";
    3303            0 :                                         auto ok = res->SetupSubEvent(accumulatedErrors);  // does setup of SubEvent header + all payload
    3304            0 :                                         if (!ok)
    3305              :                                         {
    3306            0 :                                                 __SS__ << "SubEvent is corrupt! EWT=" << res->GetEventWindowTag() << ".\n\nAccumulated Errors: " << accumulatedErrors << __E__;
    3307            0 :                                                 __SS_THROW__;
    3308            0 :                                         }
    3309            0 :                                 }
    3310            0 :                                 catch (...)
    3311              :                                 {
    3312            0 :                                         device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */);
    3313            0 :                                         DTC_TLOG(TLVL_ERROR) << otsStyleStackTrace();
    3314            0 :                                         throw;
    3315            0 :                                 }
    3316              : 
    3317            0 :                                 output.push_back(std::move(res));
    3318              : 
    3319              :                                 // Update remaining buffer size for the last-read buffer to check for more subevents
    3320            0 :                                 remainingBufferSize = lastBufferPayloadSize - lastCopySize;
    3321              : 
    3322              :                                 // Skip past the next sub-transfer DMA byte count header (same as single-buffer path)
    3323            0 :                                 daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + sizeof(uint64_t);
    3324            0 :                         }
    3325              :                         else  // SubEvent not split over multiple DMAs
    3326              :                         {
    3327              :                                 // Update the packet pointers
    3328              : 
    3329              :                                 // lastReadPtr_ is easy...
    3330            0 :                                 daqDMAInfo_.lastReadPtr = daqDMAInfo_.currentReadPtr;
    3331              : 
    3332              :                                 // Increment by the size of the data block + skip past next sub-transfer DMA byte count
    3333            0 :                                 daqDMAInfo_.currentReadPtr = reinterpret_cast<char*>(daqDMAInfo_.currentReadPtr) + subEventByteCount + sizeof(uint64_t);
    3334            0 :                                 remainingBufferSize -= subEventByteCount;
    3335              : 
    3336              :                                 try
    3337              :                                 {
    3338            0 :                                         std::string accumulatedErrors = "";
    3339            0 :                                         auto ok = res->SetupSubEvent(accumulatedErrors);  // does setup of SubEvent header + all payload
    3340            0 :                                         if (!ok)
    3341              :                                         {
    3342            0 :                                                 __SS__ << "SubEvent is corrupt! EWT=" << res->GetEventWindowTag() << ".\n\nAccumulated Errors: " << accumulatedErrors << __E__;
    3343            0 :                                                 __SS_THROW__;
    3344            0 :                                         }
    3345            0 :                                 }
    3346            0 :                                 catch (...)
    3347              :                                 {
    3348            0 :                                         device_.spy(DTC_DMA_Engine_DAQ, 3 /* for once */ | 8 /* for wide view */);
    3349            0 :                                         DTC_TLOG(TLVL_ERROR) << otsStyleStackTrace();
    3350            0 :                                         throw;
    3351            0 :                                 }
    3352              : 
    3353            0 :                                 output.push_back(std::move(res));
    3354              :                         }
    3355            0 :                 }  // end subevent header fully in current buffer
    3356              :         }      // end primary subevent extraction loop
    3357              : 
    3358            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextDAQSubEventDMA: RETURN " << output.size() << " SubEvents";
    3359            0 :         return output.size() > 0;
    3360            0 : }  // end ReadNextDAQSubEventDMA()
    3361              : 
    3362            0 : std::unique_ptr<DTCLib::DTC_DCSReplyPacket> DTCLib::DTC::ReadNextDCSPacket(int tmo_ms)
    3363              : {
    3364              :         try
    3365              :         {
    3366            0 :                 auto test = ReadNextPacket(DTC_DMA_Engine_DCS, tmo_ms);
    3367            0 :                 if (test == nullptr) return nullptr;  // Couldn't read new block
    3368              : 
    3369            0 :                 __COUTT__ << "If interpreting as a DTC_DataPacket, here is the data: " << test->toJSON();
    3370              : 
    3371            0 :                 auto output = std::make_unique<DTC_DCSReplyPacket>(*test.get());
    3372            0 :                 if (output->ROCIsCorrupt())
    3373              :                 {
    3374            0 :                         __SS__ << "ROC has set its DCS corrupt flag (check the ROC error bit details)!" << __E__;
    3375            0 :                         __SS_THROW__;
    3376            0 :                 }
    3377            0 :                 if (output->GetType() == DTC_DCSOperationType_InvalidS2C)
    3378              :                 {
    3379            0 :                         __SS__ << "DTC identifed an invalid DCS request from software!" << __E__;
    3380            0 :                         __SS_THROW__;
    3381            0 :                 }
    3382            0 :                 if (output->GetType() == DTC_DCSOperationType_Timeout)
    3383              :                 {
    3384            0 :                         __SS__ << "No response from the ROC at link " << output->GetLinkID() << " to the DCS request! The DTC identifed a ROC response timeout!" << __E__;
    3385            0 :                         __SS_THROW__;
    3386            0 :                 }
    3387            0 :                 if (lastDTCErrorBitsValue_ != output->GetDTCErrorBits())  // Note: DTC Error bits are only included in DCS reply packets
    3388              :                 {
    3389            0 :                         __COUTV__((int)output->GetDTCErrorBits());
    3390            0 :                         __COUTV__((int)lastDTCErrorBitsValue_);
    3391            0 :                         lastDTCErrorBitsValue_ = output->GetDTCErrorBits();
    3392              : 
    3393            0 :                         __SS__ << "There was one or more errors identified in DCS handling of its ROC (a DTC Soft Reset will clear these errors):" << __E__;
    3394            0 :                         if ((lastDTCErrorBitsValue_ >> 0) & 0x1)
    3395            0 :                                 ss << "\t* bit-0 is set: SERDES PLL associated with the ROC has lost lock." << __E__;
    3396            0 :                         if ((lastDTCErrorBitsValue_ >> 1) & 0x1)
    3397            0 :                                 ss << "\t* bit-1 is set: SERDES clock-data-recovery associated with the ROC has lost lock." << __E__;
    3398            0 :                         if ((lastDTCErrorBitsValue_ >> 2) & 0x1)
    3399            0 :                                 ss << "\t* bit-2 is set: Invalid packet (i.e., CRC mismatch) has been received from ROC." << __E__;
    3400            0 :                         if ((lastDTCErrorBitsValue_ >> 3) & 0x1)
    3401            0 :                                 ss << "\t* bit-3 is set: Error in DTC handling of this ROC’s DCS requests has occurred (check the DTC error bit details)." << __E__;
    3402              : 
    3403            0 :                         if (lastDTCErrorBitsValue_)  // throw exception if error
    3404              :                         {
    3405              :                                 ss << "\n\nIf interpreting as a DTC_DataPacket, here is the data: \n"
    3406            0 :                                    << test->toJSON();
    3407            0 :                                 __SS_THROW__;
    3408              :                         }
    3409            0 :                 }
    3410            0 :                 if ((lastDTCErrorBitsValue_ >> 2) & 0x1)  // Make sure CRC bit errors are always reported
    3411              :                 {
    3412            0 :                         __SS__ << "bit-2 is set: Invalid packet (i.e., CRC mismatch) has been received in response to the DCS request!" << __E__;
    3413              :                         ss << "\n\nIf interpreting as a DTC_DataPacket, here is the data: \n"
    3414            0 :                            << test->toJSON();
    3415            0 :                         __SS_THROW__;
    3416            0 :                 }
    3417              : 
    3418            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << output->toJSON();
    3419            0 :                 return output;
    3420            0 :         }
    3421            0 :         catch (...)  // make sure the dcs transaction is ended on exception
    3422              :         {
    3423            0 :                 device_.end_dcs_transaction();
    3424              : 
    3425            0 :                 __COUT_ERR__ << "Error reading the next DCS packet!" << __E__;
    3426            0 :                 throw;
    3427            0 :         }
    3428              : }
    3429              : 
    3430            0 : std::unique_ptr<DTCLib::DTC_DataPacket> DTCLib::DTC::ReadNextPacket(const DTC_DMA_Engine& engine, int tmo_ms)
    3431              : {
    3432            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket BEGIN";
    3433              :         CFOandDTC_DMAs::DMAInfo* info;
    3434            0 :         if (engine == DTC_DMA_Engine_DAQ)
    3435            0 :                 info = &daqDMAInfo_;
    3436            0 :         else if (engine == DTC_DMA_Engine_DCS)
    3437            0 :                 info = &dcsDMAInfo_;
    3438              :         else
    3439              :         {
    3440            0 :                 DTC_TLOG(TLVL_ERROR) << "ReadNextPacket: Invalid DMA Engine specified!";
    3441            0 :                 throw new DTC_DataCorruptionException();
    3442              :         }
    3443              : 
    3444            0 :         if (info->currentReadPtr != nullptr)
    3445              :         {
    3446            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket BEFORE BUFFER CHECK info->currentReadPtr="
    3447            0 :                                                                                  << (void*)info->currentReadPtr << " *nextReadPtr_=0x" << std::hex
    3448            0 :                                                                                  << *(uint16_t*)info->currentReadPtr;
    3449              :         }
    3450              :         else
    3451              :         {
    3452            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket BEFORE BUFFER CHECK info->currentReadPtr=nullptr";
    3453              :         }
    3454              : 
    3455            0 :         auto index = CFOandDTC_DMAs::GetCurrentBuffer(info);
    3456              : 
    3457              :         // Need new buffer if GetCurrentBuffer returns -1 (no buffers) or -2 (done with all held buffers)
    3458            0 :         if (index < 0)
    3459              :         {
    3460            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket Obtaining new " << (engine == DTC_DMA_Engine_DAQ ? "DAQ" : "DCS")
    3461            0 :                                                                                  << " Buffer";
    3462              : 
    3463            0 :                 void* oldBufferPtr = nullptr;
    3464            0 :                 if (info->buffer.size() > 0) oldBufferPtr = &info->buffer.back()[0];
    3465            0 :                 auto sts = ReadBuffer(engine, tmo_ms);  // does return code
    3466            0 :                 if (sts <= 0)
    3467              :                 {
    3468            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: ReadBuffer returned " << sts << ", returning nullptr";
    3469            0 :                         return nullptr;
    3470              :                 }
    3471              :                 // MUST BE ABLE TO HANDLE daqbuffer_==nullptr OR retry forever?
    3472            0 :                 info->currentReadPtr = &info->buffer.back()[0];
    3473            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket info->currentReadPtr=" << (void*)info->currentReadPtr
    3474            0 :                                                                                  << " *info->currentReadPtr=0x" << std::hex << *(unsigned*)info->currentReadPtr
    3475            0 :                                                                                  << " lastReadPtr_=" << (void*)info->lastReadPtr;
    3476            0 :                 void* bufferIndexPointer = static_cast<uint8_t*>(info->currentReadPtr) + 4;
    3477            0 :                 if (info->currentReadPtr == oldBufferPtr && info->bufferIndex == *static_cast<uint32_t*>(bufferIndexPointer))
    3478              :                 {
    3479            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket)
    3480            0 :                                 << "ReadNextPacket: New buffer is the same as old. Releasing buffer and returning nullptr";
    3481            0 :                         info->currentReadPtr = nullptr;
    3482              :                         // We didn't actually get a new buffer...this probably means there's no more data
    3483              :                         // Try and see if we're merely stuck...hopefully, all the data is out of the buffers...
    3484            0 :                         device_.read_release(engine, 1);
    3485            0 :                         return nullptr;
    3486              :                 }
    3487            0 :                 info->bufferIndex++;
    3488              : 
    3489            0 :                 info->currentReadPtr = reinterpret_cast<uint8_t*>(info->currentReadPtr) + 4;
    3490            0 :                 *static_cast<uint32_t*>(info->currentReadPtr) = info->bufferIndex;
    3491            0 :                 info->currentReadPtr = reinterpret_cast<uint8_t*>(info->currentReadPtr) + 4;
    3492              : 
    3493            0 :                 index = info->buffer.size() - 1;
    3494              :         }
    3495              : 
    3496              :         // Read the next packet
    3497            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket reading next packet from buffer: info->currentReadPtr="
    3498            0 :                                                                          << (void*)info->currentReadPtr;
    3499              : 
    3500            0 :         auto blockByteCount = *reinterpret_cast<uint16_t*>(info->currentReadPtr);
    3501            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: blockByteCount=" << blockByteCount
    3502            0 :                                                                          << ", info->currentReadPtr=" << (void*)info->currentReadPtr
    3503            0 :                                                                          << ", *nextReadPtr=" << (int)*((uint16_t*)info->currentReadPtr);
    3504            0 :         if (blockByteCount == 0 || blockByteCount == 0xcafe)
    3505              :         {
    3506            0 :                 auto test = std::make_unique<DTC_DataPacket>(info->currentReadPtr);
    3507            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "Check bad data (interpreting as DTC_DataPacket): " << test->toJSON();
    3508              : 
    3509            0 :                 if (static_cast<size_t>(index) < info->buffer.size() - 1)
    3510              :                 {
    3511            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: blockByteCount=" << blockByteCount << " (the first 16-bits) is invalid, moving to next buffer";
    3512            0 :                         auto nextBufferPtr = *info->buffer[index + 1];
    3513            0 :                         info->currentReadPtr = nextBufferPtr + 8;  // Offset past DMA header
    3514            0 :                         return ReadNextPacket(engine, tmo_ms);     // Recursion
    3515              :                 }
    3516              :                 else
    3517              :                 {
    3518            0 :                         DTC_TLOG(TLVL_ReadNextDAQPacket)
    3519            0 :                                 << "ReadNextPacket: blockByteCount is invalid=" << blockByteCount << " (the first 16-bits), and this is the last buffer! Returning nullptr!";
    3520            0 :                         info->currentReadPtr = nullptr;
    3521              :                         // This buffer is invalid, release it!
    3522              :                         // Try and see if we're merely stuck...hopefully, all the data is out of the buffers...
    3523            0 :                         device_.read_release(engine, 1);
    3524              : 
    3525            0 :                         __SS__ << "The DTC returned a packet with an invalid BlockByteCount=" << blockByteCount << " (the first 16-bits of the packet).\n\n";
    3526              :                         ss << "\n\nIf interpreting as a DTC_DataPacket, here is the data: \n"
    3527            0 :                            << test->toJSON();
    3528            0 :                         __SS_THROW__;
    3529              :                         // return nullptr;
    3530            0 :                 }
    3531            0 :         }
    3532              : 
    3533            0 :         auto test = std::make_unique<DTC_DataPacket>(info->currentReadPtr);
    3534            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: current+blockByteCount="
    3535            0 :                                                                          << (void*)(reinterpret_cast<uint8_t*>(info->currentReadPtr) + blockByteCount)
    3536            0 :                                                                          << ", end of dma buffer="
    3537            0 :                                                                          << (void*)(info->buffer[index][0] + CFOandDTC_DMAs::GetBufferByteCount(info, index) +
    3538            0 :                                                                                                 8);  // +8 because first 8 bytes are not included in byte count
    3539            0 :         if (reinterpret_cast<uint8_t*>(info->currentReadPtr) + blockByteCount >
    3540            0 :                 info->buffer[index][0] + CFOandDTC_DMAs::GetBufferByteCount(info, index) + 8)
    3541              :         {
    3542            0 :                 blockByteCount = static_cast<uint16_t>(
    3543            0 :                         info->buffer[index][0] + CFOandDTC_DMAs::GetBufferByteCount(info, index) + 8 -
    3544            0 :                         reinterpret_cast<uint8_t*>(info->currentReadPtr));  // +8 because first 8 bytes are not included in byte count
    3545            0 :                 DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: Adjusting blockByteCount to " << blockByteCount
    3546            0 :                                                                                  << " due to end-of-DMA condition";
    3547            0 :                 test->SetByte(0, blockByteCount & 0xFF);
    3548            0 :                 test->SetByte(1, (blockByteCount >> 8));
    3549              :         }
    3550              : 
    3551            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << test->toJSON();
    3552              : 
    3553              :         // Update the packet pointers
    3554              : 
    3555              :         // lastReadPtr_ is easy...
    3556            0 :         info->lastReadPtr = info->currentReadPtr;
    3557              : 
    3558              :         // Increment by the size of the data block
    3559            0 :         info->currentReadPtr = reinterpret_cast<char*>(info->currentReadPtr) + blockByteCount;
    3560              : 
    3561            0 :         DTC_TLOG(TLVL_ReadNextDAQPacket) << "ReadNextPacket: RETURN";
    3562            0 :         return test;
    3563            0 : }
    3564              : 
    3565            0 : void DTCLib::DTC::WriteDetectorEmulatorData(mu2e_databuff_t* buf, size_t sz)
    3566              : {
    3567            0 :         if (sz < dmaSize_)
    3568              :         {
    3569            0 :                 sz = dmaSize_;
    3570              :         }
    3571            0 :         auto retry = 3;
    3572              :         int errorCode;
    3573              :         do
    3574              :         {
    3575            0 :                 DTC_TLOG(TLVL_WriteDetectorEmulatorData) << "WriteDetectorEmulatorData: Writing buffer of size " << sz;
    3576            0 :                 errorCode = device_.write_data(DTC_DMA_Engine_DAQ, buf, sz);
    3577            0 :                 retry--;
    3578            0 :         } while (retry > 0 && errorCode != 0);
    3579            0 :         if (errorCode != 0)
    3580              :         {
    3581            0 :                 DTC_TLOG(TLVL_ERROR) << "WriteDetectorEmulatorData: write_data returned " << errorCode
    3582            0 :                                                          << ", throwing DTC_IOErrorException!";
    3583            0 :                 throw DTC_IOErrorException(errorCode);
    3584              :         }
    3585            0 : }
    3586              : 
    3587              : //
    3588              : // Private Functions.
    3589              : //   On success, returns number of bytes read.
    3590            0 : int DTCLib::DTC::ReadBuffer(const DTC_DMA_Engine& channel, int retries /* = 10 */)
    3591              : {
    3592              :         mu2e_databuff_t* buffer;
    3593              : 
    3594            0 :         int retry = 1;
    3595            0 :         if (retries > 0) retry = retries;
    3596              : 
    3597              :         int errorCode;
    3598            0 :         TRACE_EXIT
    3599              :         {
    3600            0 :                 DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer found " << ((errorCode > 0) ? "DATA" : "NO Data") << ". There are now " << (channel == DTC_DMA_Engine_DAQ ? daqDMAInfo_.buffer.size() : dcsDMAInfo_.buffer.size()) << " DAQ buffers held in the DTC Library";
    3601            0 :         };
    3602              : 
    3603              :         do
    3604              :         {
    3605            0 :                 DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer before device_.read_data retries=" << retries << " retry=" << retry;
    3606              :                 // WARNING NOTE: if there is existing data still sitting in unreleased buffer, the timeout will not add any delay
    3607              :                 //  read_data() on success, returns number of bytes read.
    3608            0 :                 errorCode = device_.read_data(channel, reinterpret_cast<void**>(&buffer), 1 /* tmo_ms */);
    3609              :                 // NOTE: Adding delay here significantly impacts data rates!
    3610              :                 //  if (errorCode == 0) usleep(tmo_ms*1000); //create timeout delay here to match tmo (tmo not used by read_data())
    3611              : 
    3612            0 :         } while (retry-- > 0 && errorCode == 0);  // error code of 0 is timeout
    3613              : 
    3614            0 :         if (errorCode == 0)
    3615              :         {
    3616            0 :                 DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer: Device timeout occurred! ec=" << errorCode << ", rt=" << retry;
    3617              :         }
    3618            0 :         else if (errorCode < 0)
    3619              :         {
    3620            0 :                 DTC_TLOG(TLVL_ERROR) << "ReadBuffer: read_data returned error code " << errorCode << ", throwing DTC_IOErrorException!";
    3621            0 :                 throw DTC_IOErrorException(errorCode);
    3622              :         }
    3623              :         else
    3624              :         {
    3625            0 :                 DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer buffer_=" << (void*)buffer << " errorCode=" << errorCode << " *buffer_=0x"
    3626            0 :                                                                   << std::hex << *(unsigned*)buffer;
    3627            0 :                 if (channel == DTC_DMA_Engine_DAQ)
    3628              :                 {
    3629            0 :                         daqDMAInfo_.buffer.push_back(buffer);
    3630            0 :                         DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer: There are now " << daqDMAInfo_.buffer.size()
    3631            0 :                                                                           << " DAQ buffers held in the DTC Library";
    3632              :                 }
    3633            0 :                 else if (channel == DTC_DMA_Engine_DCS)
    3634              :                 {
    3635            0 :                         dcsDMAInfo_.buffer.push_back(buffer);
    3636            0 :                         DTC_TLOG(TLVL_ReadBuffer) << "ReadBuffer: There are now " << dcsDMAInfo_.buffer.size()
    3637            0 :                                                                           << " DCS buffers held in the DTC Library";
    3638              :                 }
    3639              :         }
    3640            0 :         return errorCode;
    3641            0 : }  // ReadBuffer
    3642              : 
    3643            0 : void DTCLib::DTC::ReleaseAllBuffers(const DTC_DMA_Engine& channel)
    3644              : {
    3645            0 :         TLOG_ENTEX(1) << "ReleaseAllBuffers - channel=" << channel << "\n"
    3646            0 :                                   << otsStyleStackTrace();
    3647              : 
    3648            0 :         if (channel == DTC_DMA_Engine_DAQ)
    3649              :         {
    3650            0 :                 daqDMAInfo_.buffer.clear();
    3651            0 :                 device_.release_all(channel);
    3652              : 
    3653              :                 // clear (sub)event extraction members
    3654            0 :                 lastDMABufferWasFull_ = false;
    3655            0 :                 lastDMABufferWasMax_ = false;
    3656            0 :                 lastBufferTailCount_ = 0;
    3657            0 :                 hasLastGoodSubEventHeader_ = false;
    3658            0 :                 totalEventsParsed_ = 0;
    3659            0 :                 subEventHeaderQwsFilled_ = 0;
    3660            0 :                 needToFinishEvent_ = false;
    3661            0 :                 currentEventSize_ = 0;
    3662            0 :                 subEventByteCount_ = 0;
    3663            0 :                 extractedSubeventBytes_ = 0;
    3664            0 :                 extractedEvents_.clear();
    3665              : 
    3666            0 :                 ResetEVBAssembly();
    3667              :         }
    3668            0 :         else if (channel == DTC_DMA_Engine_DCS)
    3669              :         {
    3670            0 :                 bool lock_taken_locally = false;
    3671            0 :                 if (!device_.thread_owns_dcs_lock())
    3672              :                 {
    3673            0 :                         device_.begin_dcs_transaction();
    3674            0 :                         lock_taken_locally = true;
    3675              :                 }
    3676              : 
    3677            0 :                 dcsDMAInfo_.buffer.clear();
    3678            0 :                 device_.release_all(channel);
    3679              : 
    3680            0 :                 if (lock_taken_locally) { device_.end_dcs_transaction(); }
    3681              :         }
    3682            0 : }
    3683              : 
    3684              : // ReleaseBuffers releases the buffers that are held by ReadBuffer,
    3685              : // to release all DMA buffers and force hw and sw to align (for DCS) use ReleaseAllBuffers
    3686            0 : void DTCLib::DTC::ReleaseBuffers(const DTC_DMA_Engine& channel)  //, int count)//count==0 means all
    3687              : {
    3688            0 :         DTC_TLOG(TLVL_ReleaseBuffers) << "ReleaseBuffers BEGIN";
    3689              :         CFOandDTC_DMAs::DMAInfo* info;
    3690            0 :         if (channel == DTC_DMA_Engine_DAQ)
    3691            0 :                 info = &daqDMAInfo_;
    3692            0 :         else if (channel == DTC_DMA_Engine_DCS)
    3693            0 :                 info = &dcsDMAInfo_;
    3694              :         else
    3695              :         {
    3696            0 :                 DTC_TLOG(TLVL_ERROR) << "ReleaseBuffers: Invalid DMA Engine specified!";
    3697            0 :                 throw new DTC_DataCorruptionException();
    3698              :         }
    3699              : 
    3700            0 :         auto releaseBufferCount = CFOandDTC_DMAs::GetCurrentBuffer(info);  // not GetCurrentBuffer(info), but rather Count!!!!
    3701            0 :         if (releaseBufferCount > 0)
    3702              :         {
    3703            0 :                 DTC_TLOG(TLVL_ReleaseBuffers) << "ReleaseBuffers releasing " << releaseBufferCount << " "
    3704            0 :                                                                           << (channel == DTC_DMA_Engine_DAQ ? "DAQ" : "DCS") << " buffers.";
    3705              : 
    3706            0 :                 if (channel == DTC_DMA_Engine_DCS)
    3707            0 :                         device_.begin_dcs_transaction();
    3708            0 :                 device_.read_release(channel, releaseBufferCount);
    3709            0 :                 if (channel == DTC_DMA_Engine_DCS)
    3710            0 :                         device_.end_dcs_transaction();
    3711              : 
    3712            0 :                 for (int ii = 0; ii < releaseBufferCount; ++ii)
    3713              :                 {
    3714            0 :                         info->buffer.pop_front();
    3715              :                 }
    3716              :         }
    3717              :         // else
    3718              :         // {
    3719              :         //      DTC_TLOG(TLVL_ReleaseBuffers) << "ReleaseBuffers releasing ALL " << (channel == DTC_DMA_Engine_DAQ ? "DAQ" : "DCS")
    3720              :         //                                                        << " buffers.";
    3721              :         //      ReleaseAllBuffers(channel);
    3722              :         // }
    3723            0 :         DTC_TLOG(TLVL_ReleaseBuffers) << "ReleaseBuffers END";
    3724            0 : }
    3725              : 
    3726              : // int DTCLib::DTC::GetCurrentBuffer(DMAInfo* info)
    3727              : // {
    3728              : //      DTC_TLOG(TLVL_GetCurrentBuffer) << "GetCurrentBuffer BEGIN currentReadPtr=" << (void*)info->currentReadPtr
    3729              : //                                                                      << " buffer.size()=" << info->buffer.size();
    3730              : 
    3731              : //      if (info->buffer.size())  // might crash in ReleaseBuffers if currentReadPtr = nullptr ????????????????????????????????
    3732              : //      {
    3733              : //              DTC_TLOG(TLVL_GetCurrentBuffer) << "GetCurrentBuffer returning info->buffer.size()=" << info->buffer.size();
    3734              : //              return info->buffer.size();
    3735              : //      }
    3736              : 
    3737              : //      if (info->currentReadPtr == nullptr || info->buffer.size() == 0)
    3738              : //      {
    3739              : //              DTC_TLOG(TLVL_GetCurrentBuffer) << "GetCurrentBuffer returning -1 because not currently reading a buffer";
    3740              : //              return -1;
    3741              : //      }
    3742              : 
    3743              : // #if 0
    3744              : //      for (size_t ii = 0; ii < info->buffer.size(); ++ii)
    3745              : //      {
    3746              : //              auto bufferptr = *info->buffer[ii];
    3747              : //              uint16_t bufferSize = *reinterpret_cast<uint16_t*>(bufferptr);
    3748              : //              DTC_TLOG(TLVL_GetCurrentBuffer) << "GetCurrentBuffer bufferptr="<<(void*)bufferptr<<" bufferSize="<<bufferSize;
    3749              : //              if (info->currentReadPtr > bufferptr &&
    3750              : //                      info->currentReadPtr < bufferptr + bufferSize)
    3751              : //              {
    3752              : //                      DTC_TLOG(TLVL_GetCurrentBuffer) << "Found matching buffer at index " << ii << ".";
    3753              : //                      return ii;
    3754              : //              }
    3755              : //      }
    3756              : // #endif
    3757              : //      DTC_TLOG(TLVL_GetCurrentBuffer) << "GetCurrentBuffer returning -2: Have buffers but none match read ptr position, need new";
    3758              : //      return -2;
    3759              : // }
    3760              : 
    3761              : // uint16_t DTCLib::DTC::GetBufferByteCount(DMAInfo* info, size_t index)
    3762              : // {
    3763              : //      if (index >= info->buffer.size()) return 0;
    3764              : //      auto bufferptr = *info->buffer[index];
    3765              : //      uint16_t bufferSize = *reinterpret_cast<uint16_t*>(bufferptr);
    3766              : //      return bufferSize;
    3767              : // }
    3768              : 
    3769              : // This is on DMA Channel 1 (i.e., DCS)
    3770            0 : void DTCLib::DTC::WriteDataPacket(const DTC_DataPacket& packet)
    3771              : {
    3772            0 :         DTC_TLOG(TLVL_WriteDataPacket) << "WriteDataPacket: Writing DCS DMA packet: " << packet.toJSON();
    3773              :         mu2e_databuff_t buf;
    3774            0 :         uint64_t size = packet.GetSize() + sizeof(uint64_t);
    3775              :         //      uint64_t packetSize = packet.GetSize();
    3776            0 :         if (size < static_cast<uint64_t>(dmaSize_)) size = dmaSize_;
    3777              : 
    3778            0 :         bzero(&buf[0], size);
    3779            0 :         memcpy(&buf[0], &size, sizeof(uint64_t));
    3780            0 :         memcpy(&buf[8], packet.GetData(), packet.GetSize() * sizeof(uint8_t));
    3781              : 
    3782            0 :         Utilities::PrintBuffer(buf, size, 0, TLVL_TRACE + 30);
    3783              : 
    3784            0 :         bool lock_taken_locally = false;
    3785            0 :         if (!device_.thread_owns_dcs_lock())
    3786              :         {
    3787            0 :                 device_.begin_dcs_transaction();
    3788            0 :                 lock_taken_locally = true;
    3789              :         }
    3790              : 
    3791            0 :         auto retry = 3;
    3792              :         int errorCode;
    3793              :         do
    3794              :         {
    3795            0 :                 DTC_TLOG(TLVL_WriteDataPacket) << "Attempting to write DCS DMA data...";
    3796            0 :                 errorCode = device_.write_data(DTC_DMA_Engine_DCS, &buf, size);
    3797            0 :                 DTC_TLOG(TLVL_WriteDataPacket) << "Attempted to write DCS DMA data, errorCode=" << errorCode << ", retries=" << retry;
    3798            0 :                 retry--;
    3799            0 :         } while (retry > 0 && errorCode != 0);
    3800              : 
    3801            0 :         if (lock_taken_locally)
    3802            0 :                 device_.end_dcs_transaction();
    3803              : 
    3804            0 :         if (errorCode != 0)
    3805              :         {
    3806            0 :                 DTC_TLOG(TLVL_ERROR) << "WriteDataPacket: write_data DCS DMA returned " << errorCode << ", throwing DTC_IOErrorException! lock_taken_locally=" << lock_taken_locally;
    3807            0 :                 throw DTC_IOErrorException(errorCode);
    3808              :         }
    3809            0 : }
    3810              : 
    3811            0 : void DTCLib::DTC::WriteDMAPacket(const DTC_DMAPacket& packet)
    3812              : {
    3813            0 :         WriteDataPacket(packet.ConvertToDataPacket());
    3814            0 : }
        

Generated by: LCOV version 2.0-1