Skip to content

Commit 22ef17e

Browse files
committed
added attach/detach methods for USB storage devices
1 parent 25683bf commit 22ef17e

File tree

5 files changed

+308
-3
lines changed

5 files changed

+308
-3
lines changed

configuration.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func (v *VirtualMachineConfiguration) SetConsoleDevicesVirtualMachineConfigurati
279279
C.setConsoleDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
280280
}
281281

282-
// SetUSBControllerConfiguration sets list of network adapters. Empty by default.
282+
// SetUSBControllerConfiguration sets list of USB controllers. Empty by default.
283283
//
284284
// This is only supported on macOS 15 and newer. Older versions do nothing.
285285
func (v *VirtualMachineConfiguration) SetUSBControllersVirtualMachineConfiguration(us []USBControllerConfiguration) {

usb.go

+142-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,24 @@ package vz
77
*/
88
import "C"
99
import (
10+
"runtime/cgo"
11+
"unsafe"
12+
1013
"github.com/Code-Hex/vz/v3/internal/objc"
1114
)
1215

16+
// NewUSBMassStorageDevice initialize the runtime USB Mass Storage device object.
17+
//
18+
// This is only supported on macOS 15 and newer, error will
19+
// be returned on older versions.
20+
func NewUSBMassStorageDevice(config *USBMassStorageDeviceConfiguration) (USBDevice, error) {
21+
if err := macOSAvailable(15); err != nil {
22+
return nil, err
23+
}
24+
ptr := C.newVZUSBMassStorageDeviceWithConfiguration(objc.Ptr(config))
25+
return newUSBDevice(ptr), nil
26+
}
27+
1328
// USBControllerConfiguration for a usb controller configuration.
1429
type USBControllerConfiguration interface {
1530
objc.NSObject
@@ -23,7 +38,7 @@ func (*baseUSBControllerConfiguration) usbControllerConfiguration() {}
2338

2439
// XHCIControllerConfiguration is a configuration of the USB XHCI controller.
2540
//
26-
// This configuration creates a This configuration creates a USB XHCI controller device for the guest.
41+
// This configuration creates a USB XHCI controller device for the guest.
2742
// see: https://developer.apple.com/documentation/virtualization/vzxhcicontrollerconfiguration?language=objc
2843
type XHCIControllerConfiguration struct {
2944
*pointer
@@ -51,3 +66,129 @@ func NewXHCIControllerConfiguration() (*XHCIControllerConfiguration, error) {
5166
})
5267
return config, nil
5368
}
69+
70+
// USBController is representing a USB controller in a virtual machine.
71+
type USBController struct {
72+
dispatchQueue unsafe.Pointer
73+
*pointer
74+
}
75+
76+
func newUSBController(ptr, dispatchQueue unsafe.Pointer) *USBController {
77+
return &USBController{
78+
dispatchQueue: dispatchQueue,
79+
pointer: objc.NewPointer(ptr),
80+
}
81+
}
82+
83+
//export usbAttachDetachCompletionHandler
84+
func usbAttachDetachCompletionHandler(cgoHandleUintptr C.uintptr_t, errPtr unsafe.Pointer) {
85+
cgoHandle := cgo.Handle(cgoHandleUintptr)
86+
87+
handler := cgoHandle.Value().(func(error))
88+
89+
if err := newNSError(errPtr); err != nil {
90+
handler(err)
91+
} else {
92+
handler(nil)
93+
}
94+
}
95+
96+
// Attach attaches a USB device.
97+
//
98+
// This is only supported on macOS 15 and newer, error will
99+
// be returned on older versions.
100+
//
101+
// If the device is successfully attached to the controller, it will appear in the usbDevices property,
102+
// its usbController property will be set to point to the USB controller that it is attached to
103+
// and completion handler will return nil.
104+
// If the device was previously attached to this or another USB controller, attach function will fail
105+
// with the `vz.ErrorDeviceAlreadyAttached`. If the device cannot be initialized correctly, attach
106+
// function will fail with `vz.ErrorDeviceInitializationFailure`.
107+
func (u *USBController) Attach(device USBDevice) error {
108+
if err := macOSAvailable(15); err != nil {
109+
return err
110+
}
111+
h, errCh := makeHandler()
112+
handle := cgo.NewHandle(h)
113+
defer handle.Delete()
114+
C.attachDeviceVZUSBController(
115+
objc.Ptr(u),
116+
objc.Ptr(device),
117+
u.dispatchQueue,
118+
C.uintptr_t(handle),
119+
)
120+
return <-errCh
121+
}
122+
123+
// Detach detaches a USB device.
124+
//
125+
// This is only supported on macOS 15 and newer, error will
126+
// be returned on older versions.
127+
//
128+
// If the device is successfully detached from the controller, it will disappear from the usbDevices property,
129+
// its usbController property will be set to nil and completion handler will return nil.
130+
// If the device wasn't attached to the controller at the time of calling detach method, it will fail
131+
// with the `vz.ErrorDeviceNotFound` error.
132+
func (u *USBController) Detach(device USBDevice) error {
133+
if err := macOSAvailable(15); err != nil {
134+
return err
135+
}
136+
h, errCh := makeHandler()
137+
handle := cgo.NewHandle(h)
138+
defer handle.Delete()
139+
C.detachDeviceVZUSBController(
140+
objc.Ptr(u),
141+
objc.Ptr(device),
142+
u.dispatchQueue,
143+
C.uintptr_t(handle),
144+
)
145+
return <-errCh
146+
}
147+
148+
// USBDevices return a list of USB devices attached to controller.
149+
//
150+
// This is only supported on macOS 15 and newer, nil will
151+
// be returned on older versions.
152+
func (u *USBController) USBDevices() []USBDevice {
153+
if err := macOSAvailable(15); err != nil {
154+
return nil
155+
}
156+
nsArray := objc.NewNSArray(
157+
C.usbDevicesVZUSBController(objc.Ptr(u)),
158+
)
159+
ptrs := nsArray.ToPointerSlice()
160+
usbDevices := make([]USBDevice, len(ptrs))
161+
for i, ptr := range ptrs {
162+
usbDevices[i] = newUSBDevice(ptr)
163+
}
164+
return usbDevices
165+
}
166+
167+
// USBDevice is an interface that represents a USB device in a VM.
168+
type USBDevice interface {
169+
objc.NSObject
170+
171+
UUID() string
172+
173+
usbDevice()
174+
}
175+
176+
func newUSBDevice(ptr unsafe.Pointer) *usbDevice {
177+
return &usbDevice{
178+
pointer: objc.NewPointer(ptr),
179+
}
180+
}
181+
182+
type usbDevice struct {
183+
*pointer
184+
}
185+
186+
func (*usbDevice) usbDevice() {}
187+
188+
var _ USBDevice = (*usbDevice)(nil)
189+
190+
// UUID returns the device UUID.
191+
func (u *usbDevice) UUID() string {
192+
cs := (*char)(C.getUUIDUSBDevice(objc.Ptr(u)))
193+
return cs.String()
194+
}

virtualization.go

+21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package vz
66
# include "virtualization_11.h"
77
# include "virtualization_12.h"
88
# include "virtualization_13.h"
9+
# include "virtualization_15.h"
910
*/
1011
import "C"
1112
import (
@@ -186,6 +187,26 @@ func (v *VirtualMachine) SocketDevices() []*VirtioSocketDevice {
186187
return socketDevices
187188
}
188189

190+
// USBControllers return the list of USB controllers configured on this virtual machine. Return an empty array if no USB controller is configured.
191+
//
192+
// This is only supported on macOS 15 and newer, nil will
193+
// be returned on older versions.
194+
func (v *VirtualMachine) USBControllers() []*USBController {
195+
if err := macOSAvailable(15); err != nil {
196+
return nil
197+
}
198+
nsArray := objc.NewNSArray(
199+
C.VZVirtualMachine_usbControllers(objc.Ptr(v)),
200+
)
201+
ptrs := nsArray.ToPointerSlice()
202+
usbControllers := make([]*USBController, len(ptrs))
203+
for i, ptr := range ptrs {
204+
usbControllers[i] = newUSBController(ptr, v.dispatchQueue)
205+
}
206+
return usbControllers
207+
}
208+
209+
189210
//export changeStateOnObserver
190211
func changeStateOnObserver(newStateRaw C.int, cgoHandleUintptr C.uintptr_t) {
191212
stateHandle := cgo.Handle(cgoHandleUintptr)

virtualization_15.h

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//
22
// virtualization_15.h
3+
//
4+
// Created by codehex.
5+
//
36

47
#pragma once
58

@@ -10,8 +13,17 @@
1013
#import "virtualization_helper.h"
1114
#import <Virtualization/Virtualization.h>
1215

16+
/* exported from cgo */
17+
void usbAttachDetachCompletionHandler(uintptr_t cgoHandle, void *errPtr);
18+
1319
/* macOS 15 API */
1420
bool isNestedVirtualizationSupported();
1521
void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabled);
1622
void *newVZXHCIControllerConfiguration();
17-
void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers);
23+
void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers);
24+
const char *getUUIDUSBDevice(void *usbDevice);
25+
void *usbDevicesVZUSBController(void *usbController);
26+
void *VZVirtualMachine_usbControllers(void *machine);
27+
void attachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle);
28+
void detachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle);
29+
void *newVZUSBMassStorageDeviceWithConfiguration(void *config);

