Skip to content

Commit 0434340

Browse files
vladumEdwardSro
authored andcommitted
rdma_topo: Split NVCX_Complex into abstract and concrete classes
Introduce an abstract NVCX_Complex base (using ABC). Move the existing implementation into NVCX_DMA_Complex, which implements the abstract methods for the existing DMA PF-based platform. This allows adding other complex types that share the same interface but differ in how they compute ACS and run additional checks (e.g., IOMMU groups). Signed-off-by: Vlad Dumitrescu <vdumitrescu@nvidia.com> Signed-off-by: Edward Srouji <edwards@nvidia.com>
1 parent 3c7134e commit 0434340

1 file changed

Lines changed: 152 additions & 88 deletions

File tree

kernel-boot/rdma_topo

Lines changed: 152 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import subprocess
1616
import sys
1717
import tempfile
1818

19+
from abc import ABC, abstractmethod
1920
from base64 import b64encode, b64decode
2021
from typing import *
2122
from zlib import compress, decompress
@@ -367,7 +368,51 @@ class PCIDevice(object):
367368
return self.sysfs_device.subsystems or {}
368369

369370

370-
class NVCX_Complex(object):
371+
class NVCX_Complex(ABC):
372+
@property
373+
@abstractmethod
374+
def primary_nic(self) -> PCIDevice:
375+
"""Primary ConnectX PF for this complex."""
376+
pass
377+
378+
@abstractmethod
379+
def compute_acs(self) -> Dict[PCIDevice, str]:
380+
"""Computes the ACS values for this complex.
381+
382+
Used to implement commands which check and/or set ACS values.
383+
"""
384+
pass
385+
386+
@abstractmethod
387+
def to_dict(self) -> Dict[str, Any]:
388+
"""Returns a JSON-serializable dictionary which represents this complex.
389+
390+
Used to implement topology dump command with `-j / --json` flag.
391+
392+
Output format should be maintained for backwards compatibility.
393+
"""
394+
pass
395+
396+
@abstractmethod
397+
def check(self) -> bool:
398+
"""Runs additional checks on this complex.
399+
400+
Returns True if all checks pass, False otherwise.
401+
402+
Used to implement the `check` command.
403+
"""
404+
pass
405+
406+
@abstractmethod
407+
def __str__(self) -> str:
408+
"""Returns a string representation of this complex.
409+
410+
Used to implement the `topo` command.
411+
"""
412+
pass
413+
414+
415+
class NVCX_DMA_Complex(NVCX_Complex):
371416
"""Hold the related PCI functions together. A complex includes a CX PF, a CX
372417
DMA function, an GPU and related PCI switches in the DMA function
373418
segment."""
@@ -397,6 +442,10 @@ class NVCX_Complex(object):
397442
if pdev.device_type == "nvme":
398443
self.nvmes.add(pdev)
399444

