# `pcidb` - the Golang PCI DB library

[![Build Status](https://github.com/jaypipes/pcidb/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jaypipes/pcidb/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/jaypipes/pcidb)](https://goreportcard.com/report/github.com/jaypipes/pcidb)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)

`pcidb` is a small Golang library for programmatic querying of PCI vendor,
product and class information.

We currently test `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, you can configure `pcidb` to fetch
> a current pci-ids DB file from the network. You can enable this
> network-fetching behaviour with the `pcidb.WithEnableNetworkFetch()` function
> or set the `PCIDB_ENABLE_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
```
