/**
 * Copyright (c) UT-Battelle, LLC. 2014-2017. ALL RIGHTS RESERVED.
 * Copyright (c) NVIDIA CORPORATION & AFFILIATES, 2001-2014. ALL RIGHTS RESERVED.
 * See file LICENSE for terms.
 */

#ifndef UCT_UGNI_UDT_IFACE_H
#define UCT_UGNI_UDT_IFACE_H

#include "ugni_udt_ep.h"
#include <uct/ugni/base/ugni_types.h>
#include <uct/ugni/base/ugni_iface.h>
#include <ucs/datastruct/list.h>
#include <uct/base/uct_md.h>
#include <ucs/async/async.h>
#include <ucs/async/pipe.h>

typedef void uct_ugni_udt_desc_t;

typedef struct uct_ugni_udt_iface {
    uct_ugni_iface_t        super;        /**< Super type */
    ucs_mpool_t             free_desc;    /**< Pool of FMA descriptors for
                                               requests without bouncing buffers */
    gni_ep_handle_t         ep_any;       /**< Unbound endpoint that accept any datagram
                                               messages */
    uct_ugni_udt_desc_t     *desc_any;    /**< Segment that accepts datagram from any source */
    uct_recv_desc_t         release_desc; /**< Callback for receive desc release */
    struct {
        unsigned            udt_seg_size; /**< Max UDT size */
        size_t              rx_headroom;  /**< The size of user defined header for am */
    } config;

    pthread_t event_thread;
    pthread_mutex_t device_lock;
    pthread_cond_t device_condition;
    int events_ready;
    ucs_async_pipe_t event_pipe;
} uct_ugni_udt_iface_t;

enum {
    UCT_UGNI_UDT_EMPTY    = 0,
    UCT_UGNI_UDT_PAYLOAD  = 1
};

typedef struct uct_ugni_udt_header {
    uint8_t type;
    uint8_t am_id;
    uint8_t length;
} uct_ugni_udt_header_t;

unsigned uct_ugni_udt_progress(void *arg);

#define uct_ugni_udt_get_offset(i) ((size_t)(ucs_max(sizeof(uct_ugni_udt_header_t), ((i)->config.rx_headroom  + \
                 sizeof(uct_recv_desc_t)))))

#define uct_ugni_udt_get_diff(i) ((size_t)(uct_ugni_udt_get_offset(i) - sizeof(uct_ugni_udt_header_t)))

#define uct_ugni_udt_get_rheader(d, i) ((uct_ugni_udt_header_t *)((char *)(d) + uct_ugni_udt_get_diff(i)))
#define uct_ugni_udt_get_sheader(d, i) ((uct_ugni_udt_header_t *)((char *)uct_ugni_udt_get_rheader(d, i) + GNI_DATAGRAM_MAXSIZE))

#define uct_ugni_udt_get_rpayload(d, i) (uct_ugni_udt_get_rheader(d, i) + 1)
#define uct_ugni_udt_get_spayload(d, i) (uct_ugni_udt_get_sheader(d, i) + 1)
#define uct_ugni_udt_get_user_desc(d, i) ((char *)uct_ugni_udt_get_rpayload(d, i) - (i)->config.rx_headroom)

#define UCT_UGNI_UDT_CHECK_RC(rc, desc)                            \
if (ucs_unlikely(GNI_RC_SUCCESS != rc)) {                          \
    if (GNI_RC_ERROR_RESOURCE == rc || GNI_RC_ERROR_NOMEM == rc) { \
        ucs_debug("GNI_EpPostDataWId failed, Error status: %s %d", \
                  gni_err_str[rc], rc);                            \
        ucs_mpool_put(desc);                                       \
        return UCS_ERR_NO_RESOURCE;                                \
    } else {                                                       \
        ucs_error("GNI_EpPostDataWId failed, Error status: %s %d", \
                  gni_err_str[rc], rc);                            \
        ucs_mpool_put(desc);                                       \
        return UCS_ERR_IO_ERROR;                                   \
    }                                                              \
}

#define uct_ugni_udt_iface_nic_handle(_iface) uct_ugni_iface_nic_handle(&(_iface)->super)

static inline void uct_ugni_udt_reset_desc(uct_ugni_udt_desc_t *desc, uct_ugni_udt_iface_t *iface)
{
    uct_ugni_udt_header_t *sheader = uct_ugni_udt_get_sheader(desc, iface);
    uct_ugni_udt_header_t *rheader = uct_ugni_udt_get_rheader(desc, iface);

    memset(sheader, 0, sizeof(*sheader));
    memset(rheader, 0, sizeof(*rheader));
}

static inline int uct_ugni_udt_ep_any_post(uct_ugni_udt_iface_t *iface)
{
    gni_return_t ugni_rc;

    uct_ugni_udt_reset_desc(iface->desc_any, iface);
    uct_ugni_cdm_lock(&iface->super.cdm);
    ugni_rc = GNI_EpPostDataWId(iface->ep_any,
                                uct_ugni_udt_get_sheader(iface->desc_any, iface),
                                iface->config.udt_seg_size,
                                uct_ugni_udt_get_rheader(iface->desc_any, iface),
                                iface->config.udt_seg_size,
                                UCT_UGNI_UDT_ANY);
    uct_ugni_cdm_unlock(&iface->super.cdm);
    UCT_UGNI_UDT_CHECK_RC(ugni_rc, iface->desc_any);
    return UCS_OK;
}

#endif
