• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

k8snetworkplumbingwg / dra-driver-sriov / 26086512318

19 May 2026 08:47AM UTC coverage: 53.573% (+2.7%) from 50.902%
26086512318

Pull #95

github

web-flow
Merge 8c639e2fc into 7e91a77c2
Pull Request #95: WIP: Support custom MAC annotation

278 of 428 new or added lines in 9 files covered. (64.95%)

1 existing line in 1 file now uncovered.

2099 of 3918 relevant lines covered (53.57%)

3.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.29
/pkg/host/host.go
1
package host
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "os"
8
        "os/exec"
9
        "path/filepath"
10
        "strconv"
11
        "strings"
12
        "sync"
13

14
        "github.com/jaypipes/ghw"
15
        "k8s.io/dynamic-resource-allocation/deviceattribute"
16
        "k8s.io/klog/v2"
17

18
        configapi "github.com/k8snetworkplumbingwg/dra-driver-sriov/pkg/api/virtualfunction/v1alpha1"
19
        "github.com/k8snetworkplumbingwg/dra-driver-sriov/pkg/consts"
20
)
21

22
const (
23
        // from include/uapi/linux/if_arp.h
24
        ArphrdEther      = 1
25
        ArphrdInfiniband = 32
26
)
27

28
var (
29
        RootDir = ""
30
)
31

32
// Helper functions to build paths respecting RootDir
33

34
// buildSysPath constructs a path under /sys with RootDir prefix if set
35
func buildSysPath(path string) string {
45✔
36
        if RootDir != "" {
90✔
37
                return filepath.Join(RootDir, path)
45✔
38
        }
45✔
39
        return path
×
40
}
41

42
// buildSysBusPciPath constructs a PCI device path under /sys/bus/pci/devices
43
func buildSysBusPciPath(pciAddress, subPath string) string {
39✔
44
        basePath := filepath.Join(consts.SysBusPci, pciAddress)
39✔
45
        if subPath != "" {
69✔
46
                basePath = filepath.Join(basePath, subPath)
30✔
47
        }
30✔
48
        return buildSysPath(basePath)
39✔
49
}
50

51
// buildSysBusPciDriverPath constructs a driver path under /sys/bus/pci/drivers
52
func buildSysBusPciDriverPath(driver, subPath string) string {
×
53
        basePath := filepath.Join("/sys/bus/pci/drivers", driver)
×
54
        if subPath != "" {
×
55
                basePath = filepath.Join(basePath, subPath)
×
56
        }
×
57
        return buildSysPath(basePath)
×
58
}
59

60
// buildProcPath constructs a path under /proc with RootDir prefix if set
61
func buildProcPath(path string) string {
9✔
62
        if RootDir != "" {
18✔
63
                return filepath.Join(RootDir, path)
9✔
64
        }
9✔
65
        return path
×
66
}
67

68
// VFInfo holds information about a Virtual Function
69
type VFInfo struct {
70
        PciAddress string
71
        VFID       int
72
        DeviceID   string
73
}
74

75
// Interface defines the unified interface for all host system operations.
76
// This interface allows for easy mocking in unit tests by implementing mock versions
77
// of all the host-related methods.
78
//
79
//go:generate mockgen -destination mock/mock_host.go -source host.go
80
type Interface interface {
81
        // SR-IOV device utility functions
82
        IsSriovVF(pciAddress string) bool
83
        IsSriovPF(pciAddress string) bool
84
        GetVFList(pfPciAddress string) ([]VFInfo, error)
85

86
        // PCI device discovery functionality
87
        PCI() (*ghw.PCIInfo, error)
88

89
        // Network interface functions
90
        TryGetInterfaceName(pciAddr string) string
91
        GetNicSriovMode(pciAddr string) string
92
        GetLinkType(pciAddr string) (string, error)
93

94
        // Topology functions
95
        GetNumaNode(pciAddress string) (string, error)
96
        GetPCIeRoot(pciAddress string) (string, error)
97

98
        // Driver binding operations
99
        BindDeviceDriver(pciAddress string, config *configapi.VfConfig) (string, error)
100
        BindDeviceDriverWithMAC(pciAddress string, config *configapi.VfConfig, macAddress string) (string, error)
101
        RestoreDeviceDriver(pciAddress string, originalDriver string) error
102

103
        // Low-level driver operations
104
        GetDriverByBusAndDevice(device string) (string, error)
105
        BindDriverByBusAndDevice(device, driver string) error
106
        UnbindDriverByBusAndDevice(device string) error
107
        BindDefaultDriver(pciAddress string) error
108

109
        // Driver utility functions
110
        IsDpdkDriver(driver string) bool
111

112
        // VF MAC address configuration
113
        SetVFMacAddress(vfPciAddress string, macAddress string) error
114

115
        // VFIO device functions
116
        GetVFIODeviceFile(pciAddress string) (devFileHost, devFileContainer string, err error)
117

118
        // Kernel module management functions
119
        IsKernelModuleLoaded(moduleName string) bool
120
        LoadKernelModule(moduleName string) error
121
        EnsureDpdkModuleLoaded(driver string) error
122
        EnsureVhostModulesLoaded() error
123

124
        // RDMA device functions
125
        GetRDMADevicesForPCI(pciAddr string) []string
126
        VerifyRDMACapability(pciAddr string) bool
127
        GetRDMACharDevices(rdmaDeviceName string) ([]string, error)
128
}
129

130
// Host provides unified host system functionality for SR-IOV, PCI operations, and driver management
131
type Host struct {
132
        log          klog.Logger
133
        rdmaProvider RdmaProvider
134
}
135

136
// NewHost creates a new Host instance
137
func NewHost() Interface {
65✔
138
        return &Host{
65✔
139
                log:          klog.FromContext(context.Background()).WithName("Host"),
65✔
140
                rdmaProvider: newRdmaProvider(),
65✔
141
        }
65✔
142
}
65✔
143

