aboutsummaryrefslogblamecommitdiff
path: root/tools/numap/internal/hwids/hwids.go
blob: 6aa9d8a8aeb7ca1834ca055a392ffc1d583025a7 (plain) (tree)



















































































































































                                                                                                                
package hwids

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

var pciPath = []string{
	"/usr/share/hwdata/pci.ids",
	"/usr/share/misc/pci.ids",
}

type PCIType int

const (
	PCIVendor PCIType = iota
	PCIDevice
	PCISubsystem
)

type PciDevices map[uint16][]PciDevice

// PciDevice represents a PCI device
type PciDevice struct {
	Type                   PCIType
	Vendor, Device         uint16
	SubVendor, SubDevice   uint16
	VendorName, DeviceName string
	SubName                string
}

// Load load the hardware database for PCI devices and return a map of
// vendor -> list of devices.
func Load() (PciDevices, error) {
	// if the environment variable HWDATAPATH is set, we add it to the
	// list of paths we check for the hardware database.
	extraPath := os.Getenv("HWDATA")
	if extraPath != "" {
		pciPath = append(pciPath, extraPath)
	}

	for _, f := range pciPath {
		fh, err := os.Open(f)
		if err != nil {
			continue
		}
		defer fh.Close()
		return parse(fh)
	}
	return PciDevices{}, fmt.Errorf("hwids: could not find a pci.ids file")
}

func parse(f *os.File) (PciDevices, error) {
	devices := make(PciDevices)

	s := bufio.NewScanner(f)

	// this is to keep track of the current device. The format of the
	// file is as follow:
	// vendor  vendor_name
	//       device  device_name                             <-- single tab
	//               subvendor subdevice  subsystem_name     <-- two tabs
	// the variable is to keep track of the current vendor / device
	cur := PciDevice{}

	for s.Scan() {
		l := s.Text()
		// skip empty lines or lines that are a comment
		if len(l) == 0 || l[0] == '#' {
			continue
		}
		// lines starting with a C are the classes definitions, and
		// they are at the end of the file, which means we're done
		// parsing the devices
		if l[0] == 'C' {
			break
		}

		parts := strings.SplitN(l, "  ", 2)
		if len(parts) != 2 {
			return devices, fmt.Errorf("hwids: malformed PCI ID line (missing ID separator): %s", l)
		}

		ids, name := parts[0], parts[1]
		if len(ids) < 2 || len(name) == 0 {
			return devices, fmt.Errorf("hwids: malformed PCI ID line (empty ID or name): %s", l)
		}

		cur.Type = PCIVendor

		if ids[0] == '\t' {
			if ids[1] == '\t' {
				cur.Type = PCISubsystem
			} else {
				cur.Type = PCIDevice
			}
		}

		var err error
		switch cur.Type {
		case PCIVendor:
			_, err = fmt.Sscanf(ids, "%x", &cur.Vendor)
			cur.VendorName = name
		case PCIDevice:
			_, err = fmt.Sscanf(ids, "%x", &cur.Device)
			cur.DeviceName = name
		case PCISubsystem:
			_, err = fmt.Sscanf(ids, "%x %x", &cur.SubVendor, &cur.SubDevice)
			cur.SubName = name
		}

		if err != nil {
			return devices, fmt.Errorf("hwids: malformed PCI ID line: %s: %v", l, err)
		}

		// This is to reset the state when we are moving to a
		// different vendor or device
		switch cur.Type {
		case PCIVendor:
			cur.Device = 0
			cur.DeviceName = ""
			fallthrough
		case PCIDevice:
			cur.SubVendor = 0
			cur.SubDevice = 0
			cur.SubName = ""
		}

		_, ok := devices[cur.Vendor]
		if ok {
			_devices := devices[cur.Vendor]
			_devices = append(_devices, cur)
			devices[cur.Vendor] = _devices

		} else {
			_devices := []PciDevice{cur}
			devices[cur.Vendor] = _devices
		}
	}

	if err := s.Err(); err != nil {
		return devices, fmt.Errorf("hwids: failed to read PCI ID line: %v", err)
	}

	return devices, nil
}