| # Pyverbs | |
| Pyverbs provides a Python API over rdma-core, the Linux userspace C API for | |
| the RDMA stack. | |
| ## Goals | |
| 1. Provide easier access to RDMA: RDMA has a steep learning curve as is and | |
| the C interface requires the user to initialize multiple structs before | |
| having usable objects. Pyverbs attempts to remove much of this overhead and | |
| provide a smoother user experience. | |
| 2. Improve our code by providing a test suite for rdma-core. This means that | |
| new features will be tested before merge, and it also means that users and | |
| distros will have tests for new and existing features, as well as the means | |
| to create them quickly. | |
| 3. Stay up-to-date with rdma-core - cover new features during development and | |
| provide a test / unit-test alongside the feature. | |
| ## Limitations | |
| Python handles memory for users. As a result, memory is allocated by Pyverbs | |
| when needed (e.g. user buffer for memory region). The memory will be accessible | |
| to the users, but not allocated or freed by them. | |
| ## Usage Examples | |
| Note that all examples use a hard-coded device name ('mlx5_0'). | |
| ##### Open an IB device | |
| Import the device module and open a device by name: | |
| ```python | |
| import pyverbs.device as d | |
| ctx = d.Context(name='mlx5_0') | |
| ``` | |
| 'ctx' is Pyverbs' equivalent to rdma-core's ibv_context. At this point, the IB | |
| device is already open and ready to use. | |
| ##### Query a device | |
| ```python | |
| import pyverbs.device as d | |
| ctx = d.Context(name='mlx5_0') | |
| attr = ctx.query_device() | |
| print(attr) | |
| FW version : 16.24.0185 | |
| Node guid : 9803:9b03:0000:e4c6 | |
| Sys image GUID : 9803:9b03:0000:e4c6 | |
| Max MR size : 0xffffffffffffffff | |
| Page size cap : 0xfffffffffffff000 | |
| Vendor ID : 0x2c9 | |
| Vendor part ID : 4119 | |
| HW version : 0 | |
| Max QP : 262144 | |
| Max QP WR : 32768 | |
| Device cap flags : 3983678518 | |
| Max SGE : 30 | |
| Max SGE RD : 30 | |
| MAX CQ : 16777216 | |
| Max CQE : 4194303 | |
| Max MR : 16777216 | |
| Max PD : 16777216 | |
| Max QP RD atom : 16 | |
| Max EE RD atom : 0 | |
| Max res RD atom : 4194304 | |
| Max QP init RD atom : 16 | |
| Max EE init RD atom : 0 | |
| Atomic caps : 1 | |
| Max EE : 0 | |
| Max RDD : 0 | |
| Max MW : 16777216 | |
| Max raw IPv6 QPs : 0 | |
| Max raw ethy QP : 0 | |
| Max mcast group : 2097152 | |
| Max mcast QP attach : 240 | |
| Max AH : 2147483647 | |
| Max FMR : 0 | |
| Max map per FMR : 2147483647 | |
| Max SRQ : 8388608 | |
| Max SRQ WR : 32767 | |
| Max SRQ SGE : 31 | |
| Max PKeys : 128 | |
| local CA ack delay : 16 | |
| Phys port count : 1 | |
| ``` | |
| 'attr' is Pyverbs' equivalent to ibv_device_attr. Pyverbs will provide it to | |
| the user upon completion of the call to ibv_query_device. | |
| ##### Query GID | |
| ```python | |
| import pyverbs.device as d | |
| ctx = d.Context(name='mlx5_0') | |
| gid = ctx.query_gid(port_num=1, index=3) | |
| print(gid) | |
| 0000:0000:0000:0000:0000:ffff:0b87:3c08 | |
| ``` | |
| 'gid' is Pyverbs' equivalent to ibv_gid, provided to the user by Pyverbs. | |
| ##### Query port | |
| The following code snippet provides an example of pyverbs' equivalent of | |
| querying a port. Context's query_port() command wraps ibv_query_port(). | |
| The example below queries the first port of the device. | |
| ```python | |
| import pyverbs.device as d | |
| ctx=d.Context(name='mlx5_0') | |
| port_attr = ctx.query_port(1) | |
| print(port_attr) | |
| Port state : Active (4) | |
| Max MTU : 4096 (5) | |
| Active MTU : 1024 (3) | |
| SM lid : 0 | |
| Port lid : 0 | |
| lmc : 0x0 | |
| Link layer : Ethernet | |
| Max message size : 0x40000000 | |
| Port cap flags : IBV_PORT_CM_SUP IBV_PORT_IP_BASED_GIDS | |
| Port cap flags 2 : | |
| max VL num : 0 | |
| Bad Pkey counter : 0 | |
| Qkey violations counter : 0 | |
| Gid table len : 256 | |
| Pkey table len : 1 | |
| SM sl : 0 | |
| Subnet timeout : 0 | |
| Init type reply : 0 | |
| Active width : 4X (2) | |
| Ative speed : 25.0 Gbps (32) | |
| Phys state : Link up (5) | |
| Flags : 1 | |
| ``` | |
| ##### Extended query device | |
| The example below shows how to open a device using pyverbs and query the | |
| extended device's attributes. | |
| Context's query_device_ex() command wraps ibv_query_device_ex(). | |
| ```python | |
| import pyverbs.device as d | |
| ctx = d.Context(name='mlx5_0') | |
| attr = ctx.query_device_ex() | |
| attr.max_dm_size | |
| 131072 | |
| attr.rss_caps.max_rwq_indirection_table_size | |
| 2048 | |
| ``` | |
| #### Create RDMA objects | |
| ##### PD | |
| The following example shows how to open a device and use its context to create | |
| a PD. | |
| ```python | |
| import pyverbs.device as d | |
| from pyverbs.pd import PD | |
| with d.Context(name='mlx5_0') as ctx: | |
| pd = PD(ctx) | |
| ``` | |
| ##### MR | |
| The example below shows how to create a MR using pyverbs. Similar to C, a | |
| device must be opened prior to creation and a PD has to be allocated. | |
| ```python | |
| import pyverbs.device as d | |
| from pyverbs.pd import PD | |
| from pyverbs.mr import MR | |
| from pyverbs.libibverbs_enums import ibv_access_flags | |
| with d.Context(name='mlx5_0') as ctx: | |
| with PD(ctx) as pd: | |
| mr_len = 1000 | |
| flags = ibv_access_flags.IBV_ACCESS_LOCAL_WRITE | |
| mr = MR(pd, mr_len, flags) | |
| ``` | |
| ##### Memory window | |
| The following example shows the equivalent of creating a type 1 memory window. | |
| It includes opening a device and allocating the necessary PD. | |
| The user should unbind or close the memory window before being able to | |
| deregister an MR that the MW is bound to. | |
| ```python | |
| import pyverbs.device as d | |
| from pyverbs.pd import PD | |
| from pyverbs.mr import MW | |
| from pyverbs.libibverbs_enums import ibv_mw_type | |
| with d.Context(name='mlx5_0') as ctx: | |
| with PD(ctx) as pd: | |
| mw = MW(pd, ibv_mw_type.IBV_MW_TYPE_1) | |
| ``` | |
| ##### Device memory | |
| The following snippet shows how to allocate a DM - a direct memory object, | |
| using the device's memory. | |
| ```python | |
| import random | |
| from pyverbs.device import DM, AllocDmAttr | |
| import pyverbs.device as d | |
| with d.Context(name='mlx5_0') as ctx: | |
| attr = ctx.query_device_ex() | |
| if attr.max_dm_size != 0: | |
| dm_len = random.randint(4, attr.max_dm_size) | |
| dm_attrs = AllocDmAttr(dm_len) | |
| dm = DM(ctx, dm_attrs) | |
| ``` | |
| ##### DM MR | |
| The example below shows how to open a DMMR - device memory MR, using the | |
| device's own memory rather than a user-allocated buffer. | |
| ```python | |
| import random | |
| from pyverbs.device import DM, AllocDmAttr | |
| from pyverbs.mr import DMMR | |
| import pyverbs.device as d | |
| from pyverbs.pd import PD | |
| from pyverbs.libibverbs_enums import ibv_access_flags | |
| with d.Context(name='mlx5_0') as ctx: | |
| attr = ctx.query_device_ex() | |
| if attr.max_dm_size != 0: | |
| dm_len = random.randint(4, attr.max_dm_size) | |
| dm_attrs = AllocDmAttr(dm_len) | |
| dm_mr_len = random.randint(4, dm_len) | |
| with DM(ctx, dm_attrs) as dm: | |
| with PD(ctx) as pd: | |
| dm_mr = DMMR(pd, dm_mr_len, ibv_access_flags.IBV_ACCESS_ZERO_BASED, dm=dm, | |
| offset=0) | |
| ``` | |
| ##### CQ | |
| The following snippets show how to create CQs using pyverbs. Pyverbs supports | |
| both CQ and extended CQ (CQEX). | |
| As in C, a completion queue can be created with or without a completion | |
| channel, the snippets show that. | |
| CQ's 3rd parameter is cq_context, a user-defined context. We're using None in | |
| our snippets. | |
| ```python | |
| import random | |
| from pyverbs.cq import CompChannel, CQ | |
| import pyverbs.device as d | |
| with d.Context(name='mlx5_0') as ctx: | |
| num_cqes = random.randint(0, 200) # Just arbitrary values. Max value can be | |
| # found in device attributes | |
| comp_vector = 0 # An arbitrary value. comp_vector is limited by the | |
| # context's num_comp_vectors | |
| if random.choice([True, False]): | |
| with CompChannel(ctx) as cc: | |
| cq = CQ(ctx, num_cqes, None, cc, comp_vector) | |
| else: | |
| cq = CQ(ctx, num_cqes, None, None, comp_vector) | |
| print(cq) | |
| CQ | |
| Handle : 0 | |
| CQEs : 63 | |
| ``` | |
| ```python | |
| import random | |
| from pyverbs.cq import CqInitAttrEx, CQEX | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_create_cq_wc_flags | |
| with d.Context(name='mlx5_0') as ctx: | |
| num_cqe = random.randint(0, 200) | |
| wc_flags = ibv_create_cq_wc_flags.IBV_WC_EX_WITH_CVLAN | |
| comp_mask = 0 # Not using flags in this example | |
| # completion channel is not used in this example | |
| attrs = CqInitAttrEx(cqe=num_cqe, wc_flags=wc_flags, comp_mask=comp_mask, | |
| flags=0) | |
| print(attrs) | |
| cq_ex = CQEX(ctx, attrs) | |
| print(cq_ex) | |
| Number of CQEs : 10 | |
| WC flags : IBV_WC_EX_WITH_CVLAN | |
| comp mask : 0 | |
| flags : 0 | |
| Extended CQ: | |
| Handle : 0 | |
| CQEs : 15 | |
| ``` | |
| ##### Addressing related objects | |
| The following code demonstrates creation of GlobalRoute, AHAttr and AH objects. | |
| The example creates a global AH so it can also run on RoCE without | |
| modifications. | |
| ```python | |
| from pyverbs.addr import GlobalRoute, AHAttr, AH | |
| import pyverbs.device as d | |
| from pyverbs.pd import PD | |
| with d.Context(name='mlx5_0') as ctx: | |
| port_number = 1 | |
| gid_index = 0 # GID index 0 always exists and valid | |
| gid = ctx.query_gid(port_number, gid_index) | |
| gr = GlobalRoute(dgid=gid, sgid_index=gid_index) | |
| ah_attr = AHAttr(gr=gr, is_global=1, port_num=port_number) | |
| print(ah_attr) | |
| with PD(ctx) as pd: | |
| ah = AH(pd, attr=ah_attr) | |
| DGID : fe80:0000:0000:0000:9a03:9bff:fe00:e4bf | |
| flow label : 0 | |
| sgid index : 0 | |
| hop limit : 1 | |
| traffic class : 0 | |
| ``` | |
| ##### QP | |
| The following snippets will demonstrate creation of a QP and a simple post_send | |
| operation. For more complex examples, please see pyverbs/examples section. | |
| ```python | |
| from pyverbs.qp import QPCap, QPInitAttr, QPAttr, QP | |
| from pyverbs.addr import GlobalRoute | |
| from pyverbs.addr import AH, AHAttr | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_qp_type | |
| from pyverbs.pd import PD | |
| from pyverbs.cq import CQ | |
| import pyverbs.wr as pwr | |
| ctx = d.Context(name='mlx5_0') | |
| pd = PD(ctx) | |
| cq = CQ(ctx, 100, None, None, 0) | |
| cap = QPCap(100, 10, 1, 1, 0) | |
| qia = QPInitAttr(cap=cap, qp_type = ibv_qp_type.IBV_QPT_UD, scq=cq, rcq=cq) | |
| # A UD QP will be in RTS if a QPAttr object is provided | |
| udqp = QP(pd, qia, QPAttr()) | |
| port_num = 1 | |
| gid_index = 3 # Hard-coded for RoCE v2 interface | |
| gid = ctx.query_gid(port_num, gid_index) | |
| gr = GlobalRoute(dgid=gid, sgid_index=gid_index) | |
| ah_attr = AHAttr(gr=gr, is_global=1, port_num=port_num) | |
| ah=AH(pd, ah_attr) | |
| wr = pwr.SendWR() | |
| wr.set_wr_ud(ah, 0x1101, 0) # in real life, use real values | |
| udqp.post_send(wr) | |
| ``` | |
| ###### Extended QP | |
| An extended QP exposes a new set of QP send operations to the user - | |
| extensibility for new send opcodes, vendor specific send opcodes and even vendor | |
| specific QP types. | |
| Pyverbs now exposes the needed interface to create such a QP. | |
| Note that the IBV_QP_INIT_ATTR_SEND_OPS_FLAGS in the `comp_mask` is mandatory | |
| when using the extended QP's new post send mechanism. | |
| ```python | |
| from pyverbs.qp import QPCap, QPInitAttrEx, QPAttr, QPEx | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_qp_type, ibv_qp_init_attr_mask | |
| from pyverbs.pd import PD | |
| from pyverbs.cq import CQ | |
| ctx = d.Context(name='mlx5_0') | |
| pd = PD(ctx) | |
| cq = CQ(ctx, 100) | |
| cap = QPCap(100, 10, 1, 1, 0) | |
| qia = QPInitAttrEx(qp_type=ibv_qp_type.IBV_QPT_UD, scq=cq, rcq=cq, cap=cap, pd=pd, | |
| comp_mask=ibv_qp_init_attr_mask.IBV_QP_INIT_ATTR_SEND_OPS_FLAGS| \ | |
| ibv_qp_init_attr_mask.IBV_QP_INIT_ATTR_PD) | |
| qp = QPEx(ctx, qia) | |
| ``` | |
| ##### XRCD | |
| The following code demonstrates creation of an XRCD object. | |
| ```python | |
| from pyverbs.xrcd import XRCD, XRCDInitAttr | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_xrcd_init_attr_mask | |
| import stat | |
| import os | |
| ctx = d.Context(name='ibp0s8f0') | |
| xrcd_fd = os.open('/tmp/xrcd', os.O_RDONLY | os.O_CREAT, | |
| stat.S_IRUSR | stat.S_IRGRP) | |
| init = XRCDInitAttr(ibv_xrcd_init_attr_mask.IBV_XRCD_INIT_ATTR_FD | ibv_xrcd_init_attr_mask.IBV_XRCD_INIT_ATTR_OFLAGS, | |
| os.O_CREAT, xrcd_fd) | |
| xrcd = XRCD(ctx, init) | |
| ``` | |
| ##### SRQ | |
| The following code snippet will demonstrate creation of an XRC SRQ object. | |
| For more complex examples, please see pyverbs/tests/test_odp. | |
| ```python | |
| from pyverbs.xrcd import XRCD, XRCDInitAttr | |
| from pyverbs.srq import SRQ, SrqInitAttrEx | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_xrcd_init_attr_mask, ibv_srq_type, ibv_srq_init_attr_mask | |
| from pyverbs.cq import CQ | |
| from pyverbs.pd import PD | |
| import stat | |
| import os | |
| ctx = d.Context(name='ibp0s8f0') | |
| pd = PD(ctx) | |
| cq = CQ(ctx, 100, None, None, 0) | |
| xrcd_fd = os.open('/tmp/xrcd', os.O_RDONLY | os.O_CREAT, | |
| stat.S_IRUSR | stat.S_IRGRP) | |
| init = XRCDInitAttr(ibv_xrcd_init_attr_mask.IBV_XRCD_INIT_ATTR_FD | ibv_xrcd_init_attr_mask.IBV_XRCD_INIT_ATTR_OFLAGS, | |
| os.O_CREAT, xrcd_fd) | |
| xrcd = XRCD(ctx, init) | |
| srq_attr = SrqInitAttrEx(max_wr=10) | |
| srq_attr.srq_type = ibv_srq_type.IBV_SRQT_XRC | |
| srq_attr.pd = pd | |
| srq_attr.xrcd = xrcd | |
| srq_attr.cq = cq | |
| srq_attr.comp_mask = ibv_srq_init_attr_mask.IBV_SRQ_INIT_ATTR_TYPE | ibv_srq_init_attr_mask.IBV_SRQ_INIT_ATTR_PD | \ | |
| ibv_srq_init_attr_mask.IBV_SRQ_INIT_ATTR_CQ | ibv_srq_init_attr_mask.IBV_SRQ_INIT_ATTR_XRCD | |
| srq = SRQ(ctx, srq_attr) | |
| ``` | |
| ##### Open an mlx5 provider | |
| A provider is essentially a Context with driver-specific extra features. As | |
| such, it inherits from Context. In legacy flow Context iterates over the IB | |
| devices and opens the one matches the name given by the user (name= argument). | |
| When provider attributes are also given (attr=), the Context will assign the | |
| relevant ib_device to its device member, so that the provider will be able to | |
| open the device in its specific way as demonstated below: | |
| ```python | |
| import pyverbs.providers.mlx5.mlx5dv as m | |
| from pyverbs.pd import PD | |
| attr = m.Mlx5DVContextAttr() # Default values are fine | |
| ctx = m.Mlx5Context(attr=attr, name='rocep0s8f0') | |
| # The provider context can be used as a regular Context, e.g.: | |
| pd = PD(ctx) # Success | |
| ``` | |
| ##### Query an mlx5 provider | |
| After opening an mlx5 provider, users can use the device-specific query for | |
| non-legacy attributes. The following snippet demonstrates how to do that. | |
| ```python | |
| import pyverbs.providers.mlx5.mlx5dv as m | |
| ctx = m.Mlx5Context(attr=m.Mlx5DVContextAttr(), name='ibp0s8f0') | |
| mlx5_attrs = ctx.query_mlx5_device() | |
| print(mlx5_attrs) | |
| Version : 0 | |
| Flags : CQE v1, Support CQE 128B compression, Support CQE 128B padding, Support packet based credit mode (in RC QP) | |
| comp mask : CQE compression, SW parsing, Striding RQ, Tunnel offloads, Dynamic BF regs, Clock info update, Flow action flags | |
| CQE compression caps: | |
| max num : 64 | |
| supported formats : with hash, with RX checksum CSUM, with stride index | |
| SW parsing caps: | |
| SW parsing offloads : | |
| supported QP types : | |
| Striding RQ caps: | |
| min single stride log num of bytes: 6 | |
| max single stride log num of bytes: 13 | |
| min single wqe log num of strides: 9 | |
| max single wqe log num of strides: 16 | |
| supported QP types : Raw Packet | |
| Tunnel offloads caps: | |
| Max dynamic BF registers: 1024 | |
| Max clock info update [nsec]: 1099511 | |
| Flow action flags : 0 | |
| ``` | |
| ##### Create an mlx5 QP | |
| Using an Mlx5Context object, one can create either a legacy QP (creation | |
| process is the same) or an mlx5 QP. An mlx5 QP is a QP by inheritance but its | |
| constructor receives a keyword argument named `dv_init_attr`. If the user | |
| provides it, the QP will be created using `mlx5dv_create_qp` rather than | |
| `ibv_create_qp_ex`. The following snippet demonstrates how to create both a DC | |
| (dynamically connected) QP and a Raw Packet QP which uses mlx5-specific | |
| capabilities, unavailable using the legacy interface. Currently, pyverbs | |
| supports only creation of a DCI. DCT support will be added in one of the | |
| following PRs. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5DVQPInitAttr, Mlx5QP | |
| from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_qp_init_attr_mask, mlx5dv_dc_type, mlx5dv_qp_create_flags | |
| from pyverbs.qp import QPInitAttrEx, QPCap | |
| from pyverbs.libibverbs_enums import ibv_qp_type, ibv_qp_init_attr_mask, ibv_qp_type | |
| from pyverbs.cq import CQ | |
| from pyverbs.pd import PD | |
| with Mlx5Context(name='rocep0s8f0', attr=Mlx5DVContextAttr()) as ctx: | |
| with PD(ctx) as pd: | |
| with CQ(ctx, 100) as cq: | |
| cap = QPCap(100, 0, 1, 0) | |
| # Create a DC QP of type DCI | |
| qia = QPInitAttrEx(cap=cap, pd=pd, scq=cq, qp_type=ibv_qp_type.IBV_QPT_DRIVER, | |
| comp_mask=ibv_qp_init_attr_mask.IBV_QP_INIT_ATTR_PD, rcq=cq) | |
| attr = Mlx5DVQPInitAttr(comp_mask=mlx5dv_qp_init_attr_mask.MLX5DV_QP_INIT_ATTR_MASK_DC) | |
| attr.dc_type = mlx5dv_dc_type.MLX5DV_DCTYPE_DCI | |
| dci = Mlx5QP(ctx, qia, dv_init_attr=attr) | |
| # Create a Raw Packet QP using mlx5-specific capabilities | |
| qia.qp_type = ibv_qp_type.IBV_QPT_RAW_PACKET | |
| attr.comp_mask = mlx5dv_qp_init_attr_mask.MLX5DV_QP_INIT_ATTR_MASK_QP_CREATE_FLAGS | |
| attr.create_flags = mlx5dv_qp_create_flags.MLX5DV_QP_CREATE_ALLOW_SCATTER_TO_CQE | \ | |
| mlx5dv_qp_create_flags.MLX5DV_QP_CREATE_TIR_ALLOW_SELF_LOOPBACK_UC | \ | |
| mlx5dv_qp_create_flags.MLX5DV_QP_CREATE_TUNNEL_OFFLOADS | |
| qp = Mlx5QP(ctx, qia, dv_init_attr=attr) | |
| ``` | |
| ##### Create an mlx5 CQ | |
| Mlx5Context also allows users to create an mlx5 specific CQ. The Mlx5CQ inherits | |
| from CQEX, but its constructor receives 3 parameters instead of 2. The 3rd | |
| parameter is a keyword argument named `dv_init_attr`. If provided by the user, | |
| the CQ will be created using `mlx5dv_create_cq`. | |
| The following snippet shows this simple creation process. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5DVCQInitAttr, Mlx5CQ | |
| from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_cq_init_attr_mask, mlx5dv_cqe_comp_res_format | |
| from pyverbs.cq import CqInitAttrEx | |
| with Mlx5Context(name='rocep0s8f0', attr=Mlx5DVContextAttr()) as ctx: | |
| cqia = CqInitAttrEx() | |
| mlx5_cqia = Mlx5DVCQInitAttr(comp_mask=mlx5dv_cq_init_attr_mask.MLX5DV_CQ_INIT_ATTR_MASK_COMPRESSED_CQE, | |
| cqe_comp_res_format=mlx5dv_cqe_comp_res_format.MLX5DV_CQE_RES_FORMAT_CSUM) | |
| cq = Mlx5CQ(ctx, cqia, dv_init_attr=mlx5_cqia) | |
| ``` | |
| ##### CMID | |
| The following code snippet will demonstrate creation of a CMID object, which | |
| represents rdma_cm_id C struct, and establish connection between two peers. | |
| Currently only synchronous control path is supported (rdma_create_ep). | |
| For more complex examples, please see tests/test_rdmacm. | |
| ```python | |
| from pyverbs.qp import QPInitAttr, QPCap | |
| from pyverbs.cmid import CMID, AddrInfo | |
| from pyverbs.librdmacm_enums import rdma_port_space, RAI_PASSIVE | |
| cap = QPCap(max_recv_wr=1) | |
| qp_init_attr = QPInitAttr(cap=cap) | |
| addr = '11.137.14.124' | |
| port = '7471' | |
| # Passive side | |
| sai = AddrInfo(src=addr, src_service=port, port_space=rdma_port_space.RDMA_PS_TCP, flags=RAI_PASSIVE) | |
| sid = CMID(creator=sai, qp_init_attr=qp_init_attr) | |
| sid.listen() # listen for incoming connection requests | |
| new_id = sid.get_request() # check if there are any connection requests | |
| new_id.accept() # new_id is connected to remote peer and ready to communicate | |
| # Active side | |
| cai = AddrInfo(src=addr, dst=addr, dst_service=port, port_space=rdma_port_space.RDMA_PS_TCP) | |
| cid = CMID(creator=cai, qp_init_attr=qp_init_attr) | |
| cid.connect() # send connection request to passive addr | |
| ``` | |
| ##### ParentDomain | |
| The following code demonstrates the creation of Parent Domain object. | |
| In this example, a simple Python allocator is defined. It uses MemAlloc class to | |
| allocate aligned memory using a C style aligned_alloc. | |
| ```python | |
| from pyverbs.pd import PD, ParentDomainInitAttr, ParentDomain, \ | |
| ParentDomainContext | |
| from pyverbs.device import Context | |
| import pyverbs.mem_alloc as mem | |
| def alloc_p_func(pd, context, size, alignment, resource_type): | |
| p = mem.posix_memalign(size, alignment) | |
| return p | |
| def free_p_func(pd, context, ptr, resource_type): | |
| mem.free(ptr) | |
| ctx = Context(name='rocep0s8f0') | |
| pd = PD(ctx) | |
| pd_ctx = ParentDomainContext(pd, alloc_p_func, free_p_func) | |
| pd_attr = ParentDomainInitAttr(pd=pd, pd_context=pd_ctx) | |
| parent_domain = ParentDomain(ctx, attr=pd_attr) | |
| ``` | |
| ##### MLX5 VAR | |
| The following code snippet demonstrates how to allocate an mlx5dv_var then using | |
| it for memory address mapping, then freeing the VAR. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5VAR | |
| from pyverbs.device import Context | |
| import mmap | |
| ctx = Context(name='rocep0s8f0') | |
| var = Mlx5VAR(ctx) | |
| var_map = mmap.mmap(fileno=ctx.cmd_fd, length=var.length, offset=var.mmap_off) | |
| # There is no munmap method in mmap Python module, but by closing the mmap | |
| # instance the memory is unmapped. | |
| var_map.close() | |
| var.close() | |
| ``` | |
| ##### MLX5 PP | |
| Packet Pacing (PP) entry can be used for some device commands over the DEVX | |
| interface. It allows a rate-limited flow configuration on SQs. | |
| The following code snippet demonstrates how to allocate an mlx5dv_pp with rate | |
| limit value of 5, then frees the entry. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr, Mlx5PP | |
| from pyverbs.providers.mlx5.mlx5_enums import mlx5dv_context_attr_flags | |
| # The device must be opened as DEVX context | |
| mlx5dv_attr = Mlx5DVContextAttr(mlx5dv_context_attr_flags.MLX5DV_CONTEXT_FLAGS_DEVX) | |
| ctx = Mlx5Context(attr=mlx5dv_attr, name='rocep0s8f0') | |
| rate_limit_inbox = (5).to_bytes(length=4, byteorder='big', signed=True) | |
| pp = Mlx5PP(ctx, rate_limit_inbox) | |
| pp.close() | |
| ``` | |
| ##### MLX5 UAR | |
| User Access Region (UAR) is part of PCI address space that is mapped for direct | |
| access to the HCA from the CPU. | |
| The UAR is needed for some device commands over the DevX interface. | |
| The following code snippet demonstrates how to allocate and free an | |
| mlx5dv_devx_uar. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5UAR | |
| from pyverbs.device import Context | |
| ctx = Context(name='rocep0s8f0') | |
| uar = Mlx5UAR(ctx) | |
| uar.close() | |
| ``` | |
| ##### Import device, PD and MR | |
| Importing a device, PD and MR enables processes to share their context and then | |
| share PDs and MRs that is associated with. | |
| A process creates a device and then uses some of the Linux systems calls to dup | |
| its 'cmd_fd' member which lets other process to obtain ownership. | |
| Once other process obtains the 'cmd_fd' it can import the device, then PD(s) and | |
| MR(s) to share these objects. | |
| Like in C, Pyverbs users are responsible for unimporting the imported objects | |
| (which will also close the Pyverbs instance in our case) after they finish using | |
| them, and they have to sync between the different processes in order to | |
| coordinate the closure of the objects. | |
| Unlike in C, closing the underlying objects is currently supported only via the | |
| "original" object (meaning only by the process that creates them) and not via | |
| the imported object. This limitation is made because currently there's no | |
| reference or relation between different Pyverbs objects in different processes. | |
| But it's doable and might be added in the future. | |
| Here is a demonstration of importing a device, PD and MR in one process. | |
| ```python | |
| from pyverbs.device import Context | |
| from pyverbs.pd import PD | |
| from pyverbs.mr import MR | |
| from pyverbs.libibverbs_enums import ibv_access_flags | |
| import os | |
| ctx = Context(name='ibp0s8f0') | |
| pd = PD(ctx) | |
| mr = MR(pd, 100, ibv_access_flags.IBV_ACCESS_LOCAL_WRITE) | |
| cmd_fd_dup = os.dup(ctx.cmd_fd) | |
| imported_ctx = Context(cmd_fd=cmd_fd_dup) | |
| imported_pd = PD(imported_ctx, handle=pd.handle) | |
| imported_mr = MR(imported_pd, handle=mr.handle) | |
| # MRs can be created as usual on the imported PD | |
| secondary_mr = MR(imported_pd, 100, ibv_access_flags.IBV_ACCESS_REMOTE_READ) | |
| # Must manually unimport the imported objects (which close the object and frees | |
| # other resources that use them) before closing the "original" objects. | |
| # This prevents unexpected behaviours caused by the GC. | |
| imported_mr.unimport() | |
| imported_pd.unimport() | |
| ``` | |
| ##### Flow Steering | |
| Flow steering rules define packet matching done by the hardware. | |
| A spec describes packet matching on a specific layer (L2, L3 etc.). | |
| A flow is a collection of specs. | |
| A user QP can attach to flows in order to receive specific packets. | |
| ###### Flow and FlowAttr | |
| ```python | |
| from pyverbs.qp import QPCap, QPInitAttr, QPAttr, QP | |
| from pyverbs.flow import FlowAttr, Flow | |
| from pyverbs.spec import EthSpec | |
| import pyverbs.device as d | |
| from pyverbs.libibverbs_enums import ibv_qp_type | |
| from pyverbs.pd import PD | |
| from pyverbs.cq import CQ | |
| ctx = d.Context(name='rocep0s8f0') | |
| pd = PD(ctx) | |
| cq = CQ(ctx, 100, None, None, 0) | |
| cap = QPCap(100, 10, 1, 1, 0) | |
| qia = QPInitAttr(cap=cap, qp_type = ibv_qp_type.IBV_QPT_UD, scq=cq, rcq=cq) | |
| qp = QP(pd, qia, QPAttr()) | |
| # Create Eth spec | |
| eth_spec = EthSpec(ether_type=0x800, dst_mac="01:50:56:19:20:a7") | |
| eth_spec.src_mac = "24:8a:07:a5:28:c8" | |
| eth_spec.src_mac_mask = "ff:ff:ff:ff:ff:ff" | |
| # Create Flow | |
| flow_attr = FlowAttr(num_of_specs=1) | |
| flow_attr.specs.append(eth_spec) | |
| flow = Flow(qp, flow_attr) | |
| ``` | |
| ###### Specs | |
| Each spec holds a specific network layer parameters for matching. To enforce | |
| the match, the user sets a mask for each parameter. If the bit is set in the | |
| mask, the corresponding bit in the value should be matched. | |
| Packets coming from the wire are matched against the flow specification. If a | |
| match is found, the associated flow actions are executed on the packet. In | |
| ingress flows, the QP parameter is treated as another action of scattering the | |
| packet to the respected QP. | |
| ###### Notes | |
| * When creating specs mask will be set to FF's to all the given values (unless | |
| provided by the user). When editing a spec mask should be specified explicitly. | |
| * If a field is not provided its value and mask will be set to zeros. | |
| * Hardware only supports full / empty masks. | |
| * Ethernet, IPv4, TCP/UDP, IPv6 and ESP specs can be inner (IBV_FLOW_SPEC_INNER), | |
| but set to outer by default. | |
| ###### Ethernet spec | |
| Example of creating and editing Ethernet spec | |
| ```python | |
| from pyverbs.spec import EthSpec | |
| eth_spec = EthSpec(src_mac="ab:cd:ef:ab:cd:ef", vlan_tag=0x123, is_inner=1) | |
| eth_spec.dst_mac = "de:de:de:00:de:de" | |
| eth_spec.dst_mac_mask = "ff:ff:ff:ff:ff:ff" | |
| eth_spec.ether_type = 0x321 | |
| eth_spec.ether_type_mask = 0xffff | |
| # Resulting spec | |
| print(f'{eth_spec}') | |
| ``` | |
| Below is the output when printing the spec. | |
| Spec type : IBV_FLOW_SPEC_INNER IBV_FLOW_SPEC_ETH | |
| Size : 40 | |
| Src mac : ab:cd:ef:ab:cd:ef mask: ff:ff:ff:ff:ff:ff | |
| Dst mac : de:de:de:00:de:de mask: ff:ff:ff:ff:ff:ff | |
| Ether type : 8451 mask: 65535 | |
| Vlan tag : 8961 mask: 65535 | |
| ##### MLX5 DevX Objects | |
| A DevX object represents some underlay firmware object, the input command to | |
| create it is some raw data given by the user application which should match the | |
| device specification. | |
| Upon successful creation, the output buffer includes the raw data from the device | |
| according to its specification and is stored in the Mlx5DevxObj instance. This | |
| data can be used as part of related firmware commands to this object. | |
| In addition to creation, the user can query/modify and destroy the object. | |
| Although weakrefs and DevX objects closure are added and handled by | |
| Pyverbs, the users must manually close these objects when finished, and | |
| should not let them be handled by the GC, or by closing the Mlx5Context directly, | |
| since there's no guarantee that the DevX objects are closed in the correct order, | |
| because Mlx5DevxObj is a general class that can be any of the device's available | |
| objects. | |
| But Pyverbs does guarantee to close DevX UARs and UMEMs in order, and after | |
| closing the other DevX objects. | |
| The following code snippet shows how to allocate and destroy a PD object over DevX. | |
| ```python | |
| from pyverbs.providers.mlx5.mlx5dv import Mlx5Context, Mlx5DVContextAttr, Mlx5DevxObj | |
| import pyverbs.providers.mlx5.mlx5_enums as dve | |
| import struct | |
| attr = Mlx5DVContextAttr(dve.MLX5DV_CONTEXT_FLAGS_DEVX) | |
| ctx = Mlx5Context(attr, 'rocep8s0f0') | |
| MLX5_CMD_OP_ALLOC_PD = 0x800 | |
| MLX5_CMD_OP_ALLOC_PD_OUTLEN = 0x10 | |
| cmd_in = struct.pack('!H14s', MLX5_CMD_OP_ALLOC_PD, bytes(0)) | |
| pd = Mlx5DevxObj(ctx, cmd_in, MLX5_CMD_OP_ALLOC_PD_OUTLEN) | |
| pd.close() | |
| ``` |