Skip to content

Commit

Permalink
WIP: RFC: expose SRIOV information
Browse files Browse the repository at this point in the history
WIP patch to demonstrate the possible API

TBD: add tests
TBD: provide rationale in the commit message

Signed-off-by: Francesco Romani <fromani@redhat.com>
  • Loading branch information
ffromani committed Mar 2, 2021
1 parent dc00ad8 commit 5e67349
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 0 deletions.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,49 @@ information
`ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology
subsystem

### SRIOV

*This API is PROVISIONAL! ghw will try hard to not make breaking changes to this API, but still users are advice this new API is not
declared stable yet. We expect to declare it stable with ghw version 1.0.0*

SRIOV (Single-Root Input/Output Virtualization) is a class of PCI devices that ghw models explicitly, like gpus; but unlike
gpus, support for sriov devices is part of the `pci` package.

To get the SRIOV-specific information of a given PCI device, you may use the `GetSRIOVInfo` function like in the example below:

```go
package main

import (
"encoding/json"
"fmt"
"os"

"github.com/jaypipes/ghw"
)

func main() {
pci, err := ghw.PCI()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting PCI info: %v", err)
}

addr := "0000:00:00.0"
if len(os.Args) == 2 {
addr = os.Args[1]
}
sriovInfo := pci.GetSRIOVInfo(addr)
if sriovInfo == nil {
fmt.Fprintf(os.Stderr, "could not retrieve SRIOV device information for %s\n", addr)
return
}

