Skip to content

Commit

Permalink
Merge pull request #224 from fromanirh/snapshot-pci-tree
Browse files Browse the repository at this point in the history
snapshot: gather full pci tree, handling bridges
  • Loading branch information
jaypipes committed Mar 9, 2021
2 parents dc00ad8 + 1f10653 commit b593e32
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 122 deletions.
5 changes: 3 additions & 2 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/jaypipes/ghw/pkg/net"
"github.com/jaypipes/ghw/pkg/option"
"github.com/jaypipes/ghw/pkg/pci"
pciaddress "github.com/jaypipes/ghw/pkg/pci/address"
"github.com/jaypipes/ghw/pkg/product"
"github.com/jaypipes/ghw/pkg/topology"
)
Expand Down Expand Up @@ -125,12 +126,12 @@ const (
)

type PCIInfo = pci.Info
type PCIAddress = pci.Address
type PCIAddress = pciaddress.Address
type PCIDevice = pci.Device

var (
PCI = pci.New
PCIAddressFromString = pci.AddressFromString
PCIAddressFromString = pciaddress.FromString
)

type ProductInfo = product.Info
Expand Down
55 changes: 55 additions & 0 deletions pkg/pci/address/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package address

import (
"regexp"
"strings"
)

var (
regexAddress *regexp.Regexp = regexp.MustCompile(
`^(([0-9a-f]{0,4}):)?([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`,
)
)

type Address struct {
Domain string
Bus string
Slot string
Function string
}

// String() returns the canonical [D]BSF representation of this Address
func (addr *Address) String() string {
return addr.Domain + ":" + addr.Bus + ":" + addr.Slot + "." + addr.Function
}

// Given a string address, returns a complete Address struct, filled in with
// domain, bus, slot and function components. The address string may either
// be in $BUS:$SLOT.$FUNCTION (BSF) format or it can be a full PCI address
// that includes the 4-digit $DOMAIN information as well:
// $DOMAIN:$BUS:$SLOT.$FUNCTION.
//
// Returns "" if the address string wasn't a valid PCI address.
func FromString(address string) *Address {
addrLowered := strings.ToLower(address)
matches := regexAddress.FindStringSubmatch(addrLowered)
if len(matches) == 6 {
dom := "0000"
if matches[1] != "" {
dom = matches[2]
}
return &Address{
Domain: dom,
Bus: matches[3],
Slot: matches[4],
Function: matches[5],
}
}
return nil
}
82 changes: 82 additions & 0 deletions pkg/pci/address/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package address_test

import (
"reflect"
"strings"
"testing"

pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
)

func TestPCIAddressFromString(t *testing.T) {

tests := []struct {
addrStr string
expected *pciaddr.Address
// AddressFromString is more flexible than String() and wants
// to accept addresses not in full canonical form, as long as
// it can do the right thing - e.g. a sane default Domain exists.
// Thus we need to sometimes skip the Address -> string check.
skipStringTest bool
}{
{
addrStr: "00:00.0",
expected: &pciaddr.Address{
Domain: "0000",
Bus: "00",
Slot: "00",
Function: "0",
},
skipStringTest: true,
},
{
addrStr: "0000:00:00.0",
expected: &pciaddr.Address{
Domain: "0000",
Bus: "00",
Slot: "00",
Function: "0",
},
},
{
addrStr: "0000:03:00.0",
expected: &pciaddr.Address{
Domain: "0000",
Bus: "03",
Slot: "00",
Function: "0",
},
},
{
addrStr: "0000:03:00.A",
expected: &pciaddr.Address{
Domain: "0000",
Bus: "03",
Slot: "00",
Function: "a",
},
},
}
for x, test := range tests {
got := pciaddr.FromString(test.addrStr)
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("Test #%d failed. Expected %v but got %v", x, test.expected, got)
}

if test.skipStringTest {
continue
}

addrStr := got.String()
// addresses are case insensitive
if !strings.EqualFold(addrStr, test.addrStr) {
t.Fatalf("Test #%d failed. Expected %q but got %q (case insensitive match)", x, test.addrStr, addrStr)
}
}
}
45 changes: 7 additions & 38 deletions pkg/pci/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ import (
"encoding/json"
"fmt"
"regexp"
"strings"

"github.com/jaypipes/pcidb"

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/marshal"
"github.com/jaypipes/ghw/pkg/option"
pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
"github.com/jaypipes/ghw/pkg/topology"
"github.com/jaypipes/ghw/pkg/util"
)

// backward compatibility, to be removed in 1.0.0
type Address pciaddr.Address

// backward compatibility, to be removed in 1.0.0
var AddressFromString = pciaddr.FromString

