diff --git a/virtcontainers/pkg/types/pcipath.go b/virtcontainers/pkg/types/pcipath.go new file mode 100644 index 0000000000..7f5acd09b6 --- /dev/null +++ b/virtcontainers/pkg/types/pcipath.go @@ -0,0 +1,98 @@ +// Copyright (c) 2020 Red Hat +// +// SPDX-License-Identifier: Apache-2.0 +// + +package types + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + 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 value 0x%x out of range", v) + } + 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 +} diff --git a/virtcontainers/pkg/types/pcipath_test.go b/virtcontainers/pkg/types/pcipath_test.go new file mode 100644 index 0000000000..feb1c07a39 --- /dev/null +++ b/virtcontainers/pkg/types/pcipath_test.go @@ -0,0 +1,110 @@ +// Copyright (c) 2020 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) + +}