/*
 *
 * Copyright (c) 2013-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *  mtcr_ul.c - Mellanox Hardware Access implementation
 *
 */

//use memory mapped /dev/mem for access
#define CONFIG_ENABLE_MMAP 1
//mmap /dev/mem for memory access (does not work on sparc)
#define CONFIG_USE_DEV_MEM 1
//use pci configuration cycles for access
#define CONFIG_ENABLE_PCICONF 1

#ifndef _XOPEN_SOURCE
#if CONFIG_ENABLE_PCICONF && CONFIG_ENABLE_MMAP
/* For strerror_r */
#define _XOPEN_SOURCE 600
#elif CONFIG_ENABLE_PCICONF
#define _XOPEN_SOURCE 500
#endif
#endif

#if CONFIG_ENABLE_MMAP
#define _FILE_OFFSET_BITS 64
#endif

#define MTCR_MAP_SIZE 0x100000
#define DBG_PRINTF(...) do { if (getenv("MFT_DEBUG") != NULL) { fprintf(stderr, __VA_ARGS__); } } while (0)

#include <stdio.h>
#include <dirent.h>
#include <string.h>

#include <unistd.h>
#include <malloc.h>
#include <netinet/in.h>
#include <endian.h>
#include <byteswap.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/file.h>

#if CONFIG_ENABLE_MMAP
#include <sys/mman.h>
#include <sys/pci.h>
#include <sys/ioctl.h>
#endif

#include <bit_slice.h>
#include "tools_utils.h"
#include "mtcr_ul_com.h"
#include "mtcr_int_defs.h"
#include "mtcr_ib.h"
#include "packets_layout.h"
#include "mtcr_tools_cif.h"
#include "mtcr_icmd_cif.h"

#include "kernel/mst.h"

#define CX3_SW_ID    4099
#define CX3PRO_SW_ID 4103

typedef enum {
    Clear_Vsec_Semaphore = 0x1,
} adv_opt_t;


#define DBDF             "%4.4x:%2.2x:%2.2x.%1.1x"
#define DRIVER_CR_NAME   "/dev/"DBDF "_mstcr"
#define DRIVER_CONF_NAME "/dev/"DBDF "_mstconf"

/* Forward decl*/
static int get_inband_dev_from_pci(char *inband_dev, char *pci_dev);
int check_force_config(unsigned my_domain, unsigned my_bus, unsigned my_dev, unsigned my_func);
mfile* mopen_ul_int(const char *name, u_int32_t adv_opt);
int init_dev_info_ul(mfile *mf, const char *dev_name, unsigned domain, unsigned bus, unsigned dev, unsigned func);
/*
 * Lock file section:
 *
 * in order to support concurrency on both MEMORY/CONFIG_CYCLES access methods
 * there is need for a sync mechanism between mtcr_ul using processes
 *
 * solution: each mfile obj shall contain a file descriptor linked to a unique, device determined file
 *           which will be used as a lock for the critical sections in read/write operations.
 *           these lock files will be created per device (dbdf format) AND per interface (memory/configuration cycles).
 */

#define LOCK_FILE_DIR "/tmp/mstflint_lockfiles"
#define LOCK_FILE_FORMAT "/tmp/mstflint_lockfiles/%04x:%02x:%02x.%x_%s"
// lockfile example : /tmp/mstflint_lockfiles/0000:0b:00.0_config
//                    /tmp/mstflint_lockfiles/0000:0b:00.0_mem
// general format : /tmp/mstflint_lockfiles/<domain:bus:device.function>_<config|mem>
#define CHECK_LOCK(rc) \
    if (rc) { \
        return rc; \
    }

#define MAX_RETRY_CNT 4096

static int _flock_int(int fdlock, int operation)
{
    int cnt = 0;
    if (fdlock == 0) { // in case we failed to create the lock file we ignore the locking mechanism
        return 0;
    }
    do {
        if (flock(fdlock, operation | LOCK_NB) == 0) {
            return 0;
        } else if (errno != EWOULDBLOCK) {
            break; // BAD! lock/free failed
        }
        if ((cnt & 0xf) == 0) { // sleep every 16 retries
            usleep(1);
        }
        cnt++;
    } while (cnt < MAX_RETRY_CNT);
    perror("failed to perform lock operation.");
    return -1;

}

static int _create_lock(mfile *mf, unsigned domain, unsigned bus, unsigned dev, unsigned func)
{
    char fname[64] = { 0 };
    int rc;
    int fd = 0;
    if (!(mf->ul_ctx)) {
        goto cl_clean_up;
    }
    snprintf(fname, sizeof(fname) - 1, LOCK_FILE_FORMAT, domain, bus, dev, func, mf->tp == MST_PCICONF ? "config" : "mem");
    rc = mkdir("/tmp", 0777);
    if (rc && errno != EEXIST) {
        goto cl_clean_up;
    }
    rc = mkdir(LOCK_FILE_DIR, 0777);
    if (rc && errno != EEXIST) {
        goto cl_clean_up;
    }

    fd = open(fname, O_RDONLY | O_CREAT, 0777);
    if (fd < 0) {
        goto cl_clean_up;
    }

    ((ul_ctx_t*)mf->ul_ctx)->fdlock = fd;
    return 0;

cl_clean_up:
    fprintf(stderr, "Warrning: Failed to create lockfile: %s (parallel access not supported)\n", fname);
    return 0;
}
/*End of Lock file section */

static int _extract_dbdf_from_full_name(const char *name, unsigned *domain, unsigned *bus, unsigned *dev,
                                        unsigned *func)
{
    if (sscanf(name, "/sys/bus/pci/devices/%4x:%2x:%2x.%d/resource0", domain, bus, dev, func) == 4) {
        return 0;
    } else if (sscanf(name, "/sys/bus/pci/devices/%4x:%2x:%2x.%d/config", domain, bus, dev, func) == 4) {
        return 0;
    } else if (sscanf(name, "/proc/bus/pci/%4x:%2x/%2x.%d", domain, bus, dev, func) == 4) {
        return 0;
    } else if (sscanf(name, "/proc/bus/pci/%2x/%2x.%d", bus, dev, func) == 3) {
        *domain = 0;
        return 0;
    }
    // failed to extract dbdf format from name
    errno = EINVAL;
    return -1;
}

static int mtcr_connectx_flush(void *ptr, int fdlock)
{
    u_int32_t value;
    int rc;
    rc = _flock_int(fdlock, LOCK_EX);
    CHECK_LOCK(rc);
    *((u_int32_t*) ((char*) ptr + 0xf0380)) = 0x0;
    do {
        asm volatile ("" ::: "memory");
        u_int32_t tmp = *((u_int32_t* )((char* )ptr + 0xf0380));
        value = __be32_to_cpu(tmp);
    } while (value);
    rc = _flock_int(fdlock, LOCK_UN);
    CHECK_LOCK(rc)
    return 0;
}

int mread4_ul(mfile *mf, unsigned int offset, u_int32_t *value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    return ctx->mread4(mf, offset, value);
}

int mwrite4_ul(mfile *mf, unsigned int offset, u_int32_t value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    return ctx->mwrite4(mf, offset, value);
}

// TODO: Verify change 'data' type from void* to u_in32_t* does not mess up things
static int mread_chunk_as_multi_mread4(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    int i = 0;
    if (length % 4) {
        return EINVAL;
    }
    for (i = 0; i < length; i += 4) {
        u_int32_t value = 0;
        if (mread4_ul(mf, offset + i, &value) != 4) {
            return -1;
        }
        memcpy((char*) data + i, &value, 4);
    }
    return length;
}

static int mwrite_chunk_as_multi_mwrite4(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    int i = 0;
    if (length % 4) {
        return EINVAL;
    }
    for (i = 0; i < length; i += 4) {
        u_int32_t value = 0;
        memcpy(&value, (char*) data + i, 4);
        if (mwrite4_ul(mf, offset + i, value) != 4) {
            return -1;
        }
    }
    return length;
}


static int mst_driver_connectx_flush(mfile *mf);
int mtcr_driver_cr_mread4(mfile *mf, unsigned int offset, u_int32_t *value);
void mpci_change_ul(mfile *mf);

/*
 * Return values:
 * 0:  OK
 * <0: Error
 * 1 : Device does not support memory access
 *
 */
static
int mtcr_check_signature(mfile *mf)
{
    unsigned signature = 0;
    int rc;
    char *connectx_flush = getenv("CONNECTX_FLUSH");
    rc = mread4_ul(mf, 0xF0014, &signature);
    if (rc != 4) {
        if (!errno) {
            errno = EIO;
        }
        return -1;
    }

    switch (signature) {
    case 0xbad0cafe:     /* secure host mode device id */
        return 0;

    case 0xbadacce5:     /* returned upon mapping the UAR bar */
    case 0xffffffff:     /* returned when pci mem access is disabled (driver down) */
        return 1;
    }

    if (connectx_flush == NULL || strcmp(connectx_flush, "0")) {
        if ((signature == 0xa00190 || (signature & 0xffff) == 0x1f5 || (signature & 0xffff) == 0x1f7)
            && mf->tp == MST_PCI) {
            ul_ctx_t *ctx = mf->ul_ctx;
            ctx->connectx_flush = 1;
            if (ctx->via_driver) {
                if (mst_driver_connectx_flush(mf)) {
                    return -1;
                }
            } else if (mtcr_connectx_flush(mf->bar_virtual_addr, ctx->fdlock)) {
                return -1;
            }
        }
    }

    return 0;
}

int mst_driver_vpd_read4(mfile *mf, unsigned int offset, u_int8_t value[])
{
    int flag = 0;
    struct mst_vpd_read4_st read_vpd4;
    if (mf->tp != MST_PCICONF) {
        mpci_change_ul(mf);
        flag = 1;
    }
    memset(&read_vpd4, 0, sizeof(read_vpd4));
    read_vpd4.offset = offset;
    int ret = ioctl(mf->fd, PCICONF_VPD_READ4, &read_vpd4);
    if (ret < 0) {
        return ret;
    }
    memcpy(value, &read_vpd4.data, 4);
    if (flag) {
        mpci_change_ul(mf);
    }
    return 0;
}

#if CONFIG_ENABLE_MMAP
/*
 * The PCI interface treats multi-function devices as independent
 * devices.  The slot/function address of each device is encoded
 * in a single byte as follows:
 *
 *  7:3 = slot
 *  2:0 = function
 */
#define PCI_DEVFN(slot, func)    ((((slot) & 0x1f) << 3) | ((func) & 0x07))
#define PCI_SLOT(devfn)     (((devfn) >> 3) & 0x1f)
#define PCI_FUNC(devfn)     ((devfn) & 0x07)

static
unsigned long long mtcr_procfs_get_offset(unsigned my_bus, unsigned my_dev, unsigned my_func)
{
    FILE *f;
    unsigned irq;
    unsigned long long base_addr[6], rom_base_addr, size[6], rom_size;

    unsigned bus, dev, func;
    //unsigned vendor_id;
    //unsigned device_id;
    unsigned int cnt;

    unsigned long long offset = (unsigned long long) -1;

    char buf[4048];

    f = fopen("/proc/bus/pci/devices", "r");
    if (!f) {
        return offset;
    }

    for (;;)
        if (fgets(buf, sizeof(buf) - 1, f)) {
            unsigned dfn, vend;

            cnt = sscanf(buf, "%x %x %x %llx %llx %llx %llx %llx %llx "
                         "%llx %llx %llx %llx %llx %llx %llx %llx", &dfn, &vend, &irq, &base_addr[0], &base_addr[1],
                         &base_addr[2], &base_addr[3], &base_addr[4], &base_addr[5], &rom_base_addr, &size[0], &size[1],
                         &size[2], &size[3], &size[4], &size[5], &rom_size);
            if (cnt != 9 && cnt != 10 && cnt != 17) {
                fprintf(stderr, "proc: parse error (read only %d items)\n", cnt);
                fprintf(stderr, "the offending line in " "/proc/bus/pci/devices" " is "
                        "\"%.*s\"\n", (int) sizeof(buf), buf);
                goto error;
            }
            bus = dfn >> 8U;
            dev = PCI_SLOT(dfn & 0xff);
            func = PCI_FUNC(dfn & 0xff);
            //vendor_id = vend >> 16U;
            //device_id = vend & 0xffff;

            if (bus == my_bus && dev == my_dev && func == my_func) {
                break;
            }
        } else {
            goto error;
        }

    if (cnt != 17 || size[1] != 0 || size[0] != MTCR_MAP_SIZE) {
        if (0) {
            fprintf(stderr, "proc: unexpected region size values: "
                    "cnt=%d, size[0]=%#llx, size[1]=%#llx\n", cnt, size[0], size[1]);
        }
        if (0) {
            fprintf(stderr, "the offending line in " "/proc/bus/pci/devices"
                    " is \"%.*s\"\n", (int) sizeof(buf), buf);
        }
        goto error;
    }

    offset = ((unsigned long long) (base_addr[1]) << 32)
             + ((unsigned long long) (base_addr[0]) & ~(unsigned long long) (0xfffff));

    fclose(f);
    return offset;

error:
    fclose(f);
    errno = ENXIO;
    return offset;
}

static
unsigned long long mtcr_sysfs_get_offset(unsigned domain, unsigned bus, unsigned dev, unsigned func)
{
    unsigned long long start, end, type;
    unsigned long long offset = (unsigned long long) -1;
    FILE *f;
    int cnt;
    char mbuf[99] = "/sys/bus/pci/devices/XXXX:XX:XX.X/resource";
    sprintf(mbuf, "/sys/bus/pci/devices/%4.4x:%2.2x:%2.2x.%1.1x/resource", domain, bus, dev, func);

    f = fopen(mbuf, "r");
    if (!f) {
        return offset;
    }

    cnt = fscanf(f, "0x%llx 0x%llx 0x%llx", &start, &end, &type);
    if (cnt != 3 || end != start + MTCR_MAP_SIZE - 1) {
        if (0) {
            fprintf(stderr, "proc: unexpected region size values: "
                    "cnt=%d, start=%#llx, end=%#llx\n", cnt, start, end);
        }
        goto error;
    }

    fclose(f);
    return start;

error:
    fclose(f);
    errno = ENOENT;
    return offset;
}

//
// PCI MEMORY ACCESS FUNCTIONS
//

static
int mtcr_pcicr_mclose(mfile *mf)
{
    if (mf) {
        if (mf->bar_virtual_addr) {
            munmap(mf->bar_virtual_addr, MTCR_MAP_SIZE);
        }
        if (mf->fd > 0) {
            close(mf->fd);
        }
        if (mf->res_fd > 0) {
            close(mf->res_fd);
        }
    }
    return 0;
}