445+
@property
446+
def primary_nic(self) -> PCIDevice:
447+
return self.cx_pf
448+
400449
def __find_shared_usp(self) -> PCIDevice:
401450
"""Find the USP that is shared by both devices, the immediate downstream
402451
bus is the point in the topology where P2P traffic will switch from an
@@ -413,12 +462,103 @@ class NVCX_Complex(object):
413462
assert i.device_type == "cx_switch"
414463
return pdev
415464

416-
def get_subsystems(self):
465+
def compute_acs(self) -> Dict[PCIDevice, str]:
466+
acs: Dict[PCIDevice, str] = {}
467+
468+
# For the DSP in the shared switch toward the CX8 DMA Direct interface:
469+
# Enable these bits:
470+
# bit-4 : ACS Upstream Forwarding
471+
# bit-3 : ACS P2P Completion Redirect
472+
# bit-0 : ACS Source Validation
473+
# Disable these bits:
474+
# bit-2 : ACS P2P Request Redirect
475+
assert self.cx_dma_dsp.has_acs
476+
acs[self.cx_dma_dsp] = "xx110x1"
477+
478+
# For the DSP in the shared switch toward the GPU:
479+
# Enable the following bits:
480+
# bit-4 : ACS Upstream Forwarding
481+
# bit-2 : ACS P2P Request Redirect
482+
# bit-0 : ACS Source Validation
483+
# Disable the following bits:
484+
# bit-3 : ACS P2P Completion Redirect
485+
assert self.nvgpu_dsp.has_acs
486+
acs[self.nvgpu_dsp] = "xx101x1"
487+
488+
# Disable ACS SV on the root port, this forces the entire segment
489+
# into one iommu_group and avoids kernel bugs building groups for
490+
# irregular ACS.
491+
for pdev in self.cx_dma_dsp.iterupstream_path():
492+
if not pdev.parent:
493+
assert pdev.has_acs
494+
acs[pdev] = "xx111x0"
495+
496+
return acs
497+
498+
def to_dict(self) -> Dict[str, Any]:
499+
res = {
500+
"rdma_nic_pf_bdf": str(self.cx_pf.bdf),
501+
"rdma_dma_bdf": str(self.cx_dma.bdf),
502+
"gpu_bdf": str(self.nvgpu.bdf),
503+
"subsystems": {},
504+
}
505+
devname = self.cx_pf.vpd_name
506+
if devname:
507+
res["rdma_nic_vpd_name"] = self.cx_pf.vpd_name
508+
if self.cx_pf.numa_node is not None:
509+
res["numa_node"] = self.cx_pf.numa_node
510+
if self.nvmes:
511+
res["nvme_bdf"] = str(next(iter(self.nvmes)).bdf)
512+
513+
for pdev in sorted(
514+
itertools.chain(self.cx_pfs, [self.nvgpu, self.cx_dma], self.nvmes),
515+
key=lambda x: x.bdf,
516+
):
517+
subsys = pdev.get_subsystems()
518+
if subsys:
519+
res["subsystems"][str(pdev.bdf)] = {
520+
subsys: list(devs) for subsys, devs in subsys.items()
521+
}
522+
return res
523+
524+
def __str__(self):
525+
res = f"RDMA NIC={self.cx_pf.bdf}, GPU={self.nvgpu.bdf}, RDMA DMA Function={self.cx_dma.bdf}\n"
526+
devname = self.cx_pf.vpd_name
527+
if devname:
528+
res += f"\t{devname}\n"
529+
530+
if self.cx_pf.numa_node is not None:
531+
res += f"\tNUMA Node: {self.cx_pf.numa_node}\n"
532+
533+
if len(self.cx_pfs):
534+
res += print_list("NIC PCI device", [str(I.bdf) for I in self.cx_pfs])
535+
417536
subsystems: Dict[str, Set[str]] = collections.defaultdict(set)
418537
for pdev in itertools.chain(self.cx_pfs, [self.nvgpu, self.cx_dma], self.nvmes):
419538
for k, v in pdev.get_subsystems().items():
420539
subsystems[k].update(v)
421-
return subsystems
540+
res += print_list("RDMA device", subsystems["infiniband"])
541+
res += print_list("Net device", subsystems["net"])
542+
res += print_list("DRM device", subsystems["drm"])
543+
res += print_list("NVMe device", subsystems["nvme"])
544+
545+
return res[:-1]
546+
547+
def check(self) -> bool:
548+
# Correct iommu_groups are required to avoid NVreg_GrdmaPciTopoCheckOverride
549+
if (
550+
self.cx_dma.iommu_group == self.nvgpu.iommu_group
551+
and self.cx_dma.iommu_group is not None
552+
):
553+
check_ok(
554+
f"Kernel iommu_group for DMA {self.cx_dma.bdf} and GPU {self.nvgpu.bdf} are both {self.cx_dma.iommu_group}"
555+
)
556+
return True
557+
558+
check_fail(
559+
f"Kernel iommu_group for DMA {self.cx_dma.bdf} and GPU {self.nvgpu.bdf} are not equal {self.cx_dma.iommu_group} != {self.nvgpu.iommu_group}"
560+
)
561+
return False
422562

423563

424564
def check_parent(pdev: PCIDevice, parent_type: str):
@@ -540,7 +680,8 @@ class PCITopo(object):
540680
raise ValueError(
541681
f"CX DMA function {cx_dma} has unexpected PCI devices in the topology"
542682
)
543-
return NVCX_Complex(cx_pfs, cx_dma, nvgpu)
683+
684+
return NVCX_DMA_Complex(cx_pfs, cx_dma, nvgpu)
544685

545686
def __build_topo(self):
546687
"""Collect cross-device information together and build the NVCX_Complex
@@ -559,7 +700,7 @@ class PCITopo(object):
559700
if pdev.device_type == "cx_dma":
560701
nvcx = self.__get_nvcx_complex(pdev)
561702
self.nvcxs.append(nvcx)
562-
self.nvcxs.sort(key=lambda x: x.cx_pf.bdf)
703+
self.nvcxs.sort(key=lambda x: x.primary_nic.bdf)
563704

