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 }