Skip to content

The verbs object model: device, PD, QP, CQ, MR

S1·E1The card that says InfiniBand on an Ethernet cable · Dell lab, Round Rock, day two of a five-day PoC

S1·E1Understand~25 minsources checked todayverified against rdma-core man pages (master) and NVIDIA DOCA-Host RoCE documentation, 2026-09-07

Before you read: what do you already know?

3 quick questions. Wrong answers are fine and expected; trying first makes the lesson stick.

After this lesson you can

  • Name every object an RDMA application creates and state which other object owns it.
  • Read an `ibv_devinfo` port block on a RoCE port and explain why it reports `transport: InfiniBand (0)` with `link_layer: Ethernet` and `port_lid: 0`.
  • Distinguish `lkey` from `rkey` and state the access-flag rule that governs one-sided writes.
  • Predict which destroy calls fail while another object still references them.

Episode 1 — The card that says InfiniBand on an Ethernet cable

The situation · Dell lab, Round Rock, day two of a five-day PoC

Sixty-four PowerEdge nodes, five days of lab time, and a medical-imaging archive that must leave its TCP storage tier before the customer’s quarter-end freeze. The Dell SE has a reheated coffee he never drinks and a spreadsheet of every promise he has made here. The network lead flew in with a thin notebook and no intention of believing anything twice.

The first command anyone runs is ibv_devinfo, and the port block comes back with transport: InfiniBand (0), link_layer: Ethernet, sm_lid: 0 and port_lid: 0. The network lead stops the meeting: he bought Ethernet switches, the card claims InfiniBand, and he wants to know what shipped.

Nothing is wrong. RoCE keeps the InfiniBand transport and swaps only the link, which is why transport reads InfiniBand while link_layer reports the medium.[2] The zeros are the same fact from another angle: the LID is a layer-2 attribute of the InfiniBand stack, displayed as zero on a RoCE port, because a RoCE fabric does not require a Subnet Manager at all.[2]

That is why the object model looks like this. Applications wanted the InfiniBand transport on Ethernet the customer already owns, so the verbs objects were left untouched: a device context, a protection domain inside it, queue pairs reporting into completion queues, and memory regions that return an lkey for local use and an rkey to hand the peer.[6][3]

Read link_layer for the cable, transport for the semantics: on a RoCE port they are supposed to disagree.

Ten minutes at the whiteboard before the storage test starts. It starts with the device.

1The device and its port, read two ways

Everything starts with a device context. ibv_devinfo is the tool that prints what the driver knows about it, with the synopsis ibv_devinfo [-d device] [-i port] [-l] [-v]; -d/--ib-dev=DEVICE selects one device, -i/--ib-port=PORT one port, -l/--list prints only the device names, and -v/--verbose prints “all available information about RDMA devices”.[1] On a RoCE port the block looks like this.[2]

hca_id: mlx5_0
        transport:                      InfiniBand (0)
        fw_ver:                         16.28.0578
        node_guid:                      ...
        vendor_id:                      0x02c9
        vendor_part_id:                 4121
        phys_port_cnt:                  1
                port:   1
                        state:                  PORT_ACTIVE (4)
                        max_mtu:                4096 (5)
                        active_mtu:             1024 (3)
                        sm_lid:                 0
                        port_lid:               0
                        port_lmc:               0x00
                        link_layer:             Ethernet

Two lines in that block cause most of the confusion in a first RoCE ticket. transport: InfiniBand (0) on an Ethernet port is normal — RoCE keeps the InfiniBand transport and only swaps the link; link_layer is the field that reports the medium.[2] And sm_lid/port_lid are zero because “LID is a layer 2 attribute of the InfiniBand protocol stack, it is not set for a port and is displayed as zero when querying the port”.[2] That follows from a bigger fact: for RoCE “the presence of a Subnet Manager (SM) is not required in the fabric”, which also means path queries are impossible and the path record must be filled in by hand or by RDMA-CM.[2]

The same facts are readable without the tool: cat /sys/class/infiniband/mlx5_0/ports/1/state returns ACTIVE and cat /sys/class/infiniband/mlx5_0/ports/1/link_layer returns Ethernet, while the firmware version sits one level up, at cat /sys/class/infiniband/mlx5_0/fw_ver.[2] Keep both routes in your hands — on a customer box where mlnx-tools is missing, sysfs still answers.

2Protection domain: the scope that makes a key mean something

