Program Listing for File device_flx.cpp

Return to documentation for file (device_flx.cpp)

#include <cstdint>

#include "device.hpp"

#include "flxcard/FlxException.h"
#include "DFDebug/DFDebug.h"

#include "felix/felix_fid.h"
#include "regmap/regmap.h"
#include "block.hpp"
#include "log.hpp"


FlxDevice::FlxDevice(Config& cfg, unsigned dev_no)
    : Device(dev_no, cfg.resource.vid, cfg.resource.did, cfg.get_device_cid(dev_no))
{ }


FlxDevice::FlxDevice(unsigned dev_no)
    : Device(dev_no, 1, 0, dev_no)
{ }


int FlxDevice::open_device(u_int lock_mask)
{
    u_int current_lock{0};
    LOG_DBG("Initializing device %u asking for lock 0x%x", m_device_number, lock_mask);
    try {
        current_lock = m_dev.get_lock_mask(m_device_number);
        LOG_DBG("Device %d had locks 0x%x", m_device_number, current_lock);
    }
    catch(FlxException &ex) {
        LOG_ERR("Cannot get lock mask for device %u", m_device_number);
        LOG_ERR("%s", ex.errorString(ex.getErrorId()).c_str());
        return ex.getErrorId();
    }

    try {
        m_dev.card_open(m_device_number, lock_mask);
    }
    catch(FlxException &ex) {
        LOG_ERR("Could not open device %u", m_device_number);
        LOG_ERR("%s", ex.errorString(ex.getErrorId()).c_str());
        return ex.getErrorId();
    }

    // Clear various latched signals
    if (current_lock == 0) {
        m_dev.m_bar2->TTC_BUSY_CLEAR = 1;
        uint64_t c;
        c = m_dev.m_bar2->XOFF_FM_HIGH_THRESH.CROSS_LATCHED;
        m_dev.m_bar2->XOFF_FM_HIGH_THRESH.CROSS_LATCHED = c;
        c = m_dev.m_bar2->DMA_BUSY_STATUS.ENABLE;
        m_dev.m_bar2->DMA_BUSY_STATUS.ENABLE = c;
        c = m_dev.m_bar2->FM_BUSY_CHANNEL_STATUS.BUSY;
        m_dev.m_bar2->FM_BUSY_CHANNEL_STATUS.BUSY = c;
        c = m_dev.m_bar2->BUSY_MAIN_OUTPUT_FIFO_STATUS.HIGH_THRESH_CROSSED_LATCHED;
        m_dev.m_bar2->BUSY_MAIN_OUTPUT_FIFO_STATUS.HIGH_THRESH_CROSSED_LATCHED = c;
    }

    m_firmware_type = m_dev.firmware_type();
    return 0;
}


void FlxDevice::close_device()
{
    //stop all DMAs ?
    //flxcard->dma_stop(dmaid);
    LOG_DBG("Closing device %d", m_device_number);
    m_dev.card_close();
}


bool FlxDevice::is_primary()
{
    return not static_cast<bool>(m_dev.m_bar2->PCIE_ENDPOINT);
}


unsigned int FlxDevice::get_card_endpoints()
{
    return m_dev.m_bar2->NUMBER_OF_PCIE_ENDPOINTS;
}


unsigned int FlxDevice::get_card_model()
{
    return m_dev.m_bar2->CARD_TYPE;
}


u_int FlxDevice::get_regmap_version()
{
    return m_dev.m_bar2->REG_MAP_VERSION;
}


u_int FlxDevice::get_number_of_channels()
{
    return m_dev.m_bar2->NUM_OF_CHANNELS;
}


bool FlxDevice::get_wide_mode()
{
    return (m_dev.m_bar2->WIDE_MODE == 1 ? true : false);
}


u_int FlxDevice::get_block_size()
{
    return m_dev.m_bar2->BLOCKSIZE;
}


u_int FlxDevice::get_trailer_size()
{
    u_int tsize = m_dev.m_bar2->CHUNK_TRAILER_32B == 1 ? 4 : 2;
    if( tsize != sizeof(subchunk_trailer_t) ){
        throw std::runtime_error("SW/FW trailer size mismatch");
    }
    return tsize;
}


