414 lines
14 KiB
Markdown
414 lines
14 KiB
Markdown
# `pcidb` - the Golang PCI DB library [](https://travis-ci.org/jaypipes/pcidb)
|
|
|
|
`pcidb` is a small Golang library for programmatic querying of PCI vendor,
|
|
product and class information.
|
|
|
|
We currently [test](https://travis-ci.org/jaypipes/pcidb/) `pcidb` on Linux, Windows and MacOSX.
|
|
|
|
## Usage
|
|
|
|
`pcidb` contains a PCI database inspection and querying facility that allows
|
|
developers to query for information about hardware device classes, vendor and
|
|
product information.
|
|
|
|
The `pcidb.New()` function returns a `pcidb.PCIDB` struct or an error if the
|
|
PCI database could not be loaded.
|
|
|
|
> `pcidb`'s default behaviour is to first search for pci-ids DB files on the
|
|
> local host system in well-known filesystem paths. If `pcidb` cannot find a
|
|
> pci-ids DB file on the local host system, it will then fetch a current
|
|
> pci-ids DB file from the network. You can disable this network-fetching
|
|
> behaviour with the `pcidb.WithDisableNetworkFetch()` function or set the
|
|
> `PCIDB_DISABLE_NETWORK_FETCH` to a non-0 value.
|
|
|
|
The `pcidb.PCIDB` struct contains a number of fields that may be queried for
|
|
PCI information:
|
|
|
|
* `pcidb.PCIDB.Classes` is a map, keyed by the PCI class ID (a hex-encoded
|
|
string) of pointers to `pcidb.Class` structs, one for each class of PCI
|
|
device known to `pcidb`
|
|
* `pcidb.PCIDB.Vendors` is a map, keyed by the PCI vendor ID (a hex-encoded
|
|
string) of pointers to `pcidb.Vendor` structs, one for each PCI vendor
|
|
known to `pcidb`
|
|
* `pcidb.PCIDB.Products` is a map, keyed by the PCI product ID* (a hex-encoded
|
|
string) of pointers to `pcidb.Product` structs, one for each PCI product
|
|
known to `pcidb`
|
|
|
|
**NOTE**: PCI products are often referred to by their "device ID". We use
|
|
the term "product ID" in `pcidb` because it more accurately reflects what the
|
|
identifier is for: a specific product line produced by the vendor.
|
|
|
|
### Overriding the root mountpoint `pcidb` uses
|
|
|
|
The default root mountpoint that `pcidb` uses when looking for information
|
|
about the host system is `/`. So, for example, when looking up known PCI IDS DB
|
|
files on Linux, `pcidb` will attempt to discover a pciids DB file at
|
|
`/usr/share/misc/pci.ids`. If you are calling `pcidb` from a system that has an
|
|
alternate root mountpoint, you can either set the `PCIDB_CHROOT` environment
|
|
variable to that alternate path, or call the `pcidb.New()` function with the
|
|
`pcidb.WithChroot()` modifier.
|
|
|
|
For example, if you are executing from within an application container that has
|
|
bind-mounted the root host filesystem to the mount point `/host`, you would set
|
|
`PCIDB_CHROOT` to `/host` so that pcidb can find files like
|
|
`/usr/share/misc/pci.ids` at `/host/usr/share/misc/pci.ids`.
|
|
|
|
Alternately, you can use the `pcidb.WithChroot()` function like so:
|
|
|
|
```go
|
|
pci := pcidb.New(pcidb.WithChroot("/host"))
|
|
```
|
|
|
|
### PCI device classes
|
|
|
|
Let's take a look at the PCI device class information and how to query the PCI
|
|
database for class, subclass, and programming interface information.
|
|
|
|
Each `pcidb.Class` struct contains the following fields:
|
|
|
|
* `pcidb.Class.ID` is the hex-encoded string identifier for the device
|
|
class
|
|
* `pcidb.Class.Name` is the common name/description of the class
|
|
* `pcidb.Class.Subclasses` is an array of pointers to
|
|
`pcidb.Subclass` structs, one for each subclass in the device class
|
|
|
|
Each `pcidb.Subclass` struct contains the following fields:
|
|
|
|
* `pcidb.Subclass.ID` is the hex-encoded string identifier for the device
|
|
subclass
|
|
* `pcidb.Subclass.Name` is the common name/description of the subclass
|
|
* `pcidb.Subclass.ProgrammingInterfaces` is an array of pointers to
|
|
`pcidb.ProgrammingInterface` structs, one for each programming interface
|
|
for the device subclass
|
|
|
|
Each `pcidb.ProgrammingInterface` struct contains the following fields:
|
|
|
|
* `pcidb.ProgrammingInterface.ID` is the hex-encoded string identifier for
|
|
the programming interface
|
|
* `pcidb.ProgrammingInterface.Name` is the common name/description for the
|
|
programming interface
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jaypipes/pcidb"
|
|
)
|
|
|
|
func main() {
|
|
pci, err := pcidb.New()
|
|
if err != nil {
|
|
fmt.Printf("Error getting PCI info: %v", err)
|
|
}
|
|
|
|
for _, devClass := range pci.Classes {
|
|
fmt.Printf(" Device class: %v ('%v')\n", devClass.Name, devClass.ID)
|
|
for _, devSubclass := range devClass.Subclasses {
|
|
fmt.Printf(" Device subclass: %v ('%v')\n", devSubclass.Name, devSubclass.ID)
|
|
for _, progIface := range devSubclass.ProgrammingInterfaces {
|
|
fmt.Printf(" Programming interface: %v ('%v')\n", progIface.Name, progIface.ID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Example output from my personal workstation, snipped for brevity:
|
|
|
|
```
|
|
...
|
|
Device class: Serial bus controller ('0c')
|
|
Device subclass: FireWire (IEEE 1394) ('00')
|
|
Programming interface: Generic ('00')
|
|
Programming interface: OHCI ('10')
|
|
Device subclass: ACCESS Bus ('01')
|
|
Device subclass: SSA ('02')
|
|
Device subclass: USB controller ('03')
|
|
Programming interface: UHCI ('00')
|
|
Programming interface: OHCI ('10')
|
|
Programming interface: EHCI ('20')
|
|
Programming interface: XHCI ('30')
|
|
Programming interface: Unspecified ('80')
|
|
Programming interface: USB Device ('fe')
|
|
Device subclass: Fibre Channel ('04')
|
|
Device subclass: SMBus ('05')
|
|
Device subclass: InfiniBand ('06')
|
|
Device subclass: IPMI SMIC interface ('07')
|
|
Device subclass: SERCOS interface ('08')
|
|
Device subclass: CANBUS ('09')
|
|
...
|
|
```
|
|
|
|
### PCI vendors and products
|
|
|
|
Let's take a look at the PCI vendor information and how to query the PCI
|
|
database for vendor information and the products a vendor supplies.
|
|
|
|
Each `pcidb.Vendor` struct contains the following fields:
|
|
|
|
* `pcidb.Vendor.ID` is the hex-encoded string identifier for the vendor
|
|
* `pcidb.Vendor.Name` is the common name/description of the vendor
|
|
* `pcidb.Vendor.Products` is an array of pointers to `pcidb.Product`
|
|
structs, one for each product supplied by the vendor
|
|
|
|
Each `pcidb.Product` struct contains the following fields:
|
|
|
|
* `pcidb.Product.VendorID` is the hex-encoded string identifier for the
|
|
product's vendor
|
|
* `pcidb.Product.ID` is the hex-encoded string identifier for the product
|
|
* `pcidb.Product.Name` is the common name/description of the subclass
|
|
* `pcidb.Product.Subsystems` is an array of pointers to
|
|
`pcidb.Product` structs, one for each "subsystem" (sometimes called
|
|
"sub-device" in PCI literature) for the product
|
|
|
|
**NOTE**: A subsystem product may have a different vendor than its "parent" PCI
|
|
product. This is sometimes referred to as the "sub-vendor".
|
|
|
|
Here's some example code that demonstrates listing the PCI vendors with the
|
|
most known products:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/jaypipes/pcidb"
|
|
)
|
|
|
|
type ByCountProducts []*pcidb.Vendor
|
|
|
|
func (v ByCountProducts) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v ByCountProducts) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
func (v ByCountProducts) Less(i, j int) bool {
|
|
return len(v[i].Products) > len(v[j].Products)
|
|
}
|
|
|
|
func main() {
|
|
pci, err := pcidb.New()
|
|
if err != nil {
|
|
fmt.Printf("Error getting PCI info: %v", err)
|
|
}
|
|
|
|
vendors := make([]*pcidb.Vendor, len(pci.Vendors))
|
|
x := 0
|
|
for _, vendor := range pci.Vendors {
|
|
vendors[x] = vendor
|
|
x++
|
|
}
|
|
|
|
sort.Sort(ByCountProducts(vendors))
|
|
|
|
fmt.Println("Top 5 vendors by product")
|
|
fmt.Println("====================================================")
|
|
for _, vendor := range vendors[0:5] {
|
|
fmt.Printf("%v ('%v') has %d products\n", vendor.Name, vendor.ID, len(vendor.Products))
|
|
}
|
|
}
|
|
```
|
|
|
|
which yields (on my local workstation as of July 7th, 2018):
|
|
|
|
```
|
|
Top 5 vendors by product
|
|
====================================================
|
|
Intel Corporation ('8086') has 3389 products
|
|
NVIDIA Corporation ('10de') has 1358 products
|
|
Advanced Micro Devices, Inc. [AMD/ATI] ('1002') has 886 products
|
|
National Instruments ('1093') has 601 products
|
|
Chelsio Communications Inc ('1425') has 525 products
|
|
```
|
|
|
|
The following is an example of querying the PCI product and subsystem
|
|
information to find the products which have the most number of subsystems that
|
|
have a different vendor than the top-level product. In other words, the two
|
|
products which have been re-sold or re-manufactured with the most number of
|
|
different companies.
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/jaypipes/pcidb"
|
|
)
|
|
|
|
type ByCountSeparateSubvendors []*pcidb.Product
|
|
|
|
func (v ByCountSeparateSubvendors) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v ByCountSeparateSubvendors) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
func (v ByCountSeparateSubvendors) Less(i, j int) bool {
|
|
iVendor := v[i].VendorID
|
|
iSetSubvendors := make(map[string]bool, 0)
|
|
iNumDiffSubvendors := 0
|
|
jVendor := v[j].VendorID
|
|
jSetSubvendors := make(map[string]bool, 0)
|
|
jNumDiffSubvendors := 0
|
|
|
|
for _, sub := range v[i].Subsystems {
|
|
if sub.VendorID != iVendor {
|
|
iSetSubvendors[sub.VendorID] = true
|
|
}
|
|
}
|
|
iNumDiffSubvendors = len(iSetSubvendors)
|
|
|
|
for _, sub := range v[j].Subsystems {
|
|
if sub.VendorID != jVendor {
|
|
jSetSubvendors[sub.VendorID] = true
|
|
}
|
|
}
|
|
jNumDiffSubvendors = len(jSetSubvendors)
|
|
|
|
return iNumDiffSubvendors > jNumDiffSubvendors
|
|
}
|
|
|
|
func main() {
|
|
pci, err := pcidb.New()
|
|
if err != nil {
|
|
fmt.Printf("Error getting PCI info: %v", err)
|
|
}
|
|
|
|
products := make([]*pcidb.Product, len(pci.Products))
|
|
x := 0
|
|
for _, product := range pci.Products {
|
|
products[x] = product
|
|
x++
|
|
}
|
|
|
|
sort.Sort(ByCountSeparateSubvendors(products))
|
|
|
|
fmt.Println("Top 2 products by # different subvendors")
|
|
fmt.Println("====================================================")
|
|
for _, product := range products[0:2] {
|
|
vendorID := product.VendorID
|
|
vendor := pci.Vendors[vendorID]
|
|
setSubvendors := make(map[string]bool, 0)
|
|
|
|
for _, sub := range product.Subsystems {
|
|
if sub.VendorID != vendorID {
|
|
setSubvendors[sub.VendorID] = true
|
|
}
|
|
}
|
|
fmt.Printf("%v ('%v') from %v\n", product.Name, product.ID, vendor.Name)
|
|
fmt.Printf(" -> %d subsystems under the following different vendors:\n", len(setSubvendors))
|
|
for subvendorID, _ := range setSubvendors {
|
|
subvendor, exists := pci.Vendors[subvendorID]
|
|
subvendorName := "Unknown subvendor"
|
|
if exists {
|
|
subvendorName = subvendor.Name
|
|
}
|
|
fmt.Printf(" - %v ('%v')\n", subvendorName, subvendorID)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
which yields (on my local workstation as of July 7th, 2018):
|
|
|
|
```
|
|
Top 2 products by # different subvendors
|
|
====================================================
|
|
RTL-8100/8101L/8139 PCI Fast Ethernet Adapter ('8139') from Realtek Semiconductor Co., Ltd.
|
|
-> 34 subsystems under the following different vendors:
|
|
- OVISLINK Corp. ('149c')
|
|
- EPoX Computer Co., Ltd. ('1695')
|
|
- Red Hat, Inc ('1af4')
|
|
- Mitac ('1071')
|
|
- Netgear ('1385')
|
|
- Micro-Star International Co., Ltd. [MSI] ('1462')
|
|
- Hangzhou Silan Microelectronics Co., Ltd. ('1904')
|
|
- Compex ('11f6')
|
|
- Edimax Computer Co. ('1432')
|
|
- KYE Systems Corporation ('1489')
|
|
- ZyXEL Communications Corporation ('187e')
|
|
- Acer Incorporated [ALI] ('1025')
|
|
- Matsushita Electric Industrial Co., Ltd. ('10f7')
|
|
- Ruby Tech Corp. ('146c')
|
|
- Belkin ('1799')
|
|
- Allied Telesis ('1259')
|
|
- Unex Technology Corp. ('1429')
|
|
- CIS Technology Inc ('1436')
|
|
- D-Link System Inc ('1186')
|
|
- Ambicom Inc ('1395')
|
|
- AOPEN Inc. ('a0a0')
|
|
- TTTech Computertechnik AG (Wrong ID) ('0357')
|
|
- Gigabyte Technology Co., Ltd ('1458')
|
|
- Packard Bell B.V. ('1631')
|
|
- Billionton Systems Inc ('14cb')
|
|
- Kingston Technologies ('2646')
|
|
- Accton Technology Corporation ('1113')
|
|
- Samsung Electronics Co Ltd ('144d')
|
|
- Biostar Microtech Int'l Corp ('1565')
|
|
- U.S. Robotics ('16ec')
|
|
- KTI ('8e2e')
|
|
- Hewlett-Packard Company ('103c')
|
|
- ASUSTeK Computer Inc. ('1043')
|
|
- Surecom Technology ('10bd')
|
|
Bt878 Video Capture ('036e') from Brooktree Corporation
|
|
-> 30 subsystems under the following different vendors:
|
|
- iTuner ('aa00')
|
|
- Nebula Electronics Ltd. ('0071')
|
|
- DViCO Corporation ('18ac')
|
|
- iTuner ('aa05')
|
|
- iTuner ('aa0d')
|
|
- LeadTek Research Inc. ('107d')
|
|
- Avermedia Technologies Inc ('1461')
|
|
- Chaintech Computer Co. Ltd ('270f')
|
|
- iTuner ('aa07')
|
|
- iTuner ('aa0a')
|
|
- Microtune, Inc. ('1851')
|
|
- iTuner ('aa01')
|
|
- iTuner ('aa04')
|
|
- iTuner ('aa06')
|
|
- iTuner ('aa0f')
|
|
- iTuner ('aa02')
|
|
- iTuner ('aa0b')
|
|
- Pinnacle Systems, Inc. (Wrong ID) ('bd11')
|
|
- Rockwell International ('127a')
|
|
- Askey Computer Corp. ('144f')
|
|
- Twinhan Technology Co. Ltd ('1822')
|
|
- Anritsu Corp. ('1852')
|
|
- iTuner ('aa08')
|
|
- Hauppauge computer works Inc. ('0070')
|
|
- Pinnacle Systems Inc. ('11bd')
|
|
- Conexant Systems, Inc. ('14f1')
|
|
- iTuner ('aa09')
|
|
- iTuner ('aa03')
|
|
- iTuner ('aa0c')
|
|
- iTuner ('aa0e')
|
|
```
|
|
|
|
## Developers
|
|
|
|
Contributions to `pcidb` are welcomed! Fork the repo on GitHub and submit a pull
|
|
request with your proposed changes. Or, feel free to log an issue for a feature
|
|
request or bug report.
|
|
|
|
### Running tests
|
|
|
|
You can run unit tests easily using the `make test` command, like so:
|
|
|
|
|
|
```
|
|
[jaypipes@uberbox pcidb]$ make test
|
|
go test github.com/jaypipes/pcidb
|
|
ok github.com/jaypipes/pcidb 0.045s
|
|
```
|