144
// Global Helpers instance for use throughout the application
145
var (
146
        Helpers     Interface
147
        helpersOnce sync.Once
148
)
149

150
// initHelpers initializes the global Helpers instance
151
func initHelpers() {
×
152
        helpersOnce.Do(func() {
×
153
                Helpers = NewHost()
×
154
        })
×
155
}
156

157
// GetHelpers returns the global Helpers instance, initializing it if necessary
158
func GetHelpers() Interface {
×
159
        initHelpers()
×
160
        return Helpers
×
161
}
×
162

163
// SetRdmaProvider sets the RDMA provider for a Host instance
164
// This is primarily used for injecting mock providers in unit tests
165
func (h *Host) SetRdmaProvider(provider RdmaProvider) {
10✔
166
        h.rdmaProvider = provider
10✔
167
}
10✔
168

169
// SR-IOV Detection Functions
170

171
// IsSriovVF checks if a PCI device is an SR-IOV Virtual Function
172
func (h *Host) IsSriovVF(pciAddress string) bool {
3✔
173
        // Check if physfn symlink exists - this indicates it's a VF
3✔
174
        physfnPath := buildSysBusPciPath(pciAddress, "physfn")
3✔
175
        if _, err := os.Lstat(physfnPath); err == nil {
4✔
176
                return true
1✔
177
        }
1✔
178
        return false
2✔
179
}
180

181
// IsSriovPF checks if a PCI device is an SR-IOV Physical Function
182
func (h *Host) IsSriovPF(pciAddress string) bool {
3✔
183
        // Check if virtfn0 symlink exists - this indicates it's a PF with VFs
3✔
184
        virtfnPath := buildSysBusPciPath(pciAddress, "virtfn0")
3✔
185
        if _, err := os.Lstat(virtfnPath); err == nil {
4✔
186
                return true
1✔
187
        }
1✔
188
        return false
2✔
189
}
190

191
// GetVFList returns list of VFs for a given PF with their VF IDs and device IDs
192
func (h *Host) GetVFList(pfPciAddress string) ([]VFInfo, error) {
6✔
193
        var vfList []VFInfo
6✔
194

6✔
195
        pfPath := buildSysBusPciPath(pfPciAddress, "")
6✔
196
        entries, err := os.ReadDir(pfPath)
6✔
197
        if err != nil {
7✔
198
                return nil, fmt.Errorf("failed to read PF directory: %v", err)
1✔
199
        }
1✔
200

201
        for _, entry := range entries {
10✔
202
                if strings.HasPrefix(entry.Name(), "virtfn") {
10✔
203
                        linkPath := filepath.Join(pfPath, entry.Name())
5✔
204
                        target, err := os.Readlink(linkPath)
5✔
205
                        if err != nil {
5✔
206
                                continue
×
207
                        }
208

209
                        // Extract VF ID from directory name (virtfn0 -> 0, virtfn1 -> 1, etc.)
210
                        vfIDStr := strings.TrimPrefix(entry.Name(), "virtfn")
5✔
211
                        vfID, err := strconv.Atoi(vfIDStr)
5✔
212
                        if err != nil {
6✔
213
                                klog.Error(err, "Failed to parse VF ID", "entry", entry.Name(), "pfAddress", pfPciAddress)
1✔
214
                                continue
1✔
215
                        }
216

217
                        // Extract PCI address from symlink target
218
                        vfAddr := filepath.Base(target)
4✔
219

4✔
220
                        // Read VF device ID from sysfs
4✔
221
                        deviceIDPath := buildSysBusPciPath(vfAddr, "device")
4✔
222
                        deviceIDBytes, err := os.ReadFile(deviceIDPath) /* #nosec G304 */
4✔
223
                        vfDeviceID := ""
4✔
224
                        if err != nil {
6✔
225
                                klog.Error(err, "Failed to read VF device ID", "vfAddress", vfAddr, "pfAddress", pfPciAddress)
2✔
226
                        } else {
4✔
227
                                vfDeviceID = strings.TrimSpace(string(deviceIDBytes))
2✔
228
                                // Remove 0x prefix if present
2✔
229
                                vfDeviceID = strings.TrimPrefix(vfDeviceID, "0x")
2✔
230
                        }
2✔
231

232
                        vfList = append(vfList, VFInfo{
4✔
233
                                PciAddress: vfAddr,
4✔
234
                                VFID:       vfID,
4✔
235
                                DeviceID:   vfDeviceID,
4✔
236
                        })
4✔
237
                }
238
        }
239

240
        return vfList, nil
5✔
241
}
242

243
// PCI Hardware Discovery Functions
244

245
// PCI returns PCI information using the public ghw library
246
func (h *Host) PCI() (*ghw.PCIInfo, error) {
×
247
        return ghw.PCI()
×
248
}
×
249

250
// TryGetInterfaceName tries to find the network interface name based on PCI address
251
func (h *Host) TryGetInterfaceName(pciAddr string) string {
10✔
252
        netDir := buildSysBusPciPath(pciAddr, "net")
10✔
253
        if _, err := os.Lstat(netDir); err != nil {
13✔
254
                return ""
3✔
255
        }
3✔
256

257
        fInfos, err := os.ReadDir(netDir)
7✔
258
        if err != nil {
7✔
259
                return ""
×
260
        }
×
261

262
        if len(fInfos) == 0 {
8✔
263
                return ""
1✔
264
        }
1✔
265

266
        // Return the first network interface name found
267
        return fInfos[0].Name()
6✔
268
}
269

270
// GetNicSriovMode returns the interface mode (simplified implementation)
271
// This is a simplified version that returns "legacy" mode as fallback
272
func (h *Host) GetNicSriovMode(_ string) string {
1✔
273
        // For simplicity, always return legacy mode
1✔
274
        // A full implementation would use netlink to query the eswitch mode
1✔
275
        return "legacy"
1✔
276
}
1✔
277