flx_fromhost_format FlxDevice::get_fromhost_data_format()
{
#if REGMAP_VERSION < 0x0500
    m_fromHostDataFormat = static_cast<flx_fromhost_format>(FROMHOST_FORMAT_REGMAP4);
#else
    m_fromHostDataFormat = static_cast<flx_fromhost_format>(m_dev.m_bar2->FROMHOST_DATA_FORMAT);
#endif
    return m_fromHostDataFormat;
}


flx_tohost_format FlxDevice::get_tohost_data_format()
{
#if REGMAP_VERSION < 0x0500
    return TOHOST_SUBCHUNK_TRAILER;
#else
    return static_cast<flx_tohost_format>(m_dev.m_bar2->TOHOST_DATA_FORMAT);
#endif
}


int FlxDevice::get_fromhost_dmaid()
{
    if (m_dev.m_bar2 == nullptr) {
        LOG_ERR("m_dev.m_bar2 is null");
        return -1;
    }

    int dmaid = m_dev.m_bar2->GENERIC_CONSTANTS.DESCRIPTORS - 1;
    if (dmaid < 0) {
        LOG_ERR("Invalid ToFLx DMA ID %d", dmaid);
    } else {
        LOG_TRACE("ToFlx dmaid %d", dmaid);
    }
    return dmaid;
}


int FlxDevice::get_trickle_dmaid()
{
#if REGMAP_VERSION >= 0x0500
    int dmaid = m_dev.m_bar2->GENERIC_CONSTANTS.TRICKLE_DESCRIPTOR_INDEX;
    //int trickle_offset = m_dev.m_bar2->GENERIC_CONSTANTS.TRICKLE_DESCRIPTOR_INDEX;
    //dmaid = dmaid + trickle_offset;

    if (dmaid < 0) {
        LOG_ERR("Invalid ToFLx DMA ID %d", dmaid);
    }
    LOG_TRACE("Trickle dmaid %d", dmaid);
    return dmaid;
#endif

    return -1;
}


int FlxDevice::dma_max_tlp_bytes()
{
    return m_dev.dma_max_tlp_bytes();
}


void FlxDevice::set_register(const char *key, uint64_t value)
{
    try {
        m_dev.cfg_set_option(key, value);
    }
    catch(FlxException &ex) {
        LOG_INFO("Register %s not available with current firmware version.", key);
    }
}


uint64_t FlxDevice::get_register(const char *key)
{
    try {
        return m_dev.cfg_get_option(key);
    }
    catch(FlxException &ex) {
        LOG_INFO("Register %s not available with current firmware version.", key);
        return 0;
    }
}


bool FlxDevice::check_register(const char *key)
{
  try {
        m_dev.cfg_get_option(key);
        return true;
    }
    catch(FlxException &ex) {
        LOG_INFO("Register %s not available with current firmware version.", key);
        return false;
    }
}


void FlxDevice::cancel_irq(int i)
{
    try{
        m_dev.irq_cancel(i);
    }
    catch(FlxException &ex) {
        LOG_ERR("%s", ex.errorString(ex.getErrorId()).c_str());
    }
}


void FlxDevice::irq_wait(int i)
{
    m_dev.irq_wait(i);
}


void FlxDevice::irq_enable(int i)
{
    try{
        m_dev.irq_enable(i);
    }
    catch(FlxException &ex) {
        LOG_ERR("%s", ex.errorString(ex.getErrorId()).c_str());
    }
}


void FlxDevice::irq_disable(int i)
{
    try{
        m_dev.irq_disable(i);
    }
    catch(FlxException &ex) {
        LOG_ERR("%s", ex.errorString(ex.getErrorId()).c_str());
    }
}