static
int mtcr_mmap(mfile *mf, const char *name, off_t off, int ioctl_needed)
{
    int err;
    mf->fd = open(name, O_RDWR | O_SYNC);
    if (mf->fd < 0) {
        return -1;
    }

    if (ioctl_needed && ioctl(mf->fd, PCIIOC_MMAP_IS_MEM) < 0) {
        err = errno;
        close(mf->fd);
        errno = err;
        return -1;
    }

    mf->bar_virtual_addr = mmap(NULL, MTCR_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mf->fd, off);

    if (!mf->bar_virtual_addr || mf->bar_virtual_addr == MAP_FAILED) {
        err = errno;
        close(mf->fd);
        errno = err;
        return -1;
    }
    return 0;
}

int mtcr_pcicr_mread4(mfile *mf, unsigned int offset, u_int32_t *value)
{
    ul_ctx_t *ctx = mf->ul_ctx;

    if (offset >= MTCR_MAP_SIZE) {
        errno = EINVAL;
        return 0;
    }
    if (ctx->need_flush) {
        if (mtcr_connectx_flush(mf->bar_virtual_addr, ctx->fdlock)) {
            return 0;
        }
        ctx->need_flush = 0;
    }
    u_int32_t tmp = ((u_int32_t*)mf->bar_virtual_addr)[offset / 4];
    *value = __be32_to_cpu(tmp);
    return 4;
}

int mtcr_pcicr_mwrite4(mfile *mf, unsigned int offset, u_int32_t value)
{
    ul_ctx_t *ctx = mf->ul_ctx;

    if (offset >= MTCR_MAP_SIZE) {
        errno = EINVAL;
        return 0;
    }
    *((u_int32_t*) ((char*) mf->bar_virtual_addr + offset)) = __cpu_to_be32(value);
    ctx->need_flush = ctx->connectx_flush;
    return 4;
}

static
int mtcr_pcicr_open(mfile *mf, const char *name, char *conf_name, off_t off, int ioctl_needed, u_int32_t adv_opt)
{
    int rc;
    ul_ctx_t *ctx = mf->ul_ctx;

    mf->tp = MST_PCI;

    ctx->mread4 = mtcr_pcicr_mread4;
    ctx->mwrite4 = mtcr_pcicr_mwrite4;
    ctx->mread4_block = mread_chunk_as_multi_mread4;
    ctx->mwrite4_block = mwrite_chunk_as_multi_mwrite4;
    ctx->mclose = mtcr_pcicr_mclose;

    mf->bar_virtual_addr = NULL;
    mf->fd = -1;
    ctx->connectx_flush = 0;
    ctx->need_flush = 0;

    rc = mtcr_mmap(mf, name, off, ioctl_needed);
    if (rc) {
        goto end;
    }

    rc = mtcr_check_signature(mf);
end:
    if (rc) {
        mtcr_pcicr_mclose(mf);
    } else if (conf_name != NULL) {
        mfile *conf_mf = mopen_ul_int(conf_name, adv_opt);
        if (conf_mf != NULL) {
            mf->res_fd = conf_mf->fd;
            ul_ctx_t *conf_ctx = conf_mf->ul_ctx;
            mf->res_tp = conf_mf->tp;
            mf->vsec_addr = conf_mf->vsec_addr;
            mf->vsec_supp = conf_mf->vsec_supp;
            mf->address_space = conf_mf->address_space;
            ctx->res_fdlock = conf_ctx->fdlock;
            ctx->res_mread4 = conf_ctx->mread4;
            ctx->res_mwrite4 = conf_ctx->mwrite4;
            ctx->res_mread4_block = conf_ctx->mread4_block;
            ctx->res_mwrite4_block = conf_ctx->mwrite4_block;
            free(conf_mf);
        }
    }

    return rc;
}



//
// PCI CONF ACCESS FUNCTIONS
//

#if CONFIG_ENABLE_PCICONF
/* PCI address space related enum*/
enum {
    PCI_CAP_PTR = 0x34, PCI_HDR_SIZE = 0x40, PCI_EXT_SPACE_ADDR = 0xff,

    PCI_CTRL_OFFSET = 0x4, // for space / semaphore / auto-increment bit
    PCI_COUNTER_OFFSET = 0x8,
    PCI_SEMAPHORE_OFFSET = 0xc,
    PCI_ADDR_OFFSET = 0x10,
    PCI_DATA_OFFSET = 0x14,

    PCI_FLAG_BIT_OFFS = 31,

    PCI_SPACE_BIT_OFFS = 0,
    PCI_SPACE_BIT_LEN = 16,

    PCI_STATUS_BIT_OFFS = 29,
    PCI_STATUS_BIT_LEN = 3,
};

/* Mellanox vendor specific enum */
enum {
    CAP_ID = 0x9, ICMD_DOMAIN = 0x1, CR_SPACE_DOMAIN = 0x2, SEMAPHORE_DOMAIN = 0xa, IFC_MAX_RETRIES = 2048
};

/* PCI operation enum(read or write)*/
enum {
    READ_OP = 0, WRITE_OP = 1,
};

#define READ4_PCI(mf, val_ptr, pci_offs, err_prefix, action_on_fail)    \
    do {                                                                \
        int rc;                                                         \
        int lock_rc;                                                    \
        ul_ctx_t *pci_ctx = mf->ul_ctx;                                 \
        lock_rc = _flock_int(pci_ctx->fdlock, LOCK_EX);                 \
        if (lock_rc) {                                                  \
            perror(err_prefix);                                         \
            action_on_fail;                                             \
        }                                                               \
        rc = pread(mf->fd, val_ptr, 4, pci_offs);                       \
        lock_rc = _flock_int(pci_ctx->fdlock, LOCK_UN);                 \
        if (lock_rc) {                                                  \
            perror(err_prefix);                                         \
            action_on_fail;                                             \
        }                                                               \
        if (rc != 4) {                                                 \
            if (rc < 0) {                                                 \
                perror(err_prefix);}                                     \
            action_on_fail;                                             \
        }                                                               \
        *val_ptr = __le32_to_cpu(*val_ptr); \
    } while (0)

#define WRITE4_PCI(mf, val, pci_offs, err_prefix, action_on_fail)           \
    do {                                                                \
        int rc;                                                         \
        int lock_rc;                                                    \
        u_int32_t val_le;                                               \
        ul_ctx_t *pci_ctx = mf->ul_ctx;                                 \
        val_le = __cpu_to_le32(val);                                    \
        lock_rc = _flock_int(pci_ctx->fdlock, LOCK_EX);                 \
        if (lock_rc) {                                                  \
            perror(err_prefix);                                         \
            action_on_fail;                                             \
        }                                                               \
        rc = pwrite(mf->fd, &val_le, 4, pci_offs);                      \
        lock_rc = _flock_int(pci_ctx->fdlock, LOCK_UN);                 \
        if (lock_rc) {                                                  \
            perror(err_prefix);                                         \
            action_on_fail;                                             \
        }                                                               \
        if (rc != 4) {                                                 \
            if (rc < 0) {                                                 \
                perror(err_prefix);}                                     \
            action_on_fail;                                         \
        }                                                               \
    } while (0)


#define WO_REG_ADDR_DATA 0xbadacce5
#define DEVID_OFFSET     0xf0014
#define PCICONF_ADDR_OFF 0x58
#define PCICONF_DATA_OFF 0x5c


int mtcr_driver_mread4(mfile *mf, unsigned int offset, u_int32_t *value)
{
    int rc = 4;
    struct mst_read4_st r4;
    memset(&r4, 0, sizeof(struct mst_read4_st));
    r4.address_space = (unsigned int)mf->address_space;
    r4.offset = offset;
    if ((ioctl(mf->fd, PCICONF_READ4, &r4)) < 0) {
        rc = -1;
    } else {
        *value = r4.data;
    }

    return rc;
}

int mtcr_driver_mwrite4(mfile *mf, unsigned int offset, u_int32_t value)
{
    int rc = 4;
    struct mst_write4_st r4;
    memset(&r4, 0, sizeof(struct mst_write4_st));

    r4.offset = offset;
    r4.data = value;
    r4.address_space = (unsigned int)mf->address_space;
    if (ioctl(mf->fd, PCICONF_WRITE4, &r4) < 0) {
        rc = -1;
    } else {
        rc = 4;
    }
    return rc;
}

static
int mst_driver_connectx_flush(mfile *mf)
{
    mtcr_driver_mwrite4(mf, mf->connectx_wa_slot, 0);
    u_int32_t value = 0x1;
    do {
        mtcr_driver_mread4(mf, mf->connectx_wa_slot, &value);
    } while (value);
    return 0;
}

int mtcr_driver_cr_mread4(mfile *mf, unsigned int offset, u_int32_t *value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    if (ctx->need_flush) {
        if (mst_driver_connectx_flush(mf)) {
            return 0;
        }
        ctx->need_flush = 0;
    }
    return mtcr_driver_mread4(mf, offset, value);
}

int mtcr_driver_cr_mwrite4(mfile *mf, unsigned int offset, u_int32_t value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    if (mtcr_driver_mwrite4(mf, offset, value) != 4) {
        return 0;
    }
    ctx->need_flush = ctx->connectx_flush;
    return 4;
}

static int driver_mread_chunk_as_multi_mread4(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    int i;
    for (i = 0; i < length; i += 4) {
        u_int32_t value = 0x0;

        if (mread4(mf, offset + i, &value) != 4) {
            return -1;
        }
        memcpy(data + i / 4, &value, 4);
    }
    return length;
}

static int driver_mwrite_chunk_as_multi_mwrite4(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    int i;
    if (length % 4) {
        return EINVAL;
    }
    for (i = 0; i < length; i += 4) {
        u_int32_t value;
        memcpy(&value, data + i / 4, 4);
        if (mwrite4(mf, offset + i, value) != 4) {
            return -1;
        }
    }
    return length;
}

static int driver_mwrite4_block(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    if (mf->tp == MST_PCICONF && mf->vsec_supp) {
        int left_size = 0;
        u_int32_t *dest_ptr = data;
        for (left_size = length; left_size > 0; left_size -= PCICONF_MAX_BUFFER_SIZE) {
            int towrite;
            towrite = (left_size >= PCICONF_MAX_BUFFER_SIZE) ? PCICONF_MAX_BUFFER_SIZE : left_size;
            struct mst_write4_buffer_st write4_buf;
            memset(&write4_buf, 0, sizeof(write4_buf));
            write4_buf.address_space = (unsigned int)mf->address_space;
            write4_buf.offset = offset;
            write4_buf.size = towrite;
            memcpy(write4_buf.data, dest_ptr, towrite);
            int ret = ioctl(mf->fd, PCICONF_WRITE4_BUFFER, &write4_buf);
            if (ret < 0) {
                return -1;
            }
            offset += towrite;
            dest_ptr += towrite / sizeof(u_int32_t);
        }
        return length;
    } else {

        return driver_mwrite_chunk_as_multi_mwrite4(mf, offset, data, length);
    }
}

static int driver_mread4_block(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    if (mf->tp == MST_PCICONF && mf->vsec_supp) {
        int left_size = 0;
        u_int32_t *dest_ptr = data;
        for (left_size = length; left_size > 0; left_size -= PCICONF_MAX_BUFFER_SIZE) {
            int toread = (left_size >= PCICONF_MAX_BUFFER_SIZE) ? PCICONF_MAX_BUFFER_SIZE : left_size;

            struct mst_read4_buffer_st read4_buf;
            memset(&read4_buf, 0, sizeof(read4_buf));
            read4_buf.address_space = (unsigned int)mf->address_space;
            read4_buf.offset = offset;
            read4_buf.size = toread;

            // We support backward compatibility.
            // There is a known bug with PCICONF_READ4_BUFFER ioctl and data may be corrupted.
            int ret;
            if ((ret = ioctl(mf->fd, PCICONF_READ4_BUFFER_EX, &read4_buf)) < 0) {
                if ((ret = ioctl(mf->fd, PCICONF_READ4_BUFFER, &read4_buf)) < 0) {
                    if ((ret = ioctl(mf->fd, PCICONF_READ4_BUFFER_BC, &read4_buf)) < 0) {
                        return -1;
                    }
                }
            }
            memcpy(dest_ptr, read4_buf.data, toread);
            offset += toread;
            dest_ptr += toread / sizeof(u_int32_t);
        }
        return length;
    } else {
        return driver_mread_chunk_as_multi_mread4(mf, offset, data, length);
    }
}

static
int mtcr_driver_mclose(mfile *mf)
{
    if (mf) {
        if (mf->bar_virtual_addr) {
            munmap(mf->bar_virtual_addr, MTCR_MAP_SIZE);
        }
        if (mf->fd > 0) {
            close(mf->fd);
        }
        if (mf->res_fd > 0) {
            close(mf->res_fd);
        }
    }
    return 0;
}

static
int mtcr_driver_open(mfile *mf, MType dev_type,
                     unsigned domain_p, unsigned bus_p, unsigned dev_p, unsigned func_p)
{
    int rc = 0;
    ul_ctx_t *ctx = mf->ul_ctx;
    char driver_cr_name[40];
    char driver_conf_name[40];

    sprintf(driver_cr_name, DRIVER_CR_NAME, domain_p, bus_p, dev_p, func_p);
    sprintf(driver_conf_name, DRIVER_CONF_NAME, domain_p, bus_p, dev_p, func_p);


    int cr_valid = 0;
    ctx->connectx_flush = 0;
    ctx->need_flush     = 0;
    ctx->via_driver     = 1;
    if (dev_type == MST_DRIVER_CR) {
        mf->fd = open(driver_cr_name, O_RDWR | O_SYNC);
        //Failed to open cr, go to conf
        if (mf->fd < 0) {
            goto end;
        }
        mf->tp             = MST_PCI;
        ctx->mread4        = mtcr_driver_cr_mread4;
        ctx->mwrite4       = mtcr_driver_cr_mwrite4;
        ctx->mread4_block  = driver_mread4_block;
        ctx->mwrite4_block = driver_mwrite4_block;
        ctx->mclose        = mtcr_driver_mclose;
        mf->bar_virtual_addr            = NULL;
        unsigned int slot_num;
        rc = ioctl(mf->fd, PCI_CONNECTX_WA, &slot_num);
        if (rc < 0) {
            goto end;
        }

        mf->connectx_wa_slot = CONNECTX_WA_BASE + 4 * slot_num;
        cr_valid = 1;
        rc = mtcr_check_signature(mf);

        init_dev_info_ul(mf, driver_cr_name, domain_p, bus_p, dev_p, func_p);
    }

end:
    if (rc) {
        mtcr_driver_mclose(mf);
    } else if (cr_valid) {
        mf->res_fd = open(driver_conf_name, O_RDWR | O_SYNC);
        if (mf->res_fd < 0) {
            return -1;
        }
        mf->res_tp             = MST_PCICONF;
        ctx->res_mread4        = mtcr_driver_mread4;
        ctx->res_mwrite4       = mtcr_driver_mwrite4;
        ctx->res_mread4_block  = driver_mread_chunk_as_multi_mread4;
        ctx->res_mwrite4_block = driver_mwrite_chunk_as_multi_mwrite4;
    }

    if (!cr_valid) {
        rc = 0;
        mf->fd = open(driver_conf_name, O_RDWR | O_SYNC);
        if (mf->fd < 0) {
            return -1;
        }
        struct mst_params dev_params;
        memset(&dev_params, 0, sizeof(dev_params));
        if (ioctl(mf->fd, MST_PARAMS, &dev_params) < 0) {
            fprintf(stderr, "-E- Failed to get Device PARAMS!\n");
            return -1;
        }
        mf->vsec_supp = (int)dev_params.vendor_specific_cap;
        if (dev_params.vendor_specific_cap) {
            mf->address_space = CR_SPACE_DOMAIN;
            mf->vsec_cap_mask |= ((1 << VCC_INITIALIZED) | (1 << VCC_SEMAPHORE_SPACE_SUPPORTED) |
                                  (1 << VCC_CRSPACE_SPACE_SUPPORTED) | (1 << VCC_ICMD_SPACE_SUPPORTED) |
                                  (1 << VCC_ICMD_EXT_SPACE_SUPPORTED));
        }
        mf->tp = MST_PCICONF;
        ctx->mread4        = mtcr_driver_mread4;
        ctx->mwrite4       = mtcr_driver_mwrite4;
        ctx->mread4_block  = driver_mread4_block;
        ctx->mwrite4_block = driver_mwrite4_block;
        ctx->mclose        = mtcr_driver_mclose;
        init_dev_info_ul(mf, driver_conf_name, domain_p, bus_p, dev_p, func_p);
    }

    return rc;
}