278
// GetLinkType returns the link type for a given network interface
279
// Common types: ethernet (type 1), infiniband (type 32)
280
func (h *Host) GetLinkType(pciAddr string) (string, error) {
6✔
281
        // Get the interface name first
6✔
282
        ifName := h.TryGetInterfaceName(pciAddr)
6✔
283
        if ifName == "" {
7✔
284
                return "", fmt.Errorf("unable to get interface name for PCI address %s", pciAddr)
1✔
285
        }
1✔
286

287
        // Read the type from /sys/class/net/<interface>/type
288
        typePath := buildSysPath(fmt.Sprintf("/sys/class/net/%s/type", ifName))
5✔
289
        content, err := os.ReadFile(typePath)
5✔
290
        if err != nil {
6✔
291
                return "", fmt.Errorf("failed to read link type for interface %s: %w", ifName, err)
1✔
292
        }
1✔
293

294
        typeValue := strings.TrimSpace(string(content))
4✔
295
        typeInt, err := strconv.Atoi(typeValue)
4✔
296
        if err != nil {
5✔
297
                return "", fmt.Errorf("failed to parse link type value %s for interface %s: %w", typeValue, ifName, err)
1✔
298
        }
1✔
299

300
        // Map the type value to a human-readable string
301
        // Reference: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_arp.h
302
        switch typeInt {
3✔
303
        case ArphrdEther:
1✔
304
                return consts.LinkTypeEthernet, nil
1✔
305
        case ArphrdInfiniband:
1✔
306
                return consts.LinkTypeInfiniband, nil
1✔
307
        default:
1✔
308
                // Return "unknown" for unsupported types
1✔
309
                h.log.V(1).Info("Unsupported link type, defaulting to unknown", "interface", ifName, "type", typeInt)
1✔
310
                return consts.LinkTypeUnknown, nil
1✔
311
        }
312
}
313

314
// GetNumaNode returns the NUMA node for a given PCI device.
315
// On success, error is nil and the string value represent the NUMA node affinity. Note that -1 means "no affinity".
316
// On failure, error is not nil and the string value must be ignored
317
func (h *Host) GetNumaNode(pciAddress string) (string, error) {
3✔
318
        numaNodePath := buildSysBusPciPath(pciAddress, "numa_node")
3✔
319
        content, err := os.ReadFile(numaNodePath) /* #nosec G304 */
3✔
320
        if err != nil {
4✔
321
                // If numa_node file doesn't exist, return "-1" to represent "no affinity" as the kernel would
1✔
322
                if os.IsNotExist(err) {
2✔
323
                        return "-1", nil
1✔
324
                }
1✔
325
                return "", fmt.Errorf("failed to read numa_node for %s: %v", pciAddress, err)
×
326
        }
327

328
        return strings.TrimSpace(string(content)), nil
2✔
329
}
330

331
// GetPCIeRoot returns the PCIe Root Complex for a given PCI device using the upstream Kubernetes implementation.
332
// The PCIe Root Complex is returned in the format "pci<domain>:<bus>" (e.g., "pci0000:00").
333
// This is used to identify devices that share the same PCIe Root Complex for resource alignment.
334
func (h *Host) GetPCIeRoot(pciAddress string) (string, error) {
4✔
335
        attr, err := deviceattribute.GetPCIeRootAttributeByPCIBusID(pciAddress)
4✔
336
        if err != nil {
8✔
337
                return "", fmt.Errorf("failed to get PCIe root for %s: %w", pciAddress, err)
4✔
338
        }
4✔
339

340
        // Extract the string value from the attribute
341
        if attr.Value.StringValue != nil {
×
342
                return *attr.Value.StringValue, nil
×
343
        }
×
344

345
        return "", fmt.Errorf("PCIe root attribute for %s has no string value", pciAddress)
×
346
}
347

348
// High-level Driver Management Functions
349

350
// BindDeviceDriver binds a device to the specified driver based on config.Driver:
351
// - If config.Driver == "", nothing is done
352
// - If config.Driver == "default", binds device to default driver
353
// - Otherwise, binds device to the specified driver
354
func (h *Host) BindDeviceDriver(pciAddress string, config *configapi.VfConfig) (string, error) {
2✔
355
        return h.BindDeviceDriverWithMAC(pciAddress, config, "")
2✔
356
}
2✔
357

