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

     1  // Use and distribution licensed under the Apache license version 2.
     2  //
     3  // See the COPYING file in the root project directory for full text.
     4  //
     5  
     6  package pci
     7  
     8  import (
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/jaypipes/pcidb"
    14  
    15  	"github.com/jaypipes/ghw/pkg/context"
    16  	"github.com/jaypipes/ghw/pkg/linuxpath"
    17  	"github.com/jaypipes/ghw/pkg/option"
    18  	pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
    19  	"github.com/jaypipes/ghw/pkg/topology"
    20  	"github.com/jaypipes/ghw/pkg/util"
    21  )
    22  
    23  const (
    24  	// found running `wc` against real linux systems
    25  	modAliasExpectedLength = 54
    26  )
    27  
    28  func (i *Info) load() error {
    29  	// when consuming snapshots - most notably, but not only, in tests,
    30  	// the context pkg forces the chroot value to the unpacked snapshot root.
    31  	// This is intentional, intentionally transparent and ghw is prepared to handle this case.
    32  	// However, `pcidb` is not. It doesn't know about ghw snaphots, nor it should.
    33  	// so we need to complicate things a bit. If the user explicitely supplied
    34  	// a chroot option, then we should honor it all across the stack, and passing down
    35  	// the chroot to pcidb is the right thing to do. If, however, the chroot was
    36  	// implcitely set by snapshot support, then this must be consumed by ghw only.
    37  	// In this case we should NOT pass it down to pcidb.
    38  	chroot := i.ctx.Chroot
    39  	if i.ctx.SnapshotPath != "" {
    40  		chroot = option.DefaultChroot
    41  	}
    42  	if i.db == nil {
    43  		db, err := pcidb.New(pcidb.WithChroot(chroot))
    44  		if err != nil {
    45  			return err
    46  		}
    47  		i.db = db
    48  	}
    49  	i.Devices = i.getDevices()
    50  	return nil
    51  }
    52  
    53  func getDeviceModaliasPath(ctx *context.Context, pciAddr *pciaddr.Address) string {
    54  	paths := linuxpath.New(ctx)
    55  	return filepath.Join(
    56  		paths.SysBusPciDevices,
    57  		pciAddr.String(),
    58  		"modalias",
    59  	)
    60  }
    61  
    62  func getDeviceRevision(ctx *context.Context, pciAddr *pciaddr.Address) string {
    63  	paths := linuxpath.New(ctx)
    64  	revisionPath := filepath.Join(
    65  		paths.SysBusPciDevices,
    66  		pciAddr.String(),
    67  		"revision",
    68  	)
    69  
    70  	if _, err := os.Stat(revisionPath); err != nil {
    71  		return ""
    72  	}
    73  	revision, err := os.ReadFile(revisionPath)
    74  	if err != nil {
    75  		return ""
    76  	}
    77  	return strings.TrimSpace(string(revision))
    78  }
    79  
    80  func getDeviceNUMANode(ctx *context.Context, pciAddr *pciaddr.Address) *topology.Node {
    81  	paths := linuxpath.New(ctx)
    82  	numaNodePath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "numa_node")
    83  
    84  	if _, err := os.Stat(numaNodePath); err != nil {
    85  		return nil
    86  	}
    87  
    88  	nodeIdx := util.SafeIntFromFile(ctx, numaNodePath)
    89  	if nodeIdx == -1 {
    90  		return nil
    91  	}
    92  
    93  	return &topology.Node{
    94  		ID: nodeIdx,
    95  	}
    96  }
    97  
    98  func getDeviceIommuGroup(ctx *context.Context, pciAddr *pciaddr.Address) string {
    99  	paths := linuxpath.New(ctx)
   100  	iommuGroupPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "iommu_group")
   101  
   102  	dest, err := os.Readlink(iommuGroupPath)
   103  	if err != nil {
   104  		return ""
   105  	}
   106  	return filepath.Base(dest)
   107  }
   108  
   109  func getDeviceParentAddress(ctx *context.Context, pciAddr *pciaddr.Address) string {
   110  	paths := linuxpath.New(ctx)
   111  	devPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String())
   112  
   113  	dest, err := os.Readlink(devPath)
   114  	if err != nil {
   115  		return ""
   116  	}
   117  
   118  	parentAddr := filepath.Base(filepath.Dir(dest))
   119  
   120  	if pciaddr.FromString(parentAddr) == nil {
   121  		return ""
   122  	}
   123  
   124  	return parentAddr
   125  }
   126  
   127  func getDeviceDriver(ctx *context.Context, pciAddr *pciaddr.Address) string {
   128  	paths := linuxpath.New(ctx)
   129  	driverPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String(), "driver")
   130  
   131  	if _, err := os.Stat(driverPath); err != nil {
   132  		return ""
   133  	}
   134  
   135  	dest, err := os.Readlink(driverPath)
   136  	if err != nil {
   137  		return ""
   138  	}
   139  	return filepath.Base(dest)
   140  }
   141  
   142  type deviceModaliasInfo struct {
   143  	vendorID     string
   144  	productID    string
   145  	subproductID string
   146  	subvendorID  string
   147  	classID      string
   148  	subclassID   string
   149  	progIfaceID  string
   150  }
   151  
   152  func parseModaliasFile(fp string) *deviceModaliasInfo {
   153  	if _, err := os.Stat(fp); err != nil {
   154  		return nil
   155  	}
   156  	data, err := os.ReadFile(fp)
   157  	if err != nil {
   158  		return nil
   159  	}
   160  
   161  	return parseModaliasData(string(data))
   162  }
   163  
   164  func parseModaliasData(data string) *deviceModaliasInfo {
   165  	// extra sanity check to avoid segfaults. We actually expect
   166  	// the data to be exactly long `modAliasExpectedlength`, but
   167  	// we will happily ignore any extra data we don't know how to
   168  	// handle.
   169  	if len(data) < modAliasExpectedLength {
   170  		return nil
   171  	}
   172  	// The modalias file is an encoded file that looks like this:
   173  	//
   174  	// $ cat /sys/devices/pci0000\:00/0000\:00\:03.0/0000\:03\:00.0/modalias
   175  	// pci:v000010DEd00001C82sv00001043sd00008613bc03sc00i00
   176  	//
   177  	// It is interpreted like so:
   178  	//
   179  	// pci: -- ignore
   180  	// v000010DE -- PCI vendor ID
   181  	// d00001C82 -- PCI device ID (the product/model ID)
   182  	// sv00001043 -- PCI subsystem vendor ID
   183  	// sd00008613 -- PCI subsystem device ID (subdevice product/model ID)
   184  	// bc03 -- PCI base class
   185  	// sc00 -- PCI subclass
   186  	// i00 -- programming interface
   187  	vendorID := strings.ToLower(data[9:13])
   188  	productID := strings.ToLower(data[18:22])
   189  	subvendorID := strings.ToLower(data[28:32])
   190  	subproductID := strings.ToLower(data[38:42])
   191  	classID := strings.ToLower(data[44:46])
   192  	subclassID := strings.ToLower(data[48:50])
   193  	progIfaceID := strings.ToLower(data[51:53])
   194  	return &deviceModaliasInfo{
   195  		vendorID:     vendorID,
   196  		productID:    productID,
   197  		subproductID: subproductID,
   198  		subvendorID:  subvendorID,
   199  		classID:      classID,
   200  		subclassID:   subclassID,
   201  		progIfaceID:  progIfaceID,
   202  	}
   203  }
   204  
   205  // Returns a pointer to a pcidb.Vendor struct matching the supplied vendor
   206  // ID string. If no such vendor ID string could be found, returns the
   207  // pcidb.Vendor struct populated with "unknown" vendor Name attribute and
   208  // empty Products attribute.
   209  func findPCIVendor(info *Info, vendorID string) *pcidb.Vendor {
   210  	vendor := info.db.Vendors[vendorID]
   211  	if vendor == nil {
   212  		return &pcidb.Vendor{
   213  			ID:       vendorID,
   214  			Name:     util.UNKNOWN,
   215  			Products: []*pcidb.Product{},
   216  		}
   217  	}
   218  	return vendor
   219  }
   220  
   221  // Returns a pointer to a pcidb.Product struct matching the supplied vendor
   222  // and product ID strings. If no such product could be found, returns the
   223  // pcidb.Product struct populated with "unknown" product Name attribute and
   224  // empty Subsystems attribute.
   225  func findPCIProduct(
   226  	info *Info,
   227  	vendorID string,
   228  	productID string,
   229  ) *pcidb.Product {
   230  	product := info.db.Products[vendorID+productID]
   231  	if product == nil {
   232  		return &pcidb.Product{
   233  			ID:         productID,
   234  			Name:       util.UNKNOWN,
   235  			Subsystems: []*pcidb.Product{},
   236  		}
   237  	}
   238  	return product
   239  }
   240  
   241  // Returns a pointer to a pcidb.Product struct matching the supplied vendor,
   242  // product, subvendor and subproduct ID strings. If no such product could be
   243  // found, returns the pcidb.Product struct populated with "unknown" product
   244  // Name attribute and empty Subsystems attribute.
   245  func findPCISubsystem(
   246  	info *Info,
   247  	vendorID string,
   248  	productID string,
   249  	subvendorID string,
   250  	subproductID string,
   251  ) *pcidb.Product {
   252  	product := info.db.Products[vendorID+productID]
   253  	subvendor := info.db.Vendors[subvendorID]
   254  	if subvendor != nil && product != nil {
   255  		for _, p := range product.Subsystems {
   256  			if p.ID == subproductID {
   257  				return p
   258  			}
   259  		}
   260  	}
   261  	return &pcidb.Product{
   262  		VendorID: subvendorID,
   263  		ID:       subproductID,
   264  		Name:     util.UNKNOWN,
   265  	}
   266  }
   267  
   268  // Returns a pointer to a pcidb.Class struct matching the supplied class ID
   269  // string. If no such class ID string could be found, returns the
   270  // pcidb.Class struct populated with "unknown" class Name attribute and
   271  // empty Subclasses attribute.
   272  func findPCIClass(info *Info, classID string) *pcidb.Class {
   273  	class := info.db.Classes[classID]
   274  	if class == nil {
   275  		return &pcidb.Class{
   276  			ID:         classID,
   277  			Name:       util.UNKNOWN,
   278  			Subclasses: []*pcidb.Subclass{},
   279  		}
   280  	}
   281  	return class
   282  }
   283  
   284  // Returns a pointer to a pcidb.Subclass struct matching the supplied class
   285  // and subclass ID strings.  If no such subclass could be found, returns the
   286  // pcidb.Subclass struct populated with "unknown" subclass Name attribute
   287  // and empty ProgrammingInterfaces attribute.
   288  func findPCISubclass(
   289  	info *Info,
   290  	classID string,
   291  	subclassID string,
   292  ) *pcidb.Subclass {
   293  	class := info.db.Classes[classID]
   294  	if class != nil {
   295  		for _, sc := range class.Subclasses {
   296  			if sc.ID == subclassID {
   297  				return sc
   298  			}
   299  		}
   300  	}
   301  	return &pcidb.Subclass{
   302  		ID:                    subclassID,
   303  		Name:                  util.UNKNOWN,
   304  		ProgrammingInterfaces: []*pcidb.ProgrammingInterface{},
   305  	}
   306  }
   307  
   308  // Returns a pointer to a pcidb.ProgrammingInterface struct matching the
   309  // supplied class, subclass and programming interface ID strings.  If no such
   310  // programming interface could be found, returns the
   311  // pcidb.ProgrammingInterface struct populated with "unknown" Name attribute
   312  func findPCIProgrammingInterface(
   313  	info *Info,
   314  	classID string,
   315  	subclassID string,
   316  	progIfaceID string,
   317  ) *pcidb.ProgrammingInterface {
   318  	subclass := findPCISubclass(info, classID, subclassID)
   319  	for _, pi := range subclass.ProgrammingInterfaces {
   320  		if pi.ID == progIfaceID {
   321  			return pi
   322  		}
   323  	}
   324  	return &pcidb.ProgrammingInterface{
   325  		ID:   progIfaceID,
   326  		Name: util.UNKNOWN,
   327  	}
   328  }
   329  
   330  // GetDevice returns a pointer to a Device struct that describes the PCI
   331  // device at the requested address. If no such device could be found, returns nil.
   332  func (info *Info) GetDevice(address string) *Device {
   333  	// check cached data first
   334  	if dev := info.lookupDevice(address); dev != nil {
   335  		return dev
   336  	}
   337  
   338  	pciAddr := pciaddr.FromString(address)
   339  	if pciAddr == nil {
   340  		info.ctx.Warn("error parsing the pci address %q", address)
   341  		return nil
   342  	}
   343  
   344  	// no cached data, let's get the information from system.
   345  	fp := getDeviceModaliasPath(info.ctx, pciAddr)
   346  	if fp == "" {
   347  		info.ctx.Warn("error finding modalias info for device %q", address)
   348  		return nil
   349  	}
   350  
   351  	modaliasInfo := parseModaliasFile(fp)
   352  	if modaliasInfo == nil {
   353  		info.ctx.Warn("error parsing modalias info for device %q", address)
   354  		return nil
   355  	}
   356  
   357  	device := info.getDeviceFromModaliasInfo(address, modaliasInfo)
   358  	device.Revision = getDeviceRevision(info.ctx, pciAddr)
   359  	if info.arch == topology.ArchitectureNUMA {
   360  		device.Node = getDeviceNUMANode(info.ctx, pciAddr)
   361  	}
   362  	device.Driver = getDeviceDriver(info.ctx, pciAddr)
   363  	device.ParentAddress = getDeviceParentAddress(info.ctx, pciAddr)
   364  	device.IOMMUGroup = getDeviceIommuGroup(info.ctx, pciAddr)
   365  	return device
   366  }
   367  
   368  // ParseDevice returns a pointer to a Device given its describing data.
   369  // The PCI device obtained this way may not exist in the system;
   370  // use GetDevice to get a *Device which is found in the system
   371  func (info *Info) ParseDevice(address, modalias string) *Device {
   372  	modaliasInfo := parseModaliasData(modalias)
   373  	if modaliasInfo == nil {
   374  		return nil
   375  	}
   376  	return info.getDeviceFromModaliasInfo(address, modaliasInfo)
   377  }
   378  
   379  func (info *Info) getDeviceFromModaliasInfo(
   380  	address string,
   381  	modaliasInfo *deviceModaliasInfo,
   382  ) *Device {
   383  	vendor := findPCIVendor(info, modaliasInfo.vendorID)
   384  	product := findPCIProduct(
   385  		info,
   386  		modaliasInfo.vendorID,
   387  		modaliasInfo.productID,
   388  	)
   389  	subsystem := findPCISubsystem(
   390  		info,
   391  		modaliasInfo.vendorID,
   392  		modaliasInfo.productID,
   393  		modaliasInfo.subvendorID,
   394  		modaliasInfo.subproductID,
   395  	)
   396  	class := findPCIClass(info, modaliasInfo.classID)
   397  	subclass := findPCISubclass(
   398  		info,
   399  		modaliasInfo.classID,
   400  		modaliasInfo.subclassID,
   401  	)
   402  	progIface := findPCIProgrammingInterface(
   403  		info,
   404  		modaliasInfo.classID,
   405  		modaliasInfo.subclassID,
   406  		modaliasInfo.progIfaceID,
   407  	)
   408  
   409  	return &Device{
   410  		Address:              address,
   411  		Vendor:               vendor,
   412  		Subsystem:            subsystem,
   413  		Product:              product,
   414  		Class:                class,
   415  		Subclass:             subclass,
   416  		ProgrammingInterface: progIface,
   417  	}
   418  }
   419  
   420  // getDevices returns a list of pointers to Device structs present on the
   421  // host system
   422  func (info *Info) getDevices() []*Device {
   423  	paths := linuxpath.New(info.ctx)
   424  	devs := make([]*Device, 0)
   425  	// We scan the /sys/bus/pci/devices directory which contains a collection
   426  	// of symlinks. The names of the symlinks are all the known PCI addresses
   427  	// for the host. For each address, we grab a *Device matching the
   428  	// address and append to the returned array.
   429  	links, err := os.ReadDir(paths.SysBusPciDevices)
   430  	if err != nil {
   431  		info.ctx.Warn("failed to read /sys/bus/pci/devices")
   432  		return nil
   433  	}
   434  	var dev *Device
   435  	for _, link := range links {
   436  		addr := link.Name()
   437  		dev = info.GetDevice(addr)
   438  		if dev == nil {
   439  			info.ctx.Warn("failed to get device information for PCI address %s", addr)
   440  		} else {
   441  			devs = append(devs, dev)
   442  		}
   443  	}
   444  	return devs
   445  }