#pragma once #include "udp_address.h" #if defined(_linux_) && !defined(__ANDROID__) #include #include #endif namespace NNetliba { #define CHECK_Z(x) \ { \ int rv = (x); \ if (rv != 0) { \ fprintf(stderr, "check_z failed, errno = %d\n", errno); \ Y_ABORT_UNLESS(0, "check_z"); \ } \ } ////////////////////////////////////////////////////////////////////////// const int MAX_SGE = 1; const size_t MAX_INLINE_DATA_SIZE = 16; const int MAX_OUTSTANDING_RDMA = 10; #if defined(_linux_) && !defined(__ANDROID__) class TIBContext: public TThrRefBase, TNonCopyable { ibv_context* Context; ibv_pd* ProtDomain; TMutex Lock; ~TIBContext() override { if (Context) { CHECK_Z(ibv_dealloc_pd(ProtDomain)); CHECK_Z(ibv_close_device(Context)); } } public: TIBContext(ibv_device* device) { Context = ibv_open_device(device); if (Context) { ProtDomain = ibv_alloc_pd(Context); } } bool IsValid() const { return Context != nullptr && ProtDomain != nullptr; } class TLock { TIntrusivePtr Ptr; TGuard Guard; public: TLock(TPtrArg ctx) : Ptr(ctx) , Guard(ctx->Lock) { } ibv_context* GetContext() { return Ptr->Context; } ibv_pd* GetProtDomain() { return Ptr->ProtDomain; } }; }; class TIBPort: public TThrRefBase, TNonCopyable { int Port; int LID; TIntrusivePtr IBCtx; static constexpr int MAX_GID = 16; ibv_gid MyGidArr[MAX_GID]; public: TIBPort(TPtrArg ctx, int port) : IBCtx(ctx) { ibv_port_attr portAttrs; TIBContext::TLock ibContext(IBCtx); CHECK_Z(ibv_query_port(ibContext.GetContext(), port, &portAttrs)); Port = port; LID = portAttrs.lid; for (int i = 0; i < MAX_GID; ++i) { ibv_gid& dst = MyGidArr[i]; Zero(dst); ibv_query_gid(ibContext.GetContext(), Port, i, &dst); } } int GetPort() const { return Port; } int GetLID() const { return LID; } TIBContext* GetCtx() { return IBCtx.Get(); } void GetGID(ibv_gid* res) const { *res = MyGidArr[0]; } int GetGIDIndex(const ibv_gid& arg) const { for (int i = 0; i < MAX_GID; ++i) { const ibv_gid& chk = MyGidArr[i]; if (memcmp(&chk, &arg, sizeof(chk)) == 0) { return i; } } return 0; } void GetAHAttr(ibv_wc* wc, ibv_grh* grh, ibv_ah_attr* res) { TIBContext::TLock ibContext(IBCtx); CHECK_Z(ibv_init_ah_from_wc(ibContext.GetContext(), Port, wc, grh, res)); } }; class TComplectionQueue: public TThrRefBase, TNonCopyable { ibv_cq* CQ; TIntrusivePtr IBCtx; ~TComplectionQueue() override { if (CQ) { CHECK_Z(ibv_destroy_cq(CQ)); } } public: TComplectionQueue(TPtrArg ctx, int maxCQEcount) : IBCtx(ctx) { TIBContext::TLock ibContext(IBCtx); /* ibv_cq_init_attr_ex attr; Zero(attr); attr.cqe = maxCQEcount; attr.cq_create_flags = 0; ibv_cq_ex *vcq = ibv_create_cq_ex(ibContext.GetContext(), &attr); if (vcq) { CQ = (ibv_cq*)vcq; // doubtful trick but that's life } else {*/ // no completion channel // no completion vector CQ = ibv_create_cq(ibContext.GetContext(), maxCQEcount, nullptr, nullptr, 0); // } } ibv_cq* GetCQ() { return CQ; } int Poll(ibv_wc* res, int bufSize) { Y_ASSERT(bufSize >= 1); //struct ibv_wc //{ // ui64 wr_id; // enum ibv_wc_status status; // enum ibv_wc_opcode opcode; // ui32 vendor_err; // ui32 byte_len; // ui32 imm_data;/* network byte order */ // ui32 qp_num; // ui32 src_qp; // enum ibv_wc_flags wc_flags; // ui16 pkey_index; // ui16 slid; // ui8 sl; // ui8 dlid_path_bits; //}; int rv = ibv_poll_cq(CQ, bufSize, res); if (rv < 0) { Y_ABORT_UNLESS(0, "ibv_poll_cq failed"); } if (rv > 0) { //printf("Completed wr\n"); //printf(" wr_id = %" PRIx64 "\n", wc.wr_id); //printf(" status = %d\n", wc.status); //printf(" opcode = %d\n", wc.opcode); //printf(" byte_len = %d\n", wc.byte_len); //printf(" imm_data = %d\n", wc.imm_data); //printf(" qp_num = %d\n", wc.qp_num); //printf(" src_qp = %d\n", wc.src_qp); //printf(" wc_flags = %x\n", wc.wc_flags); //printf(" slid = %d\n", wc.slid); } //rv = number_of_toggled_wc; return rv; } }; //struct ibv_mr //{ // struct ibv_context *context; // struct ibv_pd *pd; // void *addr; // size_t length; // ui32 handle; // ui32 lkey; // ui32 rkey; //}; class TMemoryRegion: public TThrRefBase, TNonCopyable { ibv_mr* MR; TIntrusivePtr IBCtx; ~TMemoryRegion() override { if (MR) { CHECK_Z(ibv_dereg_mr(MR)); } } public: TMemoryRegion(TPtrArg ctx, size_t len) : IBCtx(ctx) { TIBContext::TLock ibContext(IBCtx); int access = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ; // TODO: IBV_ACCESS_ALLOCATE_MR MR = ibv_reg_mr(ibContext.GetProtDomain(), 0, len, access); Y_ASSERT(MR); } ui32 GetLKey() const { static_assert(sizeof(ui32) == sizeof(MR->lkey), "expect sizeof(ui32) == sizeof(MR->lkey)"); return MR->lkey; } ui32 GetRKey() const { static_assert(sizeof(ui32) == sizeof(MR->lkey), "expect sizeof(ui32) == sizeof(MR->lkey)"); return MR->lkey; } char* GetData() { return MR ? (char*)MR->addr : nullptr; } bool IsCovered(const void* data, size_t len) const { size_t dataAddr = reinterpret_cast(data) / sizeof(char); size_t bufAddr = reinterpret_cast(MR->addr) / sizeof(char); return (dataAddr >= bufAddr) && (dataAddr + len <= bufAddr + MR->length); } }; class TSharedReceiveQueue: public TThrRefBase, TNonCopyable { ibv_srq* SRQ; TIntrusivePtr IBCtx; ~TSharedReceiveQueue() override { if (SRQ) { ibv_destroy_srq(SRQ); } } public: TSharedReceiveQueue(TPtrArg ctx, int maxWR) : IBCtx(ctx) { ibv_srq_init_attr attr; Zero(attr); attr.srq_context = this; attr.attr.max_sge = MAX_SGE; attr.attr.max_wr = maxWR; TIBContext::TLock ibContext(IBCtx); SRQ = ibv_create_srq(ibContext.GetProtDomain(), &attr); Y_ASSERT(SRQ); } ibv_srq* GetSRQ() { return SRQ; } void PostReceive(TPtrArg mem, ui64 id, const void* buf, size_t len) { Y_ASSERT(mem->IsCovered(buf, len)); ibv_recv_wr wr, *bad; ibv_sge sg; sg.addr = reinterpret_cast(buf) / sizeof(char); sg.length = len; sg.lkey = mem->GetLKey(); Zero(wr); wr.wr_id = id; wr.sg_list = &sg; wr.num_sge = 1; CHECK_Z(ibv_post_srq_recv(SRQ, &wr, &bad)); } }; inline void MakeAH(ibv_ah_attr* res, TPtrArg port, int lid, int serviceLevel) { Zero(*res); res->dlid = lid; res->port_num = port->GetPort(); res->sl = serviceLevel; } void MakeAH(ibv_ah_attr* res, TPtrArg port, const TUdpAddress& remoteAddr, const TUdpAddress& localAddr, int serviceLevel); class TAddressHandle: public TThrRefBase, TNonCopyable { ibv_ah* AH; TIntrusivePtr IBCtx; ~TAddressHandle() override { if (AH) { CHECK_Z(ibv_destroy_ah(AH)); } AH = nullptr; IBCtx = nullptr; } public: TAddressHandle(TPtrArg ctx, ibv_ah_attr* attr) : IBCtx(ctx) { TIBContext::TLock ibContext(IBCtx); AH = ibv_create_ah(ibContext.GetProtDomain(), attr); Y_ASSERT(AH != nullptr); } TAddressHandle(TPtrArg port, int lid, int serviceLevel) : IBCtx(port->GetCtx()) { ibv_ah_attr attr; MakeAH(&attr, port, lid, serviceLevel); TIBContext::TLock ibContext(IBCtx); AH = ibv_create_ah(ibContext.GetProtDomain(), &attr); Y_ASSERT(AH != nullptr); } TAddressHandle(TPtrArg port, const TUdpAddress& remoteAddr, const TUdpAddress& localAddr, int serviceLevel) : IBCtx(port->GetCtx()) { ibv_ah_attr attr; MakeAH(&attr, port, remoteAddr, localAddr, serviceLevel); TIBContext::TLock ibContext(IBCtx); AH = ibv_create_ah(ibContext.GetProtDomain(), &attr); Y_ASSERT(AH != nullptr); } ibv_ah* GetAH() { return AH; } bool IsValid() const { return AH != nullptr; } }; // GRH + wc -> address_handle_attr //int ibv_init_ah_from_wc(struct ibv_context *context, ui8 port_num, //struct ibv_wc *wc, struct ibv_grh *grh, //struct ibv_ah_attr *ah_attr) //ibv_create_ah_from_wc(struct ibv_pd *pd, struct ibv_wc *wc, struct ibv_grh // *grh, ui8 port_num) class TQueuePair: public TThrRefBase, TNonCopyable { protected: ibv_qp* QP; int MyPSN; // start packet sequence number TIntrusivePtr IBCtx; TIntrusivePtr CQ; TIntrusivePtr SRQ; TQueuePair(TPtrArg ctx, TPtrArg cq, TPtrArg srq, int sendQueueSize, ibv_qp_type qp_type) : IBCtx(ctx) , CQ(cq) , SRQ(srq) { MyPSN = GetCycleCount() & 0xffffff; // should be random and different on different runs, 24bit ibv_qp_init_attr attr; Zero(attr); attr.qp_context = this; // not really useful attr.send_cq = cq->GetCQ(); attr.recv_cq = cq->GetCQ(); attr.srq = srq->GetSRQ(); attr.cap.max_send_wr = sendQueueSize; attr.cap.max_recv_wr = 0; // we are using srq, no need for qp's rq attr.cap.max_send_sge = MAX_SGE; attr.cap.max_recv_sge = MAX_SGE; attr.cap.max_inline_data = MAX_INLINE_DATA_SIZE; attr.qp_type = qp_type; attr.sq_sig_all = 1; // inline sends need not be signaled, but if they are not work queue overflows TIBContext::TLock ibContext(IBCtx); QP = ibv_create_qp(ibContext.GetProtDomain(), &attr); Y_ASSERT(QP); //struct ibv_qp { // struct ibv_context *context; // void *qp_context; // struct ibv_pd *pd; // struct ibv_cq *send_cq; // struct ibv_cq *recv_cq; // struct ibv_srq *srq; // ui32 handle; // ui32 qp_num; // enum ibv_qp_state state; // enum ibv_qp_type qp_type; // pthread_mutex_t mutex; // pthread_cond_t cond; // ui32 events_completed; //}; //qp_context The value qp_context that was provided to ibv_create_qp() //qp_num The number of this Queue Pair //state The last known state of this Queue Pair. The actual state may be different from this state (in the RDMA device transitioned the state into other state) //qp_type The Transport Service Type of this Queue Pair } ~TQueuePair() override { if (QP) { CHECK_Z(ibv_destroy_qp(QP)); } } void FillSendAttrs(ibv_send_wr* wr, ibv_sge* sg, ui64 localAddr, ui32 lKey, ui64 id, size_t len) { sg->addr = localAddr; sg->length = len; sg->lkey = lKey; Zero(*wr); wr->wr_id = id; wr->sg_list = sg; wr->num_sge = 1; if (len <= MAX_INLINE_DATA_SIZE) { wr->send_flags = IBV_SEND_INLINE; } } void FillSendAttrs(ibv_send_wr* wr, ibv_sge* sg, TPtrArg mem, ui64 id, const void* data, size_t len) { ui64 localAddr = reinterpret_cast(data) / sizeof(char); ui32 lKey = 0; if (mem) { Y_ASSERT(mem->IsCovered(data, len)); lKey = mem->GetLKey(); } else { Y_ASSERT(len <= MAX_INLINE_DATA_SIZE); } FillSendAttrs(wr, sg, localAddr, lKey, id, len); } public: int GetQPN() const { if (QP) return QP->qp_num; return 0; } int GetPSN() const { return MyPSN; } // we are using srq //void PostReceive(const TMemoryRegion &mem) //{ // ibv_recv_wr wr, *bad; // ibv_sge sg; // sg.addr = mem.Addr; // sg.length = mem.Length; // sg.lkey = mem.lkey; // Zero(wr); // wr.wr_id = 13; // wr.sg_list = sg; // wr.num_sge = 1; // CHECK_Z(ibv_post_recv(QP, &wr, &bad)); //} }; class TRCQueuePair: public TQueuePair { public: TRCQueuePair(TPtrArg ctx, TPtrArg cq, TPtrArg srq, int sendQueueSize) : TQueuePair(ctx, cq, srq, sendQueueSize, IBV_QPT_RC) { } // SRQ should have receive posted void Init(const ibv_ah_attr& peerAddr, int peerQPN, int peerPSN) { Y_ASSERT(QP->qp_type == IBV_QPT_RC); ibv_qp_attr attr; //{ // enum ibv_qp_state qp_state; // enum ibv_qp_state cur_qp_state; // enum ibv_mtu path_mtu; // enum ibv_mig_state path_mig_state; // ui32 qkey; // ui32 rq_psn; // ui32 sq_psn; // ui32 dest_qp_num; // int qp_access_flags; // struct ibv_qp_cap cap; // struct ibv_ah_attr ah_attr; // struct ibv_ah_attr alt_ah_attr; // ui16 pkey_index; // ui16 alt_pkey_index; // ui8 en_sqd_async_notify; // ui8 sq_draining; // ui8 max_rd_atomic; // ui8 max_dest_rd_atomic; // ui8 min_rnr_timer; // ui8 port_num; // ui8 timeout; // ui8 retry_cnt; // ui8 rnr_retry; // ui8 alt_port_num; // ui8 alt_timeout; //}; // RESET -> INIT Zero(attr); attr.qp_state = IBV_QPS_INIT; attr.pkey_index = 0; attr.port_num = peerAddr.port_num; // for connected QP attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_ATOMIC; CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS)); // INIT -> ReadyToReceive //PostReceive(mem); attr.qp_state = IBV_QPS_RTR; attr.path_mtu = IBV_MTU_512; // allows more fine grained VL arbitration // for connected QP attr.ah_attr = peerAddr; attr.dest_qp_num = peerQPN; attr.rq_psn = peerPSN; attr.max_dest_rd_atomic = MAX_OUTSTANDING_RDMA; // number of outstanding RDMA requests attr.min_rnr_timer = 12; // recommended CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE | IBV_QP_PATH_MTU | IBV_QP_AV | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER)); // ReadyToReceive -> ReadyToTransmit attr.qp_state = IBV_QPS_RTS; // for connected QP attr.timeout = 14; // increased to 18 for sometime, 14 recommended //attr.retry_cnt = 0; // for debug purposes //attr.rnr_retry = 0; // for debug purposes attr.retry_cnt = 7; // release configuration attr.rnr_retry = 7; // release configuration (try forever) attr.sq_psn = MyPSN; attr.max_rd_atomic = MAX_OUTSTANDING_RDMA; // number of outstanding RDMA requests CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC)); } void PostSend(TPtrArg mem, ui64 id, const void* data, size_t len) { ibv_send_wr wr, *bad; ibv_sge sg; FillSendAttrs(&wr, &sg, mem, id, data, len); wr.opcode = IBV_WR_SEND; //IBV_WR_RDMA_WRITE //IBV_WR_RDMA_WRITE_WITH_IMM //IBV_WR_SEND //IBV_WR_SEND_WITH_IMM //IBV_WR_RDMA_READ //wr.imm_data = xz; CHECK_Z(ibv_post_send(QP, &wr, &bad)); } void PostRDMAWrite(ui64 remoteAddr, ui32 remoteKey, TPtrArg mem, ui64 id, const void* data, size_t len) { ibv_send_wr wr, *bad; ibv_sge sg; FillSendAttrs(&wr, &sg, mem, id, data, len); wr.opcode = IBV_WR_RDMA_WRITE; wr.wr.rdma.remote_addr = remoteAddr; wr.wr.rdma.rkey = remoteKey; CHECK_Z(ibv_post_send(QP, &wr, &bad)); } void PostRDMAWrite(ui64 remoteAddr, ui32 remoteKey, ui64 localAddr, ui32 localKey, ui64 id, size_t len) { ibv_send_wr wr, *bad; ibv_sge sg; FillSendAttrs(&wr, &sg, localAddr, localKey, id, len); wr.opcode = IBV_WR_RDMA_WRITE; wr.wr.rdma.remote_addr = remoteAddr; wr.wr.rdma.rkey = remoteKey; CHECK_Z(ibv_post_send(QP, &wr, &bad)); } void PostRDMAWriteImm(ui64 remoteAddr, ui32 remoteKey, ui32 immData, TPtrArg mem, ui64 id, const void* data, size_t len) { ibv_send_wr wr, *bad; ibv_sge sg; FillSendAttrs(&wr, &sg, mem, id, data, len); wr.opcode = IBV_WR_RDMA_WRITE_WITH_IMM; wr.imm_data = immData; wr.wr.rdma.remote_addr = remoteAddr; wr.wr.rdma.rkey = remoteKey; CHECK_Z(ibv_post_send(QP, &wr, &bad)); } }; class TUDQueuePair: public TQueuePair { TIntrusivePtr Port; public: TUDQueuePair(TPtrArg port, TPtrArg cq, TPtrArg srq, int sendQueueSize) : TQueuePair(port->GetCtx(), cq, srq, sendQueueSize, IBV_QPT_UD) , Port(port) { } // SRQ should have receive posted void Init(int qkey) { Y_ASSERT(QP->qp_type == IBV_QPT_UD); ibv_qp_attr attr; // RESET -> INIT Zero(attr); attr.qp_state = IBV_QPS_INIT; attr.pkey_index = 0; attr.port_num = Port->GetPort(); // for unconnected qp attr.qkey = qkey; CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_QKEY)); // INIT -> ReadyToReceive //PostReceive(mem); attr.qp_state = IBV_QPS_RTR; CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE)); // ReadyToReceive -> ReadyToTransmit attr.qp_state = IBV_QPS_RTS; attr.sq_psn = 0; CHECK_Z(ibv_modify_qp(QP, &attr, IBV_QP_STATE | IBV_QP_SQ_PSN)); } void PostSend(TPtrArg ah, int remoteQPN, int remoteQKey, TPtrArg mem, ui64 id, const void* data, size_t len) { ibv_send_wr wr, *bad; ibv_sge sg; FillSendAttrs(&wr, &sg, mem, id, data, len); wr.opcode = IBV_WR_SEND; wr.wr.ud.ah = ah->GetAH(); wr.wr.ud.remote_qpn = remoteQPN; wr.wr.ud.remote_qkey = remoteQKey; //IBV_WR_SEND_WITH_IMM //wr.imm_data = xz; CHECK_Z(ibv_post_send(QP, &wr, &bad)); } }; TIntrusivePtr GetIBDevice(); #else ////////////////////////////////////////////////////////////////////////// // stub for OS without IB support ////////////////////////////////////////////////////////////////////////// enum ibv_wc_opcode { IBV_WC_SEND, IBV_WC_RDMA_WRITE, IBV_WC_RDMA_READ, IBV_WC_COMP_SWAP, IBV_WC_FETCH_ADD, IBV_WC_BIND_MW, IBV_WC_RECV = 1 << 7, IBV_WC_RECV_RDMA_WITH_IMM }; enum ibv_wc_status { IBV_WC_SUCCESS, // lots of errors follow }; //struct ibv_device; //struct ibv_pd; union ibv_gid { ui8 raw[16]; struct { ui64 subnet_prefix; ui64 interface_id; } global; }; struct ibv_wc { ui64 wr_id; enum ibv_wc_status status; enum ibv_wc_opcode opcode; ui32 imm_data; /* in network byte order */ ui32 qp_num; ui32 src_qp; }; struct ibv_grh {}; struct ibv_ah_attr { ui8 sl; }; //struct ibv_cq; class TIBContext: public TThrRefBase, TNonCopyable { public: bool IsValid() const { return false; } //ibv_context *GetContext() { return 0; } //ibv_pd *GetProtDomain() { return 0; } }; class TIBPort: public TThrRefBase, TNonCopyable { public: TIBPort(TPtrArg, int) { } int GetPort() const { return 1; } int GetLID() const { return 1; } TIBContext* GetCtx() { return 0; } void GetGID(ibv_gid* res) { Zero(*res); } void GetAHAttr(ibv_wc*, ibv_grh*, ibv_ah_attr*) { } }; class TComplectionQueue: public TThrRefBase, TNonCopyable { public: TComplectionQueue(TPtrArg, int) { } //ibv_cq *GetCQ() { return 0; } int Poll(ibv_wc*, int) { return 0; } }; class TMemoryRegion: public TThrRefBase, TNonCopyable { public: TMemoryRegion(TPtrArg, size_t) { } ui32 GetLKey() const { return 0; } ui32 GetRKey() const { return 0; } char* GetData() { return 0; } bool IsCovered(const void*, size_t) const { return false; } }; class TSharedReceiveQueue: public TThrRefBase, TNonCopyable { public: TSharedReceiveQueue(TPtrArg, int) { } //ibv_srq *GetSRQ() { return SRQ; } void PostReceive(TPtrArg, ui64, const void*, size_t) { } }; inline void MakeAH(ibv_ah_attr*, TPtrArg, int, int) { } class TAddressHandle: public TThrRefBase, TNonCopyable { public: TAddressHandle(TPtrArg, ibv_ah_attr*) { } TAddressHandle(TPtrArg, int, int) { } TAddressHandle(TPtrArg, const TUdpAddress&, const TUdpAddress&, int) { } //ibv_ah *GetAH() { return AH; } bool IsValid() { return true; } }; class TQueuePair: public TThrRefBase, TNonCopyable { public: int GetQPN() const { return 0; } int GetPSN() const { return 0; } }; class TRCQueuePair: public TQueuePair { public: TRCQueuePair(TPtrArg, TPtrArg, TPtrArg, int) { } // SRQ should have receive posted void Init(const ibv_ah_attr&, int, int) { } void PostSend(TPtrArg, ui64, const void*, size_t) { } void PostRDMAWrite(ui64, ui32, TPtrArg, ui64, const void*, size_t) { } void PostRDMAWrite(ui64, ui32, ui64, ui32, ui64, size_t) { } void PostRDMAWriteImm(ui64, ui32, ui32, TPtrArg, ui64, const void*, size_t) { } }; class TUDQueuePair: public TQueuePair { TIntrusivePtr Port; public: TUDQueuePair(TPtrArg, TPtrArg, TPtrArg, int) { } // SRQ should have receive posted void Init(int) { } void PostSend(TPtrArg, int, int, TPtrArg, ui64, const void*, size_t) { } }; inline TIntrusivePtr GetIBDevice() { return 0; } #endif }