358
// BindDeviceDriverWithMAC binds a device to the specified driver and optionally sets MAC address.
359
// For vfio-pci driver: MAC is set after unbind (if needed) but before bind to vfio-pci.
360
// This ensures VF has a network interface when MAC is configured.
361
func (h *Host) BindDeviceDriverWithMAC(pciAddress string, config *configapi.VfConfig, macAddress string) (string, error) {
3✔
362
        if config.Driver == "" {
4✔
363
                h.log.V(2).Info("BindDeviceDriverWithMAC(): no driver specified, skipping", "device", pciAddress)
1✔
364
                return "", nil
1✔
365
        }
1✔
366

367
        // Get current driver before making changes
368
        currentDriver, err := h.GetDriverByBusAndDevice(pciAddress)
2✔
369
        if err != nil {
2✔
370
                return "", fmt.Errorf("failed to get current driver for device %s: %w", pciAddress, err)
×
371
        }
×
372

373
        if config.Driver == "default" {
3✔
374
                h.log.V(2).Info("BindDeviceDriverWithMAC(): binding device to default driver", "device", pciAddress)
1✔
375
                if err := h.BindDefaultDriver(pciAddress); err != nil {
1✔
376
                        return "", fmt.Errorf("failed to bind device %s to default driver: %w", pciAddress, err)
×
377
                }
×
378
                return currentDriver, nil
1✔
379
        }
380

381
        // For vfio-pci with MAC: set MAC after unbind (if needed) but before bind
382
        if config.Driver == consts.DriverVFIOPCI && macAddress != "" {
2✔
383
                // If already bound to something, unbind first
1✔
384
                if currentDriver != "" {
1✔
NEW
385
                        h.log.V(2).Info("BindDeviceDriverWithMAC(): unbinding device before setting MAC", "device", pciAddress, "currentDriver", currentDriver)
×
NEW
386
                        if err := h.UnbindDriverByBusAndDevice(pciAddress); err != nil {
×
NEW
387
                                return "", fmt.Errorf("failed to unbind device %s: %w", pciAddress, err)
×
NEW
388
                        }
×
389
                }
390

391
                // Now VF has network interface - set MAC
392
                h.log.V(2).Info("BindDeviceDriverWithMAC(): setting MAC before binding to vfio-pci", "device", pciAddress, "mac", macAddress)
1✔
393
                if err := h.SetVFMacAddress(pciAddress, macAddress); err != nil {
2✔
394
                        return "", fmt.Errorf("failed to set MAC address on device %s: %w", pciAddress, err)
1✔
395
                }
1✔
396

397
                // Now bind to vfio-pci
NEW
398
                h.log.V(2).Info("BindDeviceDriverWithMAC(): binding device to vfio-pci", "device", pciAddress)
×
NEW
399
                if err := h.BindDriverByBusAndDevice(pciAddress, config.Driver); err != nil {
×
NEW
400
                        return "", fmt.Errorf("failed to bind device %s to driver %s: %w", pciAddress, config.Driver, err)
×
NEW
401
                }
×
NEW
402
                return currentDriver, nil
×
403
        }
404

405
        // Normal case: no MAC to set, just bind
NEW
406
        h.log.V(2).Info("BindDeviceDriverWithMAC(): binding device to driver", "device", pciAddress, "driver", config.Driver)
×
407
        if err := h.BindDriverByBusAndDevice(pciAddress, config.Driver); err != nil {
×
408
                return "", fmt.Errorf("failed to bind device %s to driver %s: %w", pciAddress, config.Driver, err)
×
409
        }
×
410
        return currentDriver, nil
×
411
}
412

413
// RestoreDeviceDriver restores a device to its original driver
414
func (h *Host) RestoreDeviceDriver(pciAddress string, originalDriver string) error {
×
415
        if originalDriver == "" {
×
416
                h.log.V(2).Info("RestoreDeviceDriver(): no original driver, binding to default", "device", pciAddress)
×
417
                return h.BindDefaultDriver(pciAddress)
×
418
        }
×
419

420
        h.log.V(2).Info("RestoreDeviceDriver(): restoring device to original driver", "device", pciAddress, "driver", originalDriver)
×
421
        return h.BindDriverByBusAndDevice(pciAddress, originalDriver)
×
422
}
423

424
// BindDefaultDriver binds a device to its default driver
425
func (h *Host) BindDefaultDriver(pciAddress string) error {
1✔
426
        h.log.V(2).Info("BindDefaultDriver(): binding device to default driver", "device", pciAddress)
1✔
427

1✔
428
        curDriver, err := h.GetDriverByBusAndDevice(pciAddress)
1✔
429
        if err != nil {
1✔
430
                return err
×
431
        }
×
432
        if curDriver != "" {
1✔
433
                // If already bound to a non-DPDK driver, assume it's the default
×
434
                if !h.IsDpdkDriver(curDriver) {
×
435
                        h.log.V(2).Info("BindDefaultDriver(): device already bound to default driver",
×
436
                                "device", pciAddress, "driver", curDriver)
×
437
                        return nil
×
438
                }
×
439
                if err := h.UnbindDriverByBusAndDevice(pciAddress); err != nil {
×
440
                        return err
×
441
                }
×
442
        }
443
        if err := h.setDriverOverride(pciAddress, ""); err != nil {
1✔
444
                return err
×
445
        }
×
446
        if err := h.probeDriver(pciAddress); err != nil {
1✔
447
                return err
×
448
        }
×
449
        return nil
1✔
450
}
451

452
// Low-level Driver Operations
453

454
// BindDriverByBusAndDevice binds device to the provided driver
455
func (h *Host) BindDriverByBusAndDevice(device, driver string) error {
×
456
        h.log.V(2).Info("BindDriverByBusAndDevice(): bind device to driver",
×
457
                "device", device, "driver", driver)
×
458

×
459
        // Ensure DPDK kernel module is loaded before binding
×
460
        if err := h.EnsureDpdkModuleLoaded(driver); err != nil {
×
461
                return fmt.Errorf("failed to ensure DPDK module is loaded for driver %s: %w", driver, err)
×
462
        }
×
463

464
        curDriver, err := h.GetDriverByBusAndDevice(device)
×
465
        if err != nil {
×
466
                return err
×
467
        }
×
468
        if curDriver != "" {
×
469
                if curDriver == driver {
×
470
                        h.log.V(2).Info("BindDriverByBusAndDevice(): device already bound to driver",
×
471
                                "device", device, "driver", driver)
×
472
                        return nil
×
473
                }
×
474
                if err := h.UnbindDriverByBusAndDevice(device); err != nil {
×
475
                        return err
×
476
                }
×
477
        }
478
        if err := h.setDriverOverride(device, driver); err != nil {
×
479
                return err
×
480
        }
×
481
        if err := h.bindDriver(device, driver); err != nil {
×
482
                return err
×
483
        }
×
484
        return h.setDriverOverride(device, "")
×
485
}
486

487
// UnbindDriverByBusAndDevice unbinds device from its current driver
488
func (h *Host) UnbindDriverByBusAndDevice(device string) error {
×
489
        h.log.V(2).Info("UnbindDriverByBusAndDevice(): unbind device driver for device", "device", device)
×
490
        driver, err := h.GetDriverByBusAndDevice(device)
×
491
        if err != nil {
×
492
                return err
×
493
        }
×
494
        if driver == "" {
×
495
                h.log.V(2).Info("UnbindDriverByBusAndDevice(): device has no driver", "device", device)
×
496
                return nil
×
497
        }
×
498
        return h.unbindDriver(device, driver)
×
499
}
500

