Program Listing for File decoder.cpp

Return to documentation for file (decoder.cpp)

#include "decoder.hpp"
#include "l0id_decoder.hpp"
#include "publisher.hpp"
#include "device.hpp"
#include <cstdint>
#include <functional>


Decoder::Decoder(const Elink &elink, Publisher &publisher, flx_tohost_format fmt,
    int l0id_decoder_fmt, unsigned int block_size, uint64_t buf_vaddr) :
        m_stats(elink.fid), m_elink(elink), m_publisher(publisher),
        m_buffer_vaddr(buf_vaddr), m_block_size(block_size)
{
    switch (fmt) {
        case flx_tohost_format::TOHOST_SUBCHUNK_TRAILER:
            m_decode = [this](Block& b){return decode_subchunk_trailers(b);};
            break;
        case flx_tohost_format::TOHOST_SUBCHUNK_HEADER:
            m_decode = [this](Block& b){return decode_subchunk_headers(b);};
            break;
        default:
            ers::error(felix_log::decoder_issue(ERS_HERE, std::format("ToHost data format {} not recognised. Assuming TOHOST_SUBCHUNK_TRAILER", static_cast<int>(fmt))));
            m_decode = [this](Block& b){return decode_subchunk_trailers(b);};
    }
    if(l0id_decoder_fmt > 0){
        if (elink.type  == elink_type_t::TTC) {
            m_l0id_checker = std::make_unique<L0Decoder>(elink.fid, L0ID_FMT::TTC2H);
        }
        else if (elink.type  == elink_type_t::DAQ) {
            m_l0id_checker = std::make_unique<L0Decoder>(elink.fid, l0id_decoder_fmt);
        }
    }
}


Publisher::Result Decoder::check_block_integrity(Block & block)
{
#if REGMAP_VERSION < 0x0500
    uint16_t* marker_word = (uint16_t*)(&block);
    if (block.marker != 0xABCD and marker_word[0] != marker_word[1])
    {
        ers::error(felix_log::decoder_issue(ERS_HERE, std::format("Received invalid block header {:#x}. Block discarded.", static_cast<uint16_t>(block.marker))));
        m_stats.increment_dropped_blocks();
        return Publisher::DECODING_ERROR;
    }
#else
    unsigned bsize = (block.marker == 0xABCD) ? 1024 : ((block.marker >> 8) - 0xC0 + 1) * 1024;
    if (bsize != m_block_size) {
        ers::error(felix_log::decoder_issue(ERS_HERE, std::format("Received invalid block header {:#x} with irregular block size {}, expected {}. Block discarded.", static_cast<uint16_t>(block.marker), bsize, m_block_size)));
            m_stats.increment_dropped_blocks();
            return Publisher::DECODING_ERROR;
        }
#endif
    //Sequence number check
    uint8_t expected_seqnr = (m_last_seqnr + 1) % 32;
    if(block.sequence_number != expected_seqnr)
    {
        if(!(m_seqnr_err++ % 100)){
            ERS_DEBUG(1, std::format("received wrong sequence number: {} instead of {} (E-link: {:#x})",
            static_cast<uint32_t>(block.sequence_number), expected_seqnr, static_cast<uint32_t>(block.elink)));
        }
    }
    m_last_seqnr = block.sequence_number;
    m_last_block = (reinterpret_cast<uint64_t>(&block) - m_buffer_vaddr) & 0xffffffff;
    return Publisher::Result::OK;
}

Publisher::Result Decoder::decode(Block & block)
{
    return m_decode(block);
}