#if REGMAP_VERSION < 0x0500
encoding_t FlxDevice::elink_get_encoding(u_int channel, u_int egroup, u_int epath, bool is_to_flx)
{
    if (!is_to_flx && (m_firmware_type == FIRMW_FULL || m_firmware_type == FIRMW_MROD)) {
        return ENC_8b10b;
    }
    else {
        int enc = 0;
        if (is_to_flx) {
            uint64_t modebits = m_dev.m_bar2->CR_GBT_CTRL[channel].EGROUP_FROMHOST[egroup].FROMHOST.PATH_ENCODING;
            uint64_t mask = 0xF << 4*epath; // 4 bits per E-path
            enc = ((modebits & mask) >> 4*epath);
        }
        else {
            uint64_t modebits = m_dev.m_bar2->CR_GBT_CTRL[channel].EGROUP_TOHOST[egroup].TOHOST.PATH_ENCODING;
            uint64_t mask = 0x3 << 2*epath; // 2 bits per E-path
            enc = ((modebits & mask) >> 2*epath);
        }
        if(enc < 0 || enc > 100){
            LOG_ERR("Invalid encoding type %d \
                    for elink channel %u group %u, epath %u using DIRECT",
                    enc, channel, egroup, epath);
            return ENC_DIRECT;
        }
        else {
            return static_cast<encoding_t>(enc);
        }
    }
}
#else
encoding_t FlxDevice::elink_get_encoding(u_int channel, u_int egroup, u_int epath, bool is_to_flx)
{
    if (!is_to_flx && (m_firmware_type == FIRMW_FULL)) {
        return ENC_8b10b;
    }
    else if (!is_to_flx && (m_firmware_type == FIRMW_INTERLAKEN)){
        return ENC_INTERLAKEN;
    }
    else {
        int enc = 0;
        if (is_to_flx) {
            uint64_t modebits = m_dev.m_bar2->ENCODING_EGROUP_CTRL_GEN[channel].ENCODING_EGROUP[egroup].ENCODING_EGROUP_CTRL.PATH_ENCODING;
            uint64_t mask = 0xF << 4*epath; // 4 bits per E-path
            enc = ((modebits & mask) >> 4*epath);
        }
        else {
            uint64_t modebits = m_dev.m_bar2->DECODING_EGROUP_CTRL_GEN[channel].DECODING_EGROUP[egroup].EGROUP.PATH_ENCODING;
            uint64_t mask = 0xF << 4*epath; // 4 bits per E-path
            enc = ((modebits & mask) >> 4*epath);
        }
        if(enc < 0 || enc > 100){
            LOG_ERR("Invalid encoding type %d \
                    for elink channel %u group %u, epath %u using DIRECT",
                    enc, channel, egroup, epath);
            return ENC_DIRECT;
        }
        else {
            return static_cast<encoding_t>(enc);
        }
    }
}
#endif


bool FlxDevice::elink_is_in_dma(local_elink_t elinknr, int dmaid)
{
#if REGMAP_VERSION < 0x0500
    return true;
#else
    bool is_elink_in_dma = false;
    if (dmaid == m_dev.m_bar2->GENERIC_CONSTANTS.FROMHOST_DESCRIPTOR_INDEX){
        is_elink_in_dma = true;
    } else {
        m_dev.m_bar2->CRTOHOST_DMA_DESCRIPTOR_2.AXIS_ID = elinknr;
        unsigned int elink_dmaid = m_dev.m_bar2->CRTOHOST_DMA_DESCRIPTOR_2.DESCR_READ & 0xF;
        unsigned int max_id = m_dev.m_bar2->GENERIC_CONSTANTS.DESCRIPTORS - 1;
        if (elink_dmaid > max_id){
            LOG_ERR("Invalid DMA ID %u for E-link %u (DMA ID [0, %u]). Discarded elink.", elink_dmaid, elinknr, max_id);
        } else if (elink_dmaid == static_cast<unsigned int>(dmaid)){
            is_elink_in_dma = true;
        }
    }
    return is_elink_in_dma;
#endif
}


