Skip to content

Commit

Permalink
sriov: add support
Browse files Browse the repository at this point in the history
Add support to report SRIOV devices.
Much like GPU devices, support is added using a new top-level package.

SRIOV devices are either Physical Functions or Virtual functions.
The preferred representation for ghw is Physical Functions, whose
dependent devices will be Virtual Functions; however, for the sake of
practicality, the API also exposes soft references to Virtual Functions,
so consumers of the API can access them directly and not navigating
the parent devices.

This patch also adds support in `ghwc`, to report the sriov information,
and in the `snapshot` package, to make sure to capture all the files
in sysfs that ghw cares about.

Last but not least, lacking access to suitable non-linux systems,
support is provided only on linux OS, even though the API tries hard
not to be linux-specific.

Resolves: jaypipes#92
Signed-off-by: Francesco Romani <fromani@redhat.com>
  • Loading branch information
ffromani committed Apr 29, 2022
1 parent 2ea05cb commit 68decd7
Show file tree
Hide file tree
Showing 13 changed files with 545 additions and 15 deletions.
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,64 @@ information
`ghw.TopologyNode` struct if you'd like to dig deeper into the NUMA/topology
subsystem

### SRIOV

SRIOV (Single-Root Input/Output Virtualization) is a class of PCI devices that ghw models explicitly, like gpus.

```go
package main

import (
"fmt"

"github.com/jaypipes/ghw"
)

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

fmt.Printf("%v\n", sriov)

for _, dev := range sriov.PhysicalFunctions {
fmt.Printf(" %v\n", dev)
}
}
```

ghw discovers the SRIOV devices starting from the Physical Function (PF) and exposes them in the `PhysicalFunctions` slice.
Virtual Function (VF) are exposed as properties of the PF instance with exposes them.
However, in some cases users are interested in the VFs first, so it's clumsy to navigate the PFs to learn about VFs.
To make this easier, ghw also exposes a slice of VF instances. These instances are soft references to the very same VF objects
you can find from the PF objects.

```go
package main

import (
"fmt"

"github.com/jaypipes/ghw"
)

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

fmt.Printf("%v\n", sriov)

// you will see the very same VF data data you seen from the previous example
for _, dev := range sriov.VirtualFunctions {
fmt.Printf(" %v\n", dev)
}
}
```


### Chassis

The host's chassis information is accessible with the `ghw.Chassis()` function. This
Expand Down
48 changes: 48 additions & 0 deletions cmd/ghwc/commands/sriov.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 commands