Publisher::Result Decoder::decode_subchunk_headers(Block & block)
{
    using subchunk_header_t = subchunk_trailer_t;

    const bool first_time_decoding = (m_chunk_position == 0);

    if (first_time_decoding) {
        auto ret = check_block_integrity(block);
        if (ret == Publisher::Result::DECODING_ERROR) {
            return ret;
        }
    }

    Publisher::Result r = Publisher::OK;
    while (true) {
        const auto chunk_header = *reinterpret_cast<subchunk_header_t*>(block.data + m_chunk_position);

        uint16_t subchk_size = chunk_header.data.length;
        uint16_t length = subchk_size + sizeof(subchunk_header_t) + (-subchk_size % sizeof(subchunk_header_t));
        uint16_t next = m_chunk_position + length;

        if (next > sizeof(block.data)) {
            ers::error(felix_log::decoder_issue(ERS_HERE, std::format("Block {} of fid {:#x} (elink {:#x}) discarded due to broken chunk length. Next position {}, header value {:#x}",
                                                                        static_cast<const void *>(&block), m_elink.fid, m_elink.lid, length, chunk_header.value)));
            m_chunk_position = 0;
            m_stats.increment_processed_blocks();
            return Publisher::DECODING_ERROR;
        }
        uint8_t fw_flags = (chunk_header.data.trunc) | (chunk_header.data.err << 2) | (chunk_header.data.crcerr << 3);
        r = post_subchunk(block.data + m_chunk_position + sizeof(subchunk_header_t),
            subchk_size,
            static_cast<Decoder::SubchunkType>(chunk_header.data.type),
            fw_flags);

        if (r == Publisher::AGAIN or r == Publisher::PARTIAL) {
            return r;
        }

        if (r == Publisher::DECODING_ERROR) {
            m_scratch.clear();
            break;
        }

        if (r == Publisher::ERROR) {
            m_scratch.clear();
        }

        if (next == sizeof(block.data)) {
            if(m_elink.type == elink_type_t::TTC){
                r = m_publisher.flush(m_elink.fid);

                if (r == Publisher::ERROR) {
                    m_scratch.clear();
                }

                if (r == Publisher::FLUSH_AGAIN) {
                    return r;
                }
            }
            break;
        }

        m_chunk_position = next;
    }

    m_stats.increment_processed_blocks();
    m_chunk_position = 0;
    return r;
}


Publisher::Result Decoder::decode_subchunk_trailers(Block & block)
{
    //No chunks already decoded waiting to be sent
    if (m_subchunks.empty()) {

        auto ret = check_block_integrity(block);
        if (ret != Publisher::Result::OK){
            return ret;
        }

        //Starting from the end of the block, save location of all subchunk trailers
        int pos = m_block_size - BLOCK_HEADER_SIZE;
        unsigned int trailer_size = sizeof(subchunk_trailer_t);
        while (pos > 0) {
            subchunk_trailer_t trailer = *(reinterpret_cast<subchunk_trailer_t*>(block.data + pos - trailer_size));

#if REGMAP_VERSION < 0x0500
            // Check for FE BUSY
            if (trailer.value == 0xE05C) {
                pos -= sizeof(subchunk_trailer_t);
                trailer = *(reinterpret_cast<subchunk_trailer_t*>(block.data + pos - trailer_size));
            }
#endif
            uint32_t length = trailer.data.length;
            //padding: (-length % trailer_size)
            pos -= length + trailer_size + (-length % trailer_size);
            if (pos < 0) {
                ers::error(felix_log::decoder_issue(ERS_HERE, std::format("Block {} of fid {:#x} (elink {:#x}) discarded due to broken chunk length. Decoding position {}, trailer value {:#x}",
                                                                        static_cast<const void *>(&block), m_elink.fid, m_elink.lid, pos, trailer.value)));
                return Publisher::DECODING_ERROR;
            }

            SubchunkType t = static_cast<SubchunkType>(trailer.data.type);

            if ((t == TIMEOUT and !trailer.data.trunc) or t == NIL or t == OOB) {
                continue;
            }

            uint16_t p = static_cast<uint16_t>(pos);
            m_subchunks.emplace_back(p, trailer.value);
        }
    }

    //Process and publish subchunks
    while (not m_subchunks.empty()) {
        const auto & sc = m_subchunks[m_subchunks.size() - 1];
        subchunk_trailer_t tr;
        tr.value = sc.second;
        uint8_t fw_flags = (tr.data.trunc) | (tr.data.err << 2) | (tr.data.crcerr << 3);
        auto r = post_subchunk(block.data + sc.first, tr.data.length, static_cast<Decoder::SubchunkType>(tr.data.type), fw_flags);
        if (r == Publisher::AGAIN) {
            return r;
        }
        if (r == Publisher::ERROR) {
            m_stats.increment_dropped_blocks();
            m_subchunks.clear();
            m_scratch.clear();
            return r;
        }
        m_subchunks.resize(m_subchunks.size() - 1);
        if(m_elink.type == elink_type_t::TTC && m_subchunks.empty()){
            // Not going to fix the AGAIN/ERROR case here, as subchunk trailers are phased out
            std::ignore = m_publisher.flush(m_elink.fid);
        }
    }

    m_stats.increment_processed_blocks();
    return Publisher::OK;
}