501
// GetDriverByBusAndDevice returns driver for device on the bus
502
func (h *Host) GetDriverByBusAndDevice(device string) (string, error) {
5✔
503
        driverLink := buildSysBusPciPath(device, "driver")
5✔
504
        driverInfo, err := os.Readlink(driverLink)
5✔
505
        if err != nil {
9✔
506
                if errors.Is(err, os.ErrNotExist) {
8✔
507
                        h.log.V(2).Info("GetDriverByBusAndDevice(): driver path for device not exist", "device", device)
4✔
508
                        return "", nil
4✔
509
                }
4✔
510
                h.log.Error(err, "GetDriverByBusAndDevice(): error getting driver info for device", "device", device)
×
511
                return "", err
×
512
        }
513
        h.log.V(2).Info("GetDriverByBusAndDevice(): driver for device", "device", device, "driver", driverInfo)
1✔
514
        return filepath.Base(driverInfo), nil
1✔
515
}
516

517
// Private helper methods
518

519
// bindDriver binds device to the provided driver
520
func (h *Host) bindDriver(device, driver string) error {
×
521
        h.log.V(2).Info("bindDriver(): bind to driver", "device", device, "driver", driver)
×
522
        bindPath := buildSysBusPciDriverPath(driver, "bind")
×
523
        err := os.WriteFile(bindPath, []byte(device), os.ModeAppend)
×
524
        if err != nil {
×
525
                h.log.Error(err, "bindDriver(): failed to bind driver", "device", device, "driver", driver)
×
526
                return err
×
527
        }
×
528
        return nil
×
529
}
530

531
// unbindDriver unbinds device from the driver
532
func (h *Host) unbindDriver(device, driver string) error {
×
533
        h.log.V(2).Info("unbindDriver(): unbind from driver", "device", device, "driver", driver)
×
534
        unbindPath := buildSysBusPciDriverPath(driver, "unbind")
×
535
        err := os.WriteFile(unbindPath, []byte(device), os.ModeAppend)
×
536
        if err != nil {
×
537
                h.log.Error(err, "unbindDriver(): failed to unbind driver", "device", device, "driver", driver)
×
538
                return err
×
539
        }
×
540
        return nil
×
541
}
542

543
// probeDriver probes driver for device on the bus
544
func (h *Host) probeDriver(device string) error {
1✔
545
        h.log.V(2).Info("probeDriver(): drivers probe", "device", device)
1✔
546
        probePath := buildSysPath("/sys/bus/pci/drivers_probe")
1✔
547
        err := os.WriteFile(probePath, []byte(device), os.ModeAppend)
1✔
548
        if err != nil {
1✔
549
                h.log.Error(err, "probeDriver(): failed to trigger driver probe", "device", device)
×
550
                return err
×
551
        }
×
552
        return nil
1✔
553
}
554

555
// setDriverOverride sets driver override for the bus/device,
556
// resets override if override arg is "",
557
// if device doesn't support overriding (has no driver_override path), does nothing
558
func (h *Host) setDriverOverride(device, override string) error {
1✔
559
        driverOverridePath := buildSysBusPciPath(device, "driver_override")
1✔
560
        if _, err := os.Stat(driverOverridePath); err != nil {
2✔
561
                if os.IsNotExist(err) {
2✔
562
                        h.log.V(2).Info("setDriverOverride(): device doesn't support driver override, skip", "device", device)
1✔
563
                        return nil
1✔
564
                }
1✔
565
                return err
×
566
        }
567
        var overrideData []byte
×
568
        if override != "" {
×
569
                h.log.V(2).Info("setDriverOverride(): configure driver override for device", "device", device, "driver", override)
×
570
                overrideData = []byte(override)
×
571
        } else {
×
572
                h.log.V(2).Info("setDriverOverride(): reset driver override for device", "device", device)
×
573
                overrideData = []byte("\x00")
×
574
        }
×
575
        err := os.WriteFile(driverOverridePath, overrideData, os.ModeAppend)
×
576
        if err != nil {
×
577
                h.log.Error(err, "setDriverOverride(): fail to write driver_override for device",
×
578
                        "device", device, "driver", override)
×
579
                return err
×
580
        }
×
581
        return nil
×
582
}
583

584
// Utility Functions
585

586
// IsDpdkDriver checks if the given driver is a DPDK driver
587
func (h *Host) IsDpdkDriver(driver string) bool {
9✔
588
        dpdkDrivers := []string{consts.DriverVFIOPCI, consts.DriverUIOPCI, consts.DriverIGBUIO}
9✔
589
        for _, dpdkDriver := range dpdkDrivers {
30✔
590
                if driver == dpdkDriver {
26✔
591
                        return true
5✔
592
                }
5✔
593
        }
594
        return false
4✔
595
}
596

597
// VFIO Device Functions
598

