diff --git a/nx/include/switch/runtime/devices/usb_comms.h b/nx/include/switch/runtime/devices/usb_comms.h index 5c11d8d3..593c724b 100644 --- a/nx/include/switch/runtime/devices/usb_comms.h +++ b/nx/include/switch/runtime/devices/usb_comms.h @@ -8,16 +8,14 @@ #pragma once #include "../../types.h" +/// Initializes usbComms with the default number of interfaces (1) Result usbCommsInitialize(void); +/// Initializes usbComms with a specific number of interfaces. +Result usbCommsInitializeEx(u32 num_interfaces); + +/// Exits usbComms. void usbCommsExit(void); -/// Same as usbCommsInitialize, except this can be used after usbCommsInitialize (or instead of usbCommsInitialize), for creating new interface(s). -/// bInterface* are the values for the same fields in usb.h \ref usb_interface_descriptor. \ref usbCommsInitialize uses USB_CLASS_VENDOR_SPEC for all of these internally. -Result usbCommsInitializeEx(u32 *interface, u8 bInterfaceClass, u8 bInterfaceSubClass, u8 bInterfaceProtocol); - -/// Shutdown the specified interface. If no interfaces are remaining, this then uses \ref usbCommsExit internally. -void usbCommsExitEx(u32 interface); - /// Read data with the default interface. size_t usbCommsRead(void* buffer, size_t size); diff --git a/nx/include/switch/services/usb.h b/nx/include/switch/services/usb.h index 80e4b8d5..f58e8db4 100644 --- a/nx/include/switch/services/usb.h +++ b/nx/include/switch/services/usb.h @@ -1,12 +1,13 @@ /** * @file usb.h * @brief USB (usb:*) service IPC wrapper. - * @author yellows8 + * @author SciresM, yellows8 * @copyright libnx Authors */ #pragma once #include "../types.h" #include "../services/sm.h" +#include "../kernel/event.h" /// usb:ds Switch-as-device<>host USB comms, see also here: http://switchbrew.org/index.php?title=USB_services @@ -18,6 +19,8 @@ /* Descriptor sizes per descriptor type */ #define USB_DT_INTERFACE_SIZE 9 #define USB_DT_ENDPOINT_SIZE 7 +#define USB_DT_DEVICE_SIZE 0x12 +#define USB_DT_SS_ENDPOINT_COMPANION_SIZE 6 /// Imported from libusb, with some adjustments. struct usb_endpoint_descriptor { @@ -35,13 +38,47 @@ struct usb_interface_descriptor { uint8_t bDescriptorType; /// Must match USB_DT_INTERFACE. uint8_t bInterfaceNumber; /// See also USBDS_DEFAULT_InterfaceNumber. uint8_t bAlternateSetting; /// Must match 0. - uint8_t bNumEndpoints; /// Ignored. + uint8_t bNumEndpoints; uint8_t bInterfaceClass; uint8_t bInterfaceSubClass; uint8_t bInterfaceProtocol; uint8_t iInterface; /// Ignored. }; +/// Imported from libusb, with some adjustments. +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /// Must match USB_DT_Device. + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +}; + +/// Imported from libusb, with some adjustments. +struct usb_ss_endpoint_companion_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /// Must match USB_DT_SS_ENDPOINT_COMPANION. + uint8_t bMaxBurst; + uint8_t bmAttributes; + uint16_t wBytesPerInterval; +}; + +/// Imported from libusb, with some adjustments. +struct usb_string_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; /// Must match USB_DT_STRING. + uint16_t wData[0x40]; +}; + typedef struct { u16 idVendor; /// VID u16 idProduct; /// PID @@ -68,21 +105,27 @@ typedef struct { u32 interface_index; Service h; - Handle SetupEvent; - Handle CtrlInCompletionEvent; - Handle CtrlOutCompletionEvent; + Event SetupEvent; + Event CtrlInCompletionEvent; + Event CtrlOutCompletionEvent; } UsbDsInterface; typedef struct { bool initialized; Service h; - Handle CompletionEvent; + Event CompletionEvent; } UsbDsEndpoint; typedef enum { UsbComplexId_Default = 0x2 } UsbComplexId; +typedef enum { + UsbDeviceSpeed_Full = 0x2, + UsbDeviceSpeed_High = 0x3, + UsbDeviceSpeed_Super = 0x4, +} UsbDeviceSpeed; + /// Imported from libusb, with changed names. enum usb_class_code { USB_CLASS_PER_INTERFACE = 0, @@ -153,38 +196,63 @@ enum usb_iso_usage_type { USB_ISO_USAGE_TYPE_IMPLICIT = 2, }; -Result usbDsInitialize(UsbComplexId complexId, const UsbDsDeviceInfo* deviceinfo); - -/// Exit usbDs. Any interfaces/endpoints which are left open are automatically closed, since otherwise usb-sysmodule won't fully reset usbds to defaults. +/// Opens a session with usb:ds. +Result usbDsInitialize(void); +/// Closes the usb:ds session. Any interfaces/endpoints which are left open are automatically closed, since otherwise usb-sysmodule won't fully reset usb:ds to defaults. void usbDsExit(void); -Service* usbDsGetServiceSession(void); -Handle usbDsGetStateChangeEvent(void); - -Result usbDsGetState(u32 *out); -Result usbDsGetDsInterface(UsbDsInterface** interface, struct usb_interface_descriptor* descriptor, const char *interface_name); - -/// Wait for initialization to finish where data-transfer is usable. -Result usbDsWaitReady(void); - -/// Parse usbDsReportData from the Get*ReportData commands, where urbId is from the post-buffer commands. Will return the converted urb_status result-value. +/// Helpers +Result usbDsWaitReady(u64 timeout); Result usbDsParseReportData(UsbDsReportData *reportdata, u32 urbId, u32 *requestedSize, u32 *transferredSize); +/// IDsService +// Do not provide API access to these functions, as they're handled by usbDsInitialize(). +// Result usbDsBindDevice(UsbComplexId complexId); +// Result usbDsBindClientProcess(Handle prochandle); +Event* usbDsGetStateChangeEvent(void); +Result usbDsGetState(u32* out); + +/// Removed in 5.0.0 +Result usbDsGetDsInterface(UsbDsInterface** out, struct usb_interface_descriptor* descriptor, const char* interface_name); +Result usbDsSetVidPidBcd(const UsbDsDeviceInfo* deviceinfo); + +/// Added in 5.0.0 +Result usbDsRegisterInterface(UsbDsInterface** out, u32 intf_num); +Result usbDsClearDeviceData(void); +Result usbDsAddUsbStringDescriptor(u8* out_index, const char* string); +Result usbDsAddUsbLanguageStringDescriptor(u8* out_index, const u16* lang_ids, u16 num_langs); +Result usbDsDeleteUsbStringDescriptor(u8 index); +Result usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed speed, struct usb_device_descriptor* descriptor); +Result usbDsSetBinaryObjectStore(void* bos, size_t bos_size); +Result usbDsEnable(void); +Result usbDsDisable(void); + /// IDsInterface void usbDsInterface_Close(UsbDsInterface* interface); -Result usbDsInterface_GetDsEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, struct usb_endpoint_descriptor* descriptor); + +Result usbDsInterface_GetSetupPacket(UsbDsInterface* interface, void* buffer, size_t size); Result usbDsInterface_EnableInterface(UsbDsInterface* interface); Result usbDsInterface_DisableInterface(UsbDsInterface* interface); -Result usbDsInterface_CtrlInPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32 *urbId); -Result usbDsInterface_CtrlOutPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32 *urbId); -Result usbDsInterface_GetCtrlInReportData(UsbDsInterface* interface, UsbDsReportData *out); -Result usbDsInterface_GetCtrlOutReportData(UsbDsInterface* interface, UsbDsReportData *out); +Result usbDsInterface_CtrlInPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32* urbId); +Result usbDsInterface_CtrlOutPostBufferAsync(UsbDsInterface* interface, void* buffer, size_t size, u32* urbId); +Result usbDsInterface_GetCtrlInReportData(UsbDsInterface* interface, UsbDsReportData* out); +Result usbDsInterface_GetCtrlOutReportData(UsbDsInterface* interface, UsbDsReportData* out); Result usbDsInterface_StallCtrl(UsbDsInterface* interface); +/// Removed in 5.0.0 +Result usbDsInterface_GetDsEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, struct usb_endpoint_descriptor* descriptor); + +/// Added in 5.0.0 +Result usbDsInterface_RegisterEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, u8 endpoint_address); +Result usbDsInterface_AppendConfigurationData(UsbDsInterface* interface, UsbDeviceSpeed speed, void* buffer, size_t size); + + /// IDsEndpoint - void usbDsEndpoint_Close(UsbDsEndpoint* endpoint); -Result usbDsEndpoint_PostBufferAsync(UsbDsEndpoint* endpoint, void* buffer, size_t size, u32 *urbId); -Result usbDsEndpoint_GetReportData(UsbDsEndpoint* endpoint, UsbDsReportData *out); -Result usbDsEndpoint_StallCtrl(UsbDsEndpoint* endpoint); + +Result usbDsEndpoint_Cancel(UsbDsEndpoint* endpoint); +Result usbDsEndpoint_PostBufferAsync(UsbDsEndpoint* endpoint, void* buffer, size_t size, u32* urbId); +Result usbDsEndpoint_GetReportData(UsbDsEndpoint* endpoint, UsbDsReportData* out); +Result usbDsEndpoint_StallCtrl(UsbDsEndpoint* endpoint); +Result usbDsEndpoint_SetZlt(UsbDsEndpoint* endpoint, bool zlt); diff --git a/nx/source/runtime/devices/usb_comms.c b/nx/source/runtime/devices/usb_comms.c index 8cfa3820..47839afb 100644 --- a/nx/source/runtime/devices/usb_comms.c +++ b/nx/source/runtime/devices/usb_comms.c @@ -2,6 +2,7 @@ #include #include "types.h" #include "result.h" +#include "kernel/detect.h" #include "kernel/rwlock.h" #include "services/fatal.h" #include "services/usb.h" @@ -25,69 +26,127 @@ static usbCommsInterface g_usbCommsInterfaces[TOTAL_INTERFACES]; static RwLock g_usbCommsLock; -static Result _usbCommsInterfaceInit(usbCommsInterface *interface, u8 bInterfaceClass, u8 bInterfaceSubClass, u8 bInterfaceProtocol); +static Result _usbCommsInterfaceInit1x(u32 intf_ind); +static Result _usbCommsInterfaceInit5x(u32 intf_ind); +static Result _usbCommsInterfaceInit(u32 intf_ind); static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, size_t size, size_t *transferredSize); -Result usbCommsInitializeEx(u32 *interface, u8 bInterfaceClass, u8 bInterfaceSubClass, u8 bInterfaceProtocol) +Result usbCommsInitializeEx(u32 num_interfaces) { - bool found=0; - usbCommsInterface *inter = NULL; - + Result rc = 0; rwlockWriteLock(&g_usbCommsLock); - - if (g_usbCommsInitialized && interface==NULL) { - rwlockWriteUnlock(&g_usbCommsLock); - return 0; - } - - Result rc=0; - u32 i = 0; - - if (!g_usbCommsInitialized) rc = usbDsInitialize(UsbComplexId_Default, NULL); - - if (R_SUCCEEDED(rc)) { - for(i=0; ilock); - if (!inter->initialized) found=1; - rwlockReadUnlock(&inter->lock); - - if (found) break; + + if (g_usbCommsInitialized) { + rc = MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + } else if (num_interfaces > TOTAL_INTERFACES) { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } else { + rc = usbDsInitialize(); + + if (R_SUCCEEDED(rc)) { + + if (kernelAbove500()) { + u8 iManufacturer, iProduct, iSerialNumber; + static const u16 supported_langs[1] = {0x0409}; + // Send language descriptor + rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16)); + // Send manufacturer + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Switchbrew"); + // Send product + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "libnx USB comms"); + // Send serial number + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber"); + + // Send device descriptors + struct usb_device_descriptor device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057e, + .idProduct = 0x3000, + .bcdDevice = 0x0100, + .iManufacturer = iManufacturer, + .iProduct = iProduct, + .iSerialNumber = iSerialNumber, + .bNumConfigurations = 0x01 + }; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); + + // High Speed is USB 2.0 + device_descriptor.bcdUSB = 0x0200; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); + + // Super Speed is USB 3.0 + device_descriptor.bcdUSB = 0x0300; + // Upgrade packet size to 512 + device_descriptor.bMaxPacketSize0 = 0x09; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); + + // Define Binary Object Store + u8 bos[0x16] = { + 0x05, // .bLength + USB_DT_BOS, // .bDescriptorType + 0x16, 0x00, // .wTotalLength + 0x02, // .bNumDeviceCaps + + // USB 2.0 + 0x07, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x02, // .bDevCapabilityType + 0x02, 0x00, 0x00, 0x00, // dev_capability_data + + // USB 3.0 + 0x0A, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x03, // .bDevCapabilityType + 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00 + }; + if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos)); + } + + if (R_SUCCEEDED(rc)) { + for (u32 i = 0; i < num_interfaces; i++) { + usbCommsInterface *intf = &g_usbCommsInterfaces[i]; + rwlockWriteLock(&intf->lock); + rwlockWriteLock(&intf->lock_in); + rwlockWriteLock(&intf->lock_out); + rc = _usbCommsInterfaceInit(i); + rwlockWriteUnlock(&intf->lock_out); + rwlockWriteUnlock(&intf->lock_in); + rwlockWriteUnlock(&intf->lock); + if (R_FAILED(rc)) { + break; + } + } + } + } + + if (R_SUCCEEDED(rc) && kernelAbove500()) { + rc = usbDsEnable(); + } + + if (R_FAILED(rc)) { + usbCommsExit(); } - - if (!found) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); } - - if (R_SUCCEEDED(rc)) { - rwlockWriteLock(&inter->lock); - rwlockWriteLock(&inter->lock_in); - rwlockWriteLock(&inter->lock_out); - rc = _usbCommsInterfaceInit(inter, bInterfaceClass, bInterfaceSubClass, bInterfaceProtocol); - rwlockWriteUnlock(&inter->lock_out); - rwlockWriteUnlock(&inter->lock_in); - rwlockWriteUnlock(&inter->lock); - } - - if (R_FAILED(rc)) { - usbCommsExit(); - } - - if (R_SUCCEEDED(rc)) g_usbCommsInitialized=true; - - if (R_SUCCEEDED(rc) && interface) *interface = i; + + if (R_SUCCEEDED(rc)) g_usbCommsInitialized = true; rwlockWriteUnlock(&g_usbCommsLock); - return rc; } Result usbCommsInitialize(void) { - return usbCommsInitializeEx(NULL, USB_CLASS_VENDOR_SPEC, USB_CLASS_VENDOR_SPEC, USB_CLASS_VENDOR_SPEC); + return usbCommsInitializeEx(1); } -static void _usbCommsInterfaceExit(usbCommsInterface *interface) +static void _usbCommsInterfaceFree(usbCommsInterface *interface) { rwlockWriteLock(&interface->lock); if (!interface->initialized) { @@ -100,11 +159,6 @@ static void _usbCommsInterfaceExit(usbCommsInterface *interface) interface->initialized = 0; - usbDsInterface_DisableInterface(interface->interface); - usbDsEndpoint_Close(interface->endpoint_in); - usbDsEndpoint_Close(interface->endpoint_out); - usbDsInterface_Close(interface->interface); - interface->endpoint_in = NULL; interface->endpoint_out = NULL; interface->interface = NULL; @@ -120,26 +174,6 @@ static void _usbCommsInterfaceExit(usbCommsInterface *interface) rwlockWriteUnlock(&interface->lock); } -void usbCommsExitEx(u32 interface) -{ - u32 i; - bool found=0; - if (interface>=TOTAL_INTERFACES) return; - - _usbCommsInterfaceExit(&g_usbCommsInterfaces[interface]); - - for (i=0; iinitialized = 1; + + //The buffer for PostBufferAsync commands must be 0x1000-byte aligned. + interface->endpoint_in_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_in_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + if (R_SUCCEEDED(rc)) { + interface->endpoint_out_buffer = memalign(0x1000, 0x1000); + if (interface->endpoint_out_buffer==NULL) rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + if (R_SUCCEEDED(rc)) { + memset(interface->endpoint_in_buffer, 0, 0x1000); + memset(interface->endpoint_out_buffer, 0, 0x1000); + } + + if (R_FAILED(rc)) return rc; + + rc = usbDsRegisterInterface(&interface->interface, interface_descriptor.bInterfaceNumber); + if (R_FAILED(rc)) return rc; + + // Full Speed Config + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + + // High Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x200; + endpoint_descriptor_out.wMaxPacketSize = 0x200; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + + // Super Speed Config + endpoint_descriptor_in.wMaxPacketSize = 0x400; + endpoint_descriptor_out.wMaxPacketSize = 0x400; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); + if (R_FAILED(rc)) return rc; + + //Setup endpoints. + rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_in, endpoint_descriptor_in.bEndpointAddress); + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint_out, endpoint_descriptor_out.bEndpointAddress); + if (R_FAILED(rc)) return rc; + + rc = usbDsInterface_EnableInterface(interface->interface); + if (R_FAILED(rc)) return rc; + + return rc; +} + + +static Result _usbCommsInterfaceInit1x(u32 intf_ind) +{ + Result rc = 0; + u32 ep_num = intf_ind + 1; + usbCommsInterface *interface = &g_usbCommsInterfaces[intf_ind]; + + struct usb_interface_descriptor interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = intf_ind, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0xFF, + .bInterfaceProtocol = 0xFF, + }; + + struct usb_endpoint_descriptor endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN + ep_num, .bmAttributes = USB_TRANSFER_TYPE_BULK, .wMaxPacketSize = 0x200, }; @@ -182,7 +334,7 @@ static Result _usbCommsInterfaceInit(usbCommsInterface *interface, u8 bInterface struct usb_endpoint_descriptor endpoint_descriptor_out = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_ENDPOINT_OUT, + .bEndpointAddress = USB_ENDPOINT_OUT + ep_num, .bmAttributes = USB_TRANSFER_TYPE_BULK, .wMaxPacketSize = 0x200, }; @@ -235,7 +387,7 @@ static Result _usbCommsRead(usbCommsInterface *interface, void* buffer, size_t s UsbDsReportData reportdata; //Makes sure endpoints are ready for data-transfer / wait for init if needed. - rc = usbDsWaitReady(); + rc = usbDsWaitReady(U64_MAX); if (R_FAILED(rc)) return rc; while(size) @@ -264,8 +416,8 @@ static Result _usbCommsRead(usbCommsInterface *interface, void* buffer, size_t s if (R_FAILED(rc)) return rc; //Wait for the transfer to finish. - svcWaitSynchronizationSingle(interface->endpoint_out->CompletionEvent, U64_MAX); - svcClearEvent(interface->endpoint_out->CompletionEvent); + eventWait(&interface->endpoint_out->CompletionEvent, U64_MAX); + eventClear(&interface->endpoint_out->CompletionEvent); rc = usbDsEndpoint_GetReportData(interface->endpoint_out, &reportdata); if (R_FAILED(rc)) return rc; @@ -300,7 +452,7 @@ static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, s UsbDsReportData reportdata; //Makes sure endpoints are ready for data-transfer / wait for init if needed. - rc = usbDsWaitReady(); + rc = usbDsWaitReady(U64_MAX); if (R_FAILED(rc)) return rc; while(size) @@ -327,8 +479,8 @@ static Result _usbCommsWrite(usbCommsInterface *interface, const void* buffer, s if(R_FAILED(rc))return rc; //Wait for the transfer to finish. - svcWaitSynchronizationSingle(interface->endpoint_in->CompletionEvent, U64_MAX); - svcClearEvent(interface->endpoint_in->CompletionEvent); + eventWait(&interface->endpoint_in->CompletionEvent, U64_MAX); + eventClear(&interface->endpoint_in->CompletionEvent); rc = usbDsEndpoint_GetReportData(interface->endpoint_in, &reportdata); if (R_FAILED(rc)) return rc; diff --git a/nx/source/services/usb.c b/nx/source/services/usb.c index 48c6f3f6..f965ede9 100644 --- a/nx/source/services/usb.c +++ b/nx/source/services/usb.c @@ -6,26 +6,26 @@ #include "kernel/detect.h" #include "services/usb.h" #include "services/sm.h" +#include "runtime/util/utf.h" #define TOTAL_INTERFACES 4 -#define TOTAL_ENDPOINTS 15*2 +#define TOTAL_ENDPOINTS_IN 16 +#define TOTAL_ENDPOINTS_OUT 16 +#define TOTAL_ENDPOINTS (TOTAL_ENDPOINTS_IN+TOTAL_ENDPOINTS_OUT) static Service g_usbDsSrv; -static Handle g_usbDsStateChangeEvent = INVALID_HANDLE; +static Event g_usbDsStateChangeEvent = {0}; static UsbDsInterface g_usbDsInterfaceTable[TOTAL_INTERFACES]; -static UsbDsEndpoint g_usbDsEndpointTable[TOTAL_INTERFACES*TOTAL_ENDPOINTS]; +static UsbDsEndpoint g_usbDsEndpointTable[TOTAL_INTERFACES][TOTAL_ENDPOINTS]; static void _usbDsFreeTables(void); static Result _usbDsBindDevice(UsbComplexId complexId); static Result _usbDsBindClientProcess(Handle prochandle); -static Result _usbDsGetEvent(Service* srv, Handle* handle_out, u64 cmd_id); -static Result _usbDsSetVidPidBcd(const UsbDsDeviceInfo* deviceinfo); +static Result _usbDsGetEvent(Service* srv, Event* event_out, u64 cmd_id); -static Result _usbDsGetSession(Service* srv, Service* srv_out, u64 cmd_id, const void* buf0, size_t buf0size, const void* buf1, size_t buf1size); - -Result usbDsInitialize(UsbComplexId complexId, const UsbDsDeviceInfo* deviceinfo) +Result usbDsInitialize(void) { if (serviceIsActive(&g_usbDsSrv)) return MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); @@ -35,24 +35,21 @@ Result usbDsInitialize(UsbComplexId complexId, const UsbDsDeviceInfo* deviceinfo rc = smGetService(&g_usbDsSrv, "usb:ds"); if (R_SUCCEEDED(rc)) - rc = _usbDsBindDevice(complexId); + rc = _usbDsBindDevice(UsbComplexId_Default); if (R_SUCCEEDED(rc)) rc = _usbDsBindClientProcess(CUR_PROCESS_HANDLE); // GetStateChangeEvent if (R_SUCCEEDED(rc)) rc = _usbDsGetEvent(&g_usbDsSrv, &g_usbDsStateChangeEvent, 3); - - if (R_SUCCEEDED(rc) && deviceinfo && kernelAbove200()) { - rc = _usbDsSetVidPidBcd(deviceinfo); - } + + // Result code doesn't matter here, users can call themselves later, too. This prevents foot shooting. + if (R_SUCCEEDED(rc)) + usbDsClearDeviceData(); if (R_FAILED(rc)) { - if(g_usbDsStateChangeEvent) { - svcCloseHandle(g_usbDsStateChangeEvent); - g_usbDsStateChangeEvent = INVALID_HANDLE; - } + eventClose(&g_usbDsStateChangeEvent); serviceClose(&g_usbDsSrv); } @@ -64,24 +61,31 @@ void usbDsExit(void) { if (!serviceIsActive(&g_usbDsSrv)) return; + + if (kernelAbove500()) { + usbDsDisable(); + } _usbDsFreeTables(); - if (g_usbDsStateChangeEvent) { - svcCloseHandle(g_usbDsStateChangeEvent); - g_usbDsStateChangeEvent = 0; - } + eventClose(&g_usbDsStateChangeEvent); serviceClose(&g_usbDsSrv); } -Service* usbDsGetServiceSession(void) { - return &g_usbDsSrv; +Event* usbDsGetStateChangeEvent(void) +{ + return &g_usbDsStateChangeEvent; } -Handle usbDsGetStateChangeEvent(void) -{ - return g_usbDsStateChangeEvent; +static UsbDsInterface* _usbDsTryAllocateInterface(u32 num) { + if (num >= TOTAL_INTERFACES) return NULL; + UsbDsInterface* ptr = &g_usbDsInterfaceTable[num]; + if (ptr->initialized) return NULL; + memset(ptr, 0, sizeof(UsbDsInterface)); + ptr->initialized = true; + ptr->interface_index = num; + return ptr; } static UsbDsInterface* _usbDsAllocateInterface(void) @@ -111,7 +115,7 @@ static UsbDsEndpoint* _usbDsAllocateEndpoint(UsbDsInterface* interface) for(pos=0; posinterface_index*TOTAL_ENDPOINTS) + pos]; + ptr = &g_usbDsEndpointTable[interface->interface_index][pos]; if(ptr->initialized)continue; memset(ptr, 0, sizeof(UsbDsEndpoint)); ptr->initialized = true; @@ -121,53 +125,47 @@ static UsbDsEndpoint* _usbDsAllocateEndpoint(UsbDsInterface* interface) return NULL; } -static void _usbDsFreeInterface(UsbDsInterface* interface) -{ - if (!interface->initialized) - return; - - if (interface->CtrlOutCompletionEvent) { - svcCloseHandle(interface->CtrlOutCompletionEvent); - interface->CtrlOutCompletionEvent = 0; - } - - if (interface->CtrlInCompletionEvent) { - svcCloseHandle(interface->CtrlInCompletionEvent); - interface->CtrlInCompletionEvent = 0; - } - - if (interface->SetupEvent) { - svcCloseHandle(interface->SetupEvent); - interface->SetupEvent = 0; - } - - serviceClose(&interface->h); - - interface->initialized = false; -} - static void _usbDsFreeEndpoint(UsbDsEndpoint* endpoint) { if (!endpoint->initialized) return; + + /* Cancel any ongoing transactions. */ + usbDsEndpoint_Cancel(endpoint); - if (endpoint->CompletionEvent) { - svcCloseHandle(endpoint->CompletionEvent); - endpoint->CompletionEvent = 0; - } + eventClose(&endpoint->CompletionEvent); serviceClose(&endpoint->h); endpoint->initialized = false; } +static void _usbDsFreeInterface(UsbDsInterface* interface) +{ + if (!interface->initialized) + return; + + /* Disable interface. */ + usbDsInterface_DisableInterface(interface); + + /* Close endpoints. */ + for (u32 ep = 0; ep < TOTAL_ENDPOINTS; ep++) { + _usbDsFreeEndpoint(&g_usbDsEndpointTable[interface->interface_index][ep]); + } + + eventClose(&interface->CtrlOutCompletionEvent); + eventClose(&interface->CtrlInCompletionEvent); + eventClose(&interface->SetupEvent); + + serviceClose(&interface->h); + + interface->initialized = false; +} + static void _usbDsFreeTables(void) { - u32 pos, pos2; - for(pos=0; posresult; if (R_SUCCEEDED(rc)) { - *handle_out = r.Handles[0]; + eventLoadRemote(event_out, r.Handles[0], false); } } @@ -305,7 +303,7 @@ Result usbDsGetState(u32 *out) { return rc; } -Result usbDsWaitReady(void) { +Result usbDsWaitReady(u64 timeout) { Result rc; u32 state = 0; @@ -314,8 +312,8 @@ Result usbDsWaitReady(void) { while (R_SUCCEEDED(rc) && state != 5) { - svcWaitSynchronizationSingle(g_usbDsStateChangeEvent, U64_MAX); - svcClearEvent(g_usbDsStateChangeEvent); + eventWait(&g_usbDsStateChangeEvent, timeout); + eventClear(&g_usbDsStateChangeEvent); rc = usbDsGetState(&state); } @@ -362,7 +360,7 @@ Result usbDsParseReportData(UsbDsReportData *reportdata, u32 urbId, u32 *request return rc; } -static Result _usbDsSetVidPidBcd(const UsbDsDeviceInfo* deviceinfo) { +Result usbDsSetVidPidBcd(const UsbDsDeviceInfo* deviceinfo) { IpcCommand c; ipcInitialize(&c); @@ -569,6 +567,248 @@ Result usbDsGetDsInterface(UsbDsInterface** interface, struct usb_interface_desc return rc; } +Result usbDsRegisterInterface(UsbDsInterface** interface, u32 intf_num) +{ + UsbDsInterface* ptr = _usbDsTryAllocateInterface(intf_num); + if(ptr == NULL) + return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 intf_num; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2; + raw->intf_num = intf_num; + + Result rc = serviceIpcDispatch(&g_usbDsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + serviceCreate(&ptr->h, r.Handles[0]); + } + } + + // GetSetupEvent + if (R_SUCCEEDED(rc)) + rc = _usbDsGetEvent(&ptr->h, &ptr->SetupEvent, 1); + // GetCtrlInCompletionEvent + if (R_SUCCEEDED(rc)) + rc = _usbDsGetEvent(&ptr->h, &ptr->CtrlInCompletionEvent, 7); + // GetCtrlOutCompletionEvent + if (R_SUCCEEDED(rc)) + rc = _usbDsGetEvent(&ptr->h, &ptr->CtrlOutCompletionEvent, 9); + + if (R_FAILED(rc)) + _usbDsFreeInterface(ptr); + + if (R_SUCCEEDED(rc)) + *interface = ptr; + + return rc; +} + +Result usbDsClearDeviceData(void) { + return _usbDsCmdNoParams(&g_usbDsSrv, 5); +} + +static Result _usbDsAddUsbStringDescriptorRaw(u8 *out_index, struct usb_string_descriptor *descriptor) { + IpcCommand c; + ipcInitialize(&c); + + ipcAddSendBuffer(&c, descriptor, sizeof(*descriptor), 0); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 6; + + Result rc = serviceIpcDispatch(&g_usbDsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + u8 index; + } *resp = r.Raw; + + rc = resp->result; + if (R_SUCCEEDED(rc) && out_index) { + *out_index = resp->index; + } + } + + return rc; +} + +Result usbDsAddUsbStringDescriptor(u8* out_index, const char* string) { + struct usb_string_descriptor descriptor = { + .bDescriptorType = USB_DT_STRING, + .wData = {0}, + }; + + // Convert + u32 len = (u32)utf8_to_utf16(descriptor.wData, (const uint8_t *)string, sizeof(descriptor.wData)/sizeof(u16) - 1); + if (len > sizeof(descriptor.wData)/sizeof(u16)) len = sizeof(descriptor.wData)/sizeof(u16); + + // Set length + descriptor.bLength = 2 + 2 * len; + + return _usbDsAddUsbStringDescriptorRaw(out_index, &descriptor); +} + +Result usbDsAddUsbLanguageStringDescriptor(u8* out_index, const u16* lang_ids, u16 num_langs) { + if (num_langs > 0x40) num_langs = 0x40; + + struct usb_string_descriptor descriptor = { + .bLength = 2 + 2 * num_langs, + .bDescriptorType = USB_DT_STRING, + .wData = {0}, + }; + + for (u32 i = 0; i < num_langs; i++) { + descriptor.wData[i] = lang_ids[i]; + } + + return _usbDsAddUsbStringDescriptorRaw(out_index, &descriptor); +} + +Result usbDsDeleteUsbStringDescriptor(u8 index) { + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 index; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 7; + raw->index = index; + + Result rc = serviceIpcDispatch(&g_usbDsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + + +Result usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed speed, struct usb_device_descriptor* descriptor) { + IpcCommand c; + ipcInitialize(&c); + + ipcAddSendBuffer(&c, descriptor, USB_DT_DEVICE_SIZE, 0); + + struct { + u64 magic; + u64 cmd_id; + u32 speed; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 8; + raw->speed = speed; + + Result rc = serviceIpcDispatch(&g_usbDsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +Result usbDsSetBinaryObjectStore(void* bos, size_t bos_size) { + IpcCommand c; + ipcInitialize(&c); + + ipcAddSendBuffer(&c, bos, bos_size, 0); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 9; + + Result rc = serviceIpcDispatch(&g_usbDsSrv); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + +Result usbDsEnable(void) { + return _usbDsCmdNoParams(&g_usbDsSrv, 10); +} + +Result usbDsDisable(void) { + return _usbDsCmdNoParams(&g_usbDsSrv, 11); +} + + //IDsInterface void usbDsInterface_Close(UsbDsInterface* interface) @@ -576,6 +816,41 @@ void usbDsInterface_Close(UsbDsInterface* interface) _usbDsFreeInterface(interface); } +Result usbDsInterface_GetSetupPacket(UsbDsInterface* interface, void* buffer, size_t size) { + if(!interface->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + IpcCommand c; + ipcInitialize(&c); + + ipcAddRecvBuffer(&c, buffer, size, 0); + + struct { + u64 magic; + u64 cmd_id; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 2; + + Result rc = serviceIpcDispatch(&interface->h); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + Result usbDsInterface_GetDsEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, struct usb_endpoint_descriptor* descriptor) { if(!interface->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); @@ -583,7 +858,7 @@ Result usbDsInterface_GetDsEndpoint(UsbDsInterface* interface, UsbDsEndpoint** e UsbDsEndpoint* ptr = _usbDsAllocateEndpoint(interface); if(ptr==NULL)return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); - Result rc = _usbDsGetSession(&interface->h, &ptr->h, 0, descriptor, sizeof(struct usb_endpoint_descriptor), NULL, 0); + Result rc = _usbDsGetSession(&interface->h, &ptr->h, 0, descriptor, USB_DT_ENDPOINT_SIZE, NULL, 0); if (R_SUCCEEDED(rc)) rc = _usbDsGetEvent(&ptr->h, &ptr->CompletionEvent, 2);//GetCompletionEvent @@ -642,6 +917,93 @@ Result usbDsInterface_StallCtrl(UsbDsInterface* interface) return _usbDsCmdNoParams(&interface->h, 11); } +Result usbDsInterface_RegisterEndpoint(UsbDsInterface* interface, UsbDsEndpoint** endpoint, u8 endpoint_address) +{ + if(!interface->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + UsbDsEndpoint* ptr = _usbDsAllocateEndpoint(interface); + if(ptr==NULL)return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 ep_addr; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->ep_addr = endpoint_address; + + Result rc = serviceIpcDispatch(&interface->h); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + serviceCreate(&ptr->h, r.Handles[0]); + } + } + + if (R_SUCCEEDED(rc)) rc = _usbDsGetEvent(&ptr->h, &ptr->CompletionEvent, 2);//GetCompletionEvent + + if (R_FAILED(rc)) _usbDsFreeEndpoint(ptr); + + if (R_SUCCEEDED(rc)) *endpoint = ptr; + return rc; +} + +Result usbDsInterface_AppendConfigurationData(UsbDsInterface* interface, UsbDeviceSpeed speed, void* buffer, size_t size) { + if(!interface->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + IpcCommand c; + ipcInitialize(&c); + + ipcAddSendBuffer(&c, buffer, size, 0); + + struct { + u64 magic; + u64 cmd_id; + u32 intf_num; + u32 speed; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 12; + raw->intf_num = interface->interface_index; + raw->speed = speed; + + Result rc = serviceIpcDispatch(&interface->h); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} + //IDsEndpoint void usbDsEndpoint_Close(UsbDsEndpoint* endpoint) @@ -656,6 +1018,13 @@ Result usbDsEndpoint_PostBufferAsync(UsbDsEndpoint* endpoint, void* buffer, size return _usbDsPostBuffer(&endpoint->h, 0, buffer, size, urbId); } +Result usbDsEndpoint_Cancel(UsbDsEndpoint* endpoint) +{ + if(!endpoint->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return _usbDsCmdNoParams(&endpoint->h, 1); +} + Result usbDsEndpoint_GetReportData(UsbDsEndpoint* endpoint, UsbDsReportData *out) { if(!endpoint->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); @@ -670,3 +1039,39 @@ Result usbDsEndpoint_StallCtrl(UsbDsEndpoint* endpoint) return _usbDsCmdNoParams(&endpoint->h, 4); } +Result usbDsEndpoint_SetZlt(UsbDsEndpoint* endpoint, bool zlt) +{ + if(!endpoint->initialized)return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + IpcCommand c; + ipcInitialize(&c); + + struct { + u64 magic; + u64 cmd_id; + u32 zlt; + } *raw; + + raw = ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 12; + raw->zlt = zlt ? 1 : 0; + + Result rc = serviceIpcDispatch(&endpoint->h); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = r.Raw; + + rc = resp->result; + } + + return rc; +} +