File mudaq_device.cpp

File List > libmudaq > mudaq_device.cpp

Go to the documentation of this file

#include "mudaq_device.h"

#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>

#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <thread>

#define PAGEMAP_LENGTH 8  // each page table entry has 64 bits = 8 bytes
#define PAGE_SHIFT 12     // on x86_64: 4 kB pages, so shifts of 12 bits

using namespace std;

// ----------------------------------------------------------------------------
// additional local helper functions

int physical_address_check(uint32_t* base_address, size_t size) {
    /* Open binary file with page map table */
    FILE* pagemap = fopen("/proc/self/pagemap", "rb");
    void* virtual_address;
    unsigned long offset = 0, page_frame_number = 0, distance_from_page_boundary = 0;
    uint64_t offset_mem;

    for (uint i = 0; i < size / _pagesize(); i++) {  // loop over all pages in allocated memory
        virtual_address = base_address + i * _pagesize() / 4;  // uint32_t words
        /* go to entry for virtual_address  */
        offset = (unsigned long)virtual_address / _pagesize() * PAGEMAP_LENGTH;
        if (fseek(pagemap, (unsigned long)offset, SEEK_SET) != 0) {
            fprintf(stderr, "Failed to seek pagemap to proper location\n");
        }

        /* get page frame number and shift by page_shift to get physical address */
        if (fread(&page_frame_number, 1, PAGEMAP_LENGTH - 1, pagemap) < PAGEMAP_LENGTH - 1)
            std::cerr << "Warning: fread() read fewer bytes than expected.\n";
        page_frame_number &= 0x7FFFFFFFFFFFFF;  // clear bit 55: soft-dirty. clear this to indicate
                                                // that nothing has been written yet
        distance_from_page_boundary = (unsigned long)virtual_address % _pagesize();
        offset_mem = (page_frame_number << PAGE_SHIFT) + distance_from_page_boundary;

        // cout << hex << "Physical address: " << offset_mem << endl;
        if (offset_mem >> 32 == 0) {
            cout << dec << "Memory resides within 4 GB for page " << i << " at address " << hex
                 << offset_mem << endl;
            fclose(pagemap);
            return -1;
        }
    }
    fclose(pagemap);
    return 0;
}

int is_page_aligned(void* pointer) {
    DEBUG("diff to page: %lu", ((uintptr_t)(const void*)(pointer)) % _pagesize());
    return !(((uintptr_t)(const void*)(pointer)) % _pagesize() == 0);
}

void* align_page(void* pointer) {
    void* aligned_pointer = (void*)((uintptr_t)(const void*)pointer + _pagesize() -
                                    ((uintptr_t)(const void*)(pointer)) % _pagesize());
    return aligned_pointer;
}

void* get_next_aligned_page(void* base_address) {
    int retval = is_page_aligned(base_address);
    if (retval != 0) {
        // ERROR("Memory buffer is not page aligned");
        void* aligned_pointer = align_page(base_address);
        retval = is_page_aligned(aligned_pointer);
        return aligned_pointer;
    }

    return base_address;
}

static void _print_raw_buffer(volatile uint32_t* addr, unsigned len, unsigned block_offset = 0) {
    const unsigned BLOCK_SIZE = 8;
    const unsigned first = (block_offset * BLOCK_SIZE);
    const unsigned last = first + len - 1;

    cout << showbase << hex;

    unsigned i;
    for (i = first; i <= last; ++i) {
        if ((i % BLOCK_SIZE) == 0) {
            cout << setw(6) << i << " |";
        }
        cout << " " << setw(10) << addr[i];
        // add a newline after every complete block and after the last one.
        if ((i % BLOCK_SIZE) == (BLOCK_SIZE - 1) || (i == last)) {
            cout << endl;
        }
    }

    cout << noshowbase << dec;
}

namespace mudaq {

// MudaqDevice

mudaq::MudaqDevice::MudaqDevice(const std::string& path)
    : _fd(-1),
#ifdef NO_A10_BOARD
      _path(path)
#else
      _path(path),
      _regs_rw(nullptr),
      _regs_ro(nullptr),
      _mem_ro(nullptr),
      _mem_rw(nullptr)
#endif
{
    _last_read_address = 0;
}

bool MudaqDevice::is_ok() const {
#ifdef NO_A10_BOARD
    return true;
#else
    bool error = (_fd < 0) || (_regs_rw == nullptr) || (_regs_ro == nullptr) ||
                 (_mem_ro == nullptr) || (_mem_rw == nullptr);

    return !error;
#endif
}

bool MudaqDevice::open() {
#ifdef NO_A10_BOARD
    std::cout << "Dummy mudaq: open()" << std::endl;
    return true;
#else
    // O_SYNC only affects 'write'. not really needed but doesnt hurt and makes
    // things safer if we later decide to use 'write'.
    _fd = ::open(_path.c_str(), O_RDWR | O_SYNC);
    if (_fd < 0) {
        ERROR("could not open device '%s': %s", _path, strerror(errno));
        return false;
    }
    _regs_rw = mmap_rw(MUDAQ_REGS_RW_INDEX, MUDAQ_REGS_RW_LEN);
    _regs_ro = mmap_ro(MUDAQ_REGS_RO_INDEX, MUDAQ_REGS_RO_LEN);
    _mem_rw = mmap_rw(MUDAQ_MEM_RW_INDEX, MUDAQ_MEM_RW_LEN);
    _mem_ro = mmap_ro(MUDAQ_MEM_RO_INDEX, MUDAQ_MEM_RO_LEN);
    return (_regs_rw != nullptr) && (_regs_ro != nullptr) && (_mem_rw != nullptr) &&
           (_mem_ro != nullptr);
#endif
}

void MudaqDevice::close() {
#ifdef NO_A10_BOARD
    std::cout << "Dummy mudaq: close()" << std::endl;
#else
    munmap_wrapper(&_mem_ro, MUDAQ_MEM_RO_LEN, "could not unmap read-only memory");
    munmap_wrapper(&_mem_rw, MUDAQ_MEM_RW_LEN,
                   "could not unmap read/write memory");  // added by DvB for rw mem
    munmap_wrapper(&_regs_ro, MUDAQ_REGS_RO_LEN, "could not unmap read-only registers");
    munmap_wrapper(&_regs_rw, MUDAQ_REGS_RW_LEN, "could not unmap read/write registers");
    if (_fd >= 0 && ::close(_fd) < 0) {
        ERROR("could not close '%s': %s", _path, strerror(errno));
    }
    // invalidate the file descriptor
    _fd = -1;
#endif
}

bool MudaqDevice::operator!() const {
#ifdef NO_A10_BOARD
    return (_fd < 0);
#else
    return (_fd < 0) || (_regs_rw == nullptr) || (_regs_ro == nullptr) || (_mem_ro == nullptr) ||
           (_mem_rw == nullptr);
#endif
}

void MudaqDevice::write_memory_rw(unsigned idx, uint32_t value) {
    if (idx > 64 * 1024) {
        cout << "Invalid memory address " << idx << endl;
        exit(EXIT_FAILURE);
    } else {
        _mem_rw[idx & MUDAQ_MEM_RW_MASK] = value;
    }
}

void MudaqDevice::write_register(unsigned idx, uint32_t value) {
    if (idx > 63) {
        cout << "Invalid register address " << idx << endl;
        exit(EXIT_FAILURE);
    } else {
        _regs_rw[idx] = value;
    }
}

void MudaqDevice::write_register_wait(unsigned idx, uint32_t value, unsigned wait_ns) {
    write_register(idx, value);
    std::this_thread::sleep_for(
        std::chrono::nanoseconds(wait_ns));  // (MM): this is terrible and will sleep for a minimum
                                             // of 60 us. Do we need this anywhere?
}

void MudaqDevice::toggle_register(unsigned idx, uint32_t value, unsigned wait_ns) {
    uint32_t old_value = read_register_rw(idx);
    write_register_wait(idx, value, wait_ns);
    write_register(idx, old_value);
}

void MudaqDevice::toggle_register_fast(unsigned idx, uint32_t value) {
    uint32_t old_value = read_register_rw(idx);
    write_register(idx, value);
    write_register(idx, old_value);
}

uint32_t MudaqDevice::read_register_rw(unsigned idx) const {
    if (idx > 63) {
        cout << "Invalid register address " << idx << endl;
        exit(EXIT_FAILURE);
    }
    return _regs_rw[idx];
}

uint32_t MudaqDevice::read_register_ro(unsigned idx) const {
    if (idx > 63) {
        cout << "Invalid register address " << idx << endl;
        exit(EXIT_FAILURE);
    }
    return _regs_ro[idx];
}

uint32_t MudaqDevice::read_memory_ro(unsigned idx) const {
    if (idx > 64 * 1024) {
        cout << "Invalid memory address " << idx << endl;
        exit(EXIT_FAILURE);
    }
    return _mem_ro[idx & MUDAQ_MEM_RO_MASK];
}

uint32_t MudaqDevice::read_memory_rw(unsigned idx) const {
    if (idx > 64 * 1024 - 1) {
        cout << "Invalid memory address " << idx << endl;
        exit(EXIT_FAILURE);
    }
    return _mem_rw[idx & MUDAQ_MEM_RW_MASK];
}

void MudaqDevice::write_dummy_acknowledge(unsigned startaddr, unsigned fpga_id) {
#ifdef NO_A10_BOARD
    _regs_ro[MEM_WRITEADDR_LOW_REGISTER_R] = 3;
    _mem_ro[0] = PACKET_TYPE_SC << 26 | PACKET_TYPE_SC_WRITE << 24 |
                 ((uint16_t)(fpga_id & 0x000000FF)) << 8 | 0xBC;
    _mem_ro[1] = startaddr;
    _mem_ro[2] = 0x10000;
    _mem_ro[3] = 0x9c;
#else

#endif
}

void MudaqDevice::write_register_ro_dummy(unsigned idx, uint32_t value) {
#ifdef NO_A10_BOARD
    if (idx > 63) {
        cout << "Invalid register address " << idx << endl;
        exit(EXIT_FAILURE);
    }
    _regs_ro[idx] = value;
#else

#endif
}

void MudaqDevice::read_dummy_acknowledge(unsigned startaddr, int length, unsigned fpga_id) {
#ifdef NO_A10_BOARD
    _regs_ro[MEM_WRITEADDR_LOW_REGISTER_R] = length + 3;
    _mem_ro[0] = PACKET_TYPE_SC << 26 | PACKET_TYPE_SC_READ << 24 |
                 ((uint16_t)(fpga_id & 0x000000FF)) << 8 | 0xBC;
    _mem_ro[1] = startaddr;
    _mem_ro[2] = 0x10000 + length;

    // do some dummy counting
    dummyCounter[0] = 0;
    dummyCounter[3]++;
    dummyCounter[5] = dummyCounter[5] + 0x800;
    dummyCounter[7]++;
    dummyCounter[63] = 0;
    for (int idx = 3; idx < length + 3; idx++) {
        // TODO: here we can check what addr we write to and than fill this with
        // some more usefull data for now random
        if (startaddr == MUTRIG_CNT_ADDR_REGISTER_R) {
            // NOTE: we have an offset of +2 in the firmware
            int cidx = idx - 2 - 3;
            if (cidx % 64 == 0) {  // ASIC ID
                _mem_ro[idx] = cidx / 64;
            } else if (cidx % 64 == 1) {  // DEBUG1
                _mem_ro[idx] = dummyCounter[1];
            } else if (cidx % 64 == 2) {  // DEBUG2
                _mem_ro[idx] = dummyCounter[2];
            } else if (cidx % 64 == 3) {  // HitRate
                _mem_ro[idx] = 12345;
            } else if (cidx % 64 == 4) {  // TimeLow
                _mem_ro[idx] = dummyCounter[4];
                dummyCounter[4]++;
            } else if (cidx % 64 == 5) {  // TimeHigh
                _mem_ro[idx] = dummyCounter[5];
                dummyCounter[5]++;
            } else if (cidx % 64 == 6) {  // CRCCnt
                _mem_ro[idx] = 0;
            } else if (cidx % 64 == 7) {  // FrameRate
                _mem_ro[idx] = std::rand() / ((RAND_MAX + 1u) / 125000001);
            } else if (cidx % 64 > 7 && cidx % 64 < 8 + 32) {
                _mem_ro[idx] = std::rand() / ((RAND_MAX + 1u) / 125000001);
            } else {
                _mem_ro[idx] = 0xBEEFBEEF;
            }
        } else if (startaddr == ARRIA_TEMP_REGISTER_RW) {
            _FEB_REGS[startaddr] = _FEB_REGS[startaddr] + 5;
            _mem_ro[idx] = _FEB_REGS[startaddr];
        } else if (startaddr == MAX10_ADC_0_1_REGISTER_R) {
            _FEB_REGS[startaddr] = _FEB_REGS[startaddr] + 5;
            _mem_ro[idx] = _FEB_REGS[startaddr];
        } else if (startaddr == FIREFLY_STATUS_REGISTER_R) {
            _FEB_REGS[startaddr + idx - 3] = _FEB_REGS[startaddr + idx - 3] + 5;
            _mem_ro[idx] = _FEB_REGS[startaddr + idx - 3];
        } else {
            _mem_ro[idx] = std::rand() / ((RAND_MAX + 1u) / 4096);
        }
    }
    _mem_ro[length + 3] = 0x9c;
#else

#endif
}

void MudaqDevice::enable_led(unsigned which) {
    uint8_t pattern;
    // turn on a single led w/o changing the status of the remaining ones
    // since we only have 8 leds we need to wrap the led index
    pattern = read_register_rw(LED_REGISTER_W);
    pattern |= (1 << (which % 8));
    write_register(LED_REGISTER_W, pattern);
}

void MudaqDevice::enable_leds(uint8_t pattern) { write_register(LED_REGISTER_W, pattern); }

void MudaqDevice::disable_leds() { write_register(LED_REGISTER_W, 0x0); }

void MudaqDevice::print_registers() {
    cout << "offset + read/write registers" << endl;
    _print_raw_buffer(_regs_rw, MUDAQ_REGS_RW_LEN);
    cout << "offset + read-only registers" << endl;
    _print_raw_buffer(_regs_ro, MUDAQ_REGS_RO_LEN);
}

// ----------------------------------------------------------------------------
// mmap / munmap helper functions

volatile uint32_t* MudaqDevice::mmap_rw(unsigned idx, unsigned len) {
    off_t offset = idx * _pagesize();
    size_t size = len * sizeof(uint32_t);
    // TODO what about | MAP_POPULATE
    volatile void* rv = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, offset);
    if (rv == MAP_FAILED) {
        ERROR("could not mmap region %d in read/write mode: %s", idx, strerror(errno));
        return static_cast<volatile uint32_t*>(nullptr);
    } else {
        return static_cast<volatile uint32_t*>(rv);
    }
}

volatile uint32_t* MudaqDevice::mmap_ro(unsigned idx, unsigned len) {
    off_t offset = idx * _pagesize();
    size_t size = len * sizeof(uint32_t);
    // TODO what about | MAP_POPULATE
    volatile void* rv = mmap(nullptr, size, PROT_READ, MAP_SHARED, _fd, offset);
    if (rv == MAP_FAILED) {
        ERROR("could not mmap region %d in read-only mode: %s", idx, strerror(errno));
        return static_cast<volatile uint32_t*>(nullptr);
    } else {
        return static_cast<volatile uint32_t*>(rv);
    }
}

void MudaqDevice::munmap_wrapper(uint32_t** addr, unsigned len, const std::string& error_msg) {
    // i have to cast away volatile to allow to call munmap. using any of the
    // "correct" c++ versions, e.g. const_cast, reinterpret_cast, ... do not
    // seem to work. back to plain c-type cast
    if (munmap((*addr), len * sizeof(uint32_t)) < 0) {
        ERROR("%s: %s", error_msg, strerror(errno));
    }
    // invalidate the pointer
    (*addr) = nullptr;
}

void MudaqDevice::munmap_wrapper(volatile uint32_t** addr, unsigned len,
                                 const std::string& error_msg) {
    // cast away volatility. not required for munmap
    uint32_t** tmp = (uint32_t**)(addr);
    munmap_wrapper(tmp, len, error_msg);
}

// ----------------------------------------------------------------------------
// DmaMudaqDevice

DmaMudaqDevice::DmaMudaqDevice(const string& path)
    : MudaqDevice(path), _dmabuf_ctrl(nullptr), _last_end_of_buffer(0) {
    // boring
}

bool DmaMudaqDevice::open() {
#ifdef NO_A10_BOARD
    std::cout << "Dummy DMA mudaq: open()" << std::endl;
    return true;
#else
    if (!MudaqDevice::open())
        return false;
    _dmabuf_ctrl = mmap_ro(MUDAQ_DMABUF_CTRL_INDEX, MUDAQ_DMABUF_CTRL_WORDS);
    return (_dmabuf_ctrl != nullptr);
#endif
}

void DmaMudaqDevice::close() {
#ifdef NO_A10_BOARD
    std::cout << "Dummy DMA mudaq: close()" << std::endl;
#else
    munmap_wrapper(&_dmabuf_ctrl, MUDAQ_DMABUF_CTRL_WORDS, "could not unmap dma control buffer");
    MudaqDevice::close();
#endif
}

bool DmaMudaqDevice::operator!() const {
    return MudaqDevice::operator!() || (_dmabuf_ctrl == nullptr);
    //|| (_dmabuf_data == nullptr);
}

int DmaMudaqDevice::read_block(DataBlock& buffer, volatile uint32_t* pinned_data) {
    uint32_t end_write =
        _dmabuf_ctrl[3] >> 2;  // dma address next to be written to (last written to + 1) (in words)
    if (end_write == 0)        // This is problematic if runs get bigger than the DMA bufer
        return READ_NODATA;
    // cout <<hex<< interrupt << ", "<<end_write<<endl;
    size_t begin = _last_end_of_buffer & MUDAQ_DMABUF_DATA_WORDS_MASK;
    size_t end = (end_write - 1) & MUDAQ_DMABUF_DATA_WORDS_MASK;
    size_t len = ((end - begin + 1) & MUDAQ_DMABUF_DATA_WORDS_MASK);

    if (len == 0) {
        return READ_NODATA;
    }

    _last_end_of_buffer = end + 1;

    buffer = DataBlock(pinned_data, begin, len);
    return READ_SUCCESS;
}

int DmaMudaqDevice::get_current_interrupt_number() {
    int interrupt_number;
    int ret_val = ioctl(_fd, REQUEST_INTERRUPT_COUNTER, &interrupt_number);
    if (ret_val == -1) {
        printf("Requesting the interrupt number failed with %d \n", errno);
        return ret_val;
    } else
        return interrupt_number;
}

// in words!
uint32_t DmaMudaqDevice::last_written_addr() const {
    // returns: remoteaddress_var <= remoteaddress_var + (packet_length & "00");
    // shifted by two bits
    return (_dmabuf_ctrl[3] >> 2);
}

uint32_t DmaMudaqDevice::last_endofevent_addr() const {
    // returns: d0 := memwriteaddreoedma_long(31 downto 0);
    return _dmabuf_ctrl[0];
}

// enable interrupts
int DmaMudaqDevice::enable_continous_readout(int interTrue) {
    _last_end_of_buffer = 0;
    if (interTrue == 1) {
        write_register(DMA_REGISTER_W, 0x9);
    } else {
        write_register(DMA_REGISTER_W, SET_DMA_BIT_ENABLE(0x0));
    }
    return 0;
}

void DmaMudaqDevice::disable() { write_register(DMA_REGISTER_W, UNSET_DMA_BIT_ENABLE(0x0)); }

// ----------------------------------------------------------------------------
// convenience output functions

ostream& operator<<(ostream& os, const MudaqDevice& dev) {
    os << "MudaqDevice '" << dev._path << "' "
       << "status: " << (!dev ? "ERROR" : "ok");
    return os;
}

}  // namespace mudaq