github.com/jaypipes/ghw@v0.21.1/pkg/pci/pci.go (about)

     1  //
     2  // Use and distribution licensed under the Apache license version 2.
     3  //
     4  // See the COPYING file in the root project directory for full text.
     5  //
     6  
     7  package pci
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  
    13  	"github.com/jaypipes/pcidb"
    14  
    15  	"github.com/jaypipes/ghw/pkg/context"
    16  	"github.com/jaypipes/ghw/pkg/marshal"
    17  	"github.com/jaypipes/ghw/pkg/option"
    18  	"github.com/jaypipes/ghw/pkg/topology"
    19  	"github.com/jaypipes/ghw/pkg/util"
    20  )
    21  
    22  type Device struct {
    23  	// The PCI address of the device
    24  	Address string `json:"address"`
    25  	// The PCI address of the parent device
    26  	ParentAddress string         `json:"parent_address"`
    27  	Vendor        *pcidb.Vendor  `json:"vendor"`
    28  	Product       *pcidb.Product `json:"product"`
    29  	Revision      string         `json:"revision"`
    30  	Subsystem     *pcidb.Product `json:"subsystem"`
    31  	// optional subvendor/sub-device information
    32  	Class *pcidb.Class `json:"class"`
    33  	// optional sub-class for the device
    34  	Subclass *pcidb.Subclass `json:"subclass"`
    35  	// optional programming interface
    36  	ProgrammingInterface *pcidb.ProgrammingInterface `json:"programming_interface"`
    37  	// Topology node that the PCI device is affined to. Will be nil if the
    38  	// architecture is not NUMA.
    39  	Node   *topology.Node `json:"node,omitempty"`
    40  	Driver string         `json:"driver"`
    41  	// for IOMMU Groups see also:
    42  	// https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/virtualization_deployment_and_administration_guide/sect-iommu-deep-dive
    43  	IOMMUGroup string `json:"iommu_group"`
    44  }
    45  
    46  type devIdent struct {
    47  	ID   string `json:"id"`
    48  	Name string `json:"name"`
    49  }
    50  
    51  type devMarshallable struct {
    52  	Driver        string   `json:"driver"`
    53  	Address       string   `json:"address"`
    54  	ParentAddress string   `json:"parent_address"`
    55  	Vendor        devIdent `json:"vendor"`
    56  	Product       devIdent `json:"product"`
    57  	Revision      string   `json:"revision"`
    58  	Subsystem     devIdent `json:"subsystem"`
    59  	Class         devIdent `json:"class"`
    60  	Subclass      devIdent `json:"subclass"`
    61  	Interface     devIdent `json:"programming_interface"`
    62  	IOMMUGroup    string   `json:"iommu_group"`
    63  }
    64  
    65  // NOTE(jaypipes) Device has a custom JSON marshaller because we don't want
    66  // to serialize the entire PCIDB information for the Vendor (which includes all
    67  // of the vendor's products, etc). Instead, we simply serialize the ID and
    68  // human-readable name of the vendor, product, class, etc.
    69  func (d *Device) MarshalJSON() ([]byte, error) {
    70  	dm := devMarshallable{
    71  		Driver:        d.Driver,
    72  		Address:       d.Address,
    73  		ParentAddress: d.ParentAddress,
    74  		Vendor: devIdent{
    75  			ID:   d.Vendor.ID,
    76  			Name: d.Vendor.Name,
    77  		},
    78  		Product: devIdent{
    79  			ID:   d.Product.ID,
    80  			Name: d.Product.Name,
    81  		},
    82  		Revision: d.Revision,
    83  		Subsystem: devIdent{
    84  			ID:   d.Subsystem.ID,
    85  			Name: d.Subsystem.Name,
    86  		},
    87  		Class: devIdent{
    88  			ID:   d.Class.ID,
    89  			Name: d.Class.Name,
    90  		},
    91  		Subclass: devIdent{
    92  			ID:   d.Subclass.ID,
    93  			Name: d.Subclass.Name,
    94  		},
    95  		Interface: devIdent{
    96  			ID:   d.ProgrammingInterface.ID,
    97  			Name: d.ProgrammingInterface.Name,
    98  		},
    99  		IOMMUGroup: d.IOMMUGroup,
   100  	}
   101  	return json.Marshal(dm)
   102  }
   103  
   104  func (d *Device) String() string {
   105  	vendorName := util.UNKNOWN
   106  	if d.Vendor != nil {
   107  		vendorName = d.Vendor.Name
   108  	}
   109  	productName := util.UNKNOWN
   110  	if d.Product != nil {
   111  		productName = d.Product.Name
   112  	}
   113  	className := util.UNKNOWN
   114  	if d.Class != nil {
   115  		className = d.Class.Name
   116  	}
   117  	return fmt.Sprintf(
   118  		"%s -> driver: '%s' class: '%s' vendor: '%s' product: '%s'",
   119  		d.Address,
   120  		d.Driver,
   121  		className,
   122  		vendorName,
   123  		productName,
   124  	)
   125  }
   126  
   127  type Info struct {
   128  	db   *pcidb.PCIDB
   129  	arch topology.Architecture
   130  	ctx  *context.Context
   131  	// All PCI devices on the host system
   132  	Devices []*Device
   133  }
   134  
   135  func (i *Info) String() string {
   136  	return fmt.Sprintf("PCI (%d devices)", len(i.Devices))
   137  }
   138  
   139  // New returns a pointer to an Info struct that contains information about the
   140  // PCI devices on the host system
   141  func New(opts ...*option.Option) (*Info, error) {
   142  	merged := option.Merge(opts...)
   143  	ctx := context.New(merged)
   144  	// by default we don't report NUMA information;
   145  	// we will only if are sure we are running on NUMA architecture
   146  	info := &Info{
   147  		arch: topology.ArchitectureSMP,
   148  		ctx:  ctx,
   149  	}
   150  
   151  	// we do this trick because we need to make sure ctx.Setup() gets
   152  	// a chance to run before any subordinate package is created reusing
   153  	// our context.
   154  	loadDetectingTopology := func() error {
   155  		topo, err := topology.New(context.WithContext(ctx))
   156  		if err == nil {
   157  			info.arch = topo.Architecture
   158  		} else {
   159  			ctx.Warn("error detecting system topology: %v", err)
   160  		}
   161  		if merged.PCIDB != nil {
   162  			info.db = merged.PCIDB
   163  		}
   164  		return info.load()
   165  	}
   166  
   167  	var err error
   168  	if context.Exists(merged) {
   169  		err = loadDetectingTopology()
   170  	} else {
   171  		err = ctx.Do(loadDetectingTopology)
   172  	}
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	return info, nil
   177  }
   178  
   179  // lookupDevice gets a device from cached data
   180  func (info *Info) lookupDevice(address string) *Device {
   181  	for _, dev := range info.Devices {
   182  		if dev.Address == address {
   183  			return dev
   184  		}
   185  	}
   186  	return nil
   187  }
   188  
   189  // simple private struct used to encapsulate PCI information in a top-level
   190  // "pci" YAML/JSON map/object key
   191  type pciPrinter struct {
   192  	Info *Info `json:"pci"`
   193  }
   194  
   195  // YAMLString returns a string with the PCI information formatted as YAML
   196  // under a top-level "pci:" key
   197  func (i *Info) YAMLString() string {
   198  	return marshal.SafeYAML(i.ctx, pciPrinter{i})
   199  }
   200  
   201  // JSONString returns a string with the PCI information formatted as JSON
   202  // under a top-level "pci:" key
   203  func (i *Info) JSONString(indent bool) string {
   204  	return marshal.SafeJSON(i.ctx, pciPrinter{i}, indent)
   205  }