var (
regexAddress *regexp.Regexp = regexp.MustCompile(
`^(([0-9a-f]{0,4}):)?([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f]{1})$`,
Expand Down Expand Up @@ -142,43 +148,6 @@ func (i *Info) String() string {
return fmt.Sprintf("PCI (%d devices)", len(i.Devices))
}

type Address struct {
Domain string
Bus string
Slot string
Function string
}

// String() returns the canonical [D]BSF representation of this Address
func (addr *Address) String() string {
return addr.Domain + ":" + addr.Bus + ":" + addr.Slot + "." + addr.Function
}

// Given a string address, returns a complete Address struct, filled in with
// domain, bus, slot and function components. The address string may either
// be in $BUS:$SLOT.$FUNCTION (BSF) format or it can be a full PCI address
// that includes the 4-digit $DOMAIN information as well:
// $DOMAIN:$BUS:$SLOT.$FUNCTION.
//
// Returns "" if the address string wasn't a valid PCI address.
func AddressFromString(address string) *Address {
addrLowered := strings.ToLower(address)
matches := regexAddress.FindStringSubmatch(addrLowered)
if len(matches) == 6 {
dom := "0000"
if matches[1] != "" {
dom = matches[2]
}
return &Address{
Domain: dom,
Bus: matches[3],
Slot: matches[4],
Function: matches[5],
}
}
return nil
}

// New returns a pointer to an Info struct that contains information about the
// PCI devices on the host system
func New(opts ...*option.Option) (*Info, error) {
Expand Down
12 changes: 7 additions & 5 deletions pkg/pci/pci_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package pci

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -16,6 +15,7 @@ import (

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/linuxpath"
pciaddr "github.com/jaypipes/ghw/pkg/pci/address"
"github.com/jaypipes/ghw/pkg/topology"
"github.com/jaypipes/ghw/pkg/util"
)
Expand All @@ -34,7 +34,7 @@ func (i *Info) load() error {

func getDeviceModaliasPath(ctx *context.Context, address string) string {
paths := linuxpath.New(ctx)
pciAddr := AddressFromString(address)
pciAddr := pciaddr.FromString(address)
if pciAddr == nil {
return ""
}
Expand All @@ -47,7 +47,7 @@ func getDeviceModaliasPath(ctx *context.Context, address string) string {

func getDeviceRevision(ctx *context.Context, address string) string {
paths := linuxpath.New(ctx)
pciAddr := AddressFromString(address)
pciAddr := pciaddr.FromString(address)
if pciAddr == nil {
return ""
}
Expand Down Expand Up @@ -276,11 +276,13 @@ func findPCIProgrammingInterface(
func (info *Info) GetDevice(address string) *Device {
fp := getDeviceModaliasPath(info.ctx, address)
if fp == "" {
info.ctx.Warn("error finding modalias info for device %q", address)
return nil
}

modaliasInfo := parseModaliasFile(fp)
if modaliasInfo == nil {
info.ctx.Warn("error parsing modalias info for device %q", address)
return nil
}

Expand Down Expand Up @@ -354,15 +356,15 @@ func (info *Info) ListDevices() []*Device {
// address and append to the returned array.
links, err := ioutil.ReadDir(paths.SysBusPciDevices)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: failed to read /sys/bus/pci/devices")
info.ctx.Warn("failed to read /sys/bus/pci/devices")
return nil
}
var dev *Device
for _, link := range links {
addr := link.Name()
dev = info.GetDevice(addr)
if dev == nil {
_, _ = fmt.Fprintf(os.Stderr, "error: failed to get device information for PCI address %s\n", addr)
info.ctx.Warn("failed to get device information for PCI address %s", addr)
} else {
devs = append(devs, dev)
}
Expand Down
69 changes: 0 additions & 69 deletions pkg/pci/pci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,82 +8,13 @@ package pci_test

import (
"os"
"reflect"
"strings"
"testing"

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/marshal"
"github.com/jaypipes/ghw/pkg/pci"
)

func TestPCIAddressFromString(t *testing.T) {

tests := []struct {
addrStr string
expected *pci.Address
// AddressFromString is more flexible than String() and wants
// to accept addresses not in full canonical form, as long as
// it can do the right thing - e.g. a sane default Domain exists.
// Thus we need to sometimes skip the Address -> string check.
skipStringTest bool
}{
{
addrStr: "00:00.0",
expected: &pci.Address{
Domain: "0000",
Bus: "00",
Slot: "00",
Function: "0",
},
skipStringTest: true,
},
{
addrStr: "0000:00:00.0",
expected: &pci.Address{
Domain: "0000",
Bus: "00",
Slot: "00",
Function: "0",
},
},
{
addrStr: "0000:03:00.0",
expected: &pci.Address{
Domain: "0000",
Bus: "03",
Slot: "00",
Function: "0",
},
},
{
addrStr: "0000:03:00.A",
expected: &pci.Address{
Domain: "0000",
Bus: "03",
Slot: "00",
Function: "a",
},
},
}
for x, test := range tests {
got := pci.AddressFromString(test.addrStr)
if !reflect.DeepEqual(got, test.expected) {
t.Fatalf("Test #%d failed. Expected %v but got %v", x, test.expected, got)
}

if test.skipStringTest {
continue
}

addrStr := got.String()
// addresses are case insensitive
if !strings.EqualFold(addrStr, test.addrStr) {
t.Fatalf("Test #%d failed. Expected %q but got %q (case insensitive match)", x, test.addrStr, addrStr)
}
}
}

func TestPCI(t *testing.T) {
if _, ok := os.LookupEnv("GHW_TESTING_SKIP_PCI"); ok {
t.Skip("Skipping PCI tests.")
Expand Down
Loading

0 comments on commit b593e32

Please sign in to comment.