From 6d32ec0745d31821eddfcf77a2a314ddb146c0e8 Mon Sep 17 00:00:00 2001
From: Andrea Panattoni <apanatto@redhat.com>
Date: Fri, 25 Oct 2024 09:14:08 +0200
Subject: [PATCH 1/2] kernel: Set arguments based on CPU architecture

Kernel arguments like `intel_iommu=on` does not have sense
on AMD or ARM systems and some user might complain about
their presence, though they are likely to be harmless.

Also, on ARM systems the `iommu.passthrough` parameter is the
one to use [1].

Improve `GHWLib` to bridge CPU information from the library.
Add `CpuInfoProviderInterface` and inject it into the GenericPlugin
to implement the per CPU vendor logic.

[1] https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/kernel-parameters.txt#L2343

Signed-off-by: Andrea Panattoni <apanatto@redhat.com>
---
 pkg/consts/constants.go                    |  7 ++--
 pkg/helper/mock/mock_helper.go             | 15 +++++++
 pkg/host/internal/cpu/cpu.go               | 40 ++++++++++++++++++
 pkg/host/internal/lib/ghw/ghw.go           |  8 ++++
 pkg/host/internal/lib/ghw/mock/mock_ghw.go | 16 ++++++++
 pkg/host/manager.go                        |  5 +++
 pkg/host/mock/mock_host.go                 | 15 +++++++
 pkg/host/types/interfaces.go               | 13 ++++++
 pkg/plugins/generic/generic_plugin.go      | 27 ++++++++++++-
 pkg/plugins/generic/generic_plugin_test.go | 47 +++++++++++++++++-----
 10 files changed, 178 insertions(+), 15 deletions(-)
 create mode 100644 pkg/host/internal/cpu/cpu.go

