diff --git a/api/resource/definitions/hardware/hardware.proto b/api/resource/definitions/hardware/hardware.proto index 66e5152ec..c4849f507 100755 --- a/api/resource/definitions/hardware/hardware.proto +++ b/api/resource/definitions/hardware/hardware.proto @@ -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. diff --git a/go.mod b/go.mod index 0d15a028b..9a23e2cca 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 9361c3136..29e3be51a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/app/machined/pkg/controllers/hardware/pcidevices.go b/internal/app/machined/pkg/controllers/hardware/pcidevices.go index 847e31750..b847b462c 100644 --- a/internal/app/machined/pkg/controllers/hardware/pcidevices.go +++ b/internal/app/machined/pkg/controllers/hardware/pcidevices.go @@ -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 +} diff --git a/pkg/machinery/api/resource/definitions/hardware/hardware.pb.go b/pkg/machinery/api/resource/definitions/hardware/hardware.pb.go index 551f00338..0305a3d43 100644 --- a/pkg/machinery/api/resource/definitions/hardware/hardware.pb.go +++ b/pkg/machinery/api/resource/definitions/hardware/hardware.pb.go @@ -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" + diff --git a/pkg/machinery/api/resource/definitions/hardware/hardware_vtproto.pb.go b/pkg/machinery/api/resource/definitions/hardware/hardware_vtproto.pb.go index 435f8267d..1ac61db94 100644 --- a/pkg/machinery/api/resource/definitions/hardware/hardware_vtproto.pb.go +++ b/pkg/machinery/api/resource/definitions/hardware/hardware_vtproto.pb.go @@ -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:]) diff --git a/pkg/machinery/resources/hardware/pcidevice.go b/pkg/machinery/resources/hardware/pcidevice.go index 2fd52765a..6252aef9e 100644 --- a/pkg/machinery/resources/hardware/pcidevice.go +++ b/pkg/machinery/resources/hardware/pcidevice.go @@ -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) } diff --git a/website/content/v1.10/reference/api.md b/website/content/v1.10/reference/api.md index 332a30064..4edb06c2f 100644 --- a/website/content/v1.10/reference/api.md +++ b/website/content/v1.10/reference/api.md @@ -2616,6 +2616,7 @@ PCIDeviceSpec represents a single processor. | subclass_id | [string](#string) | | | | vendor_id | [string](#string) | | | | product_id | [string](#string) | | | +| driver | [string](#string) | | |