mirror of
https://github.com/siderolabs/talos.git
synced 2025-12-07 10:31:58 +01:00
chore: show bound driver in pcidevices info
Fixes: #10754 Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
parent
8db34624c6
commit
f90c79474b
@ -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
6
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
|
||||
|
||||
12
go.sum
12
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=
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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" +
|
||||
|
||||
@ -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:])
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -2616,6 +2616,7 @@ PCIDeviceSpec represents a single processor.
|
||||
| subclass_id | [string](#string) | | |
|
||||
| vendor_id | [string](#string) | | |
|
||||
| product_id | [string](#string) | | |
|
||||
| driver | [string](#string) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user