564705
@property
565706
def supported(self) -> bool:
@@ -571,33 +712,7 @@ class PCITopo(object):
571712
have"""
572713
acs: Dict[PCIDevice, str] = {}
573714
for nvcx in self.nvcxs:
574-
# For the DSP in the shared switch toward the CX8 DMA Direct interface:
575-
# Enable these bits:
576-
# bit-4 : ACS Upstream Forwarding
577-
# bit-3 : ACS P2P Completion Redirect
578-
# bit-0 : ACS Source Validation
579-
# Disable these bits:
580-
# bit-2 : ACS P2P Request Redirect
581-
assert nvcx.cx_dma_dsp.has_acs
582-
acs[nvcx.cx_dma_dsp] = "xx110x1"
583-
584-
# For the DSP in the shared switch toward the GPU:
585-
# Enable the following bits:
586-
# bit-4 : ACS Upstream Forwarding
587-
# bit-2 : ACS P2P Request Redirect
588-
# bit-0 : ACS Source Validation
589-
# Disable the following bits:
590-
# bit-3 : ACS P2P Completion Redirect
591-
assert nvcx.nvgpu_dsp.has_acs
592-
acs[nvcx.nvgpu_dsp] = "xx101x1"
593-
594-
# Disable ACS SV on the root port, this forces the entire segment
595-
# into one iommu_group and avoids kernel bugs building groups for
596-
# irregular ACS.
597-
for pdev in nvcx.cx_dma_dsp.iterupstream_path():
598-
if not pdev.parent:
599-
assert pdev.has_acs
600-
acs[pdev] = "xx111x0"
715+
acs.update(nvcx.compute_acs())
601716

602717
# For all other CX bridges set kernel's default ACS enable
603718
# Enable these bits:
@@ -630,11 +745,11 @@ def add_sysfs_dump_argument(parser):
630745
# -------------------------------------------------------------------
631746
def print_list(title: str, items: list[str]):
632747
if not items:
633-
return
748+
return ""
634749
if len(items) > 1:
635750
title = title + "s"
636751
list_str = ", ".join(sorted(items))
637-
print(f"\t{title}: {list_str}")
752+
return f"\t{title}: {list_str}\n"
638753

639754

640755
def args_topology(parser):
@@ -653,30 +768,7 @@ def topo_json(topo: PCITopo):
653768

654769
jtop = []
655770
for nvcx in topo.nvcxs:
656-
jnvcx = {
657-
"rdma_nic_pf_bdf": str(nvcx.cx_pf.bdf),
658-
"rdma_dma_bdf": str(nvcx.cx_dma.bdf),
659-
"gpu_bdf": str(nvcx.nvgpu.bdf),
660-
"subsystems": {},
661-
}
662-
devname = nvcx.cx_pf.vpd_name
663-
if devname:
664-
jnvcx["rdma_nic_vpd_name"] = nvcx.cx_pf.vpd_name
665-
if nvcx.cx_pf.numa_node is not None:
666-
jnvcx["numa_node"] = nvcx.cx_pf.numa_node
667-
if nvcx.nvmes:
668-
jnvcx["nvme_bdf"] = str(next(iter(nvcx.nvmes)).bdf)
669-
670-
for pdev in sorted(
671-
itertools.chain(nvcx.cx_pfs, [nvcx.nvgpu, nvcx.cx_dma], nvcx.nvmes),
672-
key=lambda x: x.bdf,
673-
):
674-
subsys = pdev.get_subsystems()
675-
if subsys:
676-
jnvcx["subsystems"][str(pdev.bdf)] = {
677-
subsys: list(devs) for subsys, devs in subsys.items()
678-
}
679-
jtop.append(jnvcx)
771+
jtop.append(nvcx.to_dict())
680772
print(json.dumps(jtop, indent=4))
681773

682774

@@ -691,25 +783,8 @@ def cmd_topology(args):
691783
return topo_json(topo)
692784

693785
for nvcx in topo.nvcxs:
694-
print(
695-
f"RDMA NIC={nvcx.cx_pf.bdf}, GPU={nvcx.nvgpu.bdf}, RDMA DMA Function={nvcx.cx_dma.bdf}"
696-
)
786+
print(nvcx)
697787

698-
devname = nvcx.cx_pf.vpd_name
699-
if devname:
700-
print(f"\t{devname}")
701-
702-
if nvcx.cx_pf.numa_node is not None:
703-
print(f"\tNUMA Node: {nvcx.cx_pf.numa_node}")
704-
705-
if len(nvcx.cx_pfs):
706-
print_list("NIC PCI device", [str(I.bdf) for I in nvcx.cx_pfs])
707-
708-
subsystems = nvcx.get_subsystems()
709-
print_list("RDMA device", subsystems["infiniband"])
710-
print_list("Net device", subsystems["net"])
711-
print_list("DRM device", subsystems["drm"])
712-
print_list("NVMe device", subsystems["nvme"])
713788
cmd_topology.__aliases__ = ("topo",)
714789

715790
# -------------------------------------------------------------------
@@ -889,19 +964,8 @@ def cmd_check(args):
889964
f"ACS for {pdev.device_type} {pdev.bdf} has incorrect values {cur_acs:07b} != {acs}, (0x{cur_acs:x} != 0x{new_acs:x})"
890965
)
891966

892-
# Correct iommu_groups are required to avoid NVreg_GrdmaPciTopoCheckOverride
893967
for nvcx in topo.nvcxs:
894-
if (
895-
nvcx.cx_dma.iommu_group == nvcx.nvgpu.iommu_group
896-
and nvcx.cx_dma.iommu_group is not None
897-
):
898-
check_ok(
899-
f"Kernel iommu_group for DMA {nvcx.cx_dma.bdf} and GPU {nvcx.nvgpu.bdf} are both {nvcx.cx_dma.iommu_group}"
900-
)
901-
else:
902-
check_fail(
903-
f"Kernel iommu_group for DMA {nvcx.cx_dma.bdf} and GPU {nvcx.nvgpu.bdf} are not equal {nvcx.cx_dma.iommu_group} != {nvcx.nvgpu.iommu_group}"
904-
)
968+
nvcx.check()
905969

906970

907971
# -------------------------------------------------------------------

0 commit comments

Comments
 (0)