@@ -16,6 +16,7 @@ import subprocess
1616import sys
1717import tempfile
1818
19+ from abc import ABC , abstractmethod
1920from base64 import b64encode , b64decode
2021from typing import *
2122from 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"\t NUMA 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
424564def 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# -------------------------------------------------------------------
631746def 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
640755def 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"\t NUMA 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" ])
713788cmd_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