A protection domain is not an object that does work — it is the boundary that makes the other objects safe. MRs, QPs, SRQs and address handles are all allocated inside a PD, and a key registered in one PD is meaningless to a QP in another.[6][3] The API is blunt about this in one place people trip over: ibv_advise_mr() takes the PD as its first argument, not the MR, because the advice applies to a scatter-gather list of keys that all live in that domain.[7]

Above the PD sits the extended device query, ibv_query_device_ex(), whose struct ibv_device_attr_ex carries orig_attr, comp_mask, odp_caps, completion_timestamp_mask, hca_core_clock, device_cap_flags_ex, tso_caps, rss_caps, max_wq_type_rq, packet_pacing_caps, raw_packet_caps, tm_caps, cq_mod_caps, max_dm_size, atomic_caps, xrc_odp_caps and phys_port_cnt_ex.[8] That structure is where you check whether a feature a customer is asking for exists on their firmware at all, rather than arguing about it.

RCReliable Connected: everything: SEND, WRITE, READ, atomics
lkey in sg_listsend_cq / recv_cqwr.ud.ah (UD)ibv_contextibv_open_device()ibv_pdibv_alloc_pd()ibv_cqon the contextibv_mrlkey · rkeyibv_qpSQ + RQibv_srqshared RQibv_ahUD address

Click a box (or Tab to it and press Enter) for its create call, its failure modes and the completion statuses it produces.

struct ibv_pd

Protection domain

ibv_alloc_pd(context)
Create-time fields
  • no create-time attributes — the PD is a scope, not a resource
Rules
  • A PD scopes MRs, QPs, SRQs and AHs together: objects from different PDs cannot be used with each other.
  • ibv_advise_mr() takes the PD as its first argument, not the MR — the classic API surprise.
  • Access checks on one-sided traffic are PD-scoped: the rkey the peer sends must belong to an MR in the QP's PD.
Fails when…
  • Using an MR's lkey on a QP from another PD → IBV_WC_LOC_PROT_ERR on the local side.
Completion statuses it produces
  • IBV_WC_LOC_PROT_ERR (4) — local key/PD mismatch
  • IBV_WC_REM_ACCESS_ERR (10) — rkey/access-flags problem on the peer
DOCA: Hidden inside doca_rdma: the DOCA context owns the PD, the app only sees doca_buf / doca_mmap.
FAE angle: When a customer reports LOC_PROT_ERR, ask which PD the MR and the QP came from before looking at the fabric — this error never leaves the host.
ibv_advise_mr(3)

Sources: ibv_devinfo(1) · ibv_create_qp(3) · ibv_create_cq_ex(3) · ibv_create_srq(3) · ibv_reg_mr(3) · ibv_poll_cq(3) · ibv_query_device_ex(3) · NVIDIA Optimized Memory Access · perftest

Click each node to see its create call, its create-time fields, its documented failure modes, and the completion statuses it produces. Start at the PD and follow the edges outward.

3QP and CQ: two queues, one completion ring

A queue pair is a send queue plus a receive queue. It is created from struct ibv_qp_init_attr, which is qp_context, send_cq, recv_cq, srq, a cap block, qp_type and sq_sig_all.[3] The cap block is max_send_wr, max_recv_wr, max_send_sge, max_recv_sge and max_inline_data.[3] Upstream verbs offers IBV_QPT_RC, IBV_QPT_UC, IBV_QPT_UD, IBV_QPT_RAW_PACKET and the vendor-specific IBV_QPT_DRIVER; the next lesson is about what each of them can actually do.[3] perftest exposes the same choice as -c, --connection=<RC/UC/UD/XRC/DC/SRD> with a default of RC, which is the fastest way to demonstrate the matrix without writing code.[12]

sq_sig_all decides how noisy the CQ is: “If set, each Work Request (WR) submitted to the SQ generates a completion entry”; unset it and only WRs flagged IBV_SEND_SIGNALED complete.[3] Attach a shared receive queue and two rules bite: creation “fails if attempting a QP type other than RC or UD with SRQ attachment”, and max_recv_wr/max_recv_sge are ignored because the SRQ owns them.[3]

The completion queue has its own surprises. ibv_create_cq_ex() “may create a CQ with size greater than or equal to the requested size” — read cqe back from the returned CQ instead of assuming.[4] The channel may be NULL if you will not use completion events, and comp_vector “must be >= 0 and < context->num_comp_vectors”.[4] An SRQ is created from struct ibv_srq_init_attr holding max_wr, max_sge and srq_limit, where srq_limit “is irrelevant for ibv_create_srq” and is armed later with ibv_modify_srq; both SRQ and CQ may return larger values than requested.[5]