599
// GetVFIODeviceFile returns VFIO device files for vfio-pci bound PCI device's PCI address
600
func (h *Host) GetVFIODeviceFile(pciAddress string) (devFileHost, devFileContainer string, err error) {
3✔
601
        h.log.V(2).Info("GetVFIODeviceFile(): getting VFIO device file", "device", pciAddress)
3✔
602

3✔
603
        const devDir = "/dev"
3✔
604

3✔
605
        // Get iommu group for this device
3✔
606
        devPath := buildSysBusPciPath(pciAddress, "")
3✔
607
        _, err = os.Lstat(devPath)
3✔
608
        if err != nil {
4✔
609
                h.log.Error(err, "GetVFIODeviceFile(): Could not get directory information for device", "device", pciAddress)
1✔
610
                err = fmt.Errorf("GetVFIODeviceFile(): Could not get directory information for device: %s, Err: %v", pciAddress, err)
1✔
611
                return devFileHost, devFileContainer, err
1✔
612
        }
1✔
613

614
        iommuDir := filepath.Join(devPath, "iommu_group")
2✔
615
        h.log.V(2).Info("GetVFIODeviceFile(): checking iommu_group", "device", pciAddress, "iommuDir", iommuDir)
2✔
616

2✔
617
        dirInfo, err := os.Lstat(iommuDir)
2✔
618
        if err != nil {
3✔
619
                h.log.Error(err, "GetVFIODeviceFile(): unable to find iommu_group", "device", pciAddress)
1✔
620
                err = fmt.Errorf("GetVFIODeviceFile(): unable to find iommu_group %v", err)
1✔
621
                return devFileHost, devFileContainer, err
1✔
622
        }
1✔
623

624
        if dirInfo.Mode()&os.ModeSymlink == 0 {
1✔
625
                h.log.Error(nil, "GetVFIODeviceFile(): invalid symlink to iommu_group", "device", pciAddress)
×
626
                err = fmt.Errorf("GetVFIODeviceFile(): invalid symlink to iommu_group %v", err)
×
627
                return devFileHost, devFileContainer, err
×
628
        }
×
629

630
        linkName, err := filepath.EvalSymlinks(iommuDir)
1✔
631
        if err != nil {
1✔
632
                h.log.Error(err, "GetVFIODeviceFile(): error reading symlink to iommu_group", "device", pciAddress)
×
633
                err = fmt.Errorf("GetVFIODeviceFile(): error reading symlink to iommu_group %v", err)
×
634
                return devFileHost, devFileContainer, err
×
635
        }
×
636
        devFileContainer = filepath.Join(devDir, "vfio", filepath.Base(linkName))
1✔
637
        devFileHost = devFileContainer
1✔
638

1✔
639
        h.log.V(2).Info("GetVFIODeviceFile(): resolved iommu group", "device", pciAddress, "iommuGroup", filepath.Base(linkName))
1✔
640

1✔
641
        // Get a file path to the iommu group name
1✔
642
        namePath := filepath.Join(linkName, "name")
1✔
643
        // Read the iommu group name
1✔
644
        // The name file will not exist on baremetal
1✔
645
        vfioName, errName := os.ReadFile(namePath) /* #nosec G304 */
1✔
646
        if errName == nil {
1✔
647
                vName := strings.TrimSpace(string(vfioName))
×
648
                h.log.V(2).Info("GetVFIODeviceFile(): read iommu group name", "device", pciAddress, "vfioName", vName)
×
649

×
650
                // if the iommu group name == vfio-noiommu then we are in a VM, adjust path to vfio device
×
651
                if vName == "vfio-noiommu" {
×
652
                        h.log.V(2).Info("GetVFIODeviceFile(): detected vfio-noiommu mode, adjusting device path", "device", pciAddress)
×
653
                        linkName = filepath.Join(filepath.Dir(linkName), "noiommu-"+filepath.Base(linkName))
×
654
                        devFileHost = filepath.Join(devDir, "vfio", filepath.Base(linkName))
×
655
                }
×
656
        } else {
1✔
657
                h.log.V(2).Info("GetVFIODeviceFile(): iommu group name file not found (baremetal mode)", "device", pciAddress)
1✔
658
        }
1✔
659

660
        h.log.V(2).Info("GetVFIODeviceFile(): successfully resolved VFIO device files",
1✔
661
                "device", pciAddress, "devFileHost", devFileHost, "devFileContainer", devFileContainer)
1✔
662

1✔
663
        return devFileHost, devFileContainer, err
1✔
664
}
665

666
// Kernel Module Management Functions
667

668
// IsKernelModuleLoaded checks if a kernel module is currently loaded
669
func (h *Host) IsKernelModuleLoaded(moduleName string) bool {
9✔
670
        // Read /proc/modules to check if the module is loaded
9✔
671
        content, err := os.ReadFile(buildProcPath("/proc/modules"))
9✔
672
        if err != nil {
10✔
673
                h.log.Error(err, "IsKernelModuleLoaded(): failed to read /proc/modules")
1✔
674
                return false
1✔
675
        }
1✔
676

677
        // Each line in /proc/modules starts with the module name
678
        lines := strings.Split(string(content), "\n")
8✔
679
        for _, line := range lines {
19✔
680
                if strings.HasPrefix(line, moduleName+" ") || line == moduleName {
17✔
681
                        h.log.V(2).Info("IsKernelModuleLoaded(): module is loaded", "module", moduleName)
6✔
682
                        return true
6✔
683
                }
6✔
684
        }
685

686
        h.log.V(2).Info("IsKernelModuleLoaded(): module is not loaded", "module", moduleName)
2✔
687
        return false
2✔
688
}
689

690
// LoadKernelModule loads a kernel module using modprobe with chroot to access host filesystem
691
func (h *Host) LoadKernelModule(moduleName string) error {
×
692
        h.log.V(2).Info("LoadKernelModule(): loading kernel module", "module", moduleName)
×
693

×
694
        cmd := exec.Command("chroot", "/proc/1/root", "modprobe", moduleName)
×
695
        output, err := cmd.CombinedOutput()
×
696
        if err != nil {
×
697
                h.log.Error(err, "LoadKernelModule(): failed to load kernel module",
×
698
                        "module", moduleName, "output", string(output))
×
699
                return fmt.Errorf("failed to load kernel module %s: %w (output: %s)",
×
700
                        moduleName, err, string(output))
×
701
        }
×
702

703
        h.log.V(2).Info("LoadKernelModule(): successfully loaded kernel module", "module", moduleName)
×
704
        return nil
×
705
}
706