static int is_wo_pciconf_gw(mfile *mf)
{
    unsigned offset = DEVID_OFFSET;
    u_int32_t data = 0;
    int rc = pwrite(mf->fd, &offset, 4, PCICONF_ADDR_OFF);
    if (rc < 0) {
        return 0;
    }
    rc = pread(mf->fd, &data, 4, PCICONF_ADDR_OFF);
    if (rc < 0) {
        return 0;
    }
    if (data == WO_REG_ADDR_DATA) {
        return 1;
    }
    return 0;
}

int pci_find_capability(mfile *mf, int cap_id)
{
    unsigned offset;
    unsigned char visited[256] = { }; /* Prevent infinite loops */
    unsigned char data[2];
    int ret;
    int lock_ret;
    ul_ctx_t *pci_ctx = mf->ul_ctx;

    // protect against parallel access
    lock_ret = _flock_int(pci_ctx->fdlock, LOCK_EX);
    if (lock_ret) {
        return 0;
    }

    ret = pread(mf->fd, data, 1, PCI_CAP_PTR);

    lock_ret = _flock_int(pci_ctx->fdlock, LOCK_UN);
    if (lock_ret) {
        return 0;
    }

    if (ret != 1) {
        return 0;
    }

    offset = data[0];
    while (1) {
        if (offset < PCI_HDR_SIZE || offset > PCI_EXT_SPACE_ADDR) {
            return 0;
        }

        lock_ret = _flock_int(pci_ctx->fdlock, LOCK_EX);
        if (lock_ret) {
            return 0;
        }

        ret = pread(mf->fd, data, sizeof data, offset);

        lock_ret = _flock_int(pci_ctx->fdlock, LOCK_UN);
        if (lock_ret) {
            return 0;
        }

        if (ret != sizeof data) {
            return 0;
        }

        visited[offset] = 1;

        if (data[0] == cap_id) {
            return offset;
        }

        offset = data[1];

        if (offset > PCI_EXT_SPACE_ADDR) {
            return 0;
        }
        if (visited[offset]) {
            return 0;
        }
    }
    return 0;
}

int mtcr_pciconf_cap9_sem(mfile *mf, int state)
{
    u_int32_t lock_val;
    u_int32_t counter = 0;
    int retries = 0;
    if (!state) { // unlock
        WRITE4_PCI(mf, 0, mf->vsec_addr + PCI_SEMAPHORE_OFFSET, "unlock semaphore", return ME_PCI_WRITE_ERROR);
    } else { // lock
        do {
            if (retries > IFC_MAX_RETRIES) {
                return ME_SEM_LOCKED;
            }
            // read semaphore untill 0x0
            READ4_PCI(mf, &lock_val, mf->vsec_addr + PCI_SEMAPHORE_OFFSET, "read counter", return ME_PCI_READ_ERROR);
            if (lock_val) { //semaphore is taken
                retries++;
                msleep(1); // wait for current op to end
                continue;
            }
            //read ticket
            READ4_PCI(mf, &counter, mf->vsec_addr + PCI_COUNTER_OFFSET, "read counter", return ME_PCI_READ_ERROR);
            //write ticket to semaphore dword
            WRITE4_PCI(mf, counter, mf->vsec_addr + PCI_SEMAPHORE_OFFSET, "write counter to semaphore",
                       return ME_PCI_WRITE_ERROR);
            // read back semaphore make sure ticket == semaphore else repeat
            READ4_PCI(mf, &lock_val, mf->vsec_addr + PCI_SEMAPHORE_OFFSET, "read counter", return ME_PCI_READ_ERROR);
            retries++;
        } while (counter != lock_val);
    }
    return ME_OK;
}

int mtcr_pciconf_wait_on_flag(mfile *mf, u_int8_t expected_val)
{
    int retries = 0;
    u_int32_t flag;
    do {
        if (retries > IFC_MAX_RETRIES) {
            return ME_PCI_IFC_TOUT;
        }
        READ4_PCI(mf, &flag, mf->vsec_addr + PCI_ADDR_OFFSET, "read flag", return ME_PCI_READ_ERROR);
        flag = EXTRACT(flag, PCI_FLAG_BIT_OFFS, 1);
        retries++;
        if ((retries & 0xf) == 0) { // dont sleep always
            msleep(1);
        }
    } while (flag != expected_val);
    return ME_OK;
}

int mtcr_pciconf_set_addr_space(mfile *mf, u_int16_t space)
{
    // read modify write
    u_int32_t val;
    READ4_PCI(mf, &val, mf->vsec_addr + PCI_CTRL_OFFSET, "read domain", return ME_PCI_READ_ERROR);
    val = MERGE(val, space, PCI_SPACE_BIT_OFFS, PCI_SPACE_BIT_LEN);
    WRITE4_PCI(mf, val, mf->vsec_addr + PCI_CTRL_OFFSET, "write domain", return ME_PCI_WRITE_ERROR);
    // read status and make sure space is supported
    READ4_PCI(mf, &val, mf->vsec_addr + PCI_CTRL_OFFSET, "read status", return ME_PCI_READ_ERROR);
    if (EXTRACT(val, PCI_STATUS_BIT_OFFS, PCI_STATUS_BIT_LEN) == 0) {
        return ME_PCI_SPACE_NOT_SUPPORTED;
    }
    return ME_OK;
}

int mtcr_pciconf_rw(mfile *mf, unsigned int offset, u_int32_t *data, int rw)
{
    int rc = ME_OK;
    u_int32_t address = offset;

    //last 2 bits must be zero as we only allow 30 bits addresses
    if (EXTRACT(address, 30, 2)) {
        if (errno == EEXIST) {  // not sure if it's OK to change errno in all cases
            errno = EINVAL;
        }
        return ME_BAD_PARAMS;
    }

    address = MERGE(address, (rw ? 1 : 0), PCI_FLAG_BIT_OFFS, 1);
    if (rw == WRITE_OP) {
        // write data
        WRITE4_PCI(mf, *data, mf->vsec_addr + PCI_DATA_OFFSET, "write value", return ME_PCI_WRITE_ERROR);
        // write address
        WRITE4_PCI(mf, address, mf->vsec_addr + PCI_ADDR_OFFSET, "write offset", return ME_PCI_WRITE_ERROR);
        // wait on flag
        rc = mtcr_pciconf_wait_on_flag(mf, 0);
    } else {
        // write address
        WRITE4_PCI(mf, address, mf->vsec_addr + PCI_ADDR_OFFSET, "write offset", return ME_PCI_WRITE_ERROR);
        // wait on flag
        rc = mtcr_pciconf_wait_on_flag(mf, 1);
        // read data
        READ4_PCI(mf, data, mf->vsec_addr + PCI_DATA_OFFSET, "read value", return ME_PCI_READ_ERROR);
    }
    return rc;
}

int mtcr_pciconf_send_pci_cmd_int(mfile *mf, int space, unsigned int offset, u_int32_t *data, int rw)
{
    int rc = ME_OK;

    // take semaphore
    rc = mtcr_pciconf_cap9_sem(mf, 1);
    if (rc) {
        return rc;
    }

    // set address space
    rc = mtcr_pciconf_set_addr_space(mf, space);
    if (rc) {
        goto cleanup;
    }

    // read/write the data
    rc = mtcr_pciconf_rw(mf, offset, data, rw);
cleanup:
    // clear semaphore
    mtcr_pciconf_cap9_sem(mf, 0);
    return rc;
}

// adrianc: no need to lock the file semaphore if we access cr-space through mellanox vendor specific cap
int mtcr_pciconf_mread4(mfile *mf, unsigned int offset, u_int32_t *value)
{
    int rc;
    rc = mtcr_pciconf_send_pci_cmd_int(mf, mf->address_space, offset, value, READ_OP);
    if (rc) {
        return -1;
    }
    return 4;
}
int mtcr_pciconf_mwrite4(mfile *mf, unsigned int offset, u_int32_t value)
{
    int rc;
    rc = mtcr_pciconf_send_pci_cmd_int(mf, mf->address_space, offset, &value, WRITE_OP);
    if (rc) {
        return -1;
    }
    return 4;
}

static int block_op_pciconf(mfile *mf, unsigned int offset, u_int32_t *data, int length, int rw)
{
    int i;
    int rc = ME_OK;
    int wrote_or_read = length;
    if (length % 4) {
        return -1;
    }
    // lock semaphore and set address space
    rc = mtcr_pciconf_cap9_sem(mf, 1);
    if (rc) {
        return -1;
    }
    // set address space
    rc = mtcr_pciconf_set_addr_space(mf, mf->address_space);
    if (rc) {
        wrote_or_read = -1;
        goto cleanup;
    }

    for (i = 0; i < length; i += 4) {
        if (mtcr_pciconf_rw(mf, offset + i, &(data[(i >> 2)]), rw)) {
            wrote_or_read = i;
            goto cleanup;
        }
    }
cleanup: mtcr_pciconf_cap9_sem(mf, 0);
    return wrote_or_read;
}

static int mread4_block_pciconf(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    return block_op_pciconf(mf, offset, data, length, READ_OP);
}

static int mwrite4_block_pciconf(mfile *mf, unsigned int offset, u_int32_t *data, int length)
{
    return block_op_pciconf(mf, offset, data, length, WRITE_OP);
}

int mtcr_pciconf_mread4_old(mfile *mf, unsigned int offset, u_int32_t *value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    unsigned int new_offset = offset;
    int rc;
    if (ctx->wo_addr) {
        new_offset |= 0x1;
    }
    // adrianc: PCI registers always in le32
    offset = __cpu_to_le32(new_offset);
    rc = _flock_int(ctx->fdlock, LOCK_EX);
    if (rc) {
        goto pciconf_read_cleanup;
    }
    rc = pwrite(mf->fd, &offset, 4, PCICONF_ADDR_OFF);
    if (rc < 0) {
        perror("write offset");
        goto pciconf_read_cleanup;
    }
    if (rc != 4) {
        rc = 0;
        goto pciconf_read_cleanup;
    }

    rc = pread(mf->fd, value, 4, PCICONF_DATA_OFF);
    if (rc < 0) {
        perror("read value");
        goto pciconf_read_cleanup;
    }
    *value = __le32_to_cpu(*value);;
pciconf_read_cleanup:
    if (_flock_int(ctx->fdlock, LOCK_UN)) {
        return rc;
    }
    return rc;
}

int mtcr_pciconf_mwrite4_old(mfile *mf, unsigned int offset, u_int32_t value)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    int rc;
    value = __cpu_to_le32(value);
    offset = __cpu_to_le32(offset);
    rc = _flock_int(ctx->fdlock, LOCK_EX);
    if (rc) {
        goto pciconf_write_cleanup;
    }
    if (ctx->wo_addr) {
        rc = pwrite(mf->fd, &value, 4, PCICONF_DATA_OFF);
        if (rc < 0) {
            perror("write value");
            goto pciconf_write_cleanup;
        }
        if (rc != 4) {
            rc = 0;
            goto pciconf_write_cleanup;
        }
        rc = pwrite(mf->fd, &offset, 4, PCICONF_ADDR_OFF);
        if (rc < 0) {
            perror("write offset");
            goto pciconf_write_cleanup;
        }

    } else {
        rc = pwrite(mf->fd, &offset, 4, PCICONF_ADDR_OFF);
        if (rc < 0) {
            perror("write offset");
            goto pciconf_write_cleanup;
        }
        if (rc != 4) {
            rc = 0;
            goto pciconf_write_cleanup;
        }
        rc = pwrite(mf->fd, &value, 4, PCICONF_DATA_OFF);
        if (rc < 0) {
            perror("write value");
            goto pciconf_write_cleanup;
        }
    }
pciconf_write_cleanup:
    if (_flock_int(ctx->fdlock, LOCK_UN)) {
        return rc;
    }
    return rc;
}

static
int mtcr_pciconf_mclose(mfile *mf)
{
    unsigned int word;
    if (mf) {
        // Adrianc: set address in PCI configuration space to be non-semaphore.
        int rc = mread4_ul(mf, 0xf0014, &word);
        if (mf->fd > 0) {
            close(mf->fd);
        }
        if (rc != 4) {
            return 1;
        }
    }

    return 0;
}

int space_to_cap_offset(int space)
{
    switch (space) {
    case AS_ICMD_EXT:
        return VCC_ICMD_EXT_SPACE_SUPPORTED;
    case AS_CR_SPACE:
        return VCC_CRSPACE_SPACE_SUPPORTED;
    case AS_ICMD:
        return VCC_ICMD_SPACE_SUPPORTED;
    case AS_NODNIC_INIT_SEG:
        return VCC_NODNIC_INIT_SEG_SPACE_SUPPORTED;
    case AS_EXPANSION_ROM:
        return VCC_EXPANSION_ROM_SPACE_SUPPORTED;
    case AS_ND_CRSPACE:
        return VCC_ND_CRSPACE_SPACE_SUPPORTED;
    case AS_SCAN_CRSPACE:
        return VCC_SCAN_CRSPACE_SPACE_SUPPORTED;
    case AS_SEMAPHORE:
        return VCC_SEMAPHORE_SPACE_SUPPORTED;
    case AS_MAC:
        return VCC_MAC_SPACE_SUPPORTED;
    default:
        return 0;
    }
}

