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: #92
Signed-off-by: Francesco Romani <fromani@redhat.com>
  • Loading branch information
ffromani committed Apr 25, 2021
1 parent f574e19 commit 1e9aa06
Show file tree
Hide file tree
Showing 13 changed files with 662 additions and 14 deletions.
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,67 @@ 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.

```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
9 changes: 9 additions & 0 deletions alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"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/sriov"
"github.com/jaypipes/ghw/pkg/topology"
)

Expand Down Expand Up @@ -146,3 +147,11 @@ type GraphicsCard = gpu.GraphicsCard
var (
GPU = gpu.New
)

type SRIOVInfo = sriov.Info
type PhysicalFunction = sriov.PhysicalFunction
type VirtualFunction = sriov.VirtualFunction

var (
SRIOV = sriov.New
)
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)
}
41 changes: 27 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 All @@ -22,6 +23,7 @@ import (
"github.com/jaypipes/ghw/pkg/net"
"github.com/jaypipes/ghw/pkg/pci"
"github.com/jaypipes/ghw/pkg/product"
"github.com/jaypipes/ghw/pkg/sriov"
"github.com/jaypipes/ghw/pkg/topology"
)

Expand All @@ -40,6 +42,7 @@ type HostInfo struct {
Baseboard *baseboard.Info `json:"baseboard"`
Product *product.Info `json:"product"`
PCI *pci.Info `json:"pci"`
SRIOV *sriov.Info `json:"sriov"`
}

// Host returns a pointer to a HostInfo struct that contains fields with
Expand Down Expand Up @@ -91,6 +94,10 @@ func Host(opts ...*WithOption) (*HostInfo, error) {
if err != nil {
return nil, err
}
sriovInfo, err := sriov.New(opts...)
if err != nil {
return nil, err
}
return &HostInfo{
ctx: ctx,
CPU: cpuInfo,
Expand All @@ -104,26 +111,32 @@ func Host(opts ...*WithOption) (*HostInfo, error) {
Baseboard: baseboardInfo,
Product: productInfo,
PCI: pciInfo,
SRIOV: sriovInfo,
}, nil
}

// 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,
info.SRIOV,
} {
b.WriteString(s.String())
b.WriteString("\n")
}
return b.String()
}

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

// Equal tells if two Addresses are equal or not
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.Slot == a.Slot && addr.Function == a.Function
}
return false
}

// String() returns the canonical [D]BSF representation of this Address
func (addr *Address) String() string {
return addr.Domain + ":" + addr.Bus + ":" + addr.Slot + "." + 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)
}
}
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 @@ -197,6 +198,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
17 changes: 17 additions & 0 deletions pkg/snapshot/clonetree_pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func scanPCIDeviceRoot(root string) (fileSpecs []string, pciRoots []string) {
"revision",
"vendor",
}

perDevEntriesOpt := []string{
"driver",
"net/*",
"physfn",
"sriov_*",
"virtfn*",
}

entries, err := ioutil.ReadDir(root)
if err != nil {
return []string{}, []string{}
Expand All @@ -93,6 +102,14 @@ func scanPCIDeviceRoot(root string) (fileSpecs []string, pciRoots []string) {
fileSpecs = append(fileSpecs, filepath.Join(pciEntry, perNetEntry))
}

for _, perNetEntryOpt := range perDevEntriesOpt {
netEntryOptPath := filepath.Join(pciEntry, perNetEntryOpt)
if items, err := filepath.Glob(netEntryOptPath); err == nil && len(items) > 0 {
fileSpecs = append(fileSpecs, netEntryOptPath)
}

}

if isPCIBridge(entryPath) {
trace("adding new PCI root %q\n", entryName)
pciRoots = append(pciRoots, pciEntry)
Expand Down
Loading

0 comments on commit 1e9aa06

Please sign in to comment.