chore: show bound driver in pcidevices info

Fixes: #10754

Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
Noel Georgi 2025-04-24 21:34:28 +05:30
parent 8db34624c6
commit f90c79474b
No known key found for this signature in database
GPG Key ID: 21A9F444075C9E36
8 changed files with 175 additions and 71 deletions

View File

@ -27,6 +27,7 @@ message PCIDeviceSpec {
string subclass_id = 6;
string vendor_id = 7;
string product_id = 8;
string driver = 9;
}
// PCIDriverRebindConfigSpec describes PCI rebind configuration.

6
go.mod
View File

@ -153,7 +153,7 @@ require (
github.com/siderolabs/go-kubeconfig v0.1.1
github.com/siderolabs/go-kubernetes v0.2.21
github.com/siderolabs/go-loadbalancer v0.4.0
github.com/siderolabs/go-pcidb v0.3.0
github.com/siderolabs/go-pcidb v0.3.1
github.com/siderolabs/go-pointer v1.0.1
github.com/siderolabs/go-procfs v0.1.2
github.com/siderolabs/go-retry v0.3.3
@ -355,8 +355,8 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/tools v0.30.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/tools v0.32.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect

12
go.sum
View File

@ -651,8 +651,8 @@ github.com/siderolabs/go-kubernetes v0.2.21 h1:+gHiyTyVz5oZy6cIVtqKbIWtAH/ejnQ7Y
github.com/siderolabs/go-kubernetes v0.2.21/go.mod h1:3qZzReVZV7e+r0DZC2cE6bBQse+CoC7SGL+EavA52G8=
github.com/siderolabs/go-loadbalancer v0.4.0 h1:nqZC4x1yZAFAtkb7eu5T1IoPaMDKu5jgQQGkk6rZa9s=
github.com/siderolabs/go-loadbalancer v0.4.0/go.mod h1:tRVouZ9i2R/TRbNUF9MqyBlV2wsjX0cxkYTjPXcI9P0=
github.com/siderolabs/go-pcidb v0.3.0 h1:jR4w1YLNY8Cv1o5jnoQ2Q+pbxcosO2FVFrAAp1RURnw=
github.com/siderolabs/go-pcidb v0.3.0/go.mod h1:4XYdmnR/o9kSzMe8dKK17wLBhPNIsisjqmU3QD1FjRk=
github.com/siderolabs/go-pcidb v0.3.1 h1:Gef+LOwxB+bCUzoFajzvHg4icMqYGAoS/msdiMmPXo0=
github.com/siderolabs/go-pcidb v0.3.1/go.mod h1:jJEvElDKbrI92cjOGCeBTc16TSIzCQD/dyP7Ayp9t+s=
github.com/siderolabs/go-pointer v1.0.1 h1:f7Yi4IK1jptS8yrT9GEbwhmGcVxvPQgBUG/weH3V3DM=
github.com/siderolabs/go-pointer v1.0.1/go.mod h1:C8Q/3pNHT4RE9e4rYR9PHeS6KPMlStRBgYrJQJNy/vA=
github.com/siderolabs/go-procfs v0.1.2 h1:bDs9hHyYGE2HO1frpmUsD60yg80VIEDrx31fkbi4C8M=
@ -834,8 +834,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
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=
@ -1007,8 +1007,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
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=

View File

@ -14,11 +14,14 @@ import (
"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/safe"
"github.com/cosi-project/runtime/pkg/state"
"github.com/siderolabs/gen/optional"
"github.com/siderolabs/go-pcidb/pkg/pcidb"
"go.uber.org/zap"
runtimetalos "github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/pkg/machinery/resources/hardware"
"github.com/siderolabs/talos/pkg/machinery/resources/v1alpha1"
)
// PCIDevicesController populates PCI device information.
@ -33,7 +36,14 @@ func (ctrl *PCIDevicesController) Name() string {
// Inputs implements controller.Controller interface.
func (ctrl *PCIDevicesController) Inputs() []controller.Input {
return nil
return []controller.Input{
{
Namespace: v1alpha1.NamespaceName,
Type: v1alpha1.ServiceType,
ID: optional.Some("udevd"),
Kind: controller.InputWeak,
},
}
}
// Outputs implements controller.Controller interface.
@ -63,69 +73,93 @@ func (ctrl *PCIDevicesController) Run(ctx context.Context, r controller.Runtime,
case <-r.EventCh():
}
deviceIDs, err := os.ReadDir("/sys/bus/pci/devices")
// we need to wait for udevd to be healthy & running so that we get the driver information too
udevdService, err := safe.ReaderGetByID[*v1alpha1.Service](ctx, r, "udevd")
if err != nil {
return fmt.Errorf("error scanning devices: %w", err)
if state.IsNotFoundError(err) {
continue
}
return fmt.Errorf("failed to get udevd service: %w", err)
}
logger.Debug("found PCI devices", zap.Int("count", len(deviceIDs)))
r.StartTrackingOutputs()
for _, deviceID := range deviceIDs {
class, err := readHexPCIInfo(deviceID.Name(), "class")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s class: %w", deviceID.Name(), err)
}
vendor, err := readHexPCIInfo(deviceID.Name(), "vendor")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s vendor: %w", deviceID.Name(), err)
}
product, err := readHexPCIInfo(deviceID.Name(), "device")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s product: %w", deviceID.Name(), err)
}
classID := pcidb.Class((class >> 16) & 0xff)
subclassID := pcidb.Subclass((class >> 8) & 0xff)
vendorID := pcidb.Vendor(vendor)
productID := pcidb.Product(product)
if err := safe.WriterModify(ctx, r, hardware.NewPCIDeviceInfo(deviceID.Name()), func(r *hardware.PCIDevice) error {
r.TypedSpec().ClassID = fmt.Sprintf("0x%02x", classID)
r.TypedSpec().SubclassID = fmt.Sprintf("0x%02x", subclassID)
r.TypedSpec().VendorID = fmt.Sprintf("0x%04x", vendorID)
r.TypedSpec().ProductID = fmt.Sprintf("0x%04x", productID)
r.TypedSpec().Class, _ = pcidb.LookupClass(classID)
r.TypedSpec().Subclass, _ = pcidb.LookupSubclass(classID, subclassID)
r.TypedSpec().Vendor, _ = pcidb.LookupVendor(vendorID)
r.TypedSpec().Product, _ = pcidb.LookupProduct(vendorID, productID)
return nil
}); err != nil {
return fmt.Errorf("error modifying output resource: %w", err)
}
}
if err = safe.CleanupOutputs[*hardware.PCIDevice](ctx, r); err != nil {
return err
if udevdService.TypedSpec().Healthy && udevdService.TypedSpec().Running {
break
}
}
deviceIDs, err := os.ReadDir("/sys/bus/pci/devices")
if err != nil {
return fmt.Errorf("error scanning devices: %w", err)
}
logger.Debug("found PCI devices", zap.Int("count", len(deviceIDs)))
r.StartTrackingOutputs()
for _, deviceID := range deviceIDs {
class, err := readHexPCIInfo(deviceID.Name(), "class")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s class: %w", deviceID.Name(), err)
}
vendor, err := readHexPCIInfo(deviceID.Name(), "vendor")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s vendor: %w", deviceID.Name(), err)
}
product, err := readHexPCIInfo(deviceID.Name(), "device")
if err != nil {
if os.IsNotExist(err) {
continue
}
return fmt.Errorf("error parsing device %s product: %w", deviceID.Name(), err)
}
driver, err := readDriverInfo(deviceID.Name())
if err != nil {
return fmt.Errorf("error parsing device %s driver: %w", deviceID.Name(), err)
}
logger.Debug("found PCI device", zap.String("deviceID", deviceID.Name()), zap.String("driver", driver))
classID := pcidb.Class((class >> 16) & 0xff)
subclassID := pcidb.Subclass((class >> 8) & 0xff)
vendorID := pcidb.Vendor(vendor)
productID := pcidb.Product(product)
if err := safe.WriterModify(ctx, r, hardware.NewPCIDeviceInfo(deviceID.Name()), func(r *hardware.PCIDevice) error {
r.TypedSpec().ClassID = fmt.Sprintf("0x%02x", classID)
r.TypedSpec().SubclassID = fmt.Sprintf("0x%02x", subclassID)
r.TypedSpec().VendorID = fmt.Sprintf("0x%04x", vendorID)
r.TypedSpec().ProductID = fmt.Sprintf("0x%04x", productID)
r.TypedSpec().Class, _ = pcidb.LookupClass(classID)
r.TypedSpec().Subclass, _ = pcidb.LookupSubclass(classID, subclassID)
r.TypedSpec().Vendor, _ = pcidb.LookupVendor(vendorID)
r.TypedSpec().Product, _ = pcidb.LookupProduct(vendorID, productID)
r.TypedSpec().Driver = driver
return nil
}); err != nil {
return fmt.Errorf("error modifying output resource: %w", err)
}
}
if err = safe.CleanupOutputs[*hardware.PCIDevice](ctx, r); err != nil {
return err
}
return nil
}
func readHexPCIInfo(deviceID, info string) (uint64, error) {
@ -136,3 +170,18 @@ func readHexPCIInfo(deviceID, info string) (uint64, error) {
return strconv.ParseUint(string(bytes.TrimSpace(contents)), 0, 64)
}
func readDriverInfo(deviceID string) (string, error) {
link, err := os.Readlink(filepath.Join("/sys/bus/pci/devices", deviceID, "driver"))
if err != nil {
// ignore if the driver doesn't exist
// this can happen if the device is not bound to a driver or a pci root port
if os.IsNotExist(err) {
return "", nil
}
return "", err
}
return filepath.Base(link), nil
}

View File

@ -134,6 +134,7 @@ type PCIDeviceSpec struct {
SubclassId string `protobuf:"bytes,6,opt,name=subclass_id,json=subclassId,proto3" json:"subclass_id,omitempty"`
VendorId string `protobuf:"bytes,7,opt,name=vendor_id,json=vendorId,proto3" json:"vendor_id,omitempty"`
ProductId string `protobuf:"bytes,8,opt,name=product_id,json=productId,proto3" json:"product_id,omitempty"`
Driver string `protobuf:"bytes,9,opt,name=driver,proto3" json:"driver,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -224,6 +225,13 @@ func (x *PCIDeviceSpec) GetProductId() string {
return ""
}
func (x *PCIDeviceSpec) GetDriver() string {
if x != nil {
return x.Driver
}
return ""
}
// PCIDriverRebindConfigSpec describes PCI rebind configuration.
type PCIDriverRebindConfigSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -569,7 +577,7 @@ const file_resource_definitions_hardware_hardware_proto_rawDesc = "" +
"\fmanufacturer\x18\x05 \x01(\tR\fmanufacturer\x12#\n" +
"\rserial_number\x18\x06 \x01(\tR\fserialNumber\x12\x1b\n" +
"\tasset_tag\x18\a \x01(\tR\bassetTag\x12!\n" +
"\fproduct_name\x18\b \x01(\tR\vproductName\"\xeb\x01\n" +
"\fproduct_name\x18\b \x01(\tR\vproductName\"\x83\x02\n" +
"\rPCIDeviceSpec\x12\x14\n" +
"\x05class\x18\x01 \x01(\tR\x05class\x12\x1a\n" +
"\bsubclass\x18\x02 \x01(\tR\bsubclass\x12\x16\n" +
@ -580,7 +588,8 @@ const file_resource_definitions_hardware_hardware_proto_rawDesc = "" +
"subclassId\x12\x1b\n" +
"\tvendor_id\x18\a \x01(\tR\bvendorId\x12\x1d\n" +
"\n" +
"product_id\x18\b \x01(\tR\tproductId\"V\n" +
"product_id\x18\b \x01(\tR\tproductId\x12\x16\n" +
"\x06driver\x18\t \x01(\tR\x06driver\"V\n" +
"\x19PCIDriverRebindConfigSpec\x12\x14\n" +
"\x05pciid\x18\x01 \x01(\tR\x05pciid\x12#\n" +
"\rtarget_driver\x18\x02 \x01(\tR\ftargetDriver\"V\n" +

View File

@ -134,6 +134,13 @@ func (m *PCIDeviceSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if len(m.Driver) > 0 {
i -= len(m.Driver)
copy(dAtA[i:], m.Driver)
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Driver)))
i--
dAtA[i] = 0x4a
}
if len(m.ProductId) > 0 {
i -= len(m.ProductId)
copy(dAtA[i:], m.ProductId)
@ -552,6 +559,10 @@ func (m *PCIDeviceSpec) SizeVT() (n int) {
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
l = len(m.Driver)
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
n += len(m.unknownFields)
return n
}
@ -1248,6 +1259,38 @@ func (m *PCIDeviceSpec) UnmarshalVT(dAtA []byte) error {
}
m.ProductId = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 9:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Driver", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return protohelpers.ErrInvalidLength
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return protohelpers.ErrInvalidLength
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Driver = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])

View File

@ -32,6 +32,7 @@ type PCIDeviceSpec struct {
SubclassID string `yaml:"subclass_id" protobuf:"6"`
VendorID string `yaml:"vendor_id" protobuf:"7"`
ProductID string `yaml:"product_id" protobuf:"8"`
Driver string `yaml:"driver,omitempty" protobuf:"9"`
}
// NewPCIDeviceInfo initializes a PCIDeviceInfo resource.
@ -78,7 +79,7 @@ func (PCIDeviceExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
func init() {
proto.RegisterDefaultTypes()
err := protobuf.RegisterDynamic[PCIDeviceSpec](PCIDeviceType, &PCIDevice{})
err := protobuf.RegisterDynamic(PCIDeviceType, &PCIDevice{})
if err != nil {
panic(err)
}

View File

@ -2616,6 +2616,7 @@ PCIDeviceSpec represents a single processor.
| subclass_id | [string](#string) | | |
| vendor_id | [string](#string) | | |
| product_id | [string](#string) | | |
| driver | [string](#string) | | |