Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
device: Introduce PciSlot and PciPath types
Browse files Browse the repository at this point in the history
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.

Signed-off-by: David Gibson <[email protected]>
  • Loading branch information
dgibson committed Oct 27, 2020
1 parent 1c0dccb commit 185b3ab
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 0 deletions.
98 changes: 98 additions & 0 deletions virtcontainers/pkg/types/pcipath.go
Original file line number Diff line number Diff line change
@@ -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
}
110 changes: 110 additions & 0 deletions virtcontainers/pkg/types/pcipath_test.go
Original file line number Diff line number Diff line change
@@ -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)

}

0 comments on commit 185b3ab

Please sign in to comment.