diff --git a/pkg/consts/constants.go b/pkg/consts/constants.go
index f3c076111..f7025c90d 100644
--- a/pkg/consts/constants.go
+++ b/pkg/consts/constants.go
@@ -121,9 +121,10 @@ const (
 		`IMPORT{program}="/etc/udev/switchdev-vf-link-name.sh $attr{phys_port_name}", ` +
 		`NAME="%s_$env{NUMBER}"`
 
-	KernelArgPciRealloc = "pci=realloc"
-	KernelArgIntelIommu = "intel_iommu=on"
-	KernelArgIommuPt    = "iommu=pt"
+	KernelArgPciRealloc       = "pci=realloc"
+	KernelArgIntelIommu       = "intel_iommu=on"
+	KernelArgIommuPt          = "iommu=pt"
+	KernelArgIommuPassthrough = "iommu.passthrough=1"
 
 	// Feature gates
 	// ParallelNicConfigFeatureGate: allow to configure nics in parallel
diff --git a/pkg/helper/mock/mock_helper.go b/pkg/helper/mock/mock_helper.go
index cfca2a768..432d741be 100644
--- a/pkg/helper/mock/mock_helper.go
+++ b/pkg/helper/mock/mock_helper.go
@@ -351,6 +351,21 @@ func (mr *MockHostHelpersInterfaceMockRecorder) EnableService(service interface{
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableService", reflect.TypeOf((*MockHostHelpersInterface)(nil).EnableService), service)
 }
 
+// GetCPUVendor mocks base method.
+func (m *MockHostHelpersInterface) GetCPUVendor() (types.CPUVendor, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetCPUVendor")
+	ret0, _ := ret[0].(types.CPUVendor)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetCPUVendor indicates an expected call of GetCPUVendor.
+func (mr *MockHostHelpersInterfaceMockRecorder) GetCPUVendor() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCPUVendor", reflect.TypeOf((*MockHostHelpersInterface)(nil).GetCPUVendor))
+}
+
 // GetCheckPointNodeState mocks base method.
 func (m *MockHostHelpersInterface) GetCheckPointNodeState() (*v1.SriovNetworkNodeState, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/host/internal/cpu/cpu.go b/pkg/host/internal/cpu/cpu.go
new file mode 100644
index 000000000..fd02157e6
--- /dev/null
+++ b/pkg/host/internal/cpu/cpu.go
@@ -0,0 +1,40 @@
+package cpu
+
+import (
+	"fmt"
+
+	ghwPkg "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/ghw"
+	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types"
+)
+
+type cpuInfoProvider struct {
+	ghwLib ghwPkg.GHWLib
+}
+
+func New(ghwLib ghwPkg.GHWLib) *cpuInfoProvider {
+	return &cpuInfoProvider{
+		ghwLib: ghwLib,
+	}
+}
+
+func (c *cpuInfoProvider) GetCPUVendor() (types.CPUVendor, error) {
+	cpuInfo, err := c.ghwLib.CPU()
+	if err != nil {
+		return -1, fmt.Errorf("can't retrieve the CPU vendor: %w", err)
+	}
+
+	if len(cpuInfo.Processors) == 0 {
+		return -1, fmt.Errorf("wrong CPU information retrieved: %v", cpuInfo)
+	}
+
+	switch cpuInfo.Processors[0].Vendor {
+	case "GenuineIntel":
+		return types.CPUVendorIntel, nil
+	case "AuthenticAMD":
+		return types.CPUVendorAMD, nil
+	case "ARM":
+		return types.CPUVendorARM, nil
+	}
+
+	return -1, fmt.Errorf("unknown CPU vendor: %s", cpuInfo.Processors[0].Vendor)
+}
diff --git a/pkg/host/internal/lib/ghw/ghw.go b/pkg/host/internal/lib/ghw/ghw.go
index 6a6829604..d518977e4 100644
--- a/pkg/host/internal/lib/ghw/ghw.go
+++ b/pkg/host/internal/lib/ghw/ghw.go
@@ -2,6 +2,7 @@ package ghw
 
 import (
 	"github.com/jaypipes/ghw"
+	"github.com/jaypipes/ghw/pkg/cpu"
 )
 
 func New() GHWLib {
@@ -12,6 +13,9 @@ func New() GHWLib {
 type GHWLib interface {
 	// PCI returns a pointer to an Info that provide methods to access info about devices
 	PCI() (Info, error)
+
+	// CPU returns a pointer to an Info that provide methods to access info about devices
+	CPU() (*cpu.Info, error)
 }
 
 // Info interface provide methods to access info about devices
@@ -27,3 +31,7 @@ type libWrapper struct{}
 func (w *libWrapper) PCI() (Info, error) {
 	return ghw.PCI()
 }
+
+func (w *libWrapper) CPU() (*cpu.Info, error) {
+	return ghw.CPU()
+}
diff --git a/pkg/host/internal/lib/ghw/mock/mock_ghw.go b/pkg/host/internal/lib/ghw/mock/mock_ghw.go
index 2e2b4b5c5..9d6092362 100644
--- a/pkg/host/internal/lib/ghw/mock/mock_ghw.go
+++ b/pkg/host/internal/lib/ghw/mock/mock_ghw.go
@@ -9,6 +9,7 @@ import (
 
 	gomock "github.com/golang/mock/gomock"
 	ghw "github.com/jaypipes/ghw"
+	cpu "github.com/jaypipes/ghw/pkg/cpu"
 	ghw0 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/ghw"
 )
 
@@ -35,6 +36,21 @@ func (m *MockGHWLib) EXPECT() *MockGHWLibMockRecorder {
 	return m.recorder
 }
 
+// CPU mocks base method.
+func (m *MockGHWLib) CPU() (*cpu.Info, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CPU")
+	ret0, _ := ret[0].(*cpu.Info)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// CPU indicates an expected call of CPU.
+func (mr *MockGHWLibMockRecorder) CPU() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CPU", reflect.TypeOf((*MockGHWLib)(nil).CPU))
+}
+
 // PCI mocks base method.
 func (m *MockGHWLib) PCI() (ghw0.Info, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/host/manager.go b/pkg/host/manager.go
index 02a77a659..44bd45807 100644
--- a/pkg/host/manager.go
+++ b/pkg/host/manager.go
@@ -2,6 +2,7 @@ package host
 
 import (
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/bridge"
+	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/cpu"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/infiniband"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/kernel"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/dputils"
@@ -30,6 +31,7 @@ type HostManagerInterface interface {
 	types.VdpaInterface
 	types.InfinibandInterface
 	types.BridgeInterface
+	types.CPUInfoProviderInterface
 }
 
 type hostManager struct {
@@ -42,6 +44,7 @@ type hostManager struct {
 	types.VdpaInterface
 	types.InfinibandInterface
 	types.BridgeInterface
+	types.CPUInfoProviderInterface
 }
 
 func NewHostManager(utilsInterface utils.CmdInterface) (HostManagerInterface, error) {
@@ -61,6 +64,7 @@ func NewHostManager(utilsInterface utils.CmdInterface) (HostManagerInterface, er
 	}
 	br := bridge.New()
 	sr := sriov.New(utilsInterface, k, n, u, v, ib, netlinkLib, dpUtils, sriovnetLib, ghwLib, br)
+	cpuInfoProvider := cpu.New(ghwLib)
 	return &hostManager{
 		utilsInterface,
 		k,
@@ -71,5 +75,6 @@ func NewHostManager(utilsInterface utils.CmdInterface) (HostManagerInterface, er
 		v,
 		ib,
 		br,
+		cpuInfoProvider,
 	}, nil
 }
diff --git a/pkg/host/mock/mock_host.go b/pkg/host/mock/mock_host.go
index cb4d1480a..5ebed46aa 100644
--- a/pkg/host/mock/mock_host.go
+++ b/pkg/host/mock/mock_host.go
@@ -321,6 +321,21 @@ func (mr *MockHostManagerInterfaceMockRecorder) EnableService(service interface{
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableService", reflect.TypeOf((*MockHostManagerInterface)(nil).EnableService), service)
 }
 
+// GetCPUVendor mocks base method.
+func (m *MockHostManagerInterface) GetCPUVendor() (types.CPUVendor, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetCPUVendor")
+	ret0, _ := ret[0].(types.CPUVendor)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetCPUVendor indicates an expected call of GetCPUVendor.
+func (mr *MockHostManagerInterfaceMockRecorder) GetCPUVendor() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCPUVendor", reflect.TypeOf((*MockHostManagerInterface)(nil).GetCPUVendor))
+}
+
 // GetCurrentKernelArgs mocks base method.
 func (m *MockHostManagerInterface) GetCurrentKernelArgs() (string, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/host/types/interfaces.go b/pkg/host/types/interfaces.go
index 5918dca34..c6e0c8faf 100644
--- a/pkg/host/types/interfaces.go
+++ b/pkg/host/types/interfaces.go
@@ -187,3 +187,16 @@ type InfinibandInterface interface {
 	// ConfigureVfGUID configures and sets a GUID for an IB VF device
 	ConfigureVfGUID(vfAddr string, pfAddr string, vfID int, pfLink netlink.Link) error
 }
+
+type CPUVendor int
+
+const (
+	CPUVendorIntel CPUVendor = iota
+	CPUVendorAMD
+	CPUVendorARM
+)
+
+type CPUInfoProviderInterface interface {
+	// Retrieve the CPU vendor of the current system
+	GetCPUVendor() (CPUVendor, error)
+}
diff --git a/pkg/plugins/generic/generic_plugin.go b/pkg/plugins/generic/generic_plugin.go
index 14b1903e5..552f8142a 100644
--- a/pkg/plugins/generic/generic_plugin.go
+++ b/pkg/plugins/generic/generic_plugin.go
@@ -13,6 +13,7 @@ import (
 	sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/helper"
+	hostTypes "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types"
 	plugin "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/plugins"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
@@ -419,9 +420,31 @@ func (p *GenericPlugin) shouldConfigureBridges() bool {
 
 func (p *GenericPlugin) addVfioDesiredKernelArg(state *sriovnetworkv1.SriovNetworkNodeState) {
 	driverState := p.DriverStateMap[Vfio]
+
+	kernelArgFnByCPUVendor := map[hostTypes.CPUVendor]func(){
+		hostTypes.CPUVendorIntel: func() {
+			p.addToDesiredKernelArgs(consts.KernelArgIntelIommu)
+			p.addToDesiredKernelArgs(consts.KernelArgIommuPt)
+		},
+		hostTypes.CPUVendorAMD: func() {
+			p.addToDesiredKernelArgs(consts.KernelArgIommuPt)
+		},
+		hostTypes.CPUVendorARM: func() {
+			p.addToDesiredKernelArgs(consts.KernelArgIommuPassthrough)
+		},
+	}
+
 	if !driverState.DriverLoaded && driverState.NeedDriverFunc(state, driverState) {
-		p.addToDesiredKernelArgs(consts.KernelArgIntelIommu)
-		p.addToDesiredKernelArgs(consts.KernelArgIommuPt)
+		cpuVendor, err := p.helpers.GetCPUVendor()
+		if err != nil {
+			log.Log.Error(err, "can't get CPU vendor, falling back to Intel")
+			cpuVendor = hostTypes.CPUVendorIntel
+		}
+
+		addKernelArgFn := kernelArgFnByCPUVendor[cpuVendor]
+		if addKernelArgFn != nil {
+			addKernelArgFn()
+		}
 	}
 }
 
diff --git a/pkg/plugins/generic/generic_plugin_test.go b/pkg/plugins/generic/generic_plugin_test.go
index 0d6701a64..0a6674712 100644
--- a/pkg/plugins/generic/generic_plugin_test.go
+++ b/pkg/plugins/generic/generic_plugin_test.go
@@ -10,6 +10,7 @@ import (
 	sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
 	mock_helper "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/helper/mock"
+	hostTypes "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types"
 	plugin "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/plugins"
 	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
 )
@@ -850,8 +851,9 @@ var _ = Describe("Generic plugin", func() {
 			Expect(changed).To(BeTrue())
 		})
 
-		It("should detect changes on status due to missing kernel args", func() {
-			networkNodeState := &sriovnetworkv1.SriovNetworkNodeState{
+		Context("Kernel Args", func() {
+
+			vfioNetworkNodeState := &sriovnetworkv1.SriovNetworkNodeState{
 				Spec: sriovnetworkv1.SriovNetworkNodeStateSpec{
 					Interfaces: sriovnetworkv1.Interfaces{{
 						PciAddress: "0000:00:00.0",
@@ -896,16 +898,41 @@ var _ = Describe("Generic plugin", func() {
 				},
 			}
 
-			// Load required kernel args.
-			genericPlugin.(*GenericPlugin).addVfioDesiredKernelArg(networkNodeState)
+			It("should detect changes on status due to missing kernel args", func() {
+				hostHelper.EXPECT().GetCPUVendor().Return(hostTypes.CPUVendorIntel, nil)
 
-			hostHelper.EXPECT().GetCurrentKernelArgs().Return("", nil)
-			hostHelper.EXPECT().IsKernelArgsSet("", consts.KernelArgIntelIommu).Return(false)
-			hostHelper.EXPECT().IsKernelArgsSet("", consts.KernelArgIommuPt).Return(false)
+				// Load required kernel args.
+				genericPlugin.(*GenericPlugin).addVfioDesiredKernelArg(vfioNetworkNodeState)
 
-			changed, err := genericPlugin.CheckStatusChanges(networkNodeState)
-			Expect(err).ToNot(HaveOccurred())
-			Expect(changed).To(BeTrue())
+				Expect(genericPlugin.(*GenericPlugin).DesiredKernelArgs).To(Equal(map[string]bool{
+					consts.KernelArgIntelIommu: false,
+					consts.KernelArgIommuPt:    false,
+				}))
+
+				hostHelper.EXPECT().GetCurrentKernelArgs().Return("", nil)
+				hostHelper.EXPECT().IsKernelArgsSet("", consts.KernelArgIntelIommu).Return(false)
+				hostHelper.EXPECT().IsKernelArgsSet("", consts.KernelArgIommuPt).Return(false)
+
+				changed, err := genericPlugin.CheckStatusChanges(vfioNetworkNodeState)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(changed).To(BeTrue())
+			})
+
+			It("should set the correct kernel args on AMD CPUs", func() {
+				hostHelper.EXPECT().GetCPUVendor().Return(hostTypes.CPUVendorAMD, nil)
+				genericPlugin.(*GenericPlugin).addVfioDesiredKernelArg(vfioNetworkNodeState)
+				Expect(genericPlugin.(*GenericPlugin).DesiredKernelArgs).To(Equal(map[string]bool{
+					consts.KernelArgIommuPt: false,
+				}))
+			})
+
+			It("should set the correct kernel args on ARM CPUs", func() {
+				hostHelper.EXPECT().GetCPUVendor().Return(hostTypes.CPUVendorARM, nil)
+				genericPlugin.(*GenericPlugin).addVfioDesiredKernelArg(vfioNetworkNodeState)
+				Expect(genericPlugin.(*GenericPlugin).DesiredKernelArgs).To(Equal(map[string]bool{
+					consts.KernelArgIommuPassthrough: false,
+				}))
+			})
 		})
 
 		It("should load vfio_pci driver", func() {

From 5522c96101c673ad15efbc5a7acd1596283bc19c Mon Sep 17 00:00:00 2001
From: Andrea Panattoni <apanatto@redhat.com>
Date: Fri, 25 Oct 2024 09:38:44 +0200
Subject: [PATCH 2/2] Update `github.com/jaypipes/ghw`

To include
- https://github.com/jaypipes/ghw/pull/387

Signed-off-by: Andrea Panattoni <apanatto@redhat.com>
---
 go.mod                                     |  21 +--
 go.sum                                     |  50 +++---
 pkg/host/internal/lib/ghw/ghw.go           |  12 +-
 pkg/host/internal/lib/ghw/mock/mock_ghw.go |  44 +-----
 pkg/host/internal/sriov/sriov.go           |   2 +-
 pkg/host/internal/sriov/sriov_test.go      | 176 ++++++++++-----------
 pkg/platforms/openstack/openstack.go       |   4 +-
 7 files changed, 130 insertions(+), 179 deletions(-)

diff --git a/go.mod b/go.mod
index 0353c7ec1..350dbb82d 100644
--- a/go.mod
+++ b/go.mod
@@ -15,8 +15,8 @@ require (
 	github.com/google/renameio/v2 v2.0.0
 	github.com/google/uuid v1.3.1
 	github.com/hashicorp/go-retryablehttp v0.7.7
-	github.com/jaypipes/ghw v0.9.0
-	github.com/jaypipes/pcidb v1.0.0
+	github.com/jaypipes/ghw v0.13.1-0.20241024164530-c1bfc6e6cd6a
+	github.com/jaypipes/pcidb v1.0.1
 	github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0
 	github.com/k8snetworkplumbingwg/sriov-network-device-plugin v0.0.0-20221127172732-a5a7395122e3
 	github.com/k8snetworkplumbingwg/sriovnet v1.2.0
@@ -33,7 +33,7 @@ require (
 	github.com/prometheus/client_model v0.5.0
 	github.com/prometheus/common v0.45.0
 	github.com/safchain/ethtool v0.3.0
-	github.com/spf13/cobra v1.7.0
+	github.com/spf13/cobra v1.8.0
 	github.com/stretchr/testify v1.8.4
 	github.com/vishvananda/netlink v1.2.1-beta.2.0.20240221172127-ec7bcb248e94
 	github.com/vishvananda/netns v0.0.4
@@ -131,6 +131,7 @@ require (
 	github.com/robfig/cron v1.2.0 // indirect
 	github.com/rogpeppe/go-internal v1.10.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/samber/lo v1.47.0 // indirect
 	github.com/shopspring/decimal v1.2.0 // indirect
 	github.com/spf13/afero v1.9.4 // indirect
 	github.com/spf13/cast v1.5.0 // indirect
@@ -141,16 +142,16 @@ require (
 	go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go4.org v0.0.0-20200104003542-c7e774b10ea0 // indirect
-	golang.org/x/crypto v0.21.0 // indirect
+	golang.org/x/crypto v0.23.0 // indirect
 	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
-	golang.org/x/mod v0.13.0 // indirect
-	golang.org/x/net v0.23.0 // indirect
+	golang.org/x/mod v0.17.0 // indirect
+	golang.org/x/net v0.25.0 // indirect
 	golang.org/x/oauth2 v0.13.0 // indirect
-	golang.org/x/sync v0.4.0 // indirect
+	golang.org/x/sync v0.7.0 // indirect
 	golang.org/x/sys v0.20.0 // indirect
-	golang.org/x/term v0.18.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/tools v0.14.0 // indirect
+	golang.org/x/term v0.20.0 // indirect
+	golang.org/x/text v0.16.0 // indirect
+	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
 	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
diff --git a/go.sum b/go.sum
index 6f90a1a94..4d8d4c171 100644
--- a/go.sum
+++ b/go.sum
@@ -119,7 +119,7 @@ github.com/coreos/vcontext v0.0.0-20190529201340-22b159166068/go.mod h1:E+6hug9b
 github.com/coreos/vcontext v0.0.0-20191017033345-260217907eb5/go.mod h1:E+6hug9bFSe0KZ2ZAzr8M9F5JlArJjv5D1JS7KSkPKE=
 github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 h1:uSmlDgJGbUB0bwQBcZomBTottKwEDF5fF8UjSwKSzWM=
 github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687/go.mod h1:Salmysdw7DAVuobBW/LwsKKgpyCPHUhjyJoMJD+ZJiI=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@@ -147,7 +147,6 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
 github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
 github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@@ -285,13 +284,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
 github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
 github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/jaypipes/ghw v0.9.0 h1:TWF4wNIGtZcgDJaiNcFgby5BR8s2ixcUe0ydxNO2McY=
-github.com/jaypipes/ghw v0.9.0/go.mod h1:dXMo19735vXOjpIBDyDYSp31sB2u4hrtRCMxInqQ64k=
-github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8=
-github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk=
+github.com/jaypipes/ghw v0.13.1-0.20241024164530-c1bfc6e6cd6a h1:orxBMCkYww7RFCk3iCDP9DC3l+yKtp4VdWtctCTyjPQ=
+github.com/jaypipes/ghw v0.13.1-0.20241024164530-c1bfc6e6cd6a/go.mod h1:F4UM7Ix55ONYwD3Lck2S4BI+hKezOwtizuJxXDFsioo=
+github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
+github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -336,7 +334,6 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQth
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@@ -411,6 +408,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
 github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
+github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
+github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@@ -423,10 +422,8 @@ github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD
 github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
 github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
-github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
-github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace h1:9PNP1jnUjRhfmGMlkXHjYPishpcw4jpSt/V/xYY3FMA=
 github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -494,8 +491,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
-golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -533,8 +530,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
-golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -570,8 +567,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -595,8 +592,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
-golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -638,7 +635,6 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -651,8 +647,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
-golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
+golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -662,8 +658,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -721,8 +717,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/host/internal/lib/ghw/ghw.go b/pkg/host/internal/lib/ghw/ghw.go
index d518977e4..2a4ba609d 100644
--- a/pkg/host/internal/lib/ghw/ghw.go
+++ b/pkg/host/internal/lib/ghw/ghw.go
@@ -3,6 +3,7 @@ package ghw
 import (
 	"github.com/jaypipes/ghw"
 	"github.com/jaypipes/ghw/pkg/cpu"
+	"github.com/jaypipes/ghw/pkg/pci"
 )
 
 func New() GHWLib {
@@ -12,23 +13,16 @@ func New() GHWLib {
 //go:generate ../../../../../bin/mockgen -destination mock/mock_ghw.go -source ghw.go
 type GHWLib interface {
 	// PCI returns a pointer to an Info that provide methods to access info about devices
-	PCI() (Info, error)
+	PCI() (*pci.Info, error)
 
 	// CPU returns a pointer to an Info that provide methods to access info about devices
 	CPU() (*cpu.Info, error)
 }
 
-// Info interface provide methods to access info about devices
-type Info interface {
-	// ListDevices returns a list of pointers to Device structs present on the
-	// host system
-	ListDevices() []*ghw.PCIDevice
-}
-
 type libWrapper struct{}
 
 // PCI returns a pointer to an Info that provide methods to access info about devices
-func (w *libWrapper) PCI() (Info, error) {
+func (w *libWrapper) PCI() (*pci.Info, error) {
 	return ghw.PCI()
 }
 
diff --git a/pkg/host/internal/lib/ghw/mock/mock_ghw.go b/pkg/host/internal/lib/ghw/mock/mock_ghw.go
index 9d6092362..ded8784bf 100644
--- a/pkg/host/internal/lib/ghw/mock/mock_ghw.go
+++ b/pkg/host/internal/lib/ghw/mock/mock_ghw.go
@@ -8,9 +8,8 @@ import (
 	reflect "reflect"
 
 	gomock "github.com/golang/mock/gomock"
-	ghw "github.com/jaypipes/ghw"
 	cpu "github.com/jaypipes/ghw/pkg/cpu"
-	ghw0 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/internal/lib/ghw"
+	pci "github.com/jaypipes/ghw/pkg/pci"
 )
 
 // MockGHWLib is a mock of GHWLib interface.
@@ -52,10 +51,10 @@ func (mr *MockGHWLibMockRecorder) CPU() *gomock.Call {
 }
 
 // PCI mocks base method.
-func (m *MockGHWLib) PCI() (ghw0.Info, error) {
+func (m *MockGHWLib) PCI() (*pci.Info, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "PCI")
-	ret0, _ := ret[0].(ghw0.Info)
+	ret0, _ := ret[0].(*pci.Info)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
@@ -65,40 +64,3 @@ func (mr *MockGHWLibMockRecorder) PCI() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PCI", reflect.TypeOf((*MockGHWLib)(nil).PCI))
 }
-
-// MockInfo is a mock of Info interface.
-type MockInfo struct {
-	ctrl     *gomock.Controller
-	recorder *MockInfoMockRecorder
-}
-
-// MockInfoMockRecorder is the mock recorder for MockInfo.
-type MockInfoMockRecorder struct {
-	mock *MockInfo
-}
-
-// NewMockInfo creates a new mock instance.
-func NewMockInfo(ctrl *gomock.Controller) *MockInfo {
-	mock := &MockInfo{ctrl: ctrl}
-	mock.recorder = &MockInfoMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockInfo) EXPECT() *MockInfoMockRecorder {
-	return m.recorder
-}
-
-// ListDevices mocks base method.
-func (m *MockInfo) ListDevices() []*ghw.PCIDevice {
-	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "ListDevices")
-	ret0, _ := ret[0].([]*ghw.PCIDevice)
-	return ret0
-}
-
-// ListDevices indicates an expected call of ListDevices.
-func (mr *MockInfoMockRecorder) ListDevices() *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDevices", reflect.TypeOf((*MockInfo)(nil).ListDevices))
-}
diff --git a/pkg/host/internal/sriov/sriov.go b/pkg/host/internal/sriov/sriov.go
index bf9919a7e..3e5989bae 100644
--- a/pkg/host/internal/sriov/sriov.go
+++ b/pkg/host/internal/sriov/sriov.go
@@ -217,7 +217,7 @@ func (s *sriov) DiscoverSriovDevices(storeManager store.ManagerInterface) ([]sri
 		return nil, fmt.Errorf("DiscoverSriovDevices(): error getting PCI info: %v", err)
 	}
 
-	devices := pci.ListDevices()
+	devices := pci.Devices
 	if len(devices) == 0 {
 		return nil, fmt.Errorf("DiscoverSriovDevices(): could not retrieve PCI devices")
 	}
diff --git a/pkg/host/internal/sriov/sriov_test.go b/pkg/host/internal/sriov/sriov_test.go
index f30e93773..319bacf54 100644
--- a/pkg/host/internal/sriov/sriov_test.go
+++ b/pkg/host/internal/sriov/sriov_test.go
@@ -7,7 +7,7 @@ import (
 	"syscall"
 
 	"github.com/golang/mock/gomock"
-	"github.com/jaypipes/ghw"
+	"github.com/jaypipes/ghw/pkg/pci"
 	"github.com/jaypipes/pcidb"
 	"github.com/vishvananda/netlink"
 
@@ -57,12 +57,7 @@ var _ = Describe("SRIOV", func() {
 	})
 
 	Context("DiscoverSriovDevices", func() {
-		var (
-			ghwInfoMock *ghwMockPkg.MockInfo
-		)
 		BeforeEach(func() {
-			ghwInfoMock = ghwMockPkg.NewMockInfo(testCtrl)
-			ghwLibMock.EXPECT().PCI().Return(ghwInfoMock, nil)
 			origNicMap := sriovnetworkv1.NicIDMap
 			sriovnetworkv1.InitNicIDMapFromList([]string{
 				"15b3 101d 101e",
@@ -73,7 +68,7 @@ var _ = Describe("SRIOV", func() {
 		})
 
 		It("discovered", func() {
-			ghwInfoMock.EXPECT().ListDevices().Return(getTestPCIDevices())
+			ghwLibMock.EXPECT().PCI().Return(getTestPCIDevices(), nil)
 			dputilsLibMock.EXPECT().IsSriovVF("0000:d8:00.0").Return(false)
 			dputilsLibMock.EXPECT().IsSriovVF("0000:d8:00.2").Return(true)
 			dputilsLibMock.EXPECT().IsSriovVF("0000:3b:00.0").Return(false)
@@ -628,91 +623,94 @@ var _ = Describe("SRIOV", func() {
 	})
 })
 
-func getTestPCIDevices() []*ghw.PCIDevice {
-	return []*ghw.PCIDevice{{
-		Driver:  "mlx5_core",
-		Address: "0000:d8:00.0",
-		Vendor: &pcidb.Vendor{
-			ID:   "15b3",
-			Name: "Mellanox Technologies",
-		},
-		Product: &pcidb.Product{
-			ID:   "101d",
-			Name: "MT2892 Family [ConnectX-6 Dx]",
-		},
-		Revision: "0x00",
-		Subsystem: &pcidb.Product{
-			ID:   "0083",
-			Name: "unknown",
-		},
-		Class: &pcidb.Class{
-			ID:   "02",
-			Name: "Network controller",
-		},
-		Subclass: &pcidb.Subclass{
-			ID:   "00",
-			Name: "Ethernet controller",
-		},
-		ProgrammingInterface: &pcidb.ProgrammingInterface{
-			ID:   "00",
-			Name: "unknonw",
-		},
-	},
-		{
-			Driver:  "mlx5_core",
-			Address: "0000:d8:00.2",
-			Vendor: &pcidb.Vendor{
-				ID:   "15b3",
-				Name: "Mellanox Technologies",
-			},
-			Product: &pcidb.Product{
-				ID:   "101e",
-				Name: "ConnectX Family mlx5Gen Virtual Function",
-			},
-			Revision: "0x00",
-			Subsystem: &pcidb.Product{
-				ID:   "0083",
-				Name: "unknown",
+func getTestPCIDevices() *pci.Info {
+	return &pci.Info{
+		Devices: []*pci.Device{
+			{
+				Driver:  "mlx5_core",
+				Address: "0000:d8:00.0",
+				Vendor: &pcidb.Vendor{
+					ID:   "15b3",
+					Name: "Mellanox Technologies",
+				},
+				Product: &pcidb.Product{
+					ID:   "101d",
+					Name: "MT2892 Family [ConnectX-6 Dx]",
+				},
+				Revision: "0x00",
+				Subsystem: &pcidb.Product{
+					ID:   "0083",
+					Name: "unknown",
+				},
+				Class: &pcidb.Class{
+					ID:   "02",
+					Name: "Network controller",
+				},
+				Subclass: &pcidb.Subclass{
+					ID:   "00",
+					Name: "Ethernet controller",
+				},
+				ProgrammingInterface: &pcidb.ProgrammingInterface{
+					ID:   "00",
+					Name: "unknonw",
+				},
 			},
-			Class: &pcidb.Class{
-				ID:   "02",
-				Name: "Network controller",
+			{
+				Driver:  "mlx5_core",
+				Address: "0000:d8:00.2",
+				Vendor: &pcidb.Vendor{
+					ID:   "15b3",
+					Name: "Mellanox Technologies",
+				},
+				Product: &pcidb.Product{
+					ID:   "101e",
+					Name: "ConnectX Family mlx5Gen Virtual Function",
+				},
+				Revision: "0x00",
+				Subsystem: &pcidb.Product{
+					ID:   "0083",
+					Name: "unknown",
+				},
+				Class: &pcidb.Class{
+					ID:   "02",
+					Name: "Network controller",
+				},
+				Subclass: &pcidb.Subclass{
+					ID:   "00",
+					Name: "Ethernet controller",
+				},
+				ProgrammingInterface: &pcidb.ProgrammingInterface{
+					ID:   "00",
+					Name: "unknonw",
+				},
 			},
-			Subclass: &pcidb.Subclass{
-				ID:   "00",
-				Name: "Ethernet controller",
-			},
-			ProgrammingInterface: &pcidb.ProgrammingInterface{
-				ID:   "00",
-				Name: "unknonw",
-			},
-		},
-		{
-			Driver:  "mlx5_core",
-			Address: "0000:3b:00.0",
-			Vendor: &pcidb.Vendor{
-				ID:   "15b3",
-				Name: "Mellanox Technologies",
-			},
-			Product: &pcidb.Product{
-				ID:   "aaaa", // not supported
-				Name: "not supported",
-			},
-			Class: &pcidb.Class{
-				ID:   "02",
-				Name: "Network controller",
-			},
-		},
-		{
-			Driver:  "test",
-			Address: "0000:d7:16.5",
-			Vendor: &pcidb.Vendor{
-				ID:   "8086",
-				Name: "Intel Corporation",
+			{
+				Driver:  "mlx5_core",
+				Address: "0000:3b:00.0",
+				Vendor: &pcidb.Vendor{
+					ID:   "15b3",
+					Name: "Mellanox Technologies",
+				},
+				Product: &pcidb.Product{
+					ID:   "aaaa", // not supported
+					Name: "not supported",
+				},
+				Class: &pcidb.Class{
+					ID:   "02",
+					Name: "Network controller",
+				},
 			},
-			Class: &pcidb.Class{
-				ID:   "11", // not network device
-				Name: "Signal processing controller",
+			{
+				Driver:  "test",
+				Address: "0000:d7:16.5",
+				Vendor: &pcidb.Vendor{
+					ID:   "8086",
+					Name: "Intel Corporation",
+				},
+				Class: &pcidb.Class{
+					ID:   "11", // not network device
+					Name: "Signal processing controller",
+				},
 			},
 		},
 	}
diff --git a/pkg/platforms/openstack/openstack.go b/pkg/platforms/openstack/openstack.go
index 8968c96be..608ba6f87 100644
--- a/pkg/platforms/openstack/openstack.go
+++ b/pkg/platforms/openstack/openstack.go
@@ -362,7 +362,7 @@ func (o *openstackContext) CreateOpenstackDevicesInfo() error {
 		return fmt.Errorf("CreateOpenstackDevicesInfo(): error getting PCI info: %v", err)
 	}
 
-	devices := pci.ListDevices()
+	devices := pci.Devices
 	if len(devices) == 0 {
 		return fmt.Errorf("CreateOpenstackDevicesInfo(): could not retrieve PCI devices")
 	}
@@ -421,7 +421,7 @@ func (o *openstackContext) DiscoverSriovDevicesVirtual() ([]sriovnetworkv1.Inter
 		return nil, fmt.Errorf("DiscoverSriovDevicesVirtual(): error getting PCI info: %v", err)
 	}
 
-	devices := pci.ListDevices()
+	devices := pci.Devices
 	if len(devices) == 0 {
 		return nil, fmt.Errorf("DiscoverSriovDevicesVirtual(): could not retrieve PCI devices")
 	}