707
// EnsureDpdkModuleLoaded ensures that the kernel module for a DPDK driver is loaded
708
func (h *Host) EnsureDpdkModuleLoaded(driver string) error {
3✔
709
        if !h.IsDpdkDriver(driver) {
4✔
710
                h.log.V(2).Info("EnsureDpdkModuleLoaded(): driver is not a DPDK driver, skipping module check", "driver", driver)
1✔
711
                return nil
1✔
712
        }
1✔
713

714
        // Map DPDK driver names to their corresponding kernel module names
715
        var modulesNames []string
2✔
716
        switch driver {
2✔
717
        case consts.DriverVFIOPCI:
1✔
718
                modulesNames = []string{"vfio", "vfio_pci"}
1✔
719
        default:
1✔
720
                return fmt.Errorf("unknown DPDK driver: %s", driver)
1✔
721
        }
722

723
        // Check which modules need to be loaded
724
        var modulesToLoad []string
1✔
725
        for _, moduleName := range modulesNames {
3✔
726
                if h.IsKernelModuleLoaded(moduleName) {
4✔
727
                        h.log.V(2).Info("EnsureDpdkModuleLoaded(): kernel module already loaded", "driver", driver, "module", moduleName)
2✔
728
                } else {
2✔
729
                        modulesToLoad = append(modulesToLoad, moduleName)
×
730
                }
×
731
        }
732

733
        // If all modules are already loaded, return early
734
        if len(modulesToLoad) == 0 {
2✔
735
                h.log.V(2).Info("EnsureDpdkModuleLoaded(): all required modules already loaded", "driver", driver)
1✔
736
                return nil
1✔
737
        }
1✔
738

739
        // Load missing modules
740
        var errors []error
×
741
        for _, moduleName := range modulesToLoad {
×
742
                h.log.Info("EnsureDpdkModuleLoaded(): loading kernel module for DPDK driver", "driver", driver, "module", moduleName)
×
743
                if err := h.LoadKernelModule(moduleName); err != nil {
×
744
                        h.log.Error(err, "EnsureDpdkModuleLoaded(): failed to load module", "driver", driver, "module", moduleName)
×
745
                        errors = append(errors, fmt.Errorf("failed to load module %s: %w", moduleName, err))
×
746
                        continue
×
747
                }
748

749
                // Verify module was loaded successfully
750
                if !h.IsKernelModuleLoaded(moduleName) {
×
751
                        err := fmt.Errorf("module %s was not loaded after LoadKernelModule call", moduleName)
×
752
                        h.log.Error(err, "EnsureDpdkModuleLoaded(): module verification failed", "driver", driver, "module", moduleName)
×
753
                        errors = append(errors, err)
×
754
                } else {
×
755
                        h.log.Info("EnsureDpdkModuleLoaded(): successfully loaded kernel module", "driver", driver, "module", moduleName)
×
756
                }
×
757
        }
758

759
        // If we encountered any errors, return them
760
        if len(errors) > 0 {
×
761
                return fmt.Errorf("failed to load %d out of %d required kernel modules for DPDK driver %s: %v", len(errors), len(modulesToLoad), driver, errors)
×
762
        }
×
763
        return nil
×
764
}
765

766
// EnsureVhostModulesLoaded ensures that the tun and vhost_net kernel modules are loaded
767
func (h *Host) EnsureVhostModulesLoaded() error {
1✔
768
        // Modules required for vhost functionality
1✔
769
        modulesNames := []string{"tun", "vhost_net"}
1✔
770

1✔
771
        // Check which modules need to be loaded
1✔
772
        var modulesToLoad []string
1✔
773
        for _, moduleName := range modulesNames {
3✔
774
                if h.IsKernelModuleLoaded(moduleName) {
4✔
775
                        h.log.V(2).Info("EnsureVhostModulesLoaded(): kernel module already loaded", "module", moduleName)
2✔
776
                } else {
2✔
777
                        modulesToLoad = append(modulesToLoad, moduleName)
×
778
                }
×
779
        }
780

781
        // If all modules are already loaded, return early
782
        if len(modulesToLoad) == 0 {
2✔
783
                h.log.V(2).Info("EnsureVhostModulesLoaded(): all required vhost modules already loaded")
1✔
784
                return nil
1✔
785
        }
1✔
786

787
        // Load missing modules
788
        var errors []error
×
789
        for _, moduleName := range modulesToLoad {
×
790
                h.log.Info("EnsureVhostModulesLoaded(): loading kernel module for vhost functionality", "module", moduleName)
×
791
                if err := h.LoadKernelModule(moduleName); err != nil {
×
792
                        h.log.Error(err, "EnsureVhostModulesLoaded(): failed to load module", "module", moduleName)
×
793
                        errors = append(errors, fmt.Errorf("failed to load module %s: %w", moduleName, err))
×
794
                        continue
×
795
                }
796

797
                // Verify module was loaded successfully
798
                if !h.IsKernelModuleLoaded(moduleName) {
×
799
                        err := fmt.Errorf("module %s was not loaded after LoadKernelModule call", moduleName)
×
800
                        h.log.Error(err, "EnsureVhostModulesLoaded(): module verification failed", "module", moduleName)
×
801
                        errors = append(errors, err)
×
802
                } else {
×
803
                        h.log.Info("EnsureVhostModulesLoaded(): successfully loaded kernel module", "module", moduleName)
×
804
                }
×
805
        }
806

807
        // If we encountered any errors, return them
808
        if len(errors) > 0 {
×
809
                return fmt.Errorf("failed to load %d out of %d required kernel modules for vhost functionality: %v", len(errors), len(modulesToLoad), errors)
×
810
        }
×
811
        return nil
×
812
}
813

