diff --git a/configuration.go b/configuration.go index d18c03e..3847005 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 USB controllers. 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 0000000..a803693 --- /dev/null +++ b/usb.go @@ -0,0 +1,194 @@ +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 ( + "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 + + usbControllerConfiguration() +} + +type baseUSBControllerConfiguration struct{} + +func (*baseUSBControllerConfiguration) usbControllerConfiguration() {} + +// XHCIControllerConfiguration is a configuration of the USB XHCI controller. +// +// 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 +} + +// 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 f4459b6..0eab2bd 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 071e6b0..f056c67 100644 --- a/virtualization_15.h +++ b/virtualization_15.h @@ -1,5 +1,8 @@ // // virtualization_15.h +// +// Created by codehex. +// #pragma once @@ -10,6 +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); +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 b5c942f..e169fc8 100644 --- a/virtualization_15.m +++ b/virtualization_15.m @@ -1,6 +1,8 @@ // // virtualization_15.m // +// Created by codehex. +// #import "virtualization_15.h" /*! @@ -31,3 +33,158 @@ 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(); +} + +/*! + @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