json.NewEncoder(os.Stdout).Encode(sriovInfo)
}
```

If the function returns `nil`, then the device is not recognized as a SRIOV device.

### Chassis

The host's chassis information is accessible with the `ghw.Chassis()` function. This
Expand Down
7 changes: 7 additions & 0 deletions pkg/pci/pci_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ func (info *Info) GetDevice(address string) *Device {
return device
}

// GetSRIOVInfo returns a pointer to a SRIOV struct that describes the SRIOV attributes of
// the PCI device at the requested address. If no such device could be found, or if the
// device exists but it is not recognized as SRIOV device, returns nil
func (info *Info) GetSRIOVInfo(address string) *SRIOVInfo {
return getDeviceSriovInfo(info.ctx, address)
}

// ParseDevice returns a pointer to a Device given its describing data.
// The PCI device obtained this way may not exist in the system;
// use GetDevice to get a *Device which is found in the system
Expand Down
1 change: 1 addition & 0 deletions pkg/pci/pci_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type pciTestCase struct {
addr string
node int
revision string
isSRIOV bool
}

// nolint: gocyclo
Expand Down
28 changes: 28 additions & 0 deletions pkg/pci/sriov.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci

type VFInfo struct {
ID int `json:"id"`
PCIAddress string `json:"pci_address"`
}

type SRIOVPhysFn struct {
MaxVFNum int `json:"max_vf_num"`
VFs []VFInfo `json:"vfs"`
}

type SRIOVVirtFn struct {
ParentPCIAddress string `json:"parent_pci_address"`
}

type SRIOVInfo struct {
Driver string `json:"driver"`
Interfaces []string `json:"interfaces"`
PhysFn *SRIOVPhysFn `json:"physfn,omitempty"`
VirtFn *SRIOVVirtFn `json:"virtfn,omitempty"`
}
104 changes: 104 additions & 0 deletions pkg/pci/sriov_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/jaypipes/ghw/pkg/context"
"github.com/jaypipes/ghw/pkg/linuxpath"
"github.com/jaypipes/ghw/pkg/util"
)

func getDeviceSriovInfo(ctx *context.Context, address string) *SRIOVInfo {
paths := linuxpath.New(ctx)
pciAddr := AddressFromString(address)
if pciAddr == nil {
return nil
}
devPath := filepath.Join(paths.SysBusPciDevices, pciAddr.String())

// see: https://doc.dpdk.org/guides/linux_gsg/linux_drivers.html
driver := ""
if dest, err := os.Readlink(filepath.Join(devPath, "driver")); err == nil {
driver = filepath.Base(dest)
}

if dest, err := os.Readlink(filepath.Join(devPath, "physfn")); err == nil {
// it's a virtual function!
return &SRIOVInfo{
Driver: driver,
Interfaces: findNetworks(ctx, devPath),
VirtFn: &SRIOVVirtFn{
ParentPCIAddress: filepath.Base(dest),
},
}
}
// it's either a physical function or a non-SRIOV device
if buf, err := ioutil.ReadFile(filepath.Join(devPath, "sriov_totalvfs")); err == nil {
// it seems a physical function
maxVFs, err := strconv.Atoi(strings.TrimSpace(string(buf)))
if err != nil {
ctx.Warn("error reading sriov_totalvfn for %q: %v", err)
return nil
}

return &SRIOVInfo{
Driver: driver,
Interfaces: findNetworks(ctx, devPath),
PhysFn: &SRIOVPhysFn{
MaxVFNum: maxVFs,
VFs: findVFsFromPF(ctx, devPath),
},
}
}
// not a SRIOV device
return nil
}

func findNetworks(ctx *context.Context, devPath string) []string {
netPath := filepath.Join(devPath, "net")

netEntries, err := ioutil.ReadDir(netPath)
if err != nil {
ctx.Warn("cannot enumerate network names for %q: %v", devPath, err)
return nil
}

var networks []string
for _, netEntry := range netEntries {
networks = append(networks, netEntry.Name())
}

return networks
}

func findVFsFromPF(ctx *context.Context, devPath string) []VFInfo {
numVfs := util.SafeIntFromFile(ctx, filepath.Join(devPath, "sriov_numvfs"))
if numVfs == -1 {
return nil
}

var vfs []VFInfo
for vfnIdx := 0; vfnIdx < numVfs; vfnIdx++ {
virtFn := fmt.Sprintf("virtfn%d", vfnIdx)
vfnDest, err := os.Readlink(filepath.Join(devPath, virtFn))
if err != nil {
ctx.Warn("error reading backing device for virtfn %q physfn %q: %v", virtFn, devPath, err)
return nil
}
vfs = append(vfs, VFInfo{
ID: vfnIdx,
PCIAddress: filepath.Base(vfnDest),
})
}
return vfs
}
48 changes: 48 additions & 0 deletions pkg/pci/sriov_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Use and distribution licensed under the Apache license version 2.
//
// See the COPYING file in the root project directory for full text.
//

package pci_test

import (
"fmt"
"testing"
)

// nolint: gocyclo
func TestPCICrosscheckSRIOV(t *testing.T) {
info := pciTestSetup(t)

tCases := []pciTestCase{
{
addr: "0000:07:03.0",
isSRIOV: false,
},
{
addr: "0000:05:11.0",
isSRIOV: true,
},
{
addr: "0000:05:00.1",
isSRIOV: true,
},
}
for _, tCase := range tCases {
t.Run(fmt.Sprintf("%s (sriov=%v)", tCase.addr, tCase.isSRIOV), func(t *testing.T) {
dev := info.GetDevice(tCase.addr)
if dev == nil {
t.Fatalf("got nil device for address %q", tCase.addr)
}
sriovInfo := info.GetSRIOVInfo(tCase.addr)
if tCase.isSRIOV && sriovInfo == nil {
t.Fatalf("expected SRIOV info for device at address %q", tCase.addr)
}
if !tCase.isSRIOV && sriovInfo != nil {
t.Fatalf("unexpected SRIOV info for device at address %q", tCase.addr)
}
})
}

}
6 changes: 6 additions & 0 deletions pkg/snapshot/clonetree.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package snapshot

import (
"errors"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -199,6 +200,11 @@ func copyLink(path, targetPath string) error {
return err
}
if err := os.Symlink(target, targetPath); err != nil {
// multiple sources can link back to the same entry: a notable example is
// all the SRIOV VFs linking back to the same driver.
if errors.Is(err, os.ErrExist) {
return nil
}
return err
}

Expand Down
Binary file not shown.

0 comments on commit 5e67349

Please sign in to comment.