Teardown has dependencies you should be able to recite: ibv_destroy_srq() fails while any QP is still associated with it, and ibv_destroy_qp() fails if the QP is still in a multicast group.[5][3]

4Memory registration: lkey, rkey, and what registration costs

ibv_reg_mr(pd, addr, length, access) pins a range of memory and gives the HCA a translation for it; ibv_reg_mr_iova() is the same with an explicit HCA virtual address.[6] The access flags are IBV_ACCESS_LOCAL_WRITE, REMOTE_WRITE, REMOTE_READ, REMOTE_ATOMIC, FLUSH_GLOBAL, FLUSH_PERSISTENT, MW_BIND, ZERO_BASED, ON_DEMAND, HUGETLB and RELAXED_ORDERING.[6] Two rules matter more than the list: “If IBV_ACCESS_REMOTE_WRITE or IBV_ACCESS_REMOTE_ATOMIC is set, then IBV_ACCESS_LOCAL_WRITE must be set too”, and “Local read access is always enabled for the MR” — there is no IBV_ACCESS_LOCAL_READ.[6]

Registration returns two keys. The lkey goes in the local QP’s scatter-gather entries; the rkey is what you hand the peer so it can do one-sided reads, writes and atomics against that range.[6] ibv_dereg_mr() fails while a memory window is still bound to the MR.[6]

Registration cost is page-pinning cost, which is why on-demand paging exists. NVIDIA describes ODP as “a technique to alleviate much of the shortcomings of memory registration”: an explicit ODP MR “does not need to have valid mappings at registration time”, and an implicit one gives the application “a special memory key that represents their complete address space”.[10] The implicit recipe from the man page is IBV_ACCESS_ON_DEMAND with addr 0 and length SIZE_MAX.[6] ODP moves the cost to first touch, so ibv_advise_mr(pd, IBV_ADVISE_MR_ADVICE_PREFETCH, IBV_ADVISE_MR_FLAG_FLUSH, sg_list, num_sge) exists to pay it early; with IBV_ADVISE_MR_FLAG_FLUSH the call is synchronous and the pages are guaranteed present in the HCA on success, without it the call is best-effort.[7] NVIDIA notes one flat limitation: “ODP does not support contiguous pages.”[10]

Finally, keep the vocabulary aligned with the DOCA side of the house, because Dell customers will mix them in one sentence: DOCA RDMA supports InfiniBand and “Ethernet using RoCE”, and on the BlueField platform the doca_dev handed to DOCA RDMA must be a scalable function - with one exception, the DPA datapath, which currently supports PFs only.[11]

RCReliable Connected: everything: SEND, WRITE, READ, atomics
lkey in sg_listsend_cq / recv_cqwr.ud.ah (UD)ibv_contextibv_open_device()ibv_pdibv_alloc_pd()ibv_cqon the contextibv_mrlkey · rkeyibv_qpSQ + RQibv_srqshared RQibv_ahUD address

Click a box (or Tab to it and press Enter) for its create call, its failure modes and the completion statuses it produces.

struct ibv_mr

Memory region

ibv_reg_mr(pd, addr, length, access)   # or ibv_reg_mr_iova(pd, addr, length, hca_va, access)
Create-time fields
  • access: IBV_ACCESS_LOCAL_WRITE · REMOTE_WRITE · REMOTE_READ · REMOTE_ATOMIC · MW_BIND · ZERO_BASED · ON_DEMAND · HUGETLB · RELAXED_ORDERING · FLUSH_GLOBAL · FLUSH_PERSISTENT
  • output: lkey (used locally in sg_list) and rkey (handed to the peer for one-sided ops)
Rules
  • Hard rule: "If IBV_ACCESS_REMOTE_WRITE or IBV_ACCESS_REMOTE_ATOMIC is set, then IBV_ACCESS_LOCAL_WRITE must be set too."
  • "Local read access is always enabled for the MR" — there is no IBV_ACCESS_LOCAL_READ.
  • Implicit ODP: set IBV_ACCESS_ON_DEMAND, addr = 0, length = SIZE_MAX → one key for the whole address space.
  • Prefetch with ibv_advise_mr(pd, IBV_ADVISE_MR_ADVICE_PREFETCH, IBV_ADVISE_MR_FLAG_FLUSH, sg_list, num_sge); without FLUSH it is best-effort.
Fails when…
  • ibv_dereg_mr() "fails if any memory window is still bound to this MR" — unbind the MW first.
  • NVIDIA states flatly: "ODP does not support contiguous pages."