import (
"fmt"

"github.com/jaypipes/ghw"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

// sriovCmd represents the listing command
var sriovCmd = &cobra.Command{
Use: "sriov",
Short: "Show SRIOV devices information for the host system",
RunE: showSRIOV,
}

// showSRIOV show SRIOV physical device information for the host system.
func showSRIOV(cmd *cobra.Command, args []string) error {
sriov, err := ghw.SRIOV()
if err != nil {
return errors.Wrap(err, "error getting SRIOV info")
}

switch outputFormat {
case outputFormatHuman:
fmt.Printf("%v\n", sriov)

for _, dev := range sriov.PhysicalFunctions {
fmt.Printf(" %v\n", dev)
}
case outputFormatJSON:
fmt.Printf("%s\n", sriov.JSONString(pretty))
case outputFormatYAML:
fmt.Printf("%s", sriov.YAMLString())
}
return nil
}

func init() {
rootCmd.AddCommand(sriovCmd)
}
33 changes: 19 additions & 14 deletions host.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package ghw

import (
"fmt"
"strings"

"github.com/jaypipes/ghw/pkg/context"

Expand Down Expand Up @@ -110,20 +111,24 @@ func Host(opts ...*WithOption) (*HostInfo, error) {
// String returns a newline-separated output of the HostInfo's component
// structs' String-ified output
func (info *HostInfo) String() string {
return fmt.Sprintf(
"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
info.Block.String(),
info.CPU.String(),
info.GPU.String(),
info.Memory.String(),
info.Network.String(),
info.Topology.String(),
info.Chassis.String(),
info.BIOS.String(),
info.Baseboard.String(),
info.Product.String(),
info.PCI.String(),
)
var b strings.Builder
for _, s := range []fmt.Stringer{
info.Block,
info.CPU,
info.GPU,
info.Memory,
info.Network,
info.Topology,
info.Chassis,
info.BIOS,
info.Baseboard,
info.Product,
info.PCI,
} {
b.WriteString(s.String())
b.WriteString("\n")
}
return b.String()
}

// YAMLString returns a string with the host information formatted as YAML
Expand Down
10 changes: 10 additions & 0 deletions pkg/pci/address/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ type Address struct {
Function string
}

func (addr *Address) Equal(a *Address) bool {
if addr == nil && a == nil {
return true
}
if addr != nil && a != nil {
return addr.Domain == a.Domain && addr.Bus == a.Bus && addr.Device == a.Device && addr.Function == a.Function
}
return false
}

// String() returns the canonical [D]BDF representation of this Address
func (addr *Address) String() string {
return addr.Domain + ":" + addr.Bus + ":" + addr.Device + "." + addr.Function
Expand Down
8 changes: 8 additions & 0 deletions pkg/pci/address/address_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ func TestPCIAddressFromString(t *testing.T) {
}
}
}

func TestPCIAddressEqual(t *testing.T) {
addr1 := pciaddr.FromString("0000:03:00.A")
addr2 := pciaddr.FromString("03:00.A")
if addr1.Equal(addr2) == false {
t.Fatalf("addr1 %v and addr2 %v should be equal", addr1, addr2)
}
}
28 changes: 28 additions & 0 deletions pkg/pci/function.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

// Function describes an SR-IOV physical or virtual function. Physical functions
// will have no Parent Function struct pointer and will have one or more Function
// structs in the Functions field.
type Function struct {
Device // All Functions are PCI Devices
// Parent contains a pointer to the parent physical function.
// Will be empty when this is a physical function
Parent *Function `json:"parent,omitempty"`
// MaxVFs contains the maximum number of supported virtual
// functions for this physical function
MaxVFs int `json:"max_vfs"`
// Functions contains the physical function's virtual functions
Functions []*Function `json:"functions"`
}

// IsPhysical returns true if the PCIe function is a physical function, false
// if it is a virtual function
func (f *Function) IsPhysical() bool {
return f.Parent == nil
}
109 changes: 109 additions & 0 deletions pkg/pci/function_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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"
pciaddress "github.com/jaypipes/ghw/pkg/pci/address"
"github.com/jaypipes/ghw/pkg/util"
)

func (info *Info) scanDevice(dev *pci.Device) error {
paths := linuxpath.New(info.ctx)
devPath := filepath.Join(paths.SysBusPciDevices, dev.Address)

buf, err := ioutil.ReadFile(filepath.Join(devPath, "sriov_totalvfs"))
if err != nil {
// is not a physfn. Since we will fill virtfn from physfn, we can give up now
return nil
}

maxVFs, err := strconv.Atoi(strings.TrimSpace(string(buf)))
if err != nil {
info.ctx.Warn("error reading sriov_totalvfn for %q: %v", devPath, err)
return nil
}

virtFNs := findVFsFromPF(info, info, dev.Address, devPath)
physFN := PhysicalFunction{
Device: info.newDevice(dev, devPath),
MaxVFNum: maxVFs,
VFs: virtFNs,
}

info.PhysicalFunctions = append(info.PhysicalFunctions, &physFN)
for idx := 0; idx < len(virtFNs); idx++ {
info.VirtualFunctions = append(info.VirtualFunctions, &virtFNs[idx])
}

return nil
}

func findVFsFromPF(info *Info, pciInfo *pci.Info, parentAddr, parentPath string) []VirtualFunction {
numVfs := util.SafeIntFromFile(info.ctx, filepath.Join(parentPath, "sriov_numvfs"))
if numVfs == -1 {
return nil
}

var vfs []VirtualFunction
for vfnIdx := 0; vfnIdx < numVfs; vfnIdx++ {
virtFn := fmt.Sprintf("virtfn%d", vfnIdx)
vfnDest, err := os.Readlink(filepath.Join(parentPath, virtFn))
if err != nil {
info.ctx.Warn("error reading backing device for virtfn %q physfn %q: %v", virtFn, parentPath, err)
return nil
}

vfnPath := filepath.Clean(filepath.Join(parentPath, vfnDest))
vfnAddr := filepath.Base(vfnDest)
vfnDev := pciInfo.GetDevice(vfnAddr)
if vfnDev == nil {
info.ctx.Warn("error finding the PCI device for virtfn %s physfn %s", vfnAddr, parentAddr)
return nil
}

vfs = append(vfs, VirtualFunction{
Device: info.newDevice(vfnDev, vfnPath),
Index: vfnIdx,
ParentAddress: pciaddress.FromString(parentAddr),
})
}
return vfs
}

func (info *Info) newDevice(dev *pci.Device, devPath string) Device {
// see: https://doc.dpdk.org/guides/linux_gsg/linux_drivers.html
return Device{
Address: pciaddress.FromString(dev.Address),
Interfaces: findNetworkInterfaceNames(info.ctx, devPath),
PCI: dev,
}
}

func findNetworkInterfaceNames(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
}
Loading

0 comments on commit 68decd7

Please sign in to comment.