void Decoder::on_successful_send()
{
    m_stats.update_processed_chunk(m_scratch.get_status_byte(), m_scratch.byte_size());
    if (m_l0id_checker) {
        if ( m_l0id_checker->check_tohost_chunk(m_scratch.iov) ) {
            m_stats.increment_oosequence_l0id();
        }
    }
    if ( m_elink.has_streams ){ m_elink.fid &= 0xFFFFFFFFFFFFFF00; }
    m_scratch.clear();
}


Publisher::Result Decoder::post_subchunk(
    uint8_t* data, uint32_t length, SubchunkType type, uint8_t fw_flags)
{
    //First switch case mostly addresses error conditions
    switch(type) {
        case FIRST:
        case WHOLE:
        if (length == 0) {
            m_stats.increment_empty_chunks();
            return Publisher::OK;
        }
        if (not m_scratch.empty()) {
            //If scratch space not empty mark trucantion and publish leftover
            m_scratch.update_status_byte(SW_TRUNC);
            auto r = m_publisher.publish(m_elink.fid, m_scratch.get_iov(), m_scratch.byte_size(), m_last_block, m_scratch.get_status_byte());
            if (Publisher::OK != r) {
                return r;
            } else {
                on_successful_send();
            }
        }
        break;


        case LAST:
        case MIDDLE:
            if (m_scratch.empty()) {
                //If the scratch empty mark truncation and add subschunk
                m_scratch.update_status_byte(SW_TRUNC);
            }
            m_scratch.update_status_byte(fw_flags);
            m_scratch.push_back(iovec{data, length});
            break;

        case TIMEOUT:
            if (fw_flags & FW_TRUNC) {
                return post_subchunk(data, length, m_scratch.empty() ? WHOLE : LAST, fw_flags);
            } else {
                //Discarded if no truncation flagged by firmware
                return Publisher::OK;
            }

        case NIL:
        case OOB:
            return Publisher::OK;

        default:
            ers::error(felix_log::decoder_issue(ERS_HERE, std::format("invalid subchunk type={}",static_cast<int>(type))));
            return Publisher::DECODING_ERROR;
    }

    //Second switch case, no error conditions
    switch(type) {

        case FIRST:
        {
            m_scratch.update_status_byte(fw_flags);
            m_scratch.push_back(iovec{data, length});
            if ( m_elink.has_streams ){ m_elink.fid |= data[0]; }
            break;
        }

        case WHOLE:
        {
            m_scratch.update_status_byte(fw_flags);
            m_scratch.push_back(iovec{data, length});
            if ( m_elink.has_streams ){ m_elink.fid |= data[0]; }
            auto r =  m_publisher.publish(m_elink.fid, m_scratch.get_iov(), m_scratch.byte_size(), m_last_block, m_scratch.get_status_byte());
            if (Publisher::AGAIN == r) {
                m_scratch.remove_last_entry();
            } else {
                on_successful_send();
            }
            return r;
        }

        case LAST:
        {
            auto r = m_publisher.publish(m_elink.fid, m_scratch.get_iov(), m_scratch.byte_size(), m_last_block, m_scratch.get_status_byte());
            if (Publisher::AGAIN == r) {
                m_scratch.remove_last_entry();
            } else {
                on_successful_send();
            }
            return r;
        }

        default:
            break;
    }

    return Publisher::OK;
}


ToHostElinkStats Decoder::get_decoder_stats_increment(ToHostElinkStats & previous)
{
    return m_stats.get_increment(previous);
}