24#include <infiniband/verbs.h>
28#include <sys/select.h>
53 #include <boost/python.hpp>
54namespace bp = boost::python;
67 uint32_t rxQueueDepth) {
68 return std::make_shared<rpr::Server>(
69 deviceName, ibPort, gidIndex,
maxPayload, rxQueueDepth);
77rpr::Server::Server(
const std::string& deviceName,
81 uint32_t rxQueueDepth)
82 :
rpr::
Core(deviceName, ibPort, gidIndex, maxPayload),
85 cq_(nullptr), qp_(nullptr), mr_(nullptr), comp_channel_(nullptr),
88 numBufs_(rxQueueDepth),
101 memset(hostGid_, 0, 16);
102 memset(fpgaGid_, 0, 16);
103 wakeFd_[0] = wakeFd_[1] = -1;
120 uint64_t slabSize64 =
static_cast<uint64_t
>(numBufs_) * bufSize_;
121 if (slabSize64 > UINT32_MAX)
123 "RX slab too large: %u * %u = %" PRIu64
" exceeds 4 GiB",
124 numBufs_, bufSize_, slabSize64));
125 slabSize_ =
static_cast<uint32_t
>(slabSize64);
127 void* slabPtr =
nullptr;
128 if (posix_memalign(&slabPtr, 4096, slabSize_) != 0 || !slabPtr)
130 "Failed to allocate RX slab (%u bytes)",
132 slab_ =
static_cast<uint8_t*
>(slabPtr);
133 memset(slab_, 0, slabSize_);
138 mr_ = ibv_reg_mr(
pd_, slab_, slabSize_,
139 IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
142 "ibv_reg_mr failed"));
144 mrAddr_ =
reinterpret_cast<uint64_t
>(slab_);
147 log_->info(
"MR: addr=0x%016" PRIx64
" rkey=0x%08x size=%u",
148 mrAddr_, mrRkey_, slabSize_);
158 comp_channel_ = ibv_create_comp_channel(
ctx_);
161 "ibv_create_comp_channel failed"));
163 int flags = fcntl(comp_channel_->fd, F_GETFL);
165 fcntl(comp_channel_->fd, F_SETFL, flags | O_NONBLOCK) < 0)
167 "failed to set comp channel fd non-blocking"));
170 if (pipe(wakeFd_) != 0)
172 "wake self-pipe creation failed"));
173 for (
int i = 0; i < 2; ++i) {
174 int flags = fcntl(wakeFd_[i], F_GETFL);
175 if (flags >= 0) fcntl(wakeFd_[i], F_SETFL, flags | O_NONBLOCK);
182 cq_ = ibv_create_cq(
ctx_,
183 static_cast<int>(numBufs_),
184 this, comp_channel_, 0);
187 "ibv_create_cq failed"));
192 struct ibv_qp_init_attr qpAttr;
193 memset(&qpAttr, 0,
sizeof(qpAttr));
194 qpAttr.qp_type = IBV_QPT_RC;
195 qpAttr.sq_sig_all = 0;
196 qpAttr.send_cq = cq_;
197 qpAttr.recv_cq = cq_;
198 qpAttr.cap.max_recv_wr = numBufs_;
199 qpAttr.cap.max_send_wr = 1;
200 qpAttr.cap.max_recv_sge = 1;
201 qpAttr.cap.max_send_sge = 1;
203 qp_ = ibv_create_qp(
pd_, &qpAttr);
206 "ibv_create_qp (RC) failed"));
208 hostQpn_ = qp_->qp_num;
214 struct ibv_qp_attr attr;
215 memset(&attr, 0,
sizeof(attr));
216 attr.qp_state = IBV_QPS_INIT;
219 attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE |
220 IBV_ACCESS_REMOTE_READ |
221 IBV_ACCESS_LOCAL_WRITE;
223 if (ibv_modify_qp(qp_, &attr,
227 IBV_QP_ACCESS_FLAGS))
229 "QP RESET→INIT failed"));
252 "ibv_query_gid failed"));
254 for (
int i = 0; i < 16; ++i) {
255 if (gid.raw[i] != 0) {
262 "GID query returned all-zero for "
263 "gidIndex=%u on device '%s' "
264 "(likely out-of-range; rdma_rxe "
265 "does not validate gidIndex and "
266 "returns an empty GID for unused "
270 memcpy(hostGid_, gid.raw, 16);
277 std::mt19937 psnRng(std::random_device {}());
278 hostRqPsn_ = psnRng() & 0xFFFFFF;
279 hostSqPsn_ = psnRng() & 0xFFFFFF;
281 log_->info(
"RC QP ready: qpn=0x%06x rqPsn=0x%06x sqPsn=0x%06x",
282 hostQpn_, hostRqPsn_, hostSqPsn_);
294void rpr::Server::cleanupResources() {
304 ibv_destroy_comp_channel(comp_channel_);
305 comp_channel_ =
nullptr;
315 for (
int i = 0; i < 2; ++i) {
316 if (wakeFd_[i] >= 0) {
326void rpr::Server::setFpgaGid(
const std::string& gidBytes) {
327 if (gidBytes.size() != 16)
329 "GID must be 16 bytes, got %zu",
331 memcpy(fpgaGid_, gidBytes.c_str(), 16);
332 log_->info(
"FPGA GID stored");
338void rpr::Server::completeConnection(uint32_t fpgaQpn, uint32_t fpgaRqPsn,
339 uint32_t pmtu, uint32_t minRnrTimer) {
345 if (thread_ !=
nullptr)
347 "rocev2::Server::completeConnection",
348 "completeConnection already invoked; destroy and recreate "
349 "Server to re-establish the RC connection"));
355 if (pmtu < 1 || pmtu > 5)
357 "rocev2::Server::completeConnection",
358 "pmtu must be in the range 1..5 "
359 "(1=256 2=512 3=1024 4=2048 5=4096); got %u", pmtu));
361 log_->info(
"completeConnection: fpgaQpn=0x%06x fpgaRqPsn=0x%06x minRnrTimer=%u",
362 fpgaQpn, fpgaRqPsn, minRnrTimer);
366 for (uint32_t i = 0; i < numBufs_; ++i) postRecvWr(i);
367 log_->info(
"Pre-posted %u recv WRs", numBufs_);
372 memcpy(dgid.raw, fpgaGid_, 16);
374 struct ibv_qp_attr attr;
375 memset(&attr, 0,
sizeof(attr));
376 attr.qp_state = IBV_QPS_RTR;
377 attr.path_mtu =
static_cast<ibv_mtu
>(pmtu);
378 attr.dest_qp_num = fpgaQpn;
379 attr.rq_psn = fpgaRqPsn;
380 attr.max_dest_rd_atomic = 16;
381 attr.min_rnr_timer = minRnrTimer;
382 attr.ah_attr.is_global = 1;
383 attr.ah_attr.grh.dgid = dgid;
384 attr.ah_attr.grh.sgid_index = gidIndex_;
385 attr.ah_attr.grh.hop_limit = 64;
386 attr.ah_attr.port_num = ibPort_;
389 if (ibv_modify_qp(qp_, &attr,
395 IBV_QP_MAX_DEST_RD_ATOMIC |
396 IBV_QP_MIN_RNR_TIMER))
398 "QP INIT→RTR failed"));
401 log_->info(
"QP → RTR (minRnrTimer=%u)", minRnrTimer);
405 struct ibv_qp_attr attr;
406 memset(&attr, 0,
sizeof(attr));
407 attr.qp_state = IBV_QPS_RTS;
408 attr.sq_psn = hostSqPsn_;
412 attr.max_rd_atomic = 16;
414 if (ibv_modify_qp(qp_, &attr,
420 IBV_QP_MAX_QP_RD_ATOMIC))
422 "QP RTR→RTS failed"));
425 log_->info(
"QP → RTS — ready to receive RDMA SENDs");
428 std::shared_ptr<int> scopePtr = std::make_shared<int>(0);
429 threadEn_.store(
true);
430 thread_ =
new std::thread(&rpr::Server::runThread,
this,
431 std::weak_ptr<int>(scopePtr));
434 pthread_setname_np(thread_->native_handle(),
"RoCEv2Server");
441std::string rpr::Server::getGid()
const {
442 std::ostringstream oss;
443 for (
int i = 0; i < 16; i += 2) {
445 oss << std::hex << std::setfill(
'0')
446 << std::setw(2) <<
static_cast<int>(hostGid_[i])
447 << std::setw(2) <<
static_cast<int>(hostGid_[i+1]);
456void rpr::Server::postRecvWr(uint32_t slot) {
462 if (slot >= numBufs_)
464 "slot=%u out of range (numBufs=%u)",
467 uint8_t* bufStart = slab_ + (
static_cast<uint64_t
>(slot) * bufSize_);
470 memset(&sge, 0,
sizeof(sge));
471 sge.addr =
reinterpret_cast<uint64_t
>(bufStart);
472 sge.length = bufSize_;
473 sge.lkey = mr_->lkey;
475 struct ibv_recv_wr wr;
476 memset(&wr, 0,
sizeof(wr));
477 wr.wr_id =
static_cast<uint64_t
>(slot);
482 struct ibv_recv_wr* bad =
nullptr;
483 if (ibv_post_recv(qp_, &wr, &bad))
485 "ibv_post_recv failed (slot=%u)", slot));
497void rpr::Server::retBuffer(uint8_t* data, uint32_t meta, uint32_t rawSize) {
498 uint32_t slot = meta & 0x00FFFFFF;
500 log_->debug(
"retBuffer: re-posting slot=%u", slot);
502 if (threadEn_.load() && qp_) {
520void rpr::Server::processCompletion(
struct ibv_wc& wc) {
521 uint32_t slot =
static_cast<uint32_t
>(wc.wr_id);
525 if (slot >= numBufs_) {
526 log_->warning(
"CQ returned out-of-range wr_id=%" PRIu64
527 " (numBufs=%u); discarding",
528 static_cast<uint64_t
>(wc.wr_id), numBufs_);
532 if (wc.status != IBV_WC_SUCCESS) {
533 log_->warning(
"CQ error: %s (slot=%u)", ibv_wc_status_str(wc.status), slot);
535 if (wc.status == IBV_WC_WR_FLUSH_ERR) {
538 log_->error(
"QP in ERROR state (WR_FLUSH_ERR); exiting CQ poll thread");
539 threadEn_.store(
false);
549 if (wc.opcode != IBV_WC_RECV || !(wc.wc_flags & IBV_WC_WITH_IMM)) {
550 log_->warning(
"Unexpected opcode %d / wc_flags 0x%x (slot=%u), re-posting",
551 wc.opcode, wc.wc_flags, slot);
560 uint32_t imm = ntohl(wc.imm_data);
561 uint8_t channel =
static_cast<uint8_t
>(imm & 0xFF);
562 uint32_t dataSlot = (imm >> 8) & 0x00FFFFFF;
563 uint32_t payloadLen = wc.byte_len;
565 if (payloadLen == 0 || payloadLen > bufSize_) {
566 log_->warning(
"Bad payload len=%u (slot=%u), re-posting", payloadLen, slot);
574 uint8_t* slotPtr = slab_ + (
static_cast<uint64_t
>(slot) * bufSize_);
580 buff->setPayload(payloadLen);
583 frame->appendBuffer(buff);
584 frame->setChannel(channel);
585 frame->setFirstUser(
SsiSof);
586 frame->setLastUser(0);
588 log_->debug(
"RX wrId=%u dataSlot=%u channel=%u len=%u",
589 slot, dataSlot, channel, payloadLen);
593 frameCount_.fetch_add(1, std::memory_order_relaxed);
594 byteCount_.fetch_add(payloadLen, std::memory_order_relaxed);
608void rpr::Server::runThread(std::weak_ptr<int> lockPtr) {
609 while (!lockPtr.expired())
continue;
612 log_->info(
"RoCEv2 receive thread started (event-driven)");
618 constexpr int kPollBatch = 16;
619 struct ibv_wc wcArr[kPollBatch];
621 const int cqFd = comp_channel_->fd;
622 const int wakeFd = wakeFd_[0];
623 const int maxFd = (cqFd > wakeFd ? cqFd : wakeFd) + 1;
644 constexpr uint32_t kSpinBudget = 100000;
645 uint32_t idleSpins = 0;
647 while (threadEn_.load()) {
648 int n = ibv_poll_cq(cq_, kPollBatch, wcArr);
650 log_->error(
"ibv_poll_cq error (%d); exiting CQ poll thread", n);
654 for (
int k = 0; k < n; ++k) processCompletion(wcArr[k]);
661 if (++idleSpins < kSpinBudget)
continue;
666 if (ibv_req_notify_cq(cq_, 0)) {
667 log_->error(
"ibv_req_notify_cq failed; exiting CQ poll thread");
670 n = ibv_poll_cq(cq_, kPollBatch, wcArr);
672 log_->error(
"ibv_poll_cq error (%d); exiting CQ poll thread", n);
676 for (
int k = 0; k < n; ++k) processCompletion(wcArr[k]);
685 FD_SET(wakeFd, &rfds);
686 int s = select(maxFd, &rfds,
nullptr,
nullptr,
nullptr);
688 if (errno == EINTR)
continue;
689 log_->error(
"select() failed (errno=%d); exiting CQ poll thread", errno);
692 if (FD_ISSET(wakeFd, &rfds)) {
693 uint8_t drainBuf[64];
694 while (read(wakeFd, drainBuf,
sizeof(drainBuf)) > 0) {}
697 if (FD_ISSET(cqFd, &rfds)) {
703 while (ibv_get_cq_event(comp_channel_, &evCq, &evCtx) == 0) ++events;
704 if (events) ibv_ack_cq_events(cq_, events);
708 log_->error(
"RoCEv2 receive thread exiting on ibverbs error: %s", e.
what());
709 }
catch (
const std::exception& e) {
710 log_->error(
"RoCEv2 receive thread exiting on exception: %s", e.what());
712 log_->error(
"RoCEv2 receive thread exiting on unknown exception");
715 threadEn_.store(
false);
716 log_->info(
"RoCEv2 receive thread stopped");
725 log_->warning(
"RoCEv2 Server::acceptFrame: TX not supported, dropping");
731void rpr::Server::stop() {
737 threadEn_.store(
false);
741 if (wakeFd_[1] >= 0) {
742 const uint8_t one = 1;
743 ssize_t wr = write(wakeFd_[1], &one, 1);
748 if (thread_->joinable()) thread_->join();
755rpr::Server::~Server() { this->stop(); }
760void rpr::Server::setup_python() {
764 bp::bases<rpr::Core, ris::Master, ris::Slave>,
767 bp::init<std::string, uint8_t, uint8_t, uint32_t, uint32_t>(
768 (bp::arg(
"deviceName"),
769 bp::arg(
"ibPort") = 1,
770 bp::arg(
"gidIndex") = 0,
773 .def(
"create", &rpr::Server::create)
774 .staticmethod(
"create")
775 .def(
"setFpgaGid", &rpr::Server::setFpgaGid)
776 .def(
"completeConnection", &rpr::Server::completeConnection,
778 bp::arg(
"fpgaRqPsn"),
780 bp::arg(
"minRnrTimer") = 1))
781 .def(
"getQpn", &rpr::Server::getQpn)
782 .def(
"getGid", &rpr::Server::getGid)
783 .def(
"getRqPsn", &rpr::Server::getRqPsn)
784 .def(
"getSqPsn", &rpr::Server::getSqPsn)
785 .def(
"getMrAddr", &rpr::Server::getMrAddr)
786 .def(
"getMrRkey", &rpr::Server::getMrRkey)
787 .def(
"getFrameCount", &rpr::Server::getFrameCount)
788 .def(
"getByteCount", &rpr::Server::getByteCount)
789 .def(
"stop", &rpr::Server::stop);
791 bp::implicitly_convertible<rpr::ServerPtr, rpr::CorePtr>();
792 bp::implicitly_convertible<rpr::ServerPtr, ris::MasterPtr>();
793 bp::implicitly_convertible<rpr::ServerPtr, ris::SlavePtr>();
Generic Rogue exception type.
char const * what() const
Returns exception text for standard exception handling.
static GeneralError create(std::string src, const char *fmt,...)
Creates a formatted error instance.
RAII helper that releases the Python GIL for a scope.
static std::shared_ptr< rogue::Logging > create(const std::string &name, bool quiet=false)
Creates a logger instance.
struct ibv_context * ctx_
uint32_t maxPayload() const
std::shared_ptr< rogue::interfaces::stream::Buffer > BufferPtr
Shared pointer alias for Buffer.
std::shared_ptr< rogue::interfaces::stream::Frame > FramePtr
Shared pointer alias for Frame.
std::shared_ptr< rogue::protocols::rocev2::Server > ServerPtr
static const uint32_t DefaultMaxPayload
static const uint32_t DefaultRxQueueDepth
static const uint8_t SsiSof