Skip to content

Commit ed57841

Browse files
authored
[shimV2] adds vpci device controller (#2643)
* [shimV2] adds vpci device controller This change adds the VPCI device controller which can be used to assign/unassign virtual PCI devices to the UVM. The same then can be assigned to the containers running in the UVM. Signed-off-by: Harsh Rawat <harshrawat@microsoft.com>
1 parent 50d76a6 commit ed57841

14 files changed

Lines changed: 1378 additions & 37 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//go:build windows
2+
3+
// Package vpci provides a controller for managing virtual PCI (vPCI) device
4+
// assignments on a Utility VM (UVM). It handles assigning and removing
5+
// PCI devices from the UVM via HCS modify calls.
6+
//
7+
// # Lifecycle
8+
//
9+
// [Controller] tracks active device assignments by VMBus GUID (device identifier
10+
// within UVM) in an internal map. Each assignment is reference-counted to
11+
// support shared access by multiple callers.
12+
//
13+
// A device follows the state machine below.
14+
//
15+
// ┌─────────────────┐
16+
// │ StateReserved │
17+
// └────────┬────────┘
18+
// │ AddToVM host ok
19+
// ▼
20+
// ┌─────────────────┐ AddToVM host fails ┌─────────────────┐
21+
// │ StateAssigned │──────────────────────────►│ StateRemoved │
22+
// └────────┬────────┘ └────────┬────────┘
23+
// ┌───────────┤ │ RemoveFromVM
24+
// │ │ waitGuest ok ▼
25+
// │ ▼ (untracked)
26+
// │ ┌─────────────────┐
27+
// │ │ StateReady │◄── AddToVM (refCount++)
28+
// │ └────────┬────────┘
29+
// │ │ RemoveFromVM ok
30+
// │ ▼
31+
// │ (untracked)
32+
// │
33+
// │ ┌──────────────────────┐
34+
// └──waitGuest fail───────────────►│ StateAssignedInvalid │◄── RemoveFromVM host fails
35+
// └──────────┬───────────┘
36+
// │ RemoveFromVM ok
37+
// ▼
38+
// (untracked)
39+
//
40+
// - [Controller.Reserve] generates a unique VMBus GUID for a device and
41+
// records the reservation. If the same device is already reserved, the
42+
// existing GUID is returned.
43+
// - [Controller.AddToVM] assigns a previously reserved device to the VM
44+
// using the VMBus GUID returned by Reserve. If the device is already
45+
// ready for use in the VM, the reference count is incremented.
46+
// - [Controller.RemoveFromVM] decrements the reference count for the device
47+
// identified by VMBus GUID. When it reaches zero, the device is removed
48+
// from the VM. It also handles cleanup for devices that were reserved
49+
// but never assigned, and for devices in an invalid state.
50+
//
51+
// # Invalid Devices
52+
//
53+
// The device is marked invalid if the host-side assignment succeeds but the
54+
// guest-side notification fails or if the host-side remove call fails.
55+
// The device remains tracked as Invalid so that the caller can call
56+
// [Controller.RemoveFromVM] to perform host-side cleanup.
57+
//
58+
// # Virtual Functions
59+
//
60+
// Each Virtual Function is assigned as an independent guest device with its own
61+
// VMBus GUID. Multiple Virtual Functions on the same physical device are treated
62+
// as separate devices in the guest.
63+
//
64+
// # Guest Requests
65+
//
66+
// On LCOW, assigning a vPCI device requires a guest-side notification so the
67+
// GCS can wait for the required device paths to become available.
68+
// WCOW does not require a guest request as part of device assignment.
69+
package vpci

internal/controller/device/vpci/mocks/mock_vpci.go

Lines changed: 110 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//go:build windows
2+
3+
package vpci
4+
5+
// State represents the current state of a vPCI device assignment lifecycle.
6+
//
7+
// The normal progression is:
8+
//
9+
// StateReserved → StateAssigned → StateReady → StateRemoved+untracked
10+
//
11+
// [StateAssigned] is a transient state set within a single [Controller.AddToVM] call
12+
// after the host-side HCS modify succeeds. [waitGuestDeviceReady] is then called; on
13+
// success the device moves to [StateReady], on failure to [StateAssignedInvalid].
14+
//
15+
// A device transitions to [StateAssignedInvalid] when an operation partially succeeds
16+
// and leaves the VM in an inconsistent state. This can happen in two ways:
17+
// - [Controller.AddToVM]: host-side assignment succeeds but guest-side notification fails.
18+
// - [Controller.RemoveFromVM]: the host-side remove call fails.
19+
//
20+
// A device in [StateAssignedInvalid] can only be cleaned up via [Controller.RemoveFromVM].
21+
//
22+
// Full state-transition table:
23+
//
24+
// Current State │ Trigger │ Next State
25+
// ─────────────────────┼────────────────────────────────────────────────────┼──────────────────────
26+
// StateReserved │ AddToVM host-side succeeds │ StateAssigned
27+
// StateReserved │ AddToVM host-side fails │ StateRemoved
28+
// StateReserved │ RemoveFromVM called │ (untracked)
29+
// StateAssigned │ waitGuestDeviceReady succeeds │ StateReady
30+
// StateAssigned │ waitGuestDeviceReady fails │ StateAssignedInvalid
31+
// StateReady │ AddToVM called (refCount++) │ StateReady
32+
// StateReady │ RemoveFromVM refCount drops to 0, succeeds │ (untracked)
33+
// StateReady │ RemoveFromVM refCount drops to 0, host-side fails │ StateAssignedInvalid
34+
// StateAssignedInvalid │ RemoveFromVM succeeds │ (untracked)
35+
// StateAssignedInvalid │ RemoveFromVM host-side fails │ StateAssignedInvalid
36+
// StateRemoved │ AddToVM called │ error (call RemoveFromVM)
37+
// StateRemoved │ RemoveFromVM called │ (untracked)
38+
type State int32
39+
40+
const (
41+
// StateReserved indicates that a VMBus GUID has been generated and the
42+
// device has been recorded in the Controller, but it has not yet been
43+
// assigned to the VM via a host-side HCS modify call.
44+
// This is the initial state set by [Controller.Reserve].
45+
StateReserved State = iota
46+
47+
// StateAssigned is a transient state that indicates the host-side HCS modify
48+
// has succeeded but [waitGuestDeviceReady] has not yet been called/completed
49+
// within a single [Controller.AddToVM] invocation.
50+
// External callers should never observe this state.
51+
StateAssigned
52+
53+
// StateReady indicates the device has been fully assigned to the VM:
54+
// the host-side HCS modify succeeded and the guest-side device is ready.
55+
// The reference count may be greater than one when multiple callers share
56+
// the same device.
57+
StateReady
58+
59+
// StateAssignedInvalid indicates the device is in an inconsistent state due to a
60+
// partial failure. This state is reached in two ways:
61+
// - [Controller.AddToVM]: the host-side assignment succeeded but the
62+
// guest-side notification failed; the host-side assignment still exists
63+
// but the guest-side device is not in a usable state.
64+
// - [Controller.RemoveFromVM]: the host-side remove call failed; the
65+
// host-side assignment still exists but the reference count has been
66+
// decremented to zero.
67+
// In either case the device must be cleaned up by calling [Controller.RemoveFromVM].
68+
StateAssignedInvalid
69+
70+
// StateRemoved indicates that no host-side VM assignment exists for this device.
71+
// This state is reached in two ways:
72+
// - [Controller.AddToVM]: the host-side add call failed. The device is still
73+
// tracked in the Controller and the caller must call [Controller.RemoveFromVM]
74+
// to clean up the reservation. No further [Controller.AddToVM] calls are allowed.
75+
// - [Controller.untrack]: set as a safety marker immediately before the device
76+
// is deleted from the tracking maps. In this case the state is never externally
77+
// observable — the device is gone from the map by the time the lock is released.
78+
StateRemoved
79+
)
80+
81+
// String returns a human-readable string representation of the device State.
82+
func (s State) String() string {
83+
switch s {
84+
case StateReserved:
85+
return "Reserved"
86+
case StateAssigned:
87+
return "Assigned"
88+
case StateReady:
89+
return "Ready"
90+
case StateAssignedInvalid:
91+
return "AssignedInvalid"
92+
case StateRemoved:
93+
return "Removed"
94+
default:
95+
return "Unknown"
96+
}
97+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//go:build windows
2+
3+
package vpci
4+
5+
import (
6+
"context"
7+
8+
"github.com/Microsoft/go-winio/pkg/guid"
9+
10+
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
11+
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
12+
)
13+
14+
// Device holds the configuration required to assign a vPCI device to the VM.
15+
type Device struct {
16+
// DeviceInstanceID is the host device instance path of the vPCI device.
17+
DeviceInstanceID string
18+
19+
// VirtualFunctionIndex is the SR-IOV virtual function index to assign.
20+
VirtualFunctionIndex uint16
21+
}
22+
23+
// vmVPCI manages adding and removing vPCI devices for a Utility VM.
24+
// Implemented by [vmmanager.UtilityVM].
25+
type vmVPCI interface {
26+
// AddDevice adds a vPCI device identified by `vmBusGUID` to the Utility VM with the provided settings.
27+
AddDevice(ctx context.Context, vmbusGUID guid.GUID, settings hcsschema.VirtualPciDevice) error
28+
29+
// RemoveDevice removes the vPCI device identified by `vmBusGUID` from the Utility VM.
30+
RemoveDevice(ctx context.Context, vmbusGUID guid.GUID) error
31+
}
32+
33+
// linuxGuestVPCI exposes vPCI device operations in the LCOW guest.
34+
// Implemented by [guestmanager.Guest].
35+
type linuxGuestVPCI interface {
36+
// AddVPCIDevice adds a vPCI device to the guest.
37+
AddVPCIDevice(ctx context.Context, settings guestresource.LCOWMappedVPCIDevice) error
38+
}
39+
40+
// ==============================================================================
41+
// INTERNAL DATA STRUCTURES
42+
// ==============================================================================
43+
44+
// deviceInfo records one vPCI device's assignment state and reference count.
45+
type deviceInfo struct {
46+
// device is the immutable host device identifier used to detect duplicate
47+
// assignment requests.
48+
device Device
49+
50+
// vmBusGUID identifies the vPCI device (backed by a VMBus channel)
51+
// inside the UVM.
52+
vmBusGUID guid.GUID
53+
54+
// state is the current lifecycle state of this device assignment.
55+
// Access must be guarded by [Controller.mu].
56+
state State
57+
58+
// refCount is the number of active callers sharing this device.
59+
// Access must be guarded by [Controller.mu].
60+
refCount uint32
61+
}

internal/controller/device/vpci/utils.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package vpci
44

55
import (
6+
"fmt"
67
"path/filepath"
78
"strconv"
89
)
@@ -17,6 +18,16 @@ const (
1718
DeviceIDType = "vpci-instance-id"
1819
)
1920

21+
const (
22+
// vmBusChannelTypeGUIDFormatted is the well-known channel type GUID defined by
23+
// VMBus for all assigned devices.
24+
vmBusChannelTypeGUIDFormatted = "{44c4f61d-4444-4400-9d52-802e27ede19f}"
25+
26+
// assignedDeviceEnumerator is the VMBus enumerator prefix used in device
27+
// instance IDs for assigned devices.
28+
assignedDeviceEnumerator = "VMBUS"
29+
)
30+
2031
// IsValidDeviceType returns true if the device type is valid i.e. supported by the runtime.
2132
func IsValidDeviceType(deviceType string) bool {
2233
return (deviceType == DeviceIDType) ||
@@ -30,9 +41,28 @@ func GetDeviceInfoFromPath(rawDevicePath string) (string, uint16) {
3041
indexString := filepath.Base(rawDevicePath)
3142
index, err := strconv.ParseUint(indexString, 10, 16)
3243
if err == nil {
33-
// we have a vf index
44+
// We have a VF index.
3445
return filepath.Dir(rawDevicePath), uint16(index)
3546
}
36-
// otherwise, just use default index and full device ID given
47+
// Otherwise, just use default index and the full device ID as given.
3748
return rawDevicePath, 0
3849
}
50+
51+
// GetAssignedDeviceVMBUSInstanceID returns the instance ID of the VMBus channel
52+
// device node created when a device is assigned to a UVM via vPCI.
53+
//
54+
// When a device is assigned to a UVM via vPCI support in HCS, a new VMBus channel device node is
55+
// created in the UVM. The actual device that was assigned in is exposed as a child on this VMBus
56+
// channel device node.
57+
//
58+
// A device node's instance ID is an identifier that distinguishes that device from other devices
59+
// on the system. The GUID of a VMBus channel device node refers to that channel's unique
60+
// identifier used internally by VMBus and can be used to determine the VMBus channel
61+
// device node's instance ID.
62+
//
63+
// A VMBus channel device node's instance ID is in the form:
64+
//
65+
// "VMBUS\{channelTypeGUID}\{vmBusChannelGUID}"
66+
func GetAssignedDeviceVMBUSInstanceID(vmBusChannelGUID string) string {
67+
return fmt.Sprintf("%s\\%s\\{%s}", assignedDeviceEnumerator, vmBusChannelTypeGUIDFormatted, vmBusChannelGUID)
68+
}

0 commit comments

Comments
 (0)