runtime: Introduce PciSlot and PciPath types

This is a dedicated data type for representing PCI paths, that is, PCI
devices described by the slot numbers of the bridges we need to reach
them.

There are a number of places that uses strings with that structure for
things.  The plan is to use this data type to consolidate their
handling.  These are essentially Go equivalents of the pci::Slot and
pci::Path types introduced in the Rust agent.

Forward port of
185b3ab044

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
This commit is contained in:
David Gibson 2020-10-08 13:16:05 +11:00
parent 7464d055a7
commit 8e5fd8ee84
2 changed files with 210 additions and 0 deletions

View File

@ -0,0 +1,100 @@
// Copyright Red Hat.
//
// SPDX-License-Identifier: Apache-2.0
//
package types
import (
"fmt"
"strconv"
"strings"
)
const (
// The PCI spec reserves 5 bits for slot number (a.k.a. device
// number), giving slots 0..31
pciSlotBits = 5
maxPciSlot = (1 << pciSlotBits) - 1
)
// A PciSlot describes where a PCI device sits on a single bus
//
// This encapsulates the PCI slot number a.k.a device number, which is
// limited to a 5 bit value [0x00..0x1f] by the PCI specification
//
// XXX In order to support multifunction device's we'll need to extend
// this to include the PCI 3-bit function number as well.
type PciSlot struct{ slot uint8 }
func PciSlotFromString(s string) (PciSlot, error) {
v, err := strconv.ParseUint(s, 16, pciSlotBits)
if err != nil {
return PciSlot{}, err
}
// The 5 bit width passed to ParseUint ensures the value is <=
// maxPciSlot
return PciSlot{slot: uint8(v)}, nil
}
func PciSlotFromInt(v int) (PciSlot, error) {
if v < 0 || v > maxPciSlot {
return PciSlot{}, fmt.Errorf("PCI slot 0x%x should be in range [0..0x%x]", v, maxPciSlot)
}
return PciSlot{slot: uint8(v)}, nil
}
func (slot PciSlot) String() string {
return fmt.Sprintf("%02x", slot.slot)
}
// A PciPath describes where a PCI sits in a PCI hierarchy.
//
// Consists of a list of PCI slots, giving the slot of each bridge
// that must be traversed from the PCI root to reach the device,
// followed by the slot of the device itself
//
// When formatted into a string is written as "xx/.../yy/zz" Here, zz
// is the slot of the device on its PCI bridge, yy is the slot of the
// bridge on its parent bridge and so forth until xx is the slot of
// the "most upstream" bridge on the root bus. If a device is
// connected directly to the root bus, its PciPath is just "zz"
type PciPath struct {
slots []PciSlot
}
func (p PciPath) String() string {
tokens := make([]string, len(p.slots))
for i, slot := range p.slots {
tokens[i] = slot.String()
}
return strings.Join(tokens, "/")
}
func (p PciPath) IsNil() bool {
return p.slots == nil
}
func PciPathFromString(s string) (PciPath, error) {
if s == "" {
return PciPath{}, nil
}
tokens := strings.Split(s, "/")
slots := make([]PciSlot, len(tokens))
for i, t := range tokens {
var err error
slots[i], err = PciSlotFromString(t)
if err != nil {
return PciPath{}, err
}
}
return PciPath{slots: slots}, nil
}
func PciPathFromSlots(slots ...PciSlot) (PciPath, error) {
if len(slots) == 0 {
return PciPath{}, fmt.Errorf("PCI path needs at least one component")
}
return PciPath{slots: slots}, nil
}

View File

@ -0,0 +1,110 @@
// Copyright Red Hat.
//
// SPDX-License-Identifier: Apache-2.0
//
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPciSlot(t *testing.T) {
assert := assert.New(t)
// Valid slots
slot, err := PciSlotFromInt(0x00)
assert.NoError(err)
assert.Equal(slot, PciSlot{})
assert.Equal(slot.String(), "00")
slot, err = PciSlotFromString("00")
assert.NoError(err)
assert.Equal(slot, PciSlot{})
slot, err = PciSlotFromInt(31)
assert.NoError(err)
slot2, err := PciSlotFromString("1f")
assert.NoError(err)
assert.Equal(slot, slot2)
// Bad slots
_, err = PciSlotFromInt(-1)
assert.Error(err)
_, err = PciSlotFromInt(32)
assert.Error(err)
_, err = PciSlotFromString("20")
assert.Error(err)
_, err = PciSlotFromString("xy")
assert.Error(err)
_, err = PciSlotFromString("00/")
assert.Error(err)
_, err = PciSlotFromString("")
assert.Error(err)
}
func TestPciPath(t *testing.T) {
assert := assert.New(t)
slot3, err := PciSlotFromInt(0x03)
assert.NoError(err)
slot4, err := PciSlotFromInt(0x04)
assert.NoError(err)
slot5, err := PciSlotFromInt(0x05)
assert.NoError(err)
// Empty/nil paths
pcipath := PciPath{}
assert.True(pcipath.IsNil())
pcipath, err = PciPathFromString("")
assert.NoError(err)
assert.True(pcipath.IsNil())
assert.Equal(pcipath, PciPath{})
// Valid paths
pcipath, err = PciPathFromSlots(slot3)
assert.NoError(err)
assert.False(pcipath.IsNil())
assert.Equal(pcipath.String(), "03")
pcipath2, err := PciPathFromString("03")
assert.NoError(err)
assert.Equal(pcipath, pcipath2)
pcipath, err = PciPathFromSlots(slot3, slot4)
assert.NoError(err)
assert.False(pcipath.IsNil())
assert.Equal(pcipath.String(), "03/04")
pcipath2, err = PciPathFromString("03/04")
assert.NoError(err)
assert.Equal(pcipath, pcipath2)
pcipath, err = PciPathFromSlots(slot3, slot4, slot5)
assert.NoError(err)
assert.False(pcipath.IsNil())
assert.Equal(pcipath.String(), "03/04/05")
pcipath2, err = PciPathFromString("03/04/05")
assert.NoError(err)
assert.Equal(pcipath, pcipath2)
// Bad paths
_, err = PciPathFromSlots()
assert.Error(err)
_, err = PciPathFromString("20")
assert.Error(err)
_, err = PciPathFromString("//")
assert.Error(err)
_, err = PciPathFromString("xyz")
assert.Error(err)
}