814
// SetVFMacAddress sets the MAC address on a VF via the PF
815
// This must be called BEFORE binding the VF to vfio-pci, as vfio-bound devices
816
// don't have network interfaces and cannot have their MAC addresses set.
817
func (h *Host) SetVFMacAddress(vfPciAddress string, macAddress string) error {
1✔
818
        h.log.V(2).Info("SetVFMacAddress", "vfPciAddress", vfPciAddress, "macAddress", macAddress)
1✔
819

1✔
820
        // Get the PF for this VF
1✔
821
        pfPciAddress, err := h.GetParentPciAddress(vfPciAddress)
1✔
822
        if err != nil {
2✔
823
                return fmt.Errorf("failed to get parent PF for VF %s: %w", vfPciAddress, err)
1✔
824
        }
1✔
825

826
        // Get the PF network interface name
NEW
827
        pfNetdev := h.TryGetInterfaceName(pfPciAddress)
×
NEW
828
        if pfNetdev == "" {
×
NEW
829
                return fmt.Errorf("failed to get network interface for PF %s", pfPciAddress)
×
NEW
830
        }
×
831

832
        // Get VF list to find the VF ID
NEW
833
        vfList, err := h.GetVFList(pfPciAddress)
×
NEW
834
        if err != nil {
×
NEW
835
                return fmt.Errorf("failed to get VF list for PF %s: %w", pfPciAddress, err)
×
NEW
836
        }
×
837

838
        // Find the VF ID for this VF PCI address
NEW
839
        vfID := -1
×
NEW
840
        for _, vf := range vfList {
×
NEW
841
                if vf.PciAddress == vfPciAddress {
×
NEW
842
                        vfID = vf.VFID
×
NEW
843
                        break
×
844
                }
845
        }
846

NEW
847
        if vfID == -1 {
×
NEW
848
                return fmt.Errorf("VF %s not found in PF %s VF list", vfPciAddress, pfPciAddress)
×
NEW
849
        }
×
850

851
        // Set the MAC address via ip link command
852
        // Equivalent to: ip link set <pf> vf <vf_id> mac <mac>
853
        // #nosec G204 -- exec.Command does not invoke a shell; arguments are passed as discrete values.
NEW
854
        cmd := exec.Command("ip", "link", "set", pfNetdev, "vf", strconv.Itoa(vfID), "mac", macAddress)
×
NEW
855
        output, err := cmd.CombinedOutput()
×
NEW
856
        if err != nil {
×
NEW
857
                return fmt.Errorf("failed to set MAC address on VF %d of PF %s: %w, output: %s", vfID, pfNetdev, err, string(output))
×
NEW
858
        }
×
859

NEW
860
        h.log.V(2).Info("Successfully set MAC address on VF", "pf", pfNetdev, "vfID", vfID, "mac", macAddress)
×
NEW
861
        return nil
×
862
}
863

864
// GetParentPciAddress resolves the parent PF PCI address for an SR-IOV VF.
865
func (h *Host) GetParentPciAddress(vfPciAddress string) (string, error) {
1✔
866
        physfnPath := buildSysBusPciPath(vfPciAddress, "physfn")
1✔
867
        target, err := os.Readlink(physfnPath)
1✔
868
        if err != nil {
2✔
869
                return "", fmt.Errorf("failed to resolve parent PF for VF %s: %w", vfPciAddress, err)
1✔
870
        }
1✔
NEW
871
        return filepath.Base(target), nil
×
872
}
873

874
// RDMA Device Functions
875

876
// GetRDMADevicesForPCI returns the RDMA device names associated with a PCI address
877
// Uses the rdmamap library from Mellanox for RDMA device detection
878
func (h *Host) GetRDMADevicesForPCI(pciAddr string) []string {
5✔
879
        h.log.V(2).Info("GetRDMADevicesForPCI(): getting RDMA devices for PCI address", "device", pciAddr)
5✔
880

5✔
881
        // Use rdmaProvider to get RDMA devices for this PCI address
5✔
882
        rdmaDevices := h.rdmaProvider.GetRdmaDevicesForPcidev(pciAddr)
5✔
883

5✔
884
        if len(rdmaDevices) > 0 {
7✔
885
                h.log.Info("GetRDMADevicesForPCI(): found RDMA devices",
2✔
886
                        "pciAddress", pciAddr, "rdmaDevices", rdmaDevices)
2✔
887
        }
2✔
888
        return rdmaDevices
5✔
889
}
890

891
// VerifyRDMACapability checks if a device supports RDMA by looking for associated RDMA devices
892
func (h *Host) VerifyRDMACapability(pciAddr string) bool {
2✔
893
        h.log.V(2).Info("VerifyRDMACapability(): checking RDMA capability", "device", pciAddr)
2✔
894

2✔
895
        rdmaDevices := h.GetRDMADevicesForPCI(pciAddr)
2✔
896

2✔
897
        hasRDMA := len(rdmaDevices) > 0
2✔
898
        h.log.Info("VerifyRDMACapability(): RDMA capability check result",
2✔
899
                "device", pciAddr, "rdmaCapable", hasRDMA)
2✔
900

2✔
901
        return hasRDMA
2✔
902
}
2✔
903

904
// GetRDMACharDevices returns the character device paths for an RDMA device
905
// These are the actual device nodes (e.g., /dev/infiniband/uverbs0) that need to be
906
// exposed to containers for RDMA functionality
907
func (h *Host) GetRDMACharDevices(rdmaDeviceName string) ([]string, error) {
6✔
908
        // Validate input
6✔
909
        if rdmaDeviceName == "" {
7✔
910
                return nil, fmt.Errorf("rdmaDeviceName cannot be empty")
1✔
911
        }
1✔
912

913
        h.log.Info("GetRDMACharDevices(): getting character devices for RDMA device", "rdmaDevice", rdmaDeviceName)
5✔
914

5✔
915
        // Use rdmaProvider to get character devices for this RDMA device
5✔
916
        charDevices := h.rdmaProvider.GetRdmaCharDevices(rdmaDeviceName)
5✔
917

5✔
918
        if len(charDevices) == 0 {
6✔
919
                h.log.Info("GetRDMACharDevices(): no character devices found", "rdmaDevice", rdmaDeviceName)
1✔
920
                return []string{}, nil
1✔
921
        }
1✔
922

923
        h.log.Info("GetRDMACharDevices(): found character devices",
4✔
924
                "rdmaDevice", rdmaDeviceName, "charDevices", charDevices)
4✔
925
        return charDevices, nil
4✔
926
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc