blob: 583ad5df71b202db19a7c3f3d23ce1855940397c [file] [log] [blame]
# SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB)
# Copyright (c) 2021 Nvidia, Inc. All rights reserved. See COPYING file
"""
Test module for pyverbs' mlx5_vfio module.
"""
from threading import Thread
import unittest
import logging
import struct
import select
import errno
import time
import math
import os
from tests.mlx5_base import Mlx5DevxRcResources, Mlx5DevxTrafficBase, PortState, \
PortStatus, PORT_STATE_TIMEOUT
from pyverbs.providers.mlx5.mlx5dv import Mlx5DevxMsiVector, Mlx5DevxEq, Mlx5UAR
from pyverbs.providers.mlx5.mlx5_vfio import Mlx5VfioAttr, Mlx5VfioContext
from pyverbs.pyverbs_error import PyverbsRDMAError
import pyverbs.providers.mlx5.mlx5_enums as dve
from pyverbs.base import PyverbsRDMAErrno
import pyverbs.mem_alloc as mem
import pyverbs.dma_util as dma
class Mlx5VfioResources(Mlx5DevxRcResources):
def __init__(self, ib_port, pci_name, gid_index=None, ctx=None, activate_port_state=False):
self.pci_name = pci_name
self.ctx = ctx
super().__init__(None, ib_port, gid_index, activate_port_state=activate_port_state)
def create_context(self):
"""
Opens an mlx5 VFIO context.
Since only one context is allowed to be opened on a VFIO, the user must
pass that context for the remaining resources, which in that case, the
same context would be used.
:return: None
"""
if self.ctx:
return
try:
vfio_attr = Mlx5VfioAttr(pci_name=self.pci_name)
vfio_attr.pci_name = self.pci_name
self.ctx = Mlx5VfioContext(attr=vfio_attr)
except PyverbsRDMAError as ex:
if ex.error_code == errno.EOPNOTSUPP:
raise unittest.SkipTest(f'Mlx5 VFIO is not supported ({ex})')
raise ex
def query_gid(self):
"""
Currently Mlx5VfioResources does not support Eth port type.
Query GID would just be skipped.
"""
pass
class Mlx5VfioEqResources(Mlx5VfioResources):
def __init__(self, ib_port, pci_name, gid_index=None, ctx=None):
self.cons_index = 0
super().__init__(ib_port, pci_name, gid_index, ctx)
self.logger = logging.getLogger(self.__class__.__name__)
def create_uar(self):
super().create_uar()
self.uar['eq'] = Mlx5UAR(self.ctx, dve._MLX5DV_UAR_ALLOC_TYPE_NC)
if not self.uar['eq'].page_id:
raise PyverbsRDMAError('Failed to allocate UAR')
def get_eqe(self, cc):
from tests.mlx5_prm_structs import SwEqe
ci = self.cons_index + cc
entry = ci & (self.nent - 1)
eqe_bytes = mem.read64(self.eq.vaddr + entry * len(SwEqe()))
eqe_bytes = eqe_bytes.to_bytes(length=8, byteorder='little')
eqe = SwEqe(eqe_bytes)
if (eqe.owner & 1) ^ (not(not(ci & self.nent))):
eqe = None
elif eqe:
dma.udma_from_dev_barrier()
return eqe
def update_ci(self, cc, arm=0):
addr = self.doorbell
if arm:
addr += 8 # Adding 2 bytes according to PRM
self.cons_index += cc
val = (self.cons_index & 0xffffff) | (self.eqn << 24)
val_be = struct.unpack("<I", struct.pack(">I", val))[0]
dma.mmio_write32_as_be(addr, val_be)
dma.udma_to_dev_barrier()
def init_dveq_buff(self):
from tests.mlx5_prm_structs import SwEqe
for i in range(self.nent):
eqe_bytes = mem.read64(self.eq.vaddr + i * len(SwEqe()))
eqe_bytes = eqe_bytes.to_bytes(length=8, byteorder='little')
eqe = SwEqe(eqe_bytes)
eqe.owner = 0x1
self.update_ci(0)
def update_cc(self, cc):
if cc >= self.num_spare_eqe:
self.update_ci(cc)
cc = 0
return cc
def create_eq(self):
from tests.mlx5_prm_structs import CreateEqIn, SwEqc, CreateEqOut,\
EventType
# Using num_spare_eqe to guarantee that we update
# the ci before we polled all the entries in the EQ
self.num_spare_eqe = 0x80
self.nent = 0x80 + self.num_spare_eqe
self.msi_vector = Mlx5DevxMsiVector(self.ctx)
vector = self.msi_vector.vector
log_eq_size = math.ceil(math.log2(self.nent))
mask = 1 << EventType.PORT_STATE_CHANGE
cmd_in = CreateEqIn(sw_eqc=SwEqc(uar_page=self.uar['eq'].page_id,
log_eq_size=log_eq_size, intr=vector),
event_bitmask_63_0=mask)
self.eq = Mlx5DevxEq(self.ctx, cmd_in, len(CreateEqOut()))
self.eqn = CreateEqOut(self.eq.out_view).eqn
self.doorbell = self.uar['eq'].base_addr + 0x40
self.init_dveq_buff()
self.update_ci(0, 1)
def query_eqn(self):
pass
def process_async_events(self, fd):
from tests.mlx5_prm_structs import EventType
cc = 0
ret = os.read(fd, 8)
if not ret:
raise PyverbsRDMAErrno('Failed to read FD')
eqe = self.get_eqe(cc)
while eqe:
if eqe.event_type == EventType.PORT_STATE_CHANGE:
self.logger.debug('Caught port state change event')
return eqe.event_type
elif eqe.event_type == EventType.CQ_ERROR:
raise PyverbsRDMAError('Event type Error')
cc = self.update_cc(cc + 1)
eqe = self.get_eqe(cc)
self.update_ci(cc, 1)
class Mlx5VfioTrafficTest(Mlx5DevxTrafficBase):
"""
Test various functionality of an mlx5-vfio device.
"""
def setUp(self):
"""
Verifies that the user has passed a PCI device name to work with.
"""
self.pci_dev = self.config['pci_dev']
if not self.pci_dev:
raise unittest.SkipTest('PCI device must be passed by the user')
def create_players(self):
self.server = Mlx5VfioResources(ib_port=self.ib_port, pci_name=self.pci_dev,
activate_port_state=True)
self.client = Mlx5VfioResources(ib_port=self.ib_port, pci_name=self.pci_dev,
ctx=self.server.ctx)
def create_async_players(self):
self.server = Mlx5VfioEqResources(ib_port=self.ib_port, pci_name=self.pci_dev)
self.client = Mlx5VfioResources(ib_port=self.ib_port, pci_name=self.pci_dev,
ctx=self.server.ctx)
def vfio_process_events(self):
"""
Processes mlx5 vfio device events.
This method should run from application thread to maintain the events.
"""
# Server and client use the same context
events_fd = self.server.ctx.get_events_fd()
with select.epoll() as epoll_events:
epoll_events.register(events_fd, select.EPOLLIN)
while self.proc_events:
for fd, event in epoll_events.poll(timeout=0.1):
if fd == events_fd:
if not (event & select.EPOLLIN):
self.event_ex.append(PyverbsRDMAError(f'Unexpected vfio event: {event}'))
self.server.ctx.process_events()
def vfio_process_async_events(self):
"""
Processes mlx5 vfio device async events.
This method should run from application thread to maintain the events.
"""
from tests.mlx5_prm_structs import EventType
# Server and client use the same context
events_fd = self.server.msi_vector.fd
with select.epoll() as epoll_events:
epoll_events.register(events_fd, select.EPOLLIN)
while self.proc_events:
for fd, event in epoll_events.poll(timeout=0.1):
if fd == events_fd:
if not (event & select.EPOLLIN):
self.event_ex.append(PyverbsRDMAError(f'Unexpected vfio event: {event}'))
if self.server.process_async_events(events_fd) == EventType.PORT_STATE_CHANGE:
self.caught_event = True
def test_mlx5vfio_rc_qp_send_imm_traffic(self):
"""
Opens one mlx5 vfio context, creates two DevX RC QPs on it, and modifies
them to RTS state.
Then does SEND_IMM traffic.
"""
self.create_players()
if self.server.is_eth():
raise unittest.SkipTest(f'{self.__class__.__name__} is currently supported over IB only')
self.event_ex = []
self.proc_events = True
proc_events = Thread(target=self.vfio_process_events)
proc_events.start()
# Move the DevX QPs to RTS state
self.pre_run()
try:
# Send traffic
self.send_imm_traffic()
finally:
# Stop listening to events
self.proc_events = False
proc_events.join()
if self.event_ex:
raise PyverbsRDMAError(f'Received unexpected vfio events: {self.event_ex}')
def test_mlx5vfio_async_event(self):
"""
Opens one mlx5 vfio context, creates DevX EQ on it.
Then activates the port and catches the port state change event.
"""
self.create_async_players()
if self.server.is_eth():
raise unittest.SkipTest(f'{self.__class__.__name__} is currently supported over IB only')
self.event_ex = []
self.proc_events = True
self.caught_event = False
proc_events = Thread(target=self.vfio_process_events)
proc_async_events = Thread(target=self.vfio_process_async_events)
proc_events.start()
proc_async_events.start()
# Move the DevX QPs to RTS state
self.pre_run()
try:
# Change port state
self.server.change_port_state_with_registers(PortStatus.MLX5_PORT_UP)
admin_status, oper_status = self.server.query_port_state_with_registers()
start_state_t = time.perf_counter()
while admin_status != PortStatus.MLX5_PORT_UP or oper_status != PortStatus.MLX5_PORT_UP:
if time.perf_counter() - start_state_t >= PORT_STATE_TIMEOUT:
raise PyverbsRDMAError('Could not change the port state to UP')
admin_status, oper_status = self.server.query_port_state_with_registers()
start_state_t = time.perf_counter()
while self.server.query_port_state_with_mads(self.ib_port) < PortState.ACTIVE:
if time.perf_counter() - start_state_t >= PORT_STATE_TIMEOUT:
raise PyverbsRDMAError('Could not change the port state to ACTIVE')
time.sleep(1)
finally:
# Stop listening to events
self.proc_events = False
proc_events.join()
proc_async_events.join()
if self.event_ex:
raise PyverbsRDMAError(f'Received unexpected vfio events: {self.event_ex}')
if not self.caught_event:
raise PyverbsRDMAError('Failed to catch an async event')