#if REGMAP_VERSION < 0x0500
bool FlxDevice::elink_is_enabled(u_int channel, u_int egroup, u_int epath, bool is_to_flx)
{
    if (m_firmware_type == FIRMW_FULL) {
        return !is_to_flx && ((m_dev.m_bar2->CR_FM_PATH_ENA >> channel) & 1);
    }
    // MROD
    else if (m_firmware_type == FIRMW_MROD) {
        u_long eproc_ena = m_dev.m_bar2->MROD_EP0_CSMENABLE;
        return !is_to_flx && ((eproc_ena >> channel) & 1);
    }
    // GBT Mode
    else {
        u_long eproc_ena = is_to_flx ?
        m_dev.m_bar2->CR_GBT_CTRL[channel].EGROUP_FROMHOST[egroup].FROMHOST.EPROC_ENA :
        m_dev.m_bar2->CR_GBT_CTRL[channel].EGROUP_TOHOST[egroup].TOHOST.EPROC_ENA;
        // Central Router documentation p. 13
        switch (epath) {
            case 0: return ((eproc_ena >> 3) & 1) || ((eproc_ena >> 7) & 1);
            case 1: return ((eproc_ena >> 1) & 1) || ((eproc_ena >> 8) & 1);
            case 2: return ((eproc_ena >> 4) & 1) || ((eproc_ena >> 9) & 1);
            case 3: return ((eproc_ena >> 0) & 1) || ((eproc_ena >> 10) & 1);
            case 4: return ((eproc_ena >> 5) & 1) || ((eproc_ena >> 11) & 1);
            case 5: return ((eproc_ena >> 2) & 1) || ((eproc_ena >> 12) & 1);
            case 6: return ((eproc_ena >> 6) & 1) || ((eproc_ena >> 13) & 1);
            case 7: return ((eproc_ena >> 14) & 1);
            default: return false;
        }
    }
    return false;
}
#else
bool FlxDevice::elink_is_enabled(u_int channel, u_int egroup, u_int epath, bool is_to_flx)
{
    uint32_t enables_phase2;
    if( is_to_flx ) {
        enables_phase2 = (uint32_t) m_dev.m_bar2->ENCODING_EGROUP_CTRL_GEN[channel].ENCODING_EGROUP[egroup].ENCODING_EGROUP_CTRL.EPATH_ENA;
    } else {
        enables_phase2 = (uint32_t) m_dev.m_bar2->DECODING_EGROUP_CTRL_GEN[channel].DECODING_EGROUP[egroup].EGROUP.EPATH_ENA;
    }
    return (enables_phase2 >> epath) & 0x01;

}
#endif

uint64_t FlxDevice::get_broadcast_enable_gen(u_int channel) {
#if REGMAP_VERSION >= 0x0500
  return m_dev.m_bar2->BROADCAST_ENABLE_GEN[channel].BROADCAST_ENABLE;
#else
  return 0;
#endif
}


local_elink_t FlxDevice::get_ec_elink(bool is_to_flx, u_int channel)
{
    std::pair<int,int> ec_indices = m_dev.ec_elink_indices();
    if( is_to_flx ) {
        return ((channel<<6) + ec_indices.second);
    } else {
        return ((channel<<6) + ec_indices.first);
    }
}


local_elink_t FlxDevice::get_ic_elink(bool is_to_flx, u_int channel)
{
    std::pair<int,int> ic_indices = m_dev.ic_elink_indices();
    if( is_to_flx ) {
        return ((channel<<6) + ic_indices.second);
    } else {
        return ((channel<<6) + ic_indices.first);
    }
}


local_elink_t FlxDevice::get_aux_elink(bool is_to_flx, u_int channel)
{
    local_elink_t ic_elink = get_ic_elink(is_to_flx, channel);
#if REGMAP_VERSION < 0x0500
    return (ic_elink - 1);
#else
    return (ic_elink + 1); // Not (yet) defined
#endif
}


bool FlxDevice::elink_is_ec_enabled(bool is_to_flx, u_int channel)
{
#if REGMAP_VERSION < 0x0500
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_FROMHOST.ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_TOHOST.ENABLE);
    }
#else
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_FROMHOST_GEN[channel].MINI_EGROUP_FROMHOST.EC_ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_TOHOST_GEN[channel].MINI_EGROUP_TOHOST.EC_ENABLE);
    }
#endif
}


bool FlxDevice::elink_is_ic_enabled(bool is_to_flx, u_int channel)
{
#if REGMAP_VERSION < 0x0500
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_FROMHOST.IC_ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_TOHOST.IC_ENABLE);
    }