Completion statuses it produces
  • IBV_WC_LOC_PROT_ERR (4) — bad lkey or the local buffer is outside the MR
  • IBV_WC_REM_ACCESS_ERR (10) — the peer's rkey is wrong or its MR lacks REMOTE_WRITE/REMOTE_READ
  • IBV_WC_MW_BIND_ERR (6) — memory-window bind failed
DOCA: doca_mmap + doca_buf_inventory; ODP statistics are read with the rdma statistic tool, not a private debugfs path.
FAE angle: "Why is my first message slow" and "why does registering 512 GB take forever" are the same question: registration is page-pinning cost. ODP removes the pin and moves the cost to first touch — which is why the PREFETCH advice exists.
ibv_reg_mr(3)

Sources: ibv_devinfo(1) · ibv_create_qp(3) · ibv_create_cq_ex(3) · ibv_create_srq(3) · ibv_reg_mr(3) · ibv_poll_cq(3) · ibv_query_device_ex(3) · NVIDIA Optimized Memory Access · perftest

Open the MR panel and read the completion statuses it can produce. Predict which status a wrong rkey on the peer generates before you look.

Ten minutes at the whiteboard

How it ended

You put the port block on the screen and read it line by line: the transport really is the InfiniBand transport, link_layer is the only field that names the cable, and the zero LIDs are an artefact with no Subnet Manager behind them.[2] Then the same two values from sysfs, so nobody thinks the tool is being polite.[2] The network lead writes two words in the notebook and signs off the hardware. What you say: “The transport is InfiniBand on purpose. Only link_layer tells you what the cable is, and yours says Ethernet.” That night the storage architect circulates tomorrow’s requirement list. Line nine: a node that misses a value must be able to pull it from a peer — and the tier is drawn entirely on one-sided writes.

Lab

Read-only on the Dell-lab ConnectX or BlueField-3 host. No configuration changes, so no rollback is required.

  1. Pre-flight inventory: ibv_devinfo -l to list device names, ibdev2netdev to map each device to its netdev, and ofed_info -s for the installed stack version. Record all three before touching anything.
  2. Capture the full port block: ibv_devinfo -v -d mlx5_0 > /tmp/devinfo-mlx5_0.txt. Expected: link_layer: Ethernet, state: PORT_ACTIVE (4), port_lid: 0.
  3. Compare max_mtu with active_mtu in that file and write both down. They are frequently different, and the difference is what perftest will use by default in module 3.
  4. Repeat the sysfs cross-check: cat /sys/class/infiniband/mlx5_0/ports/1/{state,link_layer} and cat /sys/class/infiniband/mlx5_0/fw_ver. Expected: values identical to the tool output.
  5. Confirm the device-to-netdev mapping you will need in every later lab: ibdev2netdev prints lines of the form mlx5_0 port 1 <===> eth2. If a device maps to no netdev, RoCE GIDs will not exist for it — that is the subject of lesson 4.

Retrieval check

10 questions from memory. Answer before looking anything up; misses become flashcards.

Explain it to a Dell SE

Explain to a Dell SE, in four sentences, what an RDMA application has to create before it can move a single byte, and why the RoCE port on their PowerEdge reports itself as an InfiniBand transport.

14 flashcards for this lesson — 0 in deck. Spaced review lives at /review.

Sources

Facts in this lesson were checked against rdma-core man pages (master) and NVIDIA DOCA-Host RoCE documentation, 2026-09-07. Dates are when each page was fetched.

  1. ibv_devinfo(1) - rdma-core man page · fetched 2026-09-07
  2. RDMA over Converged Ethernet (DOCA-Host) - generated PDF · fetched 2026-09-07
  3. ibv_create_qp(3) - rdma-core man page · fetched 2026-09-07
  4. ibv_create_cq_ex(3) - rdma-core man page · fetched 2026-09-07
  5. ibv_create_srq(3) - rdma-core man page · fetched 2026-09-07
  6. ibv_reg_mr(3) - rdma-core man page · fetched 2026-09-07
  7. ibv_advise_mr(3) - rdma-core man page · fetched 2026-09-07
  8. ibv_query_device_ex(3) - rdma-core man page · fetched 2026-09-07
  9. ibv_poll_cq(3) - rdma-core man page · fetched 2026-09-07
  10. Optimized Memory Access (ODP UMR MW) - MLNX_OFED 24.10-1.1.4.0 LTS · fetched 2026-09-07
  11. DOCA RDMA - DOCA-Host · fetched 2026-09-07
  12. linux-rdma/perftest README · fetched 2026-09-07

The same idea elsewhere

Other lessons that cover this ground, sometimes from another course's angle.