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 }