virtualization_15.m

+131
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//
22
// virtualization_15.m
33
//
4+
// Created by codehex.
5+
//
46
#import "virtualization_15.h"
57

68
/*!
@@ -57,3 +59,132 @@ void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbContr
5759
#endif
5860
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
5961
}
62+
63+
/*!
64+
@abstract Device UUID.
65+
@discussion
66+
Device UUID from device configuration objects that conform to `VZUSBDeviceConfiguration`.
67+
@see VZUSBDeviceConfiguration
68+
*/
69+
const char *getUUIDUSBDevice(void *usbDevice)
70+
{
71+
#ifdef INCLUDE_TARGET_OSX_15
72+
if (@available(macOS 15, *)) {
73+
NSString *uuid = [[(id<VZUSBDevice>)usbDevice uuid] UUIDString];
74+
return [uuid UTF8String];
75+
}
76+
#endif
77+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
78+
}
79+
80+
/*!
81+
@abstract Return a list of USB devices attached to controller.
82+
@discussion
83+
If corresponding USB controller configuration included in VZVirtualMachineConfiguration contained any USB devices,
84+
those devices will appear here when virtual machine is started.
85+
@see VZUSBDevice
86+
@see VZUSBDeviceConfiguration
87+
@see VZUSBControllerConfiguration
88+
@see VZVirtualMachineConfiguration
89+
*/
90+
void *usbDevicesVZUSBController(void *usbController)
91+
{
92+
#ifdef INCLUDE_TARGET_OSX_15
93+
if (@available(macOS 15, *)) {
94+
return [(VZUSBController *)usbController usbDevices];
95+
}
96+
#endif
97+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
98+
}
99+
100+
/*!
101+
@abstract Return the list of USB controllers configured on this virtual machine. Return an empty array if no USB controller is configured.
102+
@see VZUSBControllerConfiguration
103+
@see VZVirtualMachineConfiguration
104+
*/
105+
void *VZVirtualMachine_usbControllers(void *machine)
106+
{
107+
#ifdef INCLUDE_TARGET_OSX_15
108+
if (@available(macOS 15, *)) {
109+
return [(VZVirtualMachine *)machine usbControllers]; // NSArray<VZUSBController *>
110+
}
111+
#endif
112+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
113+
}
114+
115+
/*!
116+
@abstract Attach a USB device.
117+
@discussion
118+
If the device is successfully attached to the controller, it will appear in the usbDevices property,
119+
its usbController property will be set to point to the USB controller that it is attached to
120+
and completion handler will return nil.
121+
If the device was previously attached to this or another USB controller, attach function will fail
122+
with the `VZErrorDeviceAlreadyAttached`. If the device cannot be initialized correctly, attach
123+
function will fail with `VZErrorDeviceInitializationFailure`.
124+
This method must be called on the virtual machine's queue.
125+
@param device USB device to attach.
126+
@param completionHandler Block called after the device has been attached or on error.
127+
The error parameter passed to the block is nil if the attach was successful.
128+
It will be also invoked on an virtual machine's queue.
129+
@see VZUSBDevice
130+
*/
131+
void attachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle)
132+
{
133+
#ifdef INCLUDE_TARGET_OSX_15
134+
if (@available(macOS 15, *)) {
135+
dispatch_sync((dispatch_queue_t)queue, ^{
136+
[(VZUSBController *)usbController attachDevice:(id<VZUSBDevice>)usbDevice
137+
completionHandler:^(NSError *error) {
138+
usbAttachDetachCompletionHandler(cgoHandle, error);
139+
}];
140+
});
141+
return;
142+
}
143+
#endif
144+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
145+
}
146+
147+
/*!
148+
@abstract Detach a USB device.
149+
@discussion
150+
If the device is successfully detached from the controller, it will disappear from the usbDevices property,
151+
its usbController property will be set to nil and completion handler will return nil.
152+
If the device wasn't attached to the controller at the time of calling detach method, it will fail
153+
with the `VZErrorDeviceNotFound` error.
154+
This method must be called on the virtual machine's queue.
155+
@param device USB device to detach.
156+
@param completionHandler Block called after the device has been detached or on error.
157+
The error parameter passed to the block is nil if the detach was successful.
158+
It will be also invoked on an virtual machine's queue.
159+
@see VZUSBDevice
160+
*/
161+
void detachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle)
162+
{
163+
#ifdef INCLUDE_TARGET_OSX_15
164+
if (@available(macOS 15, *)) {
165+
dispatch_sync((dispatch_queue_t)queue, ^{
166+
[(VZUSBController *)usbController detachDevice:(id<VZUSBDevice>)usbDevice
167+
completionHandler:^(NSError *error) {
168+
usbAttachDetachCompletionHandler(cgoHandle, error);
169+
}];
170+
});
171+
return;
172+
}
173+
#endif
174+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
175+
}
176+
177+
/*!
178+
@abstract Initialize the runtime USB Mass Storage device object.
179+
@param configuration The configuration of the USB Mass Storage device.
180+
@see VZUSBMassStorageDeviceConfiguration
181+
*/
182+
void *newVZUSBMassStorageDeviceWithConfiguration(void *config)
183+
{
184+
#ifdef INCLUDE_TARGET_OSX_15
185+
if (@available(macOS 15, *)) {
186+
return [[VZUSBMassStorageDevice alloc] initWithConfiguration:(VZUSBMassStorageDeviceConfiguration *)config];
187+
}
188+
#endif
189+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
190+
}

0 commit comments

Comments
 (0)