#else
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_FROMHOST_GEN[channel].MINI_EGROUP_FROMHOST.IC_ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_TOHOST_GEN[channel].MINI_EGROUP_TOHOST.IC_ENABLE);
    }
#endif
}


bool FlxDevice::elink_is_aux_enabled(bool is_to_flx, u_int channel)
{
#if REGMAP_VERSION < 0x0500
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_FROMHOST.SCA_AUX_ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_CTRL[channel].EC_TOHOST.SCA_AUX_ENABLE);
    }
#else
    if( is_to_flx ) {
        return(m_dev.m_bar2->MINI_EGROUP_FROMHOST_GEN[channel].MINI_EGROUP_FROMHOST.AUX_ENABLE);
    } else {
        return(m_dev.m_bar2->MINI_EGROUP_TOHOST_GEN[channel].MINI_EGROUP_TOHOST.AUX_ENABLE);
    }
#endif
}


bool FlxDevice::elink_has_stream_id(u_int channel, u_int egroup, u_int epath)
{
    if(channel > get_number_of_channels()){
        LOG_ERR("Checking stream status on invalid channel %u", channel);
        return false;
    }
    u_long enable = 0;
#if REGMAP_VERSION < 0x0500
    switch (egroup)
    {
    case 0:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP0;
        break;
    case 1:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP1;
        break;
    case 2:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP2;
        break;
    case 3:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP3;
        break;
    case 4:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP4;
        break;
    case 5:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP5;
        break;
    case 6:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].TOHOST.EGROUP6;
        break;
    default:
        LOG_ERR("Checking stream status on invalid egroup %u.", egroup);
        break;
    }
#else
    switch (egroup)
    {
    case 0:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP0;
        break;
    case 1:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP1;
        break;
    case 2:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP2;
        break;
    case 3:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP3;
        break;
    case 4:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP4;
        break;
    case 5:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP5;
        break;
    case 6:
        enable = m_dev.m_bar2->PATH_HAS_STREAM_ID[channel].HAS_STREAM_ID.EGROUP6;
        break;
    default:
        LOG_ERR("Checking stream status on invalid egroup %u.", egroup);
        break;
    }
#endif
    return static_cast<bool>( (enable >> epath) & 1);
}


std::vector<Elink> FlxDevice::read_enabled_elinks(int dmaid)
{
    std::vector<Elink> enabled_elinks;

    // If Trickle mode is active, only the Trickle E-link is enabled and returned
    bool is_trickle = (dmaid == get_trickle_dmaid()) ? true : false;
    if (is_trickle){
        local_elink_t lid = 0xffff;
        uint64_t fid = get_fid_from_ids(m_did, m_cid, lid, 0, m_vid, true, true);
        enabled_elinks.emplace_back(Elink{fid, lid, 0, elink_type_t::DAQ, encoding_t::ENC_8b10b});
        return enabled_elinks;
    }

    bool is_to_flx = (dmaid == get_fromhost_dmaid()) ? true : false;
    bool fullmode              = is_full_mode();
    u_int no_of_channels       = get_number_of_channels();
    u_int egroups_per_chan     = get_egroups_per_channel(is_to_flx);
    u_int epaths_per_egroup    = get_epaths_per_egroup(is_to_flx);

  // TTCtoHost e-link = #Nchannels group #7 path #3 (RM4) or 0x600 (RM5)
#if REGMAP_VERSION < 0x0500
    if( !is_to_flx && m_dev.m_bar2->CR_TTC_TOHOST.ENABLE) {
#else
    if( !is_to_flx && m_dev.m_bar2->TTC_TOHOST_ENABLE && elink_is_in_dma(0x600, dmaid)) {
#endif
        local_elink_t lid = TTC2HOST_LINK(no_of_channels);
        uint64_t fid = get_fid_from_ids(m_did, m_cid, lid, 0, m_vid, false, true);
        bool streams = 0;
        enabled_elinks.emplace_back(Elink{fid, lid, streams, elink_type_t::TTC, encoding_t::ENC_TTC});
        LOG_TRACE("E-Link %d:\t0x%02X\t0x%016lx\t(TTC2H)", enabled_elinks.size(), lid, fid);
    }

    // IC, EC, AUX
    if( !fullmode ) {
        for( unsigned channel = 0; channel < no_of_channels; ++channel ) {

            local_elink_t ec_elink = get_ec_elink(is_to_flx, channel);
            bool ec_enabled = elink_is_ec_enabled(is_to_flx, channel) && elink_is_in_dma(ec_elink, dmaid);

            local_elink_t ic_elink = get_ic_elink(is_to_flx, channel);
            bool ic_enabled = elink_is_ic_enabled(is_to_flx, channel) && elink_is_in_dma(ic_elink, dmaid);

            local_elink_t aux_elink = get_aux_elink(is_to_flx, channel);
            bool aux_enabled = elink_is_aux_enabled(is_to_flx, channel) && elink_is_in_dma(aux_elink, dmaid);
            if( ec_enabled ){
                uint64_t fid = get_fid_from_ids(m_did, m_cid, ec_elink, 0, m_vid, is_to_flx, false);
                enabled_elinks.emplace_back(Elink{fid, ec_elink, false, elink_type_t::DCS, encoding_t::ENC_HDLC});
                LOG_TRACE("E-Link %d:\t0x%02X\t0x%016lx\t(EC channel %d)", enabled_elinks.size(), ec_elink, fid, channel);
            }
            if( ic_enabled ){
                uint64_t fid = get_fid_from_ids(m_did, m_cid, ic_elink, 0, m_vid, is_to_flx, false);
                enabled_elinks.emplace_back(Elink{fid, ic_elink, false, elink_type_t::IC, encoding_t::ENC_HDLC});
                LOG_TRACE("E-Link %d:\t0x%02X\t0x%016lx\t(IC channel %d)", enabled_elinks.size(), ic_elink, fid, channel);
            }
            if( m_firmware_type == FIRMW_LTDB && aux_enabled ) {
                uint64_t fid = get_fid_from_ids(m_did, m_cid, aux_elink, 0, m_vid, is_to_flx, false);
                enabled_elinks.emplace_back(Elink{fid, aux_elink, false, elink_type_t::DCS, encoding_t::ENC_HDLC});
                LOG_TRACE("E-Link %d:\t0x%02X\t0x%016lx\t(AUX channel %d)", enabled_elinks.size(), aux_elink, fid, channel);
            }
        }
    }

    for( unsigned channel = 0; channel < no_of_channels; ++channel ) {
        for( unsigned egroup = 0; egroup < egroups_per_chan; ++egroup ) {
            for( unsigned epath = 0; epath < epaths_per_egroup; ++epath ) {

                local_elink_t elinknr = get_elink(channel, egroup, epath);
                bool enabled = elink_is_enabled(channel, egroup, epath, is_to_flx);
                bool in_enabled_dma = is_to_flx ? 1 : elink_is_in_dma(elinknr, dmaid);

                if( enabled && in_enabled_dma ) {
                    bool streams = !is_to_flx && elink_has_stream_id(channel, egroup, epath);
                    encoding_t enc = elink_get_encoding(channel, egroup, epath, is_to_flx);
                    elink_type_t type = elink_type_t::NONE_ELINK_TYPE;
                    if (enc == ENC_HDLC){
                        type = elink_type_t::DCS;
                    } else {
                        type = elink_type_t::DAQ;
                    }

                    uint64_t fid = get_fid_from_ids(m_did, m_cid, elinknr, 0, m_vid, is_to_flx, false);
                    enabled_elinks.emplace_back(Elink{fid, elinknr, streams, type, enc});
                    LOG_TRACE("E-Link %d:\t0x%02X\t0x%016lx\t(channel %d egroup %d epath %d) type %s, streams %c",
                        enabled_elinks.size(), elinknr, fid, channel, egroup, epath,
                        Device::get_elink_type_str(type).c_str(),
                        streams ? 'Y' : 'N');
                } //if enabled
            } //epath
        } //egroup
    } //channel

#if REGMAP_VERSION >= 0x0500
    // Add Broadcast Elinks
    if (is_to_flx) {
        // Calculate Broadcast Elinks
        uint64_t broadcast_elinks[MAX_BROADCAST_ELINKS] = {0};
        unsigned no_of_broadcast_elinks = get_broadcast_elinks(broadcast_elinks, no_of_channels);
        LOG_INFO("Adding %d broadcast elinks", no_of_broadcast_elinks);
        for( unsigned i=0; i < no_of_broadcast_elinks; ++i ) {
            bool streams = 0;
            //broadcast elinks use the same encoding as
            //elinks they broadcast into
            encoding_t enc = ENC_DIRECT;
            elink_type_t type = elink_type_t::DAQ;
            local_elink_t elinknr = static_cast<local_elink_t>(broadcast_elinks[i]);
            uint64_t fid = get_fid(0, elinknr, streams, m_vid, is_to_flx, 0);
            enabled_elinks.emplace_back(Elink{fid, elinknr, streams, type, enc});
            LOG_INFO("Broadcast for %8s \t%4d using \tE-Link %d:\t0x%02X\t0x%016lx\t(channel %d egroup %d epath %d)",
                Device::broadcast_type_str(elinknr).c_str(), Device::broadcast_for(elinknr), enabled_elinks.size(), elinknr, fid,
                Device::get_channel(elinknr), Device::get_egroup(elinknr), Device::get_epath(elinknr));
        }
    }
#endif
    return enabled_elinks;
}

bool FlxDevice::dma_enabled(int dmaid)
{
    return m_dev.dma_enabled(dmaid);
}

void FlxDevice::dma_to_host(DmaBuffer* buf, int dmaid)
{
    m_dev.dma_stop(dmaid);
    m_dev.dma_to_host(dmaid, buf->paddr, buf->size, FLX_DMA_WRAPAROUND);
    buf->pend = buf->paddr + buf->size;
    buf->pc_ptr = m_dev.dma_get_ptr(dmaid);
}


void FlxDevice::dma_from_host(DmaBuffer* buf, int dmaid)
{
    m_dev.dma_from_host(dmaid, buf->paddr, buf->size, FLX_DMA_WRAPAROUND);
    buf->pend = buf->paddr + buf->size;
    buf->pc_ptr = m_dev.dma_get_ptr(dmaid);
}


void FlxDevice::dma_from_host_trickle(DmaBuffer* buf, int dmaid, size_t config_size)
{
    if(config_size > buf->size){
        LOG_ERR("Trickle config size %lu larger than buffer size %lu", config_size, buf->size);
        return;
    }
    // DMA buffer size must be a multiple of 32 bytes
    if(config_size % 32){
        config_size += 32 - (config_size % 32);
    }
    // The firmware will read the buffer up until the config_size
    m_dev.dma_from_host(dmaid, buf->paddr, config_size, FLX_DMA_WRAPAROUND);
}


void FlxDevice::dma_set_oneshot(DmaBuffer* buf, int dmaid, size_t config_size){
    // DMA buffer size must be a multiple of 32 bytes
    if(config_size % 32){
        config_size += 32 - (config_size % 32);
    }
    // The firmware will read the buffer up until the config_size
    m_dev.dma_from_host(dmaid, buf->paddr, config_size, 0 /*FLX_DMA_ONESHOT*/);
}

uint64_t FlxDevice::dma_get_fw_ptr(int dmaid)
{
    return m_dev.dma_get_fw_ptr(dmaid);
}


uint64_t FlxDevice::dma_get_sw_ptr(int dmaid)
{
    return m_dev.dma_get_ptr(dmaid);
}


void FlxDevice::dma_set_sw_ptr(int dmaid, uint64_t p_phys)
{
    return m_dev.dma_set_ptr(dmaid, p_phys);
}


bool FlxDevice::dma_cmp_even_bits(int dmaid)
{
    return m_dev.dma_cmp_even_bits(dmaid);
}


void FlxDevice::dma_stop(unsigned int dmaid)
{
    m_dev.dma_stop(dmaid);
}

monitoring_data_t FlxDevice::hw_get_monitoring_data(unsigned int mon_mask)
{
    monitoring_data_t data;
    //memset(&data, 0, sizeof(data));
    try {
        data = m_dev.get_monitoring_data(mon_mask);
    } catch(const FlxException & e) {
        LOG_WARN(e.what());
    }
    return data;
}