// Turning on the space capability bit in vsec_cap_mask iff
// space capability supported
static
int get_space_support_status(mfile *mf, u_int16_t space)
{
    int status = mtcr_pciconf_set_addr_space(mf, space) == ME_OK ? 1 : 0;
    mf->vsec_cap_mask |= (status << space_to_cap_offset(space));
    return status;
}

static
int mtcr_pciconf_open(mfile *mf, const char *name, u_int32_t adv_opt)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    mf->fd = -1;
    mf->fd = open(name, O_RDWR | O_SYNC);
    if (mf->fd < 0) {
        return -1;
    }

    mf->tp = MST_PCICONF;

    if ((mf->vsec_addr = pci_find_capability(mf, CAP_ID))) {
        mf->vsec_supp = 1;
        // check if the needed spaces are supported
        if (adv_opt & Clear_Vsec_Semaphore) {
            mtcr_pciconf_cap9_sem(mf, 0);
        }
        if (mtcr_pciconf_cap9_sem(mf, 1)) {
            close(mf->fd);
            errno = EBUSY;
            return -1;
        }

        get_space_support_status(mf, AS_ICMD);
        get_space_support_status(mf, AS_NODNIC_INIT_SEG);
        get_space_support_status(mf, AS_EXPANSION_ROM);
        get_space_support_status(mf, AS_ND_CRSPACE);
        get_space_support_status(mf, AS_SCAN_CRSPACE);
        get_space_support_status(mf, AS_MAC);
        get_space_support_status(mf, AS_ICMD_EXT);
        get_space_support_status(mf, AS_SEMAPHORE);
        get_space_support_status(mf, AS_CR_SPACE);
        mf->vsec_cap_mask |= (1 << VCC_INITIALIZED);

        mtcr_pciconf_cap9_sem(mf, 0);
    }

    if (VSEC_SUPPORTED_UL(mf)) {
        mf->address_space = AS_CR_SPACE;
        ctx->mread4 = mtcr_pciconf_mread4;
        ctx->mwrite4 = mtcr_pciconf_mwrite4;
        ctx->mread4_block = mread4_block_pciconf;
        ctx->mwrite4_block = mwrite4_block_pciconf;
    } else {
        ctx->wo_addr = is_wo_pciconf_gw(mf);
        //printf("Write Only Address: %#x\n", ctx->wo_addr);
        ctx->mread4 = mtcr_pciconf_mread4_old;
        ctx->mwrite4 = mtcr_pciconf_mwrite4_old;
        ctx->mread4_block = mread_chunk_as_multi_mread4;
        ctx->mwrite4_block = mwrite_chunk_as_multi_mwrite4;
    }
    ctx->mclose = mtcr_pciconf_mclose;
    return 0;
}
#else
static
int mtcr_pciconf_open(mfile *mf, const char *name, u_int32_t adv_opt)
{
    return -1;
}
#endif

//
// IN-BAND ACCESS FUNCTIONS
//

static
int mtcr_inband_open(mfile *mf, const char *name)
{
#ifndef NO_INBAND
    ul_ctx_t *ctx = mf->ul_ctx;
    mf->tp = MST_IB;
    mf->flags |= MDEVS_IB;
    ctx->mread4 = mib_read4;
    ctx->mwrite4 = mib_write4;
    ctx->mread4_block = mib_readblock;
    ctx->mwrite4_block = mib_writeblock;
    ctx->maccess_reg = mib_acces_reg_mad;
    ctx->mclose = mib_close;
    char *p;
    if ((p = strstr(name, "ibdr-")) != 0 || (p = strstr(name, "iblid-")) != 0 || (p = strstr(name, "lid-")) != 0) {
        return mib_open(p, mf, 0);
    } else {
        return -1;
    }

#else
    (void) name;
    (void) mf;
    errno = ENOSYS;
    return -1;
#endif
}

static MType mtcr_parse_name(const char *name, int *force, unsigned *domain_p, unsigned *bus_p, unsigned *dev_p,
                             unsigned *func_p)
{
    unsigned my_domain = 0;
    unsigned my_bus;
    unsigned my_dev;
    unsigned my_func;
    int scnt, r;
    int force_config = 0;
    char config[] = "/config";
    char resource0[] = "/resource0";
    char procbuspci[] = "/proc/bus/pci/";
    char driver_cr_name[40];
    char driver_conf_name[40];

    unsigned len = strlen(name);
    unsigned tmp;

    if (len >= sizeof config && !strcmp(config, name + len + 1 - sizeof config)) {
        *force = 1;
        return MST_PCICONF;
    }

    if (len >= sizeof resource0 && !strcmp(resource0, name + len + 1 - sizeof resource0)) {
        *force = 1;
        return MST_PCI;
    }

    if (!strncmp(name, "/proc/bus/pci/", sizeof procbuspci - 1)) {
        *force = 1;
        return MST_PCICONF;
    }

    if (sscanf(name, "lid-%x", &tmp) == 1 || sscanf(name, "ibdr-%x", &tmp) == 1 || strstr(name, "lid-") != 0
        || strstr(name, "ibdr-") != 0) {
        *force = 1;
        return MST_IB;
    }

    if (sscanf(name, "mthca%x", &tmp) == 1 || sscanf(name, "mlx4_%x", &tmp) == 1
        || sscanf(name, "mlx5_%x", &tmp) == 1) {
        char mbuf[4048] = {0};
        char pbuf[4048] = {0};
        char *base;

        r = snprintf(mbuf, sizeof(mbuf) - 1, "/sys/class/infiniband/%s/device", name);
        if (r <= 0 || r >= (int) sizeof mbuf) {
            fprintf(stderr, "Unable to print device name %s\n", name);
            goto parse_error;
        }

        r = readlink(mbuf, pbuf, sizeof(pbuf) - 1);
        if (r < 0) {
            perror("read link");
            fprintf(stderr, "Unable to read link %s\n", mbuf);
            return MST_ERROR;
        }
        pbuf[r] = '\0';

        base = basename(pbuf);
        if (!base) {
            goto parse_error;
        }
        scnt = sscanf(base, "%x:%x:%x.%x", &my_domain, &my_bus, &my_dev, &my_func);
        if (scnt != 4) {
            goto parse_error;
        }
        if (sscanf(name, "mlx5_%x", &tmp) == 1) {
            force_config = 1;
        }
        goto name_parsed;
    }

    scnt = sscanf(name, "%x:%x.%x", &my_bus, &my_dev, &my_func);
    if (scnt == 3) {
        force_config = check_force_config(my_domain, my_bus, my_dev, my_func);
        goto name_parsed;
    }

    scnt = sscanf(name, "%x:%x:%x.%x", &my_domain, &my_bus, &my_dev, &my_func);
    if (scnt == 4) {
        force_config = check_force_config(my_domain, my_bus, my_dev, my_func);
        goto name_parsed;
    }

    scnt = sscanf(name, "pciconf-%x:%x.%x", &my_bus, &my_dev, &my_func);
    if (scnt == 3) {
        force_config = 1;
        goto name_parsed;
    }

    scnt = sscanf(name, "pciconf-%x:%x:%x.%x", &my_domain, &my_bus, &my_dev, &my_func);
    if (scnt == 4) {
        force_config = 1;
        goto name_parsed;
    }

parse_error:
    fprintf(stderr, "Unable to parse device name %s\n", name);
    errno = EINVAL;
    return MST_ERROR;

name_parsed:
    *domain_p = my_domain;
    *bus_p = my_bus;
    *dev_p = my_dev;
    *func_p = my_func;
    *force = 0;

    sprintf(driver_conf_name, DRIVER_CONF_NAME, my_domain, my_bus, my_dev, my_func);
    sprintf(driver_cr_name,   DRIVER_CR_NAME,   my_domain, my_bus, my_dev, my_func);

    if (access(driver_cr_name, F_OK) != -1) {
        return MST_DRIVER_CR;
    }

    if (access(driver_conf_name, F_OK) != -1) {
        return MST_DRIVER_CONF;
    }


#ifdef __aarch64__
    // on ARM processors MMAP not supported
    (void)force_config;
    return MST_PCICONF;
#else
    if (force_config) {
        return MST_PCICONF;
    }
    return MST_PCI;
#endif
}
#endif

int mread4_block_ul(mfile *mf, unsigned int offset, u_int32_t *data, int byte_len)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    return ctx->mread4_block(mf, offset, data, byte_len);
}

int mwrite4_block_ul(mfile *mf, unsigned int offset, u_int32_t *data, int byte_len)
{
    ul_ctx_t *ctx = mf->ul_ctx;
    return ctx->mwrite4_block(mf, offset, data, byte_len);
}

int msw_reset_ul(mfile *mf)
{
#ifndef NO_INBAND
    switch (mf->tp) {
    case MST_IB:
        return mib_swreset(mf);

    default:
        errno = EPERM;
        return -1;
    }
#else
    (void)mf;
    return -1;
#endif
}

int mhca_reset_ul(mfile *mf)
{
    (void) mf;
    errno = ENOTSUP;
    return -1;
}

static long supported_dev_ids[] = {
    /* CX2 */
    0x6340, 0x634a, 0x6368, 0x6372,
    0x6732, 0x673c, 0x6750, 0x675a,
    0x676e, 0x6746, 0x6764,
    /*****/
    0x1003,     //Connect-X3
    0x1007,     //Connect-X3Pro
    0x1011,     //Connect-IB
    0x1013,     //Connect-X4
    0x1015,     //Connect-X4Lx
    0x1017,     //Connect-X5
    0x1019,     //Connect-X5Ex
    0x101b,     //Connect-X6
    0x101d,     //Connect-X6DX
    0x101f,     //Connect-X6LX
    0x1021,     //Connect-X7
    0xc738,     //SwitchX
    0xcb20,     //Switch-IB
    0xcb84,     //Spectrum
    0xcf08,     //Switch-IB2
    0xd2f0,     //Quantum
    0xd2f2,     //Quantum2
    0xcf6c,     //Spectrum2
    0xa2d2,     //MT416842 Family BlueField integrated ConnectX-5 network controller
    0xa2d6,     //MT42822 Family BlueField2 integrated ConnectX-6DX network controller
    0xa2dc,     //MT43244 Family BlueField3 integrated ConnectX-7 network controller
    0xcf70,     //Spectrum3
    0xcf80,     //Spectrum4
    0x1976,     //Schrodinger
    -1
};

static long live_fish_id_database[] = {
    0x191,
    0x246,
    0x249,
    0x24b,
    0x24d,
    0x24e,
    0x1F6,
    0x1F8,
    0x1FF,
    0x247,
    0x209,
    0x20b,
    0x20d,
    0x20f,
    0x211,
    0x214, //BlueField2
    0x212, //Connect-X6DX
    0x216, //Connect-X6LX
    0x218, //Connect-X7
    0x21C, //BlueField3
    0x250, //Spectrum3
    0x254, //Spectrum4
    0x257, //Quantum2
    -1
};

int is_supported_devid(long devid)
{
    int i = 0;
    while (supported_dev_ids[i] != -1) {
        if (devid == supported_dev_ids[i]) {
            return 1;
        }
        i++;
    }
    i = 0;
    while (live_fish_id_database[i] != -1) {
        if (devid == live_fish_id_database[i]) {
            return 1;
        }
        i++;
    }
    return 0;
}

int is_supported_device(char *devname)
{

    char fname[64] = {0};
    char inbuf[64] = {0};
    FILE *f;
    int ret_val = 0;
    snprintf(fname, sizeof(fname) - 1, "/sys/bus/pci/devices/%s/device", devname);
    f = fopen(fname, "r");
    if (f == NULL) {
        //printf("-D- Could not open file: %s\n", fname);
        return 1;
    }
    if (fgets(inbuf, sizeof(inbuf), f)) {
        long devid = strtol(inbuf, NULL, 0);
        ret_val = is_supported_devid(devid);
    }
    fclose(f);
    return ret_val;
}

int mdevices_ul(char *buf, int len, int mask)
{
    return mdevices_v_ul(buf, len, mask, 0);
}

int mdevices_v_ul(char *buf, int len, int mask, int verbosity)
{

#define MDEVS_TAVOR_CR  0x20
#define MLNX_PCI_VENDOR_ID  0x15b3

    FILE *f;
    DIR *d;
    struct dirent *dir;
    int pos = 0;
    int sz;
    int rsz;
    int ndevs = 0;

    if (!(mask & MDEVS_TAVOR_CR)) {
        return 0;
    }

    char inbuf[64] = {0};
    char fname[64] = {0};

    d = opendir("/sys/bus/pci/devices");
    if (d == NULL) {
        return -2;
    }

    while ((dir = readdir(d)) != NULL) {
        if (dir->d_name[0] == '.') {
            continue;
        }
        sz = strlen(dir->d_name);
        if (sz > 2 && strcmp(dir->d_name + sz - 2, ".0") && !verbosity) {
            continue;
        } else if (sz > 4 && strcmp(dir->d_name + sz - 4, "00.0") && !verbosity) {
            // Skip virtual functions
            char physfn[64] = {0};
            DIR *physfndir;
            snprintf(physfn, sizeof(physfn) - 1, "/sys/bus/pci/devices/%.34s/physfn", dir->d_name);
            if ((physfndir = opendir(physfn)) != NULL) {
                closedir(physfndir);
                continue;
            }
        }
        snprintf(fname, sizeof(fname) - 1, "/sys/bus/pci/devices/%.34s/vendor", dir->d_name);
        f = fopen(fname, "r");
        if (f == NULL) {
            ndevs = -2;
            goto cleanup_dir_opened;
        }
        if (fgets(inbuf, sizeof(inbuf), f)) {
            long venid = strtoul(inbuf, NULL, 0);
            if (venid == MLNX_PCI_VENDOR_ID && is_supported_device(dir->d_name)) {
                rsz = sz + 1; //dev name size + place for Null char
                if ((pos + rsz) > len) {
                    ndevs = -1;
                    goto cleanup_file_opened;
                }
                memcpy(&buf[pos], dir->d_name, rsz);
                pos += rsz;
                ndevs++;
            }
        }
        fclose(f);
    }
    closedir(d);

    return ndevs;

cleanup_file_opened:
    fclose(f);
cleanup_dir_opened:
    closedir(d);
    return ndevs;
}

static
int read_pci_config_header(u_int16_t domain, u_int8_t bus, u_int8_t dev, u_int8_t func, u_int8_t data[0x40])
{
    char proc_dev[64];
    sprintf(proc_dev, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/config", domain, bus, dev, func);
    FILE *f = fopen(proc_dev, "r");
    if (!f) {
        //fprintf(stderr, "Failed to open (%s) for reading: %s\n", proc_dev, strerror(errno));
        return 1;
    }
    setvbuf(f, NULL, _IONBF, 0);
    if (fread(data, 0x40, 1, f) != 1) {
        fprintf(stderr, "Failed to read from (%s): %s\n", proc_dev, strerror(errno));
        fclose(f);
        return 1;
    }

    fclose(f);
    return 0;
}

int check_force_config(unsigned my_domain, unsigned my_bus, unsigned my_dev, unsigned my_func)
{
    u_int8_t conf_header[0x40];
    u_int32_t *conf_header_32p = (u_int32_t*) conf_header;
    if (read_pci_config_header(my_domain, my_bus, my_dev, my_func, conf_header)) {
        return 0;
    }
    u_int32_t devid = __le32_to_cpu(conf_header_32p[0]) >> 16;
    if (devid == CX3PRO_SW_ID || devid == CX3_SW_ID) {
        return 0;
    }
    return 1;
}
#define IB_INF "infiniband:"
#define ETH_INF "net:"

static char** get_ib_net_devs(int domain, int bus, int dev, int func, int ib_eth_)
{
    char **ib_net_devs = NULL;
    int i;
    int count = 0;
    int plan_b = 0;
    DIR           *dir;
    struct dirent *dirent;
    char **ib_net_devs_r;
    char sysfs_path[256];

    if (ib_eth_) {
        sprintf(sysfs_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%x/infiniband", domain, bus, dev, func);
    } else {
        sprintf(sysfs_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%x/net", domain, bus, dev, func);
    }

    if ((dir = opendir(sysfs_path)) == NULL) {
        sprintf(sysfs_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%x", domain, bus, dev, func);
        if ((dir = opendir(sysfs_path)) == NULL) {
            return NULL;
        }
        plan_b = 1;
    }
    while ((dirent = readdir(dir)) != NULL) {
        char *name = dirent->d_name;
        if (!strcmp(name, ".") || !strcmp(name, "..")) {
            continue;
        }
        if (plan_b) {
            char *p = NULL;
            char *devs_inf = ib_eth_ ? IB_INF : ETH_INF;
            p = strstr(name, devs_inf);
            if (!p) {
                continue;
            }
            name = p + strlen(devs_inf);
        }
        count++;
        ib_net_devs_r = (char**)realloc(ib_net_devs, (count + 1) * sizeof(char*));
        if (!ib_net_devs_r) {
            closedir(dir);
            goto mem_error;
        }
        ib_net_devs = ib_net_devs_r;

        ib_net_devs[count - 1] = (char*)malloc(strlen(name) + 1);
        if (!ib_net_devs[count - 1]) {
            closedir(dir);
            goto mem_error;
        }
        strcpy(ib_net_devs[count - 1], name);

        // Set last entry to NULL
        ib_net_devs[count] = NULL;
    }
    closedir(dir);
    return ib_net_devs;

mem_error:
    fprintf(stderr, "Memory allocation failure for ib/net devices\n");
    if (ib_net_devs) {
        for (i = 0; i < count; i++) {
            if (ib_net_devs[i]) {
                free(ib_net_devs[i]);
            }
        }

        free(ib_net_devs);
    }

    return NULL;
}

static int get_vf_devs(int domain, int bus, int dev, int func, char *buf,
                       int len)
{
    int count = 0;
    DIR *physfndir;
    struct dirent *_dirent;
    char sysfs_path[256];
    int pos = 0;

    sprintf(sysfs_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%x",
            domain, bus, dev, func);
    if ((physfndir = opendir(sysfs_path)) == NULL) {
        return count;
    }
    while ((_dirent = readdir(physfndir)) != NULL) {
        char *name = _dirent->d_name;
        int sz = 0;
        if (strstr(name, "virtfn") != name) {
            continue;
        }
        sz = strlen(name) + 1;
        if ((pos + sz) > len) {
            count = -1;
            closedir(physfndir);
            return count;
        }
        memcpy(&buf[pos], name, sz);
        pos += sz;
        count++;
    }
    closedir(physfndir);
    return count;
}

#define VIRTFN_LINK_NAME_SIZE 128
#define VIRTFN_PATH_SIZE 128
static void read_vf_info(vf_info *virtfn_info, u_int16_t domain, u_int8_t bus,
                         u_int8_t dev, u_int8_t func, char *virtfn)
{
    char linkname[VIRTFN_LINK_NAME_SIZE];
    char virtfn_path[VIRTFN_PATH_SIZE];
    int link_size;
    MType dev_type;
    unsigned vf_domain = 0;
    unsigned vf_bus = 0;
    unsigned vf_dev = 0;
    unsigned vf_func = 0;
    int force;

    sprintf(virtfn_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%x/%s",
            domain, bus, dev, func, virtfn);


    link_size = readlink(virtfn_path, linkname, VIRTFN_LINK_NAME_SIZE - 1);
    if (link_size < 0) {
        return;
    }
    linkname[link_size] = '\0';
    strncpy(virtfn_info->dev_name, basename(linkname),
            sizeof(virtfn_info->dev_name) - 1);

    dev_type = mtcr_parse_name(virtfn_info->dev_name, &force, &vf_domain,
                               &vf_bus, &vf_dev, &vf_func);
    (void)dev_type;

    virtfn_info->domain = vf_domain;
    virtfn_info->bus = vf_bus;
    virtfn_info->dev = vf_dev;
    virtfn_info->func = vf_func;

    virtfn_info->ib_devs = get_ib_net_devs(vf_domain, vf_bus, vf_dev, vf_func, 1);
    virtfn_info->net_devs = get_ib_net_devs(vf_domain, vf_bus, vf_dev, vf_func, 0);
}

vf_info* get_vf_info(u_int16_t domain, u_int8_t bus, u_int8_t dev,
                     u_int8_t func, u_int16_t *len)
{
    int vf_count = 0;
    char *vf_devs = NULL;
    vf_info *vf_arr = NULL;
    char *virtfn;
    int i;
    int size = 2048;

    // Get list of devices
    do {
        if (vf_devs) {
            free(vf_devs);
        }
        size *= 2;
        vf_devs = (char*) malloc(size);
        if (!vf_devs) {
            return NULL;
        }
        vf_count = get_vf_devs(domain, bus, dev, func, vf_devs, size);
    } while (vf_count == -1);

    if (vf_count <= 0) {
        *len = 0;
        if (vf_devs) {
            free(vf_devs);
        }
        return NULL;
    }
    *len = vf_count;
    virtfn = vf_devs;
    vf_arr = (vf_info*) malloc(sizeof(vf_info) * vf_count);
    if (!vf_arr) {
        if (vf_devs) {
            free(vf_devs);
        }
        return NULL;
    }
    memset(vf_arr, 0, sizeof(vf_info) * vf_count);

    for (i = 0; i < vf_count; i++) {
        read_vf_info(&vf_arr[i], domain, bus, dev, func, virtfn);
        virtfn += strlen(virtfn) + 1;
    }
    free(vf_devs);

    return vf_arr;
}

static void get_numa_node(u_int16_t domain, u_int8_t bus, u_int8_t dev, u_int8_t func, char *data)
{
    char numa_path[64];
    int c;
    int i = 0;
    sprintf(numa_path, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/numa_node", domain, bus, dev, func);
    FILE *f = fopen(numa_path, "rb");
    if (!f) {
        strcpy(data, "NA");
        return;
    }

    while ((c = getc(f)) != EOF && c != '\n') {
        data[i++] = c;
    }
    data[i] = '\0';
    fclose(f);
}

dev_info* mdevices_info_ul(int mask, int *len)
{
    return mdevices_info_v_ul(mask, len, 0);
}

dev_info* mdevices_info_v_ul(int mask, int *len, int verbosity)
{
    char *devs = 0;
    char *dev_name;
    int size = 2048;
    int rc;
    int i;

    // Get list of devices
    do {
        if (devs) {
            free(devs);
        }
        size *= 2;
        devs = (char*) malloc(size);
        if (!devs) {
            return NULL;
        }
        rc = mdevices_v_ul(devs, size, mask, verbosity);
    } while (rc == -1);

    if (rc <= 0) {
        *len = 0;
        if (devs) {
            free(devs);
        }
        return NULL;
    }
    // For each device read
    dev_info *dev_info_arr = (dev_info*) malloc(sizeof(dev_info) * rc);
    if (!dev_info_arr) {
        if (devs) {
            free(devs);
        }
        return NULL;
    }
    memset(dev_info_arr, 0, sizeof(dev_info) * rc);
    dev_name = devs;
    for (i = 0; i < rc; i++) {
        int domain = 0;
        int bus = 0;
        int dev = 0;
        int func = 0;

        dev_info_arr[i].ul_mode = 1;
        dev_info_arr[i].type = (Mdevs) MDEVS_TAVOR_CR;
        u_int8_t conf_header[0x40];
        u_int32_t *conf_header_32p = (u_int32_t*) conf_header;

        // update default device name
        strncpy(dev_info_arr[i].dev_name, dev_name, sizeof(dev_info_arr[i].dev_name) - 1);
        strncpy(dev_info_arr[i].pci.cr_dev, dev_name, sizeof(dev_info_arr[i].pci.cr_dev) - 1);

        // update dbdf
        if (sscanf(dev_name, "%x:%x:%x.%x", &domain, &bus, &dev, &func) != 4) {
            rc = -1;
            len = 0;
            free(dev_info_arr);
            free(devs);
            return NULL;
        }
        dev_info_arr[i].pci.domain = domain;
        dev_info_arr[i].pci.bus = bus;
        dev_info_arr[i].pci.dev = dev;
        dev_info_arr[i].pci.func = func;

        // set pci conf device
        snprintf(dev_info_arr[i].pci.conf_dev, sizeof(dev_info_arr[i].pci.conf_dev) - 1,
                 "/sys/bus/pci/devices/%04x:%02x:%02x.%x/config", domain, bus, dev, func);


        // Get attached infiniband devices
        dev_info_arr[i].pci.ib_devs  = get_ib_net_devs(domain, bus, dev, func, 1);
        dev_info_arr[i].pci.net_devs = get_ib_net_devs(domain, bus, dev, func, 0);
        get_numa_node(domain, bus, dev, func, (char*)(dev_info_arr[i].pci.numa_node));
        dev_info_arr[i].pci.virtfn_arr = get_vf_info(domain, bus, dev, func,
                                                     &(dev_info_arr[i].pci.virtfn_count));

        // read configuration space header
        if (read_pci_config_header(domain, bus, dev, func, conf_header)) {
            goto next;
        }

        dev_info_arr[i].pci.dev_id = __le32_to_cpu(conf_header_32p[0]) >> 16;
        dev_info_arr[i].pci.vend_id = __le32_to_cpu(conf_header_32p[0]) & 0xffff;
        dev_info_arr[i].pci.class_id = __le32_to_cpu(conf_header_32p[2]) >> 8;
        dev_info_arr[i].pci.subsys_id = __le32_to_cpu(conf_header_32p[11]) >> 16;
        dev_info_arr[i].pci.subsys_vend_id = __le32_to_cpu(conf_header_32p[11]) & 0xffff;



next:
        dev_name += strlen(dev_name) + 1;
    }

    free(devs);
    *len = rc;
    return dev_info_arr;
}

static void destroy_ib_net_devs(char **devs)
{
    int j;
    for (j = 0; devs[j]; j++) {
        if (devs[j]) {
            free(devs[j]);
        }
    }
    free(devs);
}

static void destroy_vf_devs(vf_info *vf_info_arr, int len)
{
    int i;
    if (vf_info_arr) {
        for (i = 0; i < len; i++) {
            if (vf_info_arr[i].ib_devs) {
                destroy_ib_net_devs(vf_info_arr[i].ib_devs);
            }
            if (vf_info_arr[i].net_devs) {
                destroy_ib_net_devs(vf_info_arr[i].net_devs);
            }
        }
        free(vf_info_arr);
    }
}

void mdevices_info_destroy_ul(dev_info *dev_info, int len)
{
    int i;
    if (dev_info) {
        for (i = 0; i < len; i++) {
            if (dev_info[i].type == MDEVS_TAVOR_CR &&
                dev_info[i].pci.ib_devs) {
                destroy_ib_net_devs(dev_info[i].pci.ib_devs);
            }
            if (dev_info[i].type == MDEVS_TAVOR_CR &&
                dev_info[i].pci.net_devs) {
                destroy_ib_net_devs(dev_info[i].pci.net_devs);
            }
            if (dev_info[i].type == MDEVS_TAVOR_CR &&
                dev_info[i].pci.virtfn_arr) {
                destroy_vf_devs(dev_info[i].pci.virtfn_arr,
                                dev_info[i].pci.virtfn_count);
            }
        }
        free(dev_info);
    }
}

/*
 * This function used to change from running on CONF to CR
 * and vice versa
 */

extern void mpci_change(mfile *mf)
{
    mf->mpci_change(mf);
    return;
}

void mpci_change_ul(mfile *mf)
{
    if (mf->res_tp == MST_PCICONF) {
        mf->res_tp = MST_PCI;
        mf->tp = MST_PCICONF;
    } else if (mf->res_tp == MST_PCI) {
        mf->res_tp = MST_PCICONF;
        mf->tp = MST_PCI;
    } else {
        return;
    }
    ul_ctx_t *ctx = mf->ul_ctx;
    /***** Switching READ WRITE FUNCS ******/
    f_mread4 tmp_mread4 = ctx->mread4;
    ctx->mread4 = ctx->res_mread4;
    ctx->res_mread4 = tmp_mread4;

    f_mwrite4 tmp_mwrite4 = ctx->mwrite4;
    ctx->mwrite4 = ctx->res_mwrite4;
    ctx->res_mwrite4 = tmp_mwrite4;

    f_mread4_block tmp_mread4_block = ctx->mread4_block;
    ctx->mread4_block = ctx->res_mread4_block;
    ctx->res_mread4_block = tmp_mread4_block;

    f_mwrite4_block tmp_mwrite4_block = ctx->mwrite4_block;
    ctx->mwrite4_block = ctx->res_mwrite4_block;
    ctx->res_mwrite4_block = tmp_mwrite4_block;

    /***** Switching FD LOCKs ******/
    int tmp_lock = ctx->res_fdlock;
    ctx->res_fdlock = ctx->fdlock;
    ctx->fdlock = tmp_lock;
    /***** Switching FDs ******/
    int fd = mf->fd;
    mf->fd = mf->res_fd;
    mf->res_fd = fd;
}

mfile* mopen_ul_int(const char *name, u_int32_t adv_opt)
{
    mfile *mf;
    off_t offset;
    unsigned domain = 0, bus = 0, dev = 0, func = 0;
    MType dev_type;
    int force;
    char rbuf[99] = "/sys/bus/pci/devices/XXXX:XX:XX.X/resource0";
    char cbuf[99] = "/sys/bus/pci/devices/XXXX:XX:XX.X/config";
    char pdbuf[99] = "/proc/bus/pci/XXXX:XX/XX.X";
    char pbuf[99] = "/proc/bus/pci/XX/XX.X";
    char pcidev[99] = "XXXX:XX:XX.X";
    int err;
    int rc;

    if (geteuid() != 0) {
        errno = EACCES;
        return NULL;
    }
    mf = (mfile*) malloc(sizeof(mfile));
    if (!mf) {
        return NULL;
    }
    memset(mf, 0, sizeof(mfile));
    mf->ul_ctx = malloc(sizeof(ul_ctx_t));
    if (!(mf->ul_ctx)) {
        goto open_failed;
    }
    memset(mf->ul_ctx, 0, sizeof(ul_ctx_t));

   // Initialize the user page list.
    mf->user_page_list.page_list = NULL;
    mf->user_page_list.page_amount = 0;

    mf->dev_name = strdup(name);
    if (!mf->dev_name) {
        goto open_failed;
    }
    mf->sock = -1; // we are not opening remotely
    mf->fd = -1;
    mf->res_fd = -1;
    mf->mpci_change = mpci_change_ul;
    dev_type = mtcr_parse_name(name, &force, &domain, &bus, &dev, &func);
    if (dev_type == MST_DRIVER_CR || dev_type == MST_DRIVER_CONF) {
        rc = mtcr_driver_open(mf, dev_type, domain, bus, dev, func);
        if (rc) {
            goto open_failed;
        }
        return mf;
    }
    if (dev_type == MST_ERROR) {
        goto open_failed;
    }
    mf->tp = dev_type;
    mf->flags = MDEVS_TAVOR_CR;
    if (dev_type == MST_PCICONF || dev_type == MST_PCI) {
        // allocate lock to sync between parallel cr space requests (CONF/MEMORY only)
        if (force) {
            // need to extract the dbdf format from the full name
            if (_extract_dbdf_from_full_name(name, &domain, &bus, &dev, &func)) {
                goto open_failed;
            }
        }
        if (_create_lock(mf, domain, bus, dev, func)) {
            goto open_failed;
        }

        sprintf(pcidev, "%4.4x:%2.2x:%2.2x.%1.1x", domain, bus, dev, func);
        if (!is_supported_device(pcidev)) {
            errno = ENOTSUP;
            goto open_failed;
        }

        if (init_dev_info_ul(mf, name, domain, bus, dev, func)) {
            goto open_failed;
        }
    }

    sprintf(cbuf, "/sys/bus/pci/devices/%4.4x:%2.2x:%2.2x.%1.1x/config", domain, bus, dev, func);

    if (force) {
        switch (dev_type) {
        case MST_PCICONF:
            rc = mtcr_pciconf_open(mf, name, adv_opt);
            break;

        case MST_PCI:
            rc = mtcr_pcicr_open(mf, name, cbuf, 0, 0, adv_opt);
            break;

        case MST_IB:
            rc = mtcr_inband_open(mf, name);
            break;

        default:
            goto open_failed;
        }

        if (0 == rc) {
            return mf;
        }
        goto open_failed;
    }

    if (dev_type == MST_PCICONF) {
        goto access_config_forced;
    }

    sprintf(rbuf, "/sys/bus/pci/devices/%4.4x:%2.2x:%2.2x.%1.1x/resource0", domain, bus, dev, func);

    rc = mtcr_pcicr_open(mf, rbuf, cbuf, 0, 0, adv_opt);
    if (rc == 0) {
        return mf;
    } else if (rc == 1) {
        goto access_config_forced;
    }

    /* Following access methods need the resource BAR */
    offset = mtcr_sysfs_get_offset(domain, bus, dev, func);
    if (offset == -1 && !domain) {
        offset = mtcr_procfs_get_offset(bus, dev, func);
    }
    if (offset == -1) {
        goto access_config_forced;
    }

    sprintf(pdbuf, "/proc/bus/pci/%4.4x:%2.2x/%2.2x.%1.1x", domain, bus, dev, func);
    rc = mtcr_pcicr_open(mf, pdbuf, cbuf, offset, 1, adv_opt);
    if (rc == 0) {
        return mf;
    } else if (rc == 1) {
        goto access_config_forced;
    }

    if (!domain) {
        sprintf(pbuf, "/proc/bus/pci/%2.2x/%2.2x.%1.1x", bus, dev, func);
        rc = mtcr_pcicr_open(mf, pbuf, cbuf, offset, 1, adv_opt);
        if (rc == 0) {
            return mf;
        } else if (rc == 1) {
            goto access_config_forced;
        }
    }

#if CONFIG_USE_DEV_MEM
    /* Non-portable, but helps some systems */
    if (!mtcr_pcicr_open(mf, "/dev/mem", cbuf, offset, 0, adv_opt)) {
        return mf;
    }
#endif

access_config_forced:
    sprintf(cbuf, "/sys/bus/pci/devices/%4.4x:%2.2x:%2.2x.%1.1x/config", domain, bus, dev, func);
    if (!mtcr_pciconf_open(mf, cbuf, adv_opt)) {
        return mf;
    }

    sprintf(pdbuf, "/proc/bus/pci/%4.4x:%2.2x/%2.2x.%1.1x", domain, bus, dev, func);
    if (!mtcr_pciconf_open(mf, pdbuf, adv_opt)) {
        return mf;
    }

    if (!domain) {
        sprintf(pbuf, "/proc/bus/pci/%2.2x/%2.2x.%1.1x", bus, dev, func);
        if (!mtcr_pciconf_open(mf, pdbuf, adv_opt)) {
            return mf;
        }
    }

open_failed:
    err = errno;
    mclose_ul(mf);
    errno = err;
    return NULL;
}

int init_dev_info_ul(mfile *mf, const char *dev_name, unsigned domain, unsigned bus, unsigned dev, unsigned func)
{
    int i;
    int devs_len;
    int ret = 0;
    dev_info *devs = mdevices_info_v_ul(0xffffffff, &devs_len, 1);
    for (i = 0; i < devs_len; i++) {
        if (devs[i].pci.domain == domain &&
            devs[i].pci.bus == bus &&
            devs[i].pci.dev == dev &&
            devs[i].pci.func == func) {
            break;
        }
    }

    if (i == devs_len) {
        ret = 1;
        goto cleanup;
    }

    mf->dinfo = malloc(sizeof(*mf->dinfo));
    if (!mf->dinfo) {
        errno = ENOMEM;
        ret = 2;
        goto cleanup;
    }

    memcpy(mf->dinfo, &devs[i], sizeof(*mf->dinfo));
    strncpy(mf->dinfo->dev_name, dev_name, sizeof(mf->dinfo->dev_name) / sizeof(mf->dinfo->dev_name[0]) - 1);
    if (mf->dinfo->type == MDEVS_TAVOR_CR) {
        if (devs[i].pci.ib_devs) {
            // count ib devs
            int j;
            char **curr = devs[i].pci.ib_devs;
            int cnt = 0;
            while (*(curr++)) {
                cnt++;
            }

            mf->dinfo->pci.ib_devs = malloc((cnt + 1) * sizeof(char*));
            if (!mf->dinfo->pci.ib_devs) {
                errno = ENOMEM;
                free(mf->dinfo);
                ret = 3;
                goto cleanup;
            }

            for (j = 0; j < cnt; j++) {
                mf->dinfo->pci.ib_devs[j] = malloc(strlen(devs[i].pci.ib_devs[j]) + 1);
                strcpy(mf->dinfo->pci.ib_devs[j], devs[i].pci.ib_devs[j]);
            }
            mf->dinfo->pci.ib_devs[cnt] = NULL;
        }

        if (devs[i].pci.net_devs) {
            // count net devs
            int j;
            char **curr = devs[i].pci.net_devs;
            int cnt = 0;
            while (*(curr++)) {
                cnt++;
            }

            mf->dinfo->pci.net_devs = malloc((cnt + 1) * sizeof(char*));
            if (!mf->dinfo->pci.net_devs) {
                errno = ENOMEM;
                if (mf->dinfo->pci.ib_devs) {
                    free(mf->dinfo->pci.ib_devs);
                }
                free(mf->dinfo);
                ret = 4;
                goto cleanup;
            }

            for (j = 0; j < cnt; j++) {
                mf->dinfo->pci.net_devs[j] = malloc(strlen(devs[i].pci.net_devs[j]) + 1);
                strcpy(mf->dinfo->pci.net_devs[j], devs[i].pci.net_devs[j]);
            }
            mf->dinfo->pci.net_devs[cnt] = NULL;
        }
    }

cleanup:
    mdevices_info_destroy_ul(devs, devs_len);
    return ret;
}


mfile* mopen_ul(const char *name)
{
    mfile *mf = mopen_ul_int(name, 0);

    return mf;
}

void free_dev_info_ul(mfile *mf)
{
    if (mf->dinfo) {
        if (mf->dinfo->pci.ib_devs) {
            char **curr = mf->dinfo->pci.ib_devs;
            while (*(curr)) {
                free(*curr);
                curr++;
            }

            free(mf->dinfo->pci.ib_devs);
        }

        if (mf->dinfo->pci.net_devs) {
            char **curr = mf->dinfo->pci.net_devs;
            while (*(curr)) {
                free(*curr);
                curr++;
            }

            free(mf->dinfo->pci.net_devs);
        }

        free(mf->dinfo);
        mf->dinfo = NULL;
    }
}

int mclose_ul(mfile *mf)
{
    if (mf != NULL) {
        ul_ctx_t *ctx = mf->ul_ctx;
        if (ctx) {
            if (ctx->mclose != NULL) {
                // close icmd if if needed
                if (mf->icmd.icmd_opened) {
                    icmd_close(mf);
                }
                ctx->mclose(mf);
            }

            if (ctx->fdlock) {
                close(ctx->fdlock);
            }
            if (ctx->res_fdlock) {
                close(ctx->res_fdlock);
            }
            free(ctx);
        }
        if (mf->dev_name) {
            free(mf->dev_name);
        }
        if(mf->user_page_list.page_amount) {
            release_dma_pages(mf, mf->user_page_list.page_amount);
        }
        free_dev_info_ul(mf);
        free(mf);
    }
    return 0;
}

#define IBDR_MAX_NAME_SIZE 128
#define BDF_NAME_SIZE 12
#define DEV_DIR_MAX_SIZE 256
static
int get_inband_dev_from_pci(char *inband_dev, char *pci_dev)
{
    unsigned domain = 0, bus = 0, dev = 0, func = 0;
    int force = 0;
    MType dev_type;
    DIR *d;
    struct dirent *dir;
    char subdirname[DEV_DIR_MAX_SIZE] = {0};
    char linkname[DEV_DIR_MAX_SIZE] = {0};
    int found = 0;
    char dirname[] = "/sys/class/infiniband";
    dev_type = mtcr_parse_name(pci_dev, &force, &domain, &bus, &dev, &func);


    d = opendir(dirname);
    if (d == NULL) {
        errno = ENODEV;
        return -2;
    }

    while ((dir = readdir(d)) != NULL) {
        unsigned curr_domain = 0, curr_bus = 0, curr_dev = 0, curr_func = 0;
        int curr_force = 0, link_size;
        if (dir->d_name[0] == '.') {
            continue;
        }
        snprintf(subdirname, DEV_DIR_MAX_SIZE - 1, "%s/%.100s/device", dirname, dir->d_name);
        link_size = readlink(subdirname, linkname, DEV_DIR_MAX_SIZE);

        if (link_size < BDF_NAME_SIZE) {
            /*
             * Fixing coverity issue to make [link_size - BDF_NAME_SIZE] valid
             */
            continue;
        }
        dev_type = mtcr_parse_name(&linkname[link_size - BDF_NAME_SIZE], &curr_force, &curr_domain, &curr_bus,
                                   &curr_dev, &curr_func);

        if (domain == curr_domain && bus == curr_bus && dev == curr_dev && func == curr_func) {
            snprintf(inband_dev, IBDR_MAX_NAME_SIZE - 1, "ibdr-0,%.100s,1", dir->d_name);
            found = 1;
            break;
        }
    }

    closedir(d);
    (void) dev_type; // avoid compiler warrnings
    if (found) {
        return 0;
    } else {
        errno = ENODEV;
        return -1;
    }
}

static
int reopen_pci_as_inband(mfile *mf)
{
    int rc;
    char inband_dev[IBDR_MAX_NAME_SIZE] = {0};
    rc = get_inband_dev_from_pci(inband_dev, mf->dev_name);
    if (rc) {
        errno = ENODEV;
        return -1;
    }

    ((ul_ctx_t*)mf->ul_ctx)->mclose(mf);
    free(mf->dev_name);
    mf->dev_name = strdup(inband_dev);

    rc = mtcr_inband_open(mf, inband_dev);
    return rc;
}

int maccess_reg_mad_ul(mfile *mf, u_int8_t *data)
{
    int rc;

    if (mf == NULL || data == NULL) {
        return ME_BAD_PARAMS;
    }

    if (mf->tp != MST_IB) {
        // Close current device and re-open as inband
        rc = reopen_pci_as_inband(mf);
        if (rc) {
            errno = ENODEV; // for compatibility untill we change mtcr to use error code
            return ME_REG_ACCESS_UNKNOWN_ERR;
        }
    }

    return ((ul_ctx_t*)mf->ul_ctx)->maccess_reg(mf, data);
}

static void mtcr_fix_endianness(u_int32_t *buf, int len)
{
    int i;

    for (i = 0; i < (len / 4); ++i) {
        u_int32_t tmp = buf[i];
        buf[i] = __be32_to_cpu(tmp);
    }
}

int mread_buffer_ul(mfile *mf, unsigned int offset, u_int8_t *data, int byte_len)
{
    int rc;
    rc = mread4_block_ul(mf, offset, (u_int32_t*) data, byte_len);
    mtcr_fix_endianness((u_int32_t*) data, byte_len);
    return rc;

}

int mwrite_buffer_ul(mfile *mf, unsigned int offset, u_int8_t *data, int byte_len)
{
    mtcr_fix_endianness((u_int32_t*) data, byte_len);
    return mwrite4_block_ul(mf, offset, (u_int32_t*) data, byte_len);
}

/*
 * Reg Access Section
 */

#define TLV_OPERATION_SIZE 4
#define OP_TLV_SIZE        16
#define REG_TLV_HEADER_LEN 4

enum {
    MAD_CLASS_REG_ACCESS = 1,
    MAD_CLASS_A_REG_ACCESS = 0x0A,
};

u_int8_t class_to_use = MAD_CLASS_REG_ACCESS;


enum {
    TLV_END = 0, TLV_OPERATION = 1, TLV_DR = 2, TLV_REG = 3, TLV_USER_DATA = 4,
};

#define REGISTER_HEADERS_SIZE 12
#define INBAND_MAX_REG_SIZE 44
#define INBAND_MAX_REG_SIZE_CLS_A 204
#define INBAND_MAX_GMP_DWORDS_NUM 55
#define INBAND_MAX_GMP_BLOCKS 16
#define INBAND_MAX_GMP_REG_SIZE INBAND_MAX_GMP_BLOCKS * INBAND_MAX_GMP_DWORDS_NUM * 4
#define ICMD_MAX_REG_SIZE (ICMD_MAX_CMD_SIZE - REGISTER_HEADERS_SIZE)
#define FWCTX_MAX_REG_SIZE 16
#define TOOLS_HCR_MAX_REG_SIZE (TOOLS_HCR_MAX_MBOX - REGISTER_HEADERS_SIZE)

static int supports_icmd(mfile *mf);
static int supports_tools_cmdif_reg(mfile *mf);
static int init_operation_tlv(struct OperationTlv *operation_tlv, u_int16_t reg_id, u_int8_t method);
static int mreg_send_wrapper(mfile *mf, u_int8_t *data, int r_icmd_size, int w_icmd_size);
static int mreg_send_raw(mfile *mf, u_int16_t reg_id, maccess_reg_method_t method, void *reg_data, u_int32_t reg_size,
                         u_int32_t r_size_reg, u_int32_t w_size_reg, int *reg_status);

int return_by_reg_status(int reg_status)
{
    switch (reg_status) {
    case 1:
        return ME_REG_ACCESS_DEV_BUSY;

    case 2:
        return ME_REG_ACCESS_VER_NOT_SUPP;

    case 3:
        return ME_REG_ACCESS_UNKNOWN_TLV;

    case 4:
        return ME_REG_ACCESS_REG_NOT_SUPP;

    case 5:
        return ME_REG_ACCESS_CLASS_NOT_SUPP;

    case 6:
        return ME_REG_ACCESS_METHOD_NOT_SUPP;

    case 7:
        return ME_REG_ACCESS_BAD_PARAM;

    case 8:
        return ME_REG_ACCESS_RES_NOT_AVLBL;

    case 9:
        return ME_REG_ACCESS_MSG_RECPT_ACK;

    case 0x22:
        return ME_REG_ACCESS_CONF_CORRUPT;

    case 0x24:
        return ME_REG_ACCESS_LEN_TOO_SMALL;

    case 0x20:
        return ME_REG_ACCESS_BAD_CONFIG;

    case 0x21:
        return ME_REG_ACCESS_ERASE_EXEEDED;

    case 0x70:
        return ME_REG_ACCESS_INTERNAL_ERROR;

    default:
        return ME_REG_ACCESS_UNKNOWN_ERR;
    }
}

int supports_reg_access_smp(mfile* mf)
{
    return (mf->flags & (MDEVS_IB | MDEVS_FWCTX)) ||
           (!(mf->flags & MDEVS_IB) && (supports_icmd(mf) || supports_tools_cmdif_reg(mf)));
}

int maccess_reg_ul(mfile *mf,
                   u_int16_t reg_id,
                   maccess_reg_method_t reg_method,
                   void *reg_data,
                   u_int32_t reg_size,
                   u_int32_t r_size_reg,
                   u_int32_t w_size_reg,
                   int *reg_status)
{
    DBG_PRINTF("Sending Access Register:\n");
    DBG_PRINTF("Register ID: 0x%04x\n", reg_id);
    DBG_PRINTF("Register Size: %d bytes\n", reg_size);
    int rc = -1;
    class_to_use = MAD_CLASS_REG_ACCESS;
    if (mf == NULL || reg_data == NULL || reg_status == NULL || reg_size <= 0) {
        return ME_BAD_PARAMS;
    }
    // check register size
    unsigned int max_size = (unsigned int) mget_max_reg_size_ul(mf, reg_method);
    if (reg_size > (unsigned int) max_size) {
        //reg too big
        return ME_REG_ACCESS_SIZE_EXCCEEDS_LIMIT;
    }

#ifndef MST_UL
    if (mf->flags & MDEVS_MLNX_OS) {
        rc = mos_reg_access_raw(mf, reg_id, reg_method, reg_data, reg_size, reg_status);
        if (!rc) {
            return ME_OK;
        }
    }
#endif

    if (reg_size <= INBAND_MAX_REG_SIZE) {
        if (supports_reg_access_smp(mf)) {
            rc = mreg_send_raw(mf, reg_id, reg_method,
                               reg_data, reg_size, r_size_reg,
                               w_size_reg, reg_status);
        }
        if (rc == ME_OK && (*reg_status == 0)) {
            DBG_PRINTF("AccessRegister SMP Sent Successfully!\n");
            return ME_OK;
        } else {
                DBG_PRINTF("AccessRegister Class SMP Failed!\n");
                DBG_PRINTF("Mad Status: 0x%08x\n", rc);
                DBG_PRINTF("Register Status: 0x%08x\n", *reg_status);
                class_to_use = MAD_CLASS_A_REG_ACCESS;
        }
    }

    if ((reg_size <= INBAND_MAX_REG_SIZE_CLS_A) &&
        (supports_reg_access_cls_a_ul(mf, reg_method))) {
        class_to_use = MAD_CLASS_A_REG_ACCESS;
        rc = mreg_send_raw(mf, reg_id, reg_method,
                           reg_data, reg_size, r_size_reg,
                           w_size_reg, reg_status);
        if (rc == ME_OK && (*reg_status == 0)) {
            DBG_PRINTF("AccessRegister Class 0xA Sent Successfully!\n");
            return ME_OK;
        } else {
            DBG_PRINTF("AccessRegister Class 0xA Failed!\n");
            DBG_PRINTF("Mad Status: 0x%08x\n", rc);
            DBG_PRINTF("Register Status: 0x%08x\n", *reg_status);
            class_to_use = MAD_CLASS_REG_ACCESS;
        }
    }

    if (supports_reg_access_gmp_ul(mf, reg_method)) {
        rc = mib_send_gmp_access_reg_mad_ul(mf, (u_int32_t *)reg_data,
                                            reg_size, reg_id,
                                            reg_method, reg_status);
        if (rc == ME_OK && *reg_status == 0) {
            DBG_PRINTF("AccessRegisterGMP Sent Successfully!\n");
            return ME_OK;
        }
        DBG_PRINTF("AccessRegisterGMP Failed!\n");
        DBG_PRINTF("Mad Status: 0x%08x\n", rc);
        DBG_PRINTF("Register Status: 0x%08x\n", *reg_status);
    }

    // Fallback - Attempting SMP as last resort.
    if (supports_reg_access_smp(mf)) {
        class_to_use = MAD_CLASS_REG_ACCESS;
        rc = mreg_send_raw(mf, reg_id, reg_method, reg_data, reg_size, r_size_reg, w_size_reg, reg_status);
    } else {
        return ME_REG_ACCESS_NOT_SUPPORTED;
    }

    if (rc) {
        return rc;
    }
    else if (*reg_status) {
        return return_by_reg_status(*reg_status);
    }
    return ME_OK;
}


int supports_reg_access_gmp_ul(mfile *mf, maccess_reg_method_t reg_method)
{
#ifndef MST_UL
    return mib_supports_reg_access_gmp(mf, reg_method);
#else
    (void)mf;
    (void)reg_method;
    return 0;
#endif
}

int supports_reg_access_cls_a_ul(mfile *mf, maccess_reg_method_t reg_method)
{
#ifndef MST_UL
    return mib_supports_reg_access_cls_a(mf, reg_method);
#else
    (void)mf;
    (void)reg_method;
    return 0;
#endif
}


int mib_send_cls_a_access_reg_mad_ul(mfile *mf, u_int8_t *data)
{
#ifndef MST_UL
    return mib_send_cls_a_access_reg_mad(mf, data);
#else
    (void)mf;
    (void)data;
    return 0;
#endif
}


int mib_send_gmp_access_reg_mad_ul(mfile *mf, u_int32_t *data,
                                   u_int32_t reg_size, u_int32_t reg_id,
                                   maccess_reg_method_t reg_method, int *reg_status)
{
#ifndef MST_UL
    return mib_send_gmp_access_reg_mad(mf, data,
                                       reg_size, reg_id,
                                       reg_method, reg_status);
#else
    (void)mf;
    (void)data;
    (void)reg_size;
    (void)reg_id;
    (void)reg_method;
    (void)reg_status;
    return 0;
#endif
}


static int init_operation_tlv(struct OperationTlv *operation_tlv, u_int16_t reg_id, u_int8_t method)
{
    memset(operation_tlv, 0, sizeof(*operation_tlv));

    operation_tlv->Type = TLV_OPERATION;
    operation_tlv->class = class_to_use;
    operation_tlv->len = TLV_OPERATION_SIZE;
    operation_tlv->method = method;
    operation_tlv->register_id = reg_id;
    return 0;
}

///////////////////  Function that sends the register via the correct interface ///////////////////////////

static int mreg_send_wrapper(mfile *mf, u_int8_t *data, int r_icmd_size, int w_icmd_size)
{
    int rc;
    if (mf->tp == MST_IB) { //inband access
        rc = maccess_reg_mad(mf, data);
        if (rc) {
            //printf("-E- 2. Access reg mad failed with rc = %#x\n", rc);
            return ME_MAD_SEND_FAILED;
        }
    } else if (supports_icmd(mf)) {
#if defined(MST_UL) && !defined(MST_UL_ICMD)
        if (mf->vsec_supp) { // we support accessing fw via icmd space
            rc = icmd_send_command_int(mf, FLASH_REG_ACCESS, data, w_icmd_size, r_icmd_size, 0);
            if (rc) {
                return rc;
            }
        } else { // send register via inband (maccess_reg_mad will open the device as inband thus consecutives calls will go to the first if)
            rc = maccess_reg_mad(mf, data);
            if (rc) {
                //printf("-E- 2. Access reg mad failed with rc = %#x\n", rc);
                return ME_MAD_SEND_FAILED;
            }
        }
#else
        rc = icmd_send_command_int(mf, FLASH_REG_ACCESS, data, w_icmd_size, r_icmd_size, 0);
        if (rc) {
            return rc;
        }
#endif
    } else if (supports_tools_cmdif_reg(mf)) {
        rc = tools_cmdif_reg_access(mf, data, w_icmd_size, r_icmd_size);
        if (rc) {
            return rc;
        }
    } else {
        return ME_NOT_IMPLEMENTED;
    }
    return ME_OK;
}

static int mreg_send_raw(mfile *mf, u_int16_t reg_id, maccess_reg_method_t method, void *reg_data, u_int32_t reg_size,
                         u_int32_t r_size_reg, u_int32_t w_size_reg, int *reg_status)
{
    //printf("-D- reg_id = %d, reg_size = %d, r_size_reg = %d , w_size_reg = %d \n",reg_id,reg_size,r_size_reg,w_size_reg);
    int mad_rc, cmdif_size = 0;
    struct OperationTlv tlv;
    struct reg_tlv tlv_info;
    u_int8_t buffer[1024] = {0};

    init_operation_tlv(&(tlv), reg_id, method);
    // Fill Reg TLV
    memset(&tlv_info, 0, sizeof(tlv_info));
    tlv_info.Type = TLV_REG;
    tlv_info.len = (reg_size + REG_TLV_HEADER_LEN) >> 2; // length is in dwords

    // Pack the mad

    cmdif_size += OperationTlv_pack(&tlv, buffer);
    cmdif_size += reg_tlv_pack(&tlv_info, buffer + OP_TLV_SIZE);
    //put the reg itself into the buffer
    memcpy(buffer + OP_TLV_SIZE + REG_TLV_HEADER_LEN, reg_data, reg_size);
    cmdif_size += reg_size;

#ifdef _ENABLE_DEBUG_
    fprintf(stdout, "-I-Tlv's of Data Sent:\n");
    fprintf(stdout, "\tOperation Tlv\n");
    OperationTlv_dump(&tlv, stdout);
    fprintf(stdout, "\tReg Tlv\n");
    reg_tlv_dump(&tlv_info, stdout);
#endif
    // printf("-D- reg_info.len = |%d, OP_TLV: %d, REG_TLV= %d, cmdif_size = %d\n", reg_info.len, OP_TLV_SIZE, REG_RLV_HEADER_LEN, cmdif_size);
    // update r/w_size_reg with the size of op tlv and reg tlv as we need to read/write them as well
    r_size_reg += OP_TLV_SIZE + REG_TLV_HEADER_LEN;
    w_size_reg += OP_TLV_SIZE + REG_TLV_HEADER_LEN;
    //printf("-D- reg_size = %d, r_size_reg = %d , w_size_reg = %d \n",reg_size,r_size_reg,w_size_reg);

    if (class_to_use == MAD_CLASS_A_REG_ACCESS) {
        mad_rc = mib_send_cls_a_access_reg_mad_ul(mf, buffer);
    } else {
        mad_rc = mreg_send_wrapper(mf, buffer, r_size_reg, w_size_reg);
    }
    // Unpack the mad
    OperationTlv_unpack(&tlv, buffer);
    reg_tlv_unpack(&tlv_info, buffer + OP_TLV_SIZE);
    // copy register back from the buffer
    memcpy(reg_data, buffer + OP_TLV_SIZE + REG_TLV_HEADER_LEN, reg_size);

#ifdef _ENABLE_DEBUG_
    fprintf(stdout, "-I-Tlv's of Data Received:\n");
    fprintf(stdout, "\tOperation Tlv\n");
    OperationTlv_dump(&tlv, stdout);
    fprintf(stdout, "\tReg Tlv\n");
    reg_tlv_dump(&tlv_info, stdout);
#endif
    // Check the return value
    *reg_status = tlv.status;
    if (mad_rc) {
        return mad_rc;
    }
    return ME_OK;
}

// needed device HW IDs
#define CONNECTX3_PRO_HW_ID 0x1f7
#define CONNECTX2_HW_ID 0x190
#define CONNECTX3_HW_ID 0x1f5
#define SWITCHX_HW_ID 0x245
#define INFINISCALE4_HW_ID 0x1b3

#define HW_ID_ADDR 0xf0014

static int supports_icmd(mfile *mf)
{
    u_int32_t dev_id = 0;

    if (mread4_ul(mf, HW_ID_ADDR, &dev_id) != 4) { // cr might be locked and retured 0xbad0cafe but we dont care we search for device that supports icmd
        return 0;
    }
    switch (dev_id & 0xffff) { // that the hw device id
    case CONNECTX2_HW_ID:
    case CONNECTX3_HW_ID:
    case CONNECTX3_PRO_HW_ID:
    case INFINISCALE4_HW_ID:
    case SWITCHX_HW_ID:
        return 0;

    default:
        break;
    }
    return 1;
}

static int supports_tools_cmdif_reg(mfile *mf)
{
    u_int32_t dev_id = 0;
    if (mread4_ul(mf, HW_ID_ADDR, &dev_id) != 4) { // cr might be locked and retured 0xbad0cafe but we dont care we search for device that supports tools cmdif
        return 0;
    }
    switch (dev_id & 0xffff) { // that the hw device id
    case CONNECTX3_HW_ID:     //Cx3
    case CONNECTX3_PRO_HW_ID:     // Cx3-pro
        if (tools_cmdif_is_supported(mf) == ME_OK) {
            return 1;
        }
        break;

    default:
        break;
    }
    return 0;
}

int mget_max_reg_size_ul(mfile *mf, maccess_reg_method_t reg_method)
{
    if (mf->acc_reg_params.max_reg_size[reg_method]) {
        return mf->acc_reg_params.max_reg_size[reg_method];
    } else if (supports_reg_access_gmp_ul(mf, reg_method)) {
        mf->acc_reg_params.max_reg_size[reg_method] = INBAND_MAX_GMP_REG_SIZE;
    } else if (supports_reg_access_cls_a_ul(mf, reg_method)) {
        mf->acc_reg_params.max_reg_size[reg_method] = INBAND_MAX_REG_SIZE_CLS_A;
    } else if (mf->flags & MDEVS_IB) {
        mf->acc_reg_params.max_reg_size[reg_method] = INBAND_MAX_REG_SIZE;
    } else if (mf->flags & MDEVS_MLNX_OS) {
        mf->acc_reg_params.max_reg_size[reg_method] = INBAND_MAX_REG_SIZE;
    } else if (mf->flags & MDEVS_FWCTX) {
        mf->acc_reg_params.max_reg_size[reg_method] = FWCTX_MAX_REG_SIZE;
    } else if (supports_icmd(mf)) {
        mf->acc_reg_params.max_reg_size[reg_method] = ICMD_MAX_REG_SIZE;
    } else if (supports_tools_cmdif_reg(mf)) {
        mf->acc_reg_params.max_reg_size[reg_method] = TOOLS_HCR_MAX_REG_SIZE;
    }
    return mf->acc_reg_params.max_reg_size[reg_method];
}

int mclear_pci_semaphore_ul(const char *name)
{
    mfile *mf;
    int rc = ME_OK;
    mf = mopen_ul_int(name, Clear_Vsec_Semaphore);
    if (!mf) {
        return ME_ERROR;
    }
    if ((mf->tp & (MST_PCICONF | MST_PCI)) == 0) {
        rc = ME_UNSUPPORTED_ACCESS_TYPE;
    }
    mclose_ul(mf);
    return rc;
}

int mvpd_read4_ul_int(mfile *mf, unsigned int offset, u_int8_t value[4])
{
    char proc_dev[64];
    if (!(mf->dinfo)) {
        errno = EPERM;
        return -1;
    }
    if ((ul_ctx_t*)mf->ul_ctx && ((ul_ctx_t*)mf->ul_ctx)->via_driver) {
        return mst_driver_vpd_read4(mf, offset, value);
    }
    u_int16_t domain = (mf->dinfo)->pci.domain;
    u_int8_t bus = (mf->dinfo)->pci.bus;
    u_int8_t dev = (mf->dinfo)->pci.dev;
    u_int8_t func = (mf->dinfo)->pci.func;
    /**************************/
    /*
     * In case ioctl is not defined or failed.
     * Secondary way using File System
     */

    sprintf(proc_dev, "/sys/bus/pci/devices/%04x:%02x:%02x.%d/vpd", domain, bus, dev, func);
    FILE *f = fopen(proc_dev, "r");
    if (!f) {
        //fprintf(stderr, "Failed to open (%s) for reading: %s\n", proc_dev, strerror(errno));
        return errno;
    }

    setvbuf(f, NULL, _IONBF, 0);
    if (fseek(f, offset, SEEK_SET)) {
        //fprintf(stderr, "Reached End Of File: %s\n", strerror(errno));
        fclose(f);
        return errno;
    }
    if (fread(value, 1, 4, f) < 1) {
        //fprintf(stderr, "Failed to read from (%s): %s\n", proc_dev, strerror(errno));
        fclose(f);
        return errno;
    }
    fclose(f);
    return 0;
}

int mvpd_read4_ul(mfile *mf, unsigned int offset, u_int8_t value[4])
{
    if (offset % 4) {
        u_int8_t qword[8] = {0};
        int rc = 0;
        unsigned int aligned_offset = (offset / 4) * 4;
        rc = mvpd_read4_ul_int(mf, aligned_offset, qword);
        if (rc) {
            return rc;
        }
        rc = mvpd_read4_ul_int(mf, aligned_offset + 4, qword + 4);
        memcpy(value, qword + (offset % 4), 4);
        return 0;
    } else {
        return mvpd_read4_ul_int(mf, offset, value);
    }
}

/************************************
* Function: m_err2str
************************************/
const char* m_err2str(MError status)
{
    switch (status) {
    case ME_OK:
        return "ME_OK";

    case ME_ERROR:
        return "General error";

    case ME_BAD_PARAMS:
        return "ME_BAD_PARAMS";

    case ME_CR_ERROR:
        return "ME_CR_ERROR";

    case ME_NOT_IMPLEMENTED:
        return "ME_NOT_IMPLEMENTED";

    case ME_SEM_LOCKED:
        return "Semaphore locked";

    case ME_MEM_ERROR:
        return "ME_MEM_ERROR";

    case ME_UNSUPPORTED_OPERATION:
        return "ME_UNSUPPORTED_OPERATION";

    case ME_GMP_MAD_UNSUPPORTED_OPERATION:
        return "Sending GMP MAD supports only Get() method, and you are trying to send Set() method\n"
               "to a register which is not small enough to send with SMP MAD.";

    case ME_MAD_SEND_FAILED:
        return "ME_MAD_SEND_FAILED";

    case ME_UNKOWN_ACCESS_TYPE:
        return "ME_UNKOWN_ACCESS_TYPE";

    case ME_UNSUPPORTED_ACCESS_TYPE:
        return "ME_UNSUPPORTED_ACCESS_TYPE";

    case ME_UNSUPPORTED_DEVICE:
        return "ME_UNSUPPORTED_DEVICE";

    // Reg access errors
    case ME_REG_ACCESS_BAD_STATUS_ERR:
        return "ME_REG_ACCESS_BAD_STATUS_ERR";

    case ME_REG_ACCESS_BAD_METHOD:
        return "Bad method";

    case ME_REG_ACCESS_NOT_SUPPORTED:
        return "The Register access is not supported by the device";

    case ME_REG_ACCESS_DEV_BUSY:
        return "Device is busy";

    case ME_REG_ACCESS_VER_NOT_SUPP:
        return "Version not supported";

    case ME_REG_ACCESS_UNKNOWN_TLV:
        return "Unknown TLV";

    case ME_REG_ACCESS_REG_NOT_SUPP:
        return "Register not supported";

    case ME_REG_ACCESS_CLASS_NOT_SUPP:
        return "Class not supported";

    case ME_REG_ACCESS_METHOD_NOT_SUPP:
        return "Method not supported";

    case ME_REG_ACCESS_BAD_PARAM:
        return "Bad parameter";

    case ME_REG_ACCESS_RES_NOT_AVLBL:
        return "Resource unavailable";

    case ME_REG_ACCESS_MSG_RECPT_ACK:
        return "Message receipt ack";

    case ME_REG_ACCESS_UNKNOWN_ERR:
        return "Unknown register error";

    case ME_REG_ACCESS_SIZE_EXCCEEDS_LIMIT:
        return "Register is too large";

    case ME_REG_ACCESS_CONF_CORRUPT:
        return "Config Section Corrupted";

    case ME_REG_ACCESS_LEN_TOO_SMALL:
        return "The given Register length is too small for the Tlv";

    case ME_REG_ACCESS_BAD_CONFIG:
        return "The configuration is rejected";

    case ME_REG_ACCESS_ERASE_EXEEDED:
        return "The erase count exceeds its limit";

    case ME_REG_ACCESS_INTERNAL_ERROR:
        return "Firmware internal error";

    // ICMD access errors
    case ME_ICMD_STATUS_CR_FAIL:
        return "ME_ICMD_STATUS_CR_FAIL";

    case ME_ICMD_STATUS_SEMAPHORE_TO:
        return "ME_ICMD_STATUS_SEMAPHORE_TO";

    case ME_ICMD_STATUS_EXECUTE_TO:
        return "ME_ICMD_STATUS_EXECUTE_TO";

    case ME_ICMD_STATUS_IFC_BUSY:
        return "ME_ICMD_STATUS_IFC_BUSY";

    case ME_ICMD_STATUS_ICMD_NOT_READY:
        return "ME_ICMD_STATUS_ICMD_NOT_READY";

    case ME_ICMD_UNSUPPORTED_ICMD_VERSION:
        return "ME_ICMD_UNSUPPORTED_ICMD_VERSION";

    case ME_ICMD_NOT_SUPPORTED:
        return "ME_REG_ACCESS_ICMD_NOT_SUPPORTED";

    case ME_ICMD_INVALID_OPCODE:
        return "ME_ICMD_INVALID_OPCODE";

    case ME_ICMD_INVALID_CMD:
        return "ME_ICMD_INVALID_CMD";

    case ME_ICMD_OPERATIONAL_ERROR:
        return "ME_ICMD_OPERATIONAL_ERROR";

    case ME_ICMD_BAD_PARAM:
        return "ME_ICMD_BAD_PARAM";

    case ME_ICMD_BUSY:
        return "ME_ICMD_BUSY";

    case ME_ICMD_ICM_NOT_AVAIL:
        return "ME_ICMD_ICM_NOT_AVAIL";

    case ME_ICMD_WRITE_PROTECT:
        return "ME_ICMD_WRITE_PROTECT";

    case ME_ICMD_UNKNOWN_STATUS:
        return "ME_ICMD_UNKNOWN_STATUS";

    case ME_ICMD_SIZE_EXCEEDS_LIMIT:
        return "ME_ICMD_SIZE_EXCEEDS_LIMIT";

    // TOOLS HCR access errors
    case ME_CMDIF_BUSY:
        return "Tools HCR busy";

    case ME_CMDIF_TOUT:
        return "Tools HCR time out.";

    case ME_CMDIF_BAD_OP:
        return "Operation not supported";

    case ME_CMDIF_NOT_SUPP:
        return "Tools HCR not supported";

    case ME_CMDIF_BAD_SYS:
        return "bad system status (driver may be down or Fw does not support this operation)";

    case ME_CMDIF_UNKN_TLV:
        return "Unknown TLV";

    case ME_CMDIF_RES_STATE:
        return "Bad reset state";

    case ME_CMDIF_UNKN_STATUS:
        return "Unknown status";

    // MAD IFC errors
    case ME_MAD_BUSY:
        return "Temporarily busy. MAD discarded. This is not an error";

    case ME_MAD_REDIRECT:
        return "Redirection. This is not an error";

    case ME_MAD_BAD_VER:
        return "Bad version";

    case ME_MAD_METHOD_NOT_SUPP:
        return "Method not supported";

    case ME_MAD_METHOD_ATTR_COMB_NOT_SUPP:
        return "Method and attribute combination isn't supported";

    case ME_MAD_BAD_DATA:
        return "Bad attribute modifier or field";

    case ME_MAD_GENERAL_ERR:
        return "Unknown MAD error";

    default:
        return "Unknown error code";
    }
}


int get_dma_pages(mfile* mf, struct mtcr_page_info* page_info,
                  int page_amount)
{
 #if !defined(__VMKERNEL_UW_NATIVE__)
    int page_size = sysconf(_SC_PAGESIZE);
    int pages_size = page_amount * page_size;
    int page_counter;
    int ret_value;
  

    // Parameters validation.
    if(!mf || !page_info)
    {
        return -1;
    }

    // Save the page amount.
    page_info->page_amount = page_amount;
    
    // Allocate user buffer.
    mf->user_page_list.page_list = memalign(page_size, pages_size);
    if (!mf->user_page_list.page_list)
    {
        return -1;
    }
    
    // We need to call mlock after the pages allocation in order to
    //   lock the virtual address space into RAM and preventing that
    //   memory from being paged to the swap area.
    mlock(mf->user_page_list.page_list, pages_size);

    mf->user_page_list.page_amount = page_amount;

    // Save the start buffer pointer as an integer.
    page_info->page_pointer_start = (unsigned long)mf->user_page_list.page_list;

    // Save the virtual address.
    for (page_counter = 0;
            page_counter < page_amount;
            page_counter++)
    {
        int current_offest = (page_size * page_counter);
        page_info->page_addresses_array[page_counter].virtual_address =
            (u_int64_t)(mf->user_page_list.page_list + current_offest);
    }

    // Pin the memory in the kernel space.
    ret_value = ioctl(mf->fd, PCICONF_GET_DMA_PAGES, page_info);

    if (ret_value)
    {
        // Failed to get dma address.
        // Release the memory.
        release_dma_pages(mf, page_counter);
        return -1;
    }

    return 0;

#else
    (void)mf;
    (void)page_info;
    (void)page_amount;
  
    // MST VMWare driver is unsupported.
    return -1;
#endif
}


int release_dma_pages(mfile* mf, int page_amount)
{
#if !defined(__VMKERNEL_UW_NATIVE__)
    struct mtcr_page_info page_info;


    // Parameter validation.
    if(!mf) {
        return -1;
    }

    page_info.page_amount = page_amount;

    ioctl(mf->fd, PCICONF_RELEASE_DMA_PAGES, &page_info);
    
    // Free the user space memory.
    free(mf->user_page_list.page_list);
    mf->user_page_list.page_list = NULL;
    mf->user_page_list.page_amount = 0;

    return 0;

#else
    (void)mf;

    // MST VMWare driver is unsupported.
    return -1;
#endif
}


int read_dword_from_conf_space(mfile *mf, u_int32_t offset, u_int32_t* data)
{
#if !defined(__VMKERNEL_UW_NATIVE__)
    int ret = 0;

    // Parameters validation.
    if(!mf || !data)
    {
        return -1;
    }
    struct mtcr_read_dword_from_config_space read_config_space;
    read_config_space.offset = offset;
    read_config_space.data = 0;

    // Read from the configuration space.
    ret = ioctl(mf->fd, PCICONF_READ_DWORD_FROM_CONFIG_SPACE, &read_config_space);
    *data = read_config_space.data;

    return ret;


#else
    (void)mf;
    (void)offset;
    (void)data;

    // MST VMWare driver is unsupported.
    return -1;
#endif

}
