From 25683bf65aa4cc6fd858eccdc28ddfb2e0e1922e Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Sun, 24 Nov 2024 11:16:36 +0900 Subject: [PATCH 1/2] initial commit for usb configuration --- configuration.go | 24 ++++++++++++++++++++ usb.go | 53 +++++++++++++++++++++++++++++++++++++++++++++ virtualization_15.h | 2 ++ virtualization_15.m | 26 ++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 usb.go diff --git a/configuration.go b/configuration.go index d18c03e1..1a8bfad1 100644 --- a/configuration.go +++ b/configuration.go @@ -6,6 +6,7 @@ package vz # include "virtualization_11.h" # include "virtualization_12.h" # include "virtualization_13.h" +# include "virtualization_15.h" */ import "C" import ( @@ -40,6 +41,7 @@ type VirtualMachineConfiguration struct { networkDeviceConfiguration []*VirtioNetworkDeviceConfiguration storageDeviceConfiguration []StorageDeviceConfiguration + usbControllerConfiguration []USBControllerConfiguration } // NewVirtualMachineConfiguration creates a new configuration. @@ -277,6 +279,28 @@ func (v *VirtualMachineConfiguration) SetConsoleDevicesVirtualMachineConfigurati C.setConsoleDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array)) } +// SetUSBControllerConfiguration sets list of network adapters. Empty by default. +// +// This is only supported on macOS 15 and newer. Older versions do nothing. +func (v *VirtualMachineConfiguration) SetUSBControllersVirtualMachineConfiguration(us []USBControllerConfiguration) { + if err := macOSAvailable(15); err != nil { + return + } + ptrs := make([]objc.NSObject, len(us)) + for i, val := range us { + ptrs[i] = val + } + array := objc.ConvertToNSMutableArray(ptrs) + C.setUSBControllersVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array)) + v.usbControllerConfiguration = us +} + +// USBControllers return the list of usb controller configuration configured in this virtual machine configuration. +// Return an empty array if no usb controller configuration is set. +func (v *VirtualMachineConfiguration) USBControllers() []USBControllerConfiguration { + return v.usbControllerConfiguration +} + // VirtualMachineConfigurationMinimumAllowedMemorySize returns minimum // amount of memory required by virtual machines. func VirtualMachineConfigurationMinimumAllowedMemorySize() uint64 { diff --git a/usb.go b/usb.go new file mode 100644 index 00000000..af7b77f3 --- /dev/null +++ b/usb.go @@ -0,0 +1,53 @@ +package vz + +/* +#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc +#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework Virtualization +# include "virtualization_15.h" +*/ +import "C" +import ( + "github.com/Code-Hex/vz/v3/internal/objc" +) + +// USBControllerConfiguration for a usb controller configuration. +type USBControllerConfiguration interface { + objc.NSObject + + usbControllerConfiguration() +} + +type baseUSBControllerConfiguration struct{} + +func (*baseUSBControllerConfiguration) usbControllerConfiguration() {} + +// XHCIControllerConfiguration is a configuration of the USB XHCI controller. +// +// This configuration creates a This configuration creates a USB XHCI controller device for the guest. +// see: https://developer.apple.com/documentation/virtualization/vzxhcicontrollerconfiguration?language=objc +type XHCIControllerConfiguration struct { + *pointer + + *baseUSBControllerConfiguration +} + +var _ USBControllerConfiguration = (*XHCIControllerConfiguration)(nil) + +// NewXHCIControllerConfiguration creates a new XHCIControllerConfiguration. +// +// This is only supported on macOS 15 and newer, error will +// be returned on older versions. +func NewXHCIControllerConfiguration() (*XHCIControllerConfiguration, error) { + if err := macOSAvailable(15); err != nil { + return nil, err + } + + config := &XHCIControllerConfiguration{ + pointer: objc.NewPointer(C.newVZXHCIControllerConfiguration()), + } + + objc.SetFinalizer(config, func(self *XHCIControllerConfiguration) { + objc.Release(self) + }) + return config, nil +} diff --git a/virtualization_15.h b/virtualization_15.h index 071e6b04..67291ea3 100644 --- a/virtualization_15.h +++ b/virtualization_15.h @@ -13,3 +13,5 @@ /* macOS 15 API */ bool isNestedVirtualizationSupported(); void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabled); +void *newVZXHCIControllerConfiguration(); +void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers); \ No newline at end of file diff --git a/virtualization_15.m b/virtualization_15.m index b5c942f1..88bf37d9 100644 --- a/virtualization_15.m +++ b/virtualization_15.m @@ -31,3 +31,29 @@ void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabl #endif RAISE_UNSUPPORTED_MACOS_EXCEPTION(); } + +/*! + @abstract Configuration for the USB XHCI controller. + @discussion This configuration creates a USB XHCI controller device for the guest. + */ +void *newVZXHCIControllerConfiguration() +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + return [[VZXHCIControllerConfiguration alloc] init]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + [(VZVirtualMachineConfiguration *)config + setUsbControllers:[(NSMutableArray *)usbControllers copy]]; + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} From 22ef17ec00c63c47d0d289cf5c8b7da8cec403f1 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Sun, 5 Jan 2025 16:12:06 +0900 Subject: [PATCH 2/2] added attach/detach methods for USB storage devices --- configuration.go | 2 +- usb.go | 143 +++++++++++++++++++++++++++++++++++++++++++- virtualization.go | 21 +++++++ virtualization_15.h | 14 ++++- virtualization_15.m | 131 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 308 insertions(+), 3 deletions(-) diff --git a/configuration.go b/configuration.go index 1a8bfad1..38470058 100644 --- a/configuration.go +++ b/configuration.go @@ -279,7 +279,7 @@ func (v *VirtualMachineConfiguration) SetConsoleDevicesVirtualMachineConfigurati C.setConsoleDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array)) } -// SetUSBControllerConfiguration sets list of network adapters. Empty by default. +// SetUSBControllerConfiguration sets list of USB controllers. Empty by default. // // This is only supported on macOS 15 and newer. Older versions do nothing. func (v *VirtualMachineConfiguration) SetUSBControllersVirtualMachineConfiguration(us []USBControllerConfiguration) { diff --git a/usb.go b/usb.go index af7b77f3..a8036934 100644 --- a/usb.go +++ b/usb.go @@ -7,9 +7,24 @@ package vz */ import "C" import ( + "runtime/cgo" + "unsafe" + "github.com/Code-Hex/vz/v3/internal/objc" ) +// NewUSBMassStorageDevice initialize the runtime USB Mass Storage device object. +// +// This is only supported on macOS 15 and newer, error will +// be returned on older versions. +func NewUSBMassStorageDevice(config *USBMassStorageDeviceConfiguration) (USBDevice, error) { + if err := macOSAvailable(15); err != nil { + return nil, err + } + ptr := C.newVZUSBMassStorageDeviceWithConfiguration(objc.Ptr(config)) + return newUSBDevice(ptr), nil +} + // USBControllerConfiguration for a usb controller configuration. type USBControllerConfiguration interface { objc.NSObject @@ -23,7 +38,7 @@ func (*baseUSBControllerConfiguration) usbControllerConfiguration() {} // XHCIControllerConfiguration is a configuration of the USB XHCI controller. // -// This configuration creates a This configuration creates a USB XHCI controller device for the guest. +// This configuration creates a USB XHCI controller device for the guest. // see: https://developer.apple.com/documentation/virtualization/vzxhcicontrollerconfiguration?language=objc type XHCIControllerConfiguration struct { *pointer @@ -51,3 +66,129 @@ func NewXHCIControllerConfiguration() (*XHCIControllerConfiguration, error) { }) return config, nil } + +// USBController is representing a USB controller in a virtual machine. +type USBController struct { + dispatchQueue unsafe.Pointer + *pointer +} + +func newUSBController(ptr, dispatchQueue unsafe.Pointer) *USBController { + return &USBController{ + dispatchQueue: dispatchQueue, + pointer: objc.NewPointer(ptr), + } +} + +//export usbAttachDetachCompletionHandler +func usbAttachDetachCompletionHandler(cgoHandleUintptr C.uintptr_t, errPtr unsafe.Pointer) { + cgoHandle := cgo.Handle(cgoHandleUintptr) + + handler := cgoHandle.Value().(func(error)) + + if err := newNSError(errPtr); err != nil { + handler(err) + } else { + handler(nil) + } +} + +// Attach attaches a USB device. +// +// This is only supported on macOS 15 and newer, error will +// be returned on older versions. +// +// If the device is successfully attached to the controller, it will appear in the usbDevices property, +// its usbController property will be set to point to the USB controller that it is attached to +// and completion handler will return nil. +// If the device was previously attached to this or another USB controller, attach function will fail +// with the `vz.ErrorDeviceAlreadyAttached`. If the device cannot be initialized correctly, attach +// function will fail with `vz.ErrorDeviceInitializationFailure`. +func (u *USBController) Attach(device USBDevice) error { + if err := macOSAvailable(15); err != nil { + return err + } + h, errCh := makeHandler() + handle := cgo.NewHandle(h) + defer handle.Delete() + C.attachDeviceVZUSBController( + objc.Ptr(u), + objc.Ptr(device), + u.dispatchQueue, + C.uintptr_t(handle), + ) + return <-errCh +} + +// Detach detaches a USB device. +// +// This is only supported on macOS 15 and newer, error will +// be returned on older versions. +// +// If the device is successfully detached from the controller, it will disappear from the usbDevices property, +// its usbController property will be set to nil and completion handler will return nil. +// If the device wasn't attached to the controller at the time of calling detach method, it will fail +// with the `vz.ErrorDeviceNotFound` error. +func (u *USBController) Detach(device USBDevice) error { + if err := macOSAvailable(15); err != nil { + return err + } + h, errCh := makeHandler() + handle := cgo.NewHandle(h) + defer handle.Delete() + C.detachDeviceVZUSBController( + objc.Ptr(u), + objc.Ptr(device), + u.dispatchQueue, + C.uintptr_t(handle), + ) + return <-errCh +} + +// USBDevices return a list of USB devices attached to controller. +// +// This is only supported on macOS 15 and newer, nil will +// be returned on older versions. +func (u *USBController) USBDevices() []USBDevice { + if err := macOSAvailable(15); err != nil { + return nil + } + nsArray := objc.NewNSArray( + C.usbDevicesVZUSBController(objc.Ptr(u)), + ) + ptrs := nsArray.ToPointerSlice() + usbDevices := make([]USBDevice, len(ptrs)) + for i, ptr := range ptrs { + usbDevices[i] = newUSBDevice(ptr) + } + return usbDevices +} + +// USBDevice is an interface that represents a USB device in a VM. +type USBDevice interface { + objc.NSObject + + UUID() string + + usbDevice() +} + +func newUSBDevice(ptr unsafe.Pointer) *usbDevice { + return &usbDevice{ + pointer: objc.NewPointer(ptr), + } +} + +type usbDevice struct { + *pointer +} + +func (*usbDevice) usbDevice() {} + +var _ USBDevice = (*usbDevice)(nil) + +// UUID returns the device UUID. +func (u *usbDevice) UUID() string { + cs := (*char)(C.getUUIDUSBDevice(objc.Ptr(u))) + return cs.String() +} diff --git a/virtualization.go b/virtualization.go index f4459b69..0eab2bd9 100644 --- a/virtualization.go +++ b/virtualization.go @@ -6,6 +6,7 @@ package vz # include "virtualization_11.h" # include "virtualization_12.h" # include "virtualization_13.h" +# include "virtualization_15.h" */ import "C" import ( @@ -186,6 +187,26 @@ func (v *VirtualMachine) SocketDevices() []*VirtioSocketDevice { return socketDevices } +// USBControllers return the list of USB controllers configured on this virtual machine. Return an empty array if no USB controller is configured. +// +// This is only supported on macOS 15 and newer, nil will +// be returned on older versions. +func (v *VirtualMachine) USBControllers() []*USBController { + if err := macOSAvailable(15); err != nil { + return nil + } + nsArray := objc.NewNSArray( + C.VZVirtualMachine_usbControllers(objc.Ptr(v)), + ) + ptrs := nsArray.ToPointerSlice() + usbControllers := make([]*USBController, len(ptrs)) + for i, ptr := range ptrs { + usbControllers[i] = newUSBController(ptr, v.dispatchQueue) + } + return usbControllers +} + + //export changeStateOnObserver func changeStateOnObserver(newStateRaw C.int, cgoHandleUintptr C.uintptr_t) { stateHandle := cgo.Handle(cgoHandleUintptr) diff --git a/virtualization_15.h b/virtualization_15.h index 67291ea3..f056c671 100644 --- a/virtualization_15.h +++ b/virtualization_15.h @@ -1,5 +1,8 @@ // // virtualization_15.h +// +// Created by codehex. +// #pragma once @@ -10,8 +13,17 @@ #import "virtualization_helper.h" #import +/* exported from cgo */ +void usbAttachDetachCompletionHandler(uintptr_t cgoHandle, void *errPtr); + /* macOS 15 API */ bool isNestedVirtualizationSupported(); void setNestedVirtualizationEnabled(void *config, bool nestedVirtualizationEnabled); void *newVZXHCIControllerConfiguration(); -void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers); \ No newline at end of file +void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbControllers); +const char *getUUIDUSBDevice(void *usbDevice); +void *usbDevicesVZUSBController(void *usbController); +void *VZVirtualMachine_usbControllers(void *machine); +void attachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle); +void detachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle); +void *newVZUSBMassStorageDeviceWithConfiguration(void *config); \ No newline at end of file diff --git a/virtualization_15.m b/virtualization_15.m index 88bf37d9..e169fc89 100644 --- a/virtualization_15.m +++ b/virtualization_15.m @@ -1,6 +1,8 @@ // // virtualization_15.m // +// Created by codehex. +// #import "virtualization_15.h" /*! @@ -57,3 +59,132 @@ void setUSBControllersVZVirtualMachineConfiguration(void *config, void *usbContr #endif RAISE_UNSUPPORTED_MACOS_EXCEPTION(); } + +/*! + @abstract Device UUID. + @discussion + Device UUID from device configuration objects that conform to `VZUSBDeviceConfiguration`. + @see VZUSBDeviceConfiguration + */ +const char *getUUIDUSBDevice(void *usbDevice) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + NSString *uuid = [[(id)usbDevice uuid] UUIDString]; + return [uuid UTF8String]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract Return a list of USB devices attached to controller. + @discussion + If corresponding USB controller configuration included in VZVirtualMachineConfiguration contained any USB devices, + those devices will appear here when virtual machine is started. + @see VZUSBDevice + @see VZUSBDeviceConfiguration + @see VZUSBControllerConfiguration + @see VZVirtualMachineConfiguration + */ +void *usbDevicesVZUSBController(void *usbController) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + return [(VZUSBController *)usbController usbDevices]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract Return the list of USB controllers configured on this virtual machine. Return an empty array if no USB controller is configured. + @see VZUSBControllerConfiguration + @see VZVirtualMachineConfiguration + */ +void *VZVirtualMachine_usbControllers(void *machine) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + return [(VZVirtualMachine *)machine usbControllers]; // NSArray + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract Attach a USB device. + @discussion + If the device is successfully attached to the controller, it will appear in the usbDevices property, + its usbController property will be set to point to the USB controller that it is attached to + and completion handler will return nil. + If the device was previously attached to this or another USB controller, attach function will fail + with the `VZErrorDeviceAlreadyAttached`. If the device cannot be initialized correctly, attach + function will fail with `VZErrorDeviceInitializationFailure`. + This method must be called on the virtual machine's queue. + @param device USB device to attach. + @param completionHandler Block called after the device has been attached or on error. + The error parameter passed to the block is nil if the attach was successful. + It will be also invoked on an virtual machine's queue. + @see VZUSBDevice + */ +void attachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + dispatch_sync((dispatch_queue_t)queue, ^{ + [(VZUSBController *)usbController attachDevice:(id)usbDevice + completionHandler:^(NSError *error) { + usbAttachDetachCompletionHandler(cgoHandle, error); + }]; + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract Detach a USB device. + @discussion + If the device is successfully detached from the controller, it will disappear from the usbDevices property, + its usbController property will be set to nil and completion handler will return nil. + If the device wasn't attached to the controller at the time of calling detach method, it will fail + with the `VZErrorDeviceNotFound` error. + This method must be called on the virtual machine's queue. + @param device USB device to detach. + @param completionHandler Block called after the device has been detached or on error. + The error parameter passed to the block is nil if the detach was successful. + It will be also invoked on an virtual machine's queue. + @see VZUSBDevice + */ +void detachDeviceVZUSBController(void *usbController, void *usbDevice, void *queue, uintptr_t cgoHandle) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + dispatch_sync((dispatch_queue_t)queue, ^{ + [(VZUSBController *)usbController detachDevice:(id)usbDevice + completionHandler:^(NSError *error) { + usbAttachDetachCompletionHandler(cgoHandle, error); + }]; + }); + return; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} + +/*! + @abstract Initialize the runtime USB Mass Storage device object. + @param configuration The configuration of the USB Mass Storage device. + @see VZUSBMassStorageDeviceConfiguration + */ +void *newVZUSBMassStorageDeviceWithConfiguration(void *config) +{ +#ifdef INCLUDE_TARGET_OSX_15 + if (@available(macOS 15, *)) { + return [[VZUSBMassStorageDevice alloc] initWithConfiguration:(VZUSBMassStorageDeviceConfiguration *)config]; + } +#endif + RAISE_UNSUPPORTED_MACOS_EXCEPTION(); +} \ No newline at end of file