2023-01-24 09:37:46 +00:00
|
|
|
package pciids
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
_ "embed" // Fallback is the embedded pci.ids db file
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// token what the Lexer retruns.
|
2023-01-24 09:37:46 +00:00
|
|
|
type token int
|
|
|
|
|
|
|
|
const (
|
2024-04-18 12:49:21 +00:00
|
|
|
// ILLEGAL a token which the Lexer does not understand.
|
2023-01-24 09:37:46 +00:00
|
|
|
ILLEGAL token = iota
|
2024-04-18 12:49:21 +00:00
|
|
|
// EOF end of file.
|
2023-01-24 09:37:46 +00:00
|
|
|
EOF
|
2024-04-18 12:49:21 +00:00
|
|
|
// WS whitespace.
|
2023-01-24 09:37:46 +00:00
|
|
|
WS
|
2024-04-18 12:49:21 +00:00
|
|
|
// NEWLINE '\n'.
|
2023-01-24 09:37:46 +00:00
|
|
|
NEWLINE
|
2024-04-18 12:49:21 +00:00
|
|
|
// COMMENT '# something'.
|
2023-01-24 09:37:46 +00:00
|
|
|
COMMENT
|
2024-04-18 12:49:21 +00:00
|
|
|
// VENDOR PCI vendor.
|
2023-01-24 09:37:46 +00:00
|
|
|
VENDOR
|
2024-04-18 12:49:21 +00:00
|
|
|
// SUBVENDOR PCI subvendor.
|
2023-01-24 09:37:46 +00:00
|
|
|
SUBVENDOR
|
2024-04-18 12:49:21 +00:00
|
|
|
// DEVICE PCI device.
|
2023-01-24 09:37:46 +00:00
|
|
|
DEVICE
|
2024-04-18 12:49:21 +00:00
|
|
|
// CLASS PCI class.
|
2023-01-24 09:37:46 +00:00
|
|
|
CLASS
|
2024-04-18 12:49:21 +00:00
|
|
|
// SUBCLASS PCI subclass.
|
2023-01-24 09:37:46 +00:00
|
|
|
SUBCLASS
|
2024-04-18 12:49:21 +00:00
|
|
|
// PROGIF PCI programming interface.
|
2023-01-24 09:37:46 +00:00
|
|
|
PROGIF
|
|
|
|
)
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// literal values from the Lexer.
|
2023-01-24 09:37:46 +00:00
|
|
|
type literal struct {
|
|
|
|
ID string
|
|
|
|
name string
|
|
|
|
SubName string
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// scanner a lexical scanner.
|
2023-01-24 09:37:46 +00:00
|
|
|
type scanner struct {
|
|
|
|
r *bufio.Reader
|
|
|
|
isVendor bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// newScanner well a new scanner ...
|
|
|
|
func newScanner(r io.Reader) *scanner {
|
|
|
|
return &scanner{r: bufio.NewReader(r)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since the pci.ids is line base we're consuming a whole line rather then only
|
2024-04-18 12:49:21 +00:00
|
|
|
// a single rune/char.
|
2023-01-24 09:37:46 +00:00
|
|
|
func (s *scanner) readline() []byte {
|
|
|
|
ln, err := s.r.ReadBytes('\n')
|
|
|
|
if err == io.EOF {
|
|
|
|
return []byte{'E', 'O', 'F'}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("ReadBytes failed with %v", err)
|
|
|
|
return []byte{}
|
|
|
|
}
|
|
|
|
return ln
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanClass(line []byte) (token, literal) {
|
|
|
|
class := string(line[1:])
|
|
|
|
return CLASS, scanEntry([]byte(class), 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanSubVendor(line []byte) (token, literal) {
|
|
|
|
trim0 := strings.TrimSpace(string(line))
|
|
|
|
subv := string(trim0[:4])
|
|
|
|
trim1 := strings.TrimSpace(trim0[4:])
|
|
|
|
subd := string(trim1[:4])
|
|
|
|
subn := strings.TrimSpace(trim1[4:])
|
|
|
|
|
|
|
|
return SUBVENDOR, literal{subv, subd, subn}
|
|
|
|
}
|
|
|
|
|
|
|
|
func scanEntry(line []byte, offset uint) literal {
|
|
|
|
trim := strings.TrimSpace(string(line))
|
|
|
|
id := string(trim[:offset])
|
|
|
|
name := strings.TrimSpace(trim[offset:])
|
|
|
|
|
|
|
|
return literal{id, name, ""}
|
|
|
|
}
|
|
|
|
|
|
|
|
func isLeadingOneTab(ln []byte) bool { return (ln[0] == '\t') && (ln[1] != '\t') }
|
|
|
|
func isLeadingTwoTabs(ln []byte) bool { return (ln[0] == '\t') && (ln[1] == '\t') }
|
|
|
|
|
|
|
|
func isHexDigit(ln []byte) bool { return (ln[0] >= '0' && ln[0] <= '9') }
|
|
|
|
func isHexLetter(ln []byte) bool { return (ln[0] >= 'a' && ln[0] <= 'f') }
|
|
|
|
|
|
|
|
func isVendor(ln []byte) bool { return isHexDigit(ln) || isHexLetter(ln) }
|
|
|
|
func isEOF(ln []byte) bool { return (ln[0] == 'E' && ln[1] == 'O' && ln[2] == 'F') }
|
|
|
|
func isComment(ln []byte) bool { return (ln[0] == '#') }
|
|
|
|
func isSubVendor(ln []byte) bool { return isLeadingTwoTabs(ln) }
|
|
|
|
func isDevice(ln []byte) bool { return isLeadingOneTab(ln) }
|
|
|
|
func isNewline(ln []byte) bool { return (ln[0] == '\n') }
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// List of known device classes, subclasses and programming interfaces.
|
2023-01-24 09:37:46 +00:00
|
|
|
func isClass(ln []byte) bool { return (ln[0] == 'C') }
|
|
|
|
func isProgIf(ln []byte) bool { return isLeadingTwoTabs(ln) }
|
|
|
|
func isSubClass(ln []byte) bool { return isLeadingOneTab(ln) }
|
|
|
|
|
|
|
|
// unread places the previously read rune back on the reader.
|
|
|
|
func (s *scanner) unread() { _ = s.r.UnreadRune() }
|
|
|
|
|
|
|
|
// scan returns the next token and literal value.
|
|
|
|
func (s *scanner) scan() (tok token, lit literal) {
|
|
|
|
|
|
|
|
line := s.readline()
|
|
|
|
|
|
|
|
if isEOF(line) {
|
|
|
|
return EOF, literal{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if isNewline(line) {
|
|
|
|
return NEWLINE, literal{ID: string('\n')}
|
|
|
|
}
|
|
|
|
|
|
|
|
if isComment(line) {
|
|
|
|
return COMMENT, literal{ID: string(line)}
|
|
|
|
}
|
|
|
|
|
|
|
|
// vendors
|
|
|
|
if isVendor(line) {
|
|
|
|
s.isVendor = true
|
|
|
|
return VENDOR, scanEntry(line, 4)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isSubVendor(line) && s.isVendor {
|
|
|
|
return scanSubVendor(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isDevice(line) && s.isVendor {
|
|
|
|
return DEVICE, scanEntry(line, 4)
|
|
|
|
}
|
|
|
|
|
|
|
|
// classes
|
|
|
|
if isClass(line) {
|
|
|
|
s.isVendor = false
|
|
|
|
return scanClass(line)
|
|
|
|
}
|
|
|
|
if isProgIf(line) && !s.isVendor {
|
|
|
|
return PROGIF, scanEntry(line, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isSubClass(line) && !s.isVendor {
|
|
|
|
return SUBCLASS, scanEntry(line, 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ILLEGAL, literal{ID: string(line)}
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// parser reads the tokens returned by the Lexer and constructs the AST.
|
2023-01-24 09:37:46 +00:00
|
|
|
type parser struct {
|
|
|
|
s *scanner
|
|
|
|
buf struct {
|
|
|
|
tok token
|
|
|
|
lit literal
|
|
|
|
n int
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-14 15:55:18 +00:00
|
|
|
// Various locations of pci.ids for different distributions. These may be more
|
2024-04-18 12:49:21 +00:00
|
|
|
// up to date then the embedded pci.ids db.
|
2023-06-14 15:55:18 +00:00
|
|
|
var defaultPCIdbPaths = []string{
|
|
|
|
"/usr/share/misc/pci.ids", // Ubuntu
|
|
|
|
"/usr/local/share/pci.ids", // RHEL like with manual update
|
|
|
|
"/usr/share/hwdata/pci.ids", // RHEL like
|
|
|
|
"/usr/share/pci.ids", // SUSE
|
|
|
|
}
|
|
|
|
|
2023-01-24 09:37:46 +00:00
|
|
|
// This is a fallback if all of the locations fail
|
2023-11-15 20:38:54 +00:00
|
|
|
//
|
2023-01-24 09:37:46 +00:00
|
|
|
//go:embed default_pci.ids
|
|
|
|
var defaultPCIdb []byte
|
|
|
|
|
|
|
|
// NewDB Parse the PCI DB in its default locations or use the default
|
|
|
|
// builtin pci.ids db.
|
2023-06-14 15:55:18 +00:00
|
|
|
func NewDB(opts ...Option) Interface {
|
|
|
|
db := &pcidb{}
|
|
|
|
for _, opt := range opts {
|
|
|
|
opt(db)
|
|
|
|
}
|
|
|
|
|
|
|
|
pcidbs := defaultPCIdbPaths
|
|
|
|
if db.path != "" {
|
|
|
|
pcidbs = append([]string{db.path}, defaultPCIdbPaths...)
|
2023-01-24 09:37:46 +00:00
|
|
|
}
|
2023-06-14 15:55:18 +00:00
|
|
|
|
2023-01-24 09:37:46 +00:00
|
|
|
return newParser(pcidbs).parse()
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// Option defines a function for passing options to the NewDB() call.
|
2023-06-14 15:55:18 +00:00
|
|
|
type Option func(*pcidb)
|
|
|
|
|
|
|
|
// WithFilePath provides an Option to set the file path
|
|
|
|
// for the pciids database used by pciids interface.
|
|
|
|
// The file path provided takes precedence over all other
|
|
|
|
// paths.
|
|
|
|
func WithFilePath(path string) Option {
|
|
|
|
return func(db *pcidb) {
|
|
|
|
db.path = path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 09:37:46 +00:00
|
|
|
// newParser will attempt to read the db pci.ids from well known places or fall
|
2024-04-18 12:49:21 +00:00
|
|
|
// back to an internal db.
|
2023-01-24 09:37:46 +00:00
|
|
|
func newParser(pcidbs []string) *parser {
|
|
|
|
|
|
|
|
for _, db := range pcidbs {
|
|
|
|
file, err := os.ReadFile(db)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return newParserFromReader(bufio.NewReader(bytes.NewReader(file)))
|
|
|
|
|
|
|
|
}
|
|
|
|
// We're using go embed above to have the byte array
|
|
|
|
// correctly initialized with the internal shipped db
|
2024-04-18 12:49:21 +00:00
|
|
|
// if we cannot find an up to date in the filesystem.
|
2023-01-24 09:37:46 +00:00
|
|
|
return newParserFromReader(bufio.NewReader(bytes.NewReader(defaultPCIdb)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func newParserFromReader(r *bufio.Reader) *parser {
|
|
|
|
return &parser{s: newScanner(r)}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) scan() (tok token, lit literal) {
|
|
|
|
|
|
|
|
if p.buf.n != 0 {
|
|
|
|
p.buf.n = 0
|
|
|
|
return p.buf.tok, p.buf.lit
|
|
|
|
}
|
|
|
|
tok, lit = p.s.scan()
|
|
|
|
p.buf.tok, p.buf.lit = tok, lit
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *parser) unscan() { p.buf.n = 1 }
|
|
|
|
|
|
|
|
var _ Interface = (*pcidb)(nil)
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// Interface returns textual description of specific attributes of PCI devices.
|
2023-01-24 09:37:46 +00:00
|
|
|
type Interface interface {
|
2023-06-14 15:55:18 +00:00
|
|
|
GetDeviceName(uint16, uint16) (string, error)
|
|
|
|
GetClassName(uint32) (string, error)
|
2023-01-24 09:37:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// GetDeviceName return the textual description of the PCI device.
|
2023-06-14 15:55:18 +00:00
|
|
|
func (d *pcidb) GetDeviceName(vendorID uint16, deviceID uint16) (string, error) {
|
|
|
|
vendor, ok := d.vendors[vendorID]
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("failed to find vendor with id '%x'", vendorID)
|
|
|
|
}
|
|
|
|
|
|
|
|
device, ok := vendor.devices[deviceID]
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("failed to find device with id '%x'", deviceID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return device.name, nil
|
2023-01-24 09:37:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// GetClassName resturn the textual description of the PCI device class.
|
2023-06-14 15:55:18 +00:00
|
|
|
func (d *pcidb) GetClassName(classID uint32) (string, error) {
|
|
|
|
class, ok := d.classes[classID]
|
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("failed to find class with id '%x'", classID)
|
|
|
|
}
|
|
|
|
return class.name, nil
|
2023-01-24 09:37:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// pcidb The complete set of PCI vendors and PCI classes.
|
2023-01-24 09:37:46 +00:00
|
|
|
type pcidb struct {
|
|
|
|
vendors map[uint16]vendor
|
|
|
|
classes map[uint32]class
|
2023-06-14 15:55:18 +00:00
|
|
|
path string
|
2023-01-24 09:37:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// vendor PCI vendors/devices/subVendors/SubDevices.
|
2023-01-24 09:37:46 +00:00
|
|
|
type vendor struct {
|
|
|
|
name string
|
|
|
|
devices map[uint16]device
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// subVendor PCI subVendor.
|
2023-01-24 09:37:46 +00:00
|
|
|
type subVendor struct {
|
|
|
|
SubDevices map[uint16]SubDevice
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// SubDevice PCI SubDevice.
|
2023-01-24 09:37:46 +00:00
|
|
|
type SubDevice struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// device PCI device.
|
2023-01-24 09:37:46 +00:00
|
|
|
type device struct {
|
|
|
|
name string
|
|
|
|
subVendors map[uint16]subVendor
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// class PCI classes/subClasses/Programming Interfaces.
|
2023-01-24 09:37:46 +00:00
|
|
|
type class struct {
|
|
|
|
name string
|
|
|
|
subClasses map[uint32]subClass
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// subClass PCI subClass.
|
2023-01-24 09:37:46 +00:00
|
|
|
type subClass struct {
|
|
|
|
name string
|
|
|
|
progIfs map[uint8]progIf
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// progIf PCI Programming Interface.
|
2023-01-24 09:37:46 +00:00
|
|
|
type progIf struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// parse parses a PCI IDS entry.
|
2023-01-24 09:37:46 +00:00
|
|
|
func (p *parser) parse() Interface {
|
|
|
|
|
|
|
|
db := &pcidb{
|
|
|
|
vendors: map[uint16]vendor{},
|
|
|
|
classes: map[uint32]class{},
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// Used for housekeeping, breadcrumb for aggregated types.
|
2023-01-24 09:37:46 +00:00
|
|
|
var hkVendor vendor
|
|
|
|
var hkDevice device
|
|
|
|
|
|
|
|
var hkClass class
|
|
|
|
var hkSubClass subClass
|
|
|
|
|
|
|
|
var hkFullID uint32 = 0
|
|
|
|
var hkFullName [2]string
|
|
|
|
|
|
|
|
for {
|
|
|
|
tok, lit := p.scan()
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// We're ignoring COMMENT, NEWLINE.
|
|
|
|
// An EOF will break the loop.
|
2023-01-24 09:37:46 +00:00
|
|
|
if tok == EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// PCI vendors -------------------------------------------------
|
|
|
|
if tok == VENDOR {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 16)
|
|
|
|
db.vendors[uint16(id)] = vendor{
|
|
|
|
name: lit.name,
|
|
|
|
devices: map[uint16]device{},
|
|
|
|
}
|
|
|
|
hkVendor = db.vendors[uint16(id)]
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == DEVICE {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 16)
|
|
|
|
hkVendor.devices[uint16(id)] = device{
|
|
|
|
name: lit.name,
|
|
|
|
subVendors: map[uint16]subVendor{},
|
|
|
|
}
|
|
|
|
hkDevice = hkVendor.devices[uint16(id)]
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == SUBVENDOR {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 16)
|
|
|
|
hkDevice.subVendors[uint16(id)] = subVendor{
|
|
|
|
SubDevices: map[uint16]SubDevice{},
|
|
|
|
}
|
|
|
|
subvendor := hkDevice.subVendors[uint16(id)]
|
|
|
|
subid, _ := strconv.ParseUint(lit.name, 16, 16)
|
|
|
|
subvendor.SubDevices[uint16(subid)] = SubDevice{
|
|
|
|
name: lit.SubName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PCI classes -------------------------------------------------
|
|
|
|
if tok == CLASS {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 32)
|
|
|
|
db.classes[uint32(id)] = class{
|
|
|
|
name: lit.name,
|
|
|
|
subClasses: map[uint32]subClass{},
|
|
|
|
}
|
|
|
|
hkClass = db.classes[uint32(id)]
|
|
|
|
|
|
|
|
hkFullID = uint32(id) << 16
|
|
|
|
hkFullID = hkFullID & 0xFFFF0000
|
|
|
|
hkFullName[0] = fmt.Sprintf("%s (%02x)", lit.name, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == SUBCLASS {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 8)
|
|
|
|
hkClass.subClasses[uint32(id)] = subClass{
|
|
|
|
name: lit.name,
|
|
|
|
progIfs: map[uint8]progIf{},
|
|
|
|
}
|
|
|
|
hkSubClass = hkClass.subClasses[uint32(id)]
|
|
|
|
|
2024-04-18 12:49:21 +00:00
|
|
|
// Clear the last detected sub class.
|
2023-01-24 09:37:46 +00:00
|
|
|
hkFullID = hkFullID & 0xFFFF0000
|
|
|
|
hkFullID = hkFullID | uint32(id)<<8
|
2024-04-18 12:49:21 +00:00
|
|
|
// Clear the last detected prog iface.
|
2023-01-24 09:37:46 +00:00
|
|
|
hkFullID = hkFullID & 0xFFFFFF00
|
|
|
|
hkFullName[1] = fmt.Sprintf("%s (%02x)", lit.name, id)
|
|
|
|
|
|
|
|
db.classes[uint32(hkFullID)] = class{
|
|
|
|
name: hkFullName[0] + " | " + hkFullName[1],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == PROGIF {
|
|
|
|
id, _ := strconv.ParseUint(lit.ID, 16, 8)
|
|
|
|
hkSubClass.progIfs[uint8(id)] = progIf{
|
|
|
|
name: lit.name,
|
|
|
|
}
|
|
|
|
|
|
|
|
finalID := hkFullID | uint32(id)
|
|
|
|
|
|
|
|
name := fmt.Sprintf("%s (%02x)", lit.name, id)
|
|
|
|
finalName := hkFullName[0] + " | " + hkFullName[1] + " | " + name
|
|
|
|
|
|
|
|
db.classes[finalID] = class{
|
|
|
|
name: finalName,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if tok == ILLEGAL {
|
|
|
|
fmt.Printf("warning: illegal token %s %s cannot parse PCI IDS, database may be incomplete ", lit.ID, lit.name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return db
|
|
|
|
}
|