1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-10 04:31:44 +00:00
nxdumptool/source/core/usb.c
Pablo Curiel 9cb2a0440e Absolute bare minimum to get this to build.
Codebase still needs to be updated to properly use log levels, but at least it's building on all of them.
2022-07-12 05:27:30 +02:00

1304 lines
45 KiB
C

/*
* usb.c
*
* Heavily based in usb_comms from libnx.
*
* Copyright (c) 2018-2020, Switchbrew and libnx contributors.
* Copyright (c) 2020-2022, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nxdumptool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nxdt_utils.h"
#include "usb.h"
#define USB_ABI_VERSION 1
#define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */
#define USB_TRANSFER_ALIGNMENT 0x1000 /* 4 KiB. */
#define USB_TRANSFER_TIMEOUT 5 /* 5 seconds. */
#define USB_DEV_VID 0x057E /* VID officially used by Nintendo in usb:ds. */
#define USB_DEV_PID 0x3000 /* PID officially used by Nintendo in usb:ds. */
#define USB_DEV_BCD_REL 0x0100 /* Device release number. Always 1.0. */
#define USB_FS_BCD_REVISION 0x0110 /* USB 1.1. */
#define USB_FS_EP0_MAX_PACKET_SIZE 0x40 /* 64 bytes. */
#define USB_FS_EP_MAX_PACKET_SIZE 0x40 /* 64 bytes. */
#define USB_HS_BCD_REVISION 0x0200 /* USB 2.0. */
#define USB_HS_EP0_MAX_PACKET_SIZE USB_FS_EP0_MAX_PACKET_SIZE /* 64 bytes. */
#define USB_HS_EP_MAX_PACKET_SIZE 0x200 /* 512 bytes. */
#define USB_SS_BCD_REVISION 0x0300 /* USB 3.0. */
#define USB_SS_EP0_MAX_PACKET_SIZE 9 /* 512 bytes (1 << 9). */
#define USB_SS_EP_MAX_PACKET_SIZE 0x400 /* 1024 bytes. */
#define USB_BOS_SIZE 0x16 /* usb_bos_descriptor + usb_2_0_extension_descriptor + usb_ss_usb_device_capability_descriptor. */
#define USB_LANGID_ENUS 0x0409
/* Type definitions. */
typedef enum {
UsbCommandType_StartSession = 0,
UsbCommandType_SendFileProperties = 1,
UsbCommandType_CancelFileTransfer = 2,
UsbCommandType_SendNspHeader = 3,
UsbCommandType_EndSession = 4
} UsbCommandType;
typedef struct {
u32 magic;
u32 cmd;
u32 cmd_block_size;
u8 reserved[0x4];
} UsbCommandHeader;
NXDT_ASSERT(UsbCommandHeader, 0x10);
typedef struct {
u8 app_ver_major;
u8 app_ver_minor;
u8 app_ver_micro;
u8 abi_version;
char git_commit[8];
u8 reserved[0x4];
} UsbCommandStartSession;
NXDT_ASSERT(UsbCommandStartSession, 0x10);
typedef struct {
u64 file_size;
u32 filename_length;
u32 nsp_header_size;
char filename[FS_MAX_PATH];
u8 reserved_2[0xF];
} UsbCommandSendFileProperties;
NXDT_ASSERT(UsbCommandSendFileProperties, 0x320);
typedef enum {
///< Expected response code.
UsbStatusType_Success = 0,
///< Internal usage.
UsbStatusType_InvalidCommandSize = 1,
UsbStatusType_WriteCommandFailed = 2,
UsbStatusType_ReadStatusFailed = 3,
///< These can be returned by the host device.
UsbStatusType_InvalidMagicWord = 4,
UsbStatusType_UnsupportedCommand = 5,
UsbStatusType_UnsupportedAbiVersion = 6,
UsbStatusType_MalformedCommand = 7,
UsbStatusType_HostIoError = 8
} UsbStatusType;
typedef struct {
u32 magic;
u32 status; ///< UsbStatusType.
u16 max_packet_size; ///< USB host endpoint max packet size.
u8 reserved[0x6];
} UsbStatus;
NXDT_ASSERT(UsbStatus, 0x10);
/// Imported from libusb, with some adjustments.
enum usb_bos_type {
USB_BT_WIRELESS_USB_DEVICE_CAPABILITY = 1,
USB_BT_USB_2_0_EXTENSION = 2,
USB_BT_SS_USB_DEVICE_CAPABILITY = 3,
USB_BT_CONTAINER_ID = 4
};
/// Imported from libusb, with some adjustments.
enum usb_2_0_extension_attributes {
USB_BM_LPM_SUPPORT = 2
};
/// Imported from libusb, with some adjustments.
enum usb_ss_usb_device_capability_attributes {
USB_BM_LTM_SUPPORT = 2
};
/// Imported from libusb, with some adjustments.
enum usb_supported_speed {
USB_LOW_SPEED_OPERATION = BIT(0),
USB_FULL_SPEED_OPERATION = BIT(1),
USB_HIGH_SPEED_OPERATION = BIT(2),
USB_SUPER_SPEED_OPERATION = BIT(3)
};
/// Imported from libusb, with some adjustments.
struct usb_bos_descriptor {
u8 bLength;
u8 bDescriptorType; ///< Must match USB_DT_BOS.
u16 wTotalLength; ///< Length of this descriptor and all of its sub descriptors.
u8 bNumDeviceCaps; ///< The number of separate device capability descriptors in the BOS.
} PACKED;
NXDT_ASSERT(struct usb_bos_descriptor, 0x5);
/// Imported from libusb, with some adjustments.
struct usb_2_0_extension_descriptor {
u8 bLength;
u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY.
u8 bDevCapabilityType; ///< Must match USB_BT_USB_2_0_EXTENSION.
u32 bmAttributes; ///< usb_2_0_extension_attributes.
} PACKED;
NXDT_ASSERT(struct usb_2_0_extension_descriptor, 0x7);
/// Imported from libusb, with some adjustments.
struct usb_ss_usb_device_capability_descriptor {
u8 bLength;
u8 bDescriptorType; ///< Must match USB_DT_DEVICE_CAPABILITY.
u8 bDevCapabilityType; ///< Must match USB_BT_SS_USB_DEVICE_CAPABILITY.
u8 bmAttributes; ///< usb_ss_usb_device_capability_attributes.
u16 wSpeedsSupported; ///< usb_supported_speed.
u8 bFunctionalitySupport; ///< The lowest speed at which all the functionality that the device supports is available to the user.
u8 bU1DevExitLat; ///< U1 Device Exit Latency.
u16 bU2DevExitLat; ///< U2 Device Exit Latency.
} PACKED;
NXDT_ASSERT(struct usb_ss_usb_device_capability_descriptor, 0xA);
/* Global variables. */
static Mutex g_usbInterfaceMutex = 0;
static UsbDsInterface *g_usbInterface = NULL;
static UsbDsEndpoint *g_usbEndpointIn = NULL, *g_usbEndpointOut = NULL;
static bool g_usbInterfaceInit = false;
static Event *g_usbStateChangeEvent = NULL;
static Thread g_usbDetectionThread = {0};
static UEvent g_usbDetectionThreadExitEvent = {0}, g_usbTimeoutEvent = {0};
static bool g_usbHostAvailable = false, g_usbSessionStarted = false, g_usbDetectionThreadExitFlag = false, g_nspTransferMode = false;
static atomic_bool g_usbDetectionThreadCreated = false;
static u8 *g_usbTransferBuffer = NULL;
static u64 g_usbTransferRemainingSize = 0, g_usbTransferWrittenSize = 0;
static u16 g_usbEndpointMaxPacketSize = 0;
/* Function prototypes. */
static bool usbCreateDetectionThread(void);
static void usbDestroyDetectionThread(void);
static void usbDetectionThreadFunc(void *arg);
static bool usbStartSession(void);
static void usbEndSession(void);
NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size);
static bool usbSendCommand(void);
static void usbLogStatusDetail(u32 status);
NX_INLINE bool usbAllocateTransferBuffer(void);
NX_INLINE void usbFreeTransferBuffer(void);
static bool usbInitializeComms(void);
static void usbCloseComms(void);
static bool usbInitializeDeviceInterface5x(void);
static bool usbInitializeDeviceInterface1x(void);
NX_INLINE bool usbIsHostAvailable(void);
NX_INLINE void usbSetZltPacket(bool enable);
NX_INLINE bool usbRead(void *buf, size_t size);
NX_INLINE bool usbWrite(void *buf, size_t size);
static bool usbTransferData(void *buf, size_t size, UsbDsEndpoint *endpoint);
bool usbInitialize(void)
{
bool ret = false;
SCOPED_LOCK(&g_usbInterfaceMutex)
{
ret = g_usbInterfaceInit;
if (ret) break;
/* Allocate USB transfer buffer. */
if (!usbAllocateTransferBuffer())
{
LOG_MSG("Failed to allocate memory for the USB transfer buffer!");
break;
}
/* Initialize USB device interface. */
if (!usbInitializeComms())
{
LOG_MSG("Failed to initialize USB device interface!");
break;
}
/* Retrieve USB state change kernel event. */
g_usbStateChangeEvent = usbDsGetStateChangeEvent();
if (!g_usbStateChangeEvent)
{
LOG_MSG("Failed to retrieve USB state change kernel event!");
break;
}
/* Create user-mode exit event. */
ueventCreate(&g_usbDetectionThreadExitEvent, true);
/* Create user-mode USB timeout event. */
ueventCreate(&g_usbTimeoutEvent, true);
/* Create USB detection thread. */
atomic_store(&g_usbDetectionThreadCreated, usbCreateDetectionThread());
if (!atomic_load(&g_usbDetectionThreadCreated)) break;
/* Update flags. */
ret = g_usbInterfaceInit = true;
}
return ret;
}
void usbExit(void)
{
/* Destroy USB detection thread before attempting to lock. */
if (atomic_load(&g_usbDetectionThreadCreated))
{
usbDestroyDetectionThread();
atomic_store(&g_usbDetectionThreadCreated, false);
}
/* Now we can safely lock. */
SCOPED_LOCK(&g_usbInterfaceMutex)
{
/* Clear USB state change kernel event. */
g_usbStateChangeEvent = NULL;
/* Close USB device interface. */
usbCloseComms();
/* Free USB transfer buffer. */
usbFreeTransferBuffer();
/* Update flag. */
g_usbInterfaceInit = false;
}
}
void *usbAllocatePageAlignedBuffer(size_t size)
{
if (!size) return NULL;
return memalign(USB_TRANSFER_ALIGNMENT, size);
}
u8 usbIsReady(void)
{
u8 ret = UsbHostSpeed_None;
SCOPED_TRY_LOCK(&g_usbInterfaceMutex)
{
if (!g_usbHostAvailable || !g_usbSessionStarted) break;
switch(g_usbEndpointMaxPacketSize)
{
case USB_FS_EP_MAX_PACKET_SIZE: /* USB 1.x. */
ret = UsbHostSpeed_FullSpeed;
break;
case USB_HS_EP_MAX_PACKET_SIZE: /* USB 2.0. */
ret = UsbHostSpeed_HighSpeed;
break;
case USB_SS_EP_MAX_PACKET_SIZE: /* USB 3.0. */
ret = UsbHostSpeed_SuperSpeed;
break;
default:
break;
}
}
return ret;
}
bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_size)
{
bool ret = false;
SCOPED_LOCK(&g_usbInterfaceMutex)
{
size_t filename_length = 0;
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || !filename || !(filename_length = strlen(filename)) || filename_length >= FS_MAX_PATH || \
(!g_nspTransferMode && ((file_size && nsp_header_size >= file_size) || g_usbTransferRemainingSize)) || (g_nspTransferMode && nsp_header_size))
{
LOG_MSG("Invalid parameters!");
goto end;
}
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_SendFileProperties, (u32)sizeof(UsbCommandSendFileProperties));
UsbCommandSendFileProperties *cmd_block = (UsbCommandSendFileProperties*)(g_usbTransferBuffer + sizeof(UsbCommandHeader));
memset(cmd_block, 0, sizeof(UsbCommandSendFileProperties));
cmd_block->file_size = file_size;
cmd_block->filename_length = (u32)filename_length;
cmd_block->nsp_header_size = nsp_header_size;
snprintf(cmd_block->filename, sizeof(cmd_block->filename), "%s", filename);
/* Send command. */
ret = usbSendCommand();
if (!ret) goto end;
/* Update variables. */
g_usbTransferRemainingSize = file_size;
g_usbTransferWrittenSize = 0;
if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size);
end:
if (!ret && g_nspTransferMode) g_nspTransferMode = false;
}
return ret;
}
bool usbSendFileData(void *data, u64 data_size)
{
bool ret = false;
SCOPED_LOCK(&g_usbInterfaceMutex)
{
void *buf = NULL;
bool zlt_required = false;
if (!g_usbTransferBuffer || !g_usbInterfaceInit || !g_usbHostAvailable || !g_usbSessionStarted || !g_usbTransferRemainingSize || !data || !data_size || data_size > USB_TRANSFER_BUFFER_SIZE || \
data_size > g_usbTransferRemainingSize)
{
LOG_MSG("Invalid parameters!");
goto end;
}
/* Optimization for buffers that already are page aligned. */
if (IS_ALIGNED((u64)data, USB_TRANSFER_ALIGNMENT))
{
buf = data;
} else {
buf = g_usbTransferBuffer;
memcpy(buf, data, data_size);
}
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet. */
/* This is automatically handled by usbDsEndpoint_PostBufferAsync(), depending on the ZLT setting from the input (write) endpoint. */
/* First, check if this is the last data chunk for this file. */
if ((g_usbTransferRemainingSize - data_size) == 0)
{
/* Enable ZLT if the last chunk size is aligned to the USB endpoint max packet size. */
if (IS_ALIGNED(data_size, g_usbEndpointMaxPacketSize))
{
zlt_required = true;
usbSetZltPacket(true);
//LOG_MSG("ZLT enabled. Last chunk size: 0x%lX bytes.", data_size);
}
} else {
/* Disable ZLT if this is the first of multiple data chunks. */
if (!g_usbTransferWrittenSize)
{
usbSetZltPacket(false);
//LOG_MSG("ZLT disabled (first chunk).");
}
}
/* Send data chunk. */
if (!(ret = usbWrite(buf, data_size)))
{
LOG_MSG("Failed to write 0x%lX bytes long file data chunk from offset 0x%lX! (total size: 0x%lX).", data_size, g_usbTransferWrittenSize, \
g_usbTransferRemainingSize + g_usbTransferWrittenSize);
goto end;
}
g_usbTransferRemainingSize -= data_size;
g_usbTransferWrittenSize += data_size;
/* Check if this is the last chunk. */
if (!g_usbTransferRemainingSize)
{
/* Check response from host device. */
if (!(ret = usbRead(g_usbTransferBuffer, sizeof(UsbStatus))))
{
LOG_MSG("Failed to read 0x%lX bytes long status block!", sizeof(UsbStatus));
goto end;
}
UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer;
if (!(ret = (cmd_status->magic == __builtin_bswap32(USB_CMD_HEADER_MAGIC))))
{
LOG_MSG("Invalid status block magic word!");
goto end;
}
if (!(ret = (cmd_status->status == UsbStatusType_Success))) usbLogStatusDetail(cmd_status->status);
}
end:
/* Disable ZLT if it was previously enabled. */
if (zlt_required) usbSetZltPacket(false);
/* Reset variables in case of errors. */
if (!ret)
{
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_nspTransferMode = false;
}
}
return ret;
}
void usbCancelFileTransfer(void)
{
SCOPED_LOCK(&g_usbInterfaceMutex)
{
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || (!g_usbTransferRemainingSize && !g_nspTransferMode)) break;
/* Reset variables right away. */
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_nspTransferMode = false;
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_CancelFileTransfer, 0);
/* Send command. We don't care about the result here. */
usbSendCommand();
}
}
bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size)
{
bool ret = false;
SCOPED_LOCK(&g_usbInterfaceMutex)
{
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || !g_nspTransferMode || !nsp_header || !nsp_header_size || \
nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader)))
{
LOG_MSG("Invalid parameters!");
break;
}
/* Disable NSP transfer mode right away. */
g_nspTransferMode = false;
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_SendNspHeader, nsp_header_size);
memcpy(g_usbTransferBuffer + sizeof(UsbCommandHeader), nsp_header, nsp_header_size);
/* Send command. */
ret = usbSendCommand();
}
return ret;
}
static bool usbCreateDetectionThread(void)
{
if (!utilsCreateThread(&g_usbDetectionThread, usbDetectionThreadFunc, NULL, 1))
{
LOG_MSG("Failed to create USB detection thread!");
return false;
}
return true;
}
static void usbDestroyDetectionThread(void)
{
/* Signal the exit event to terminate the USB detection thread */
ueventSignal(&g_usbDetectionThreadExitEvent);
/* Wait for the USB detection thread to exit. */
utilsJoinThread(&g_usbDetectionThread);
}
static void usbDetectionThreadFunc(void *arg)
{
(void)arg;
Result rc = 0;
int idx = 0;
Waiter usb_change_event_waiter = waiterForEvent(g_usbStateChangeEvent);
Waiter usb_timeout_event_waiter = waiterForUEvent(&g_usbTimeoutEvent);
Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent);
bool exit_flag = false;
while(true)
{
/* Wait until an event is triggered. */
rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter);
if (R_FAILED(rc)) continue;
/* Exit event triggered. */
if (idx == 2) break;
SCOPED_LOCK(&g_usbInterfaceMutex)
{
/* Retrieve current USB connection status. */
/* Only proceed if we're dealing with a status change. */
g_usbHostAvailable = usbIsHostAvailable();
g_usbSessionStarted = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_usbEndpointMaxPacketSize = 0;
/* Start a USB session if we're connected to a host device. */
/* This will essentially hang this thread and all other threads that call USB-related functions until: */
/* a) A session is successfully established. */
/* b) The console is disconnected from the USB host. */
/* c) The thread exit event is triggered. */
if (g_usbHostAvailable)
{
/* Wait until a session is established. */
g_usbSessionStarted = usbStartSession();
if (g_usbSessionStarted)
{
LOG_MSG("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize);
} else {
/* Update exit flag. */
exit_flag = g_usbDetectionThreadExitFlag;
}
}
}
/* Check if the exit event was triggered while waiting for a session to be established. */
if (exit_flag) break;
}
SCOPED_LOCK(&g_usbInterfaceMutex)
{
/* Close USB session if needed. */
if (g_usbHostAvailable && g_usbSessionStarted) usbEndSession();
g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false;
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0;
g_usbEndpointMaxPacketSize = 0;
}
threadExit();
}
static bool usbStartSession(void)
{
UsbCommandStartSession *cmd_block = NULL;
bool ret = false;
if (!g_usbInterfaceInit || !g_usbTransferBuffer)
{
LOG_MSG("Invalid parameters!");
goto end;
}
usbPrepareCommandHeader(UsbCommandType_StartSession, (u32)sizeof(UsbCommandStartSession));
cmd_block = (UsbCommandStartSession*)(g_usbTransferBuffer + sizeof(UsbCommandHeader));
memset(cmd_block, 0, sizeof(UsbCommandStartSession));
cmd_block->app_ver_major = VERSION_MAJOR;
cmd_block->app_ver_minor = VERSION_MINOR;
cmd_block->app_ver_micro = VERSION_MICRO;
cmd_block->abi_version = USB_ABI_VERSION;
snprintf(cmd_block->git_commit, sizeof(cmd_block->git_commit), GIT_COMMIT);
ret = usbSendCommand();
if (ret)
{
/* Get the endpoint max packet size from the response sent by the USB host. */
/* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */
/* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */
g_usbEndpointMaxPacketSize = ((UsbStatus*)g_usbTransferBuffer)->max_packet_size;
if (g_usbEndpointMaxPacketSize != USB_FS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_HS_EP_MAX_PACKET_SIZE && g_usbEndpointMaxPacketSize != USB_SS_EP_MAX_PACKET_SIZE)
{
LOG_MSG("Invalid endpoint max packet size value received from USB host: 0x%04X.", g_usbEndpointMaxPacketSize);
/* Reset flags. */
ret = false;
g_usbEndpointMaxPacketSize = 0;
}
}
end:
return ret;
}
static void usbEndSession(void)
{
if (!g_usbInterfaceInit || !g_usbTransferBuffer || !g_usbHostAvailable || !g_usbSessionStarted)
{
LOG_MSG("Invalid parameters!");
return;
}
/* Prepare command data. */
usbPrepareCommandHeader(UsbCommandType_EndSession, 0);
/* Send command. We don't care about the result here. */
usbSendCommand();
}
NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size)
{
if (cmd > UsbCommandType_EndSession) return;
UsbCommandHeader *cmd_header = (UsbCommandHeader*)g_usbTransferBuffer;
memset(cmd_header, 0, sizeof(UsbCommandHeader));
cmd_header->magic = __builtin_bswap32(USB_CMD_HEADER_MAGIC);
cmd_header->cmd = cmd;
cmd_header->cmd_block_size = cmd_block_size;
}
static bool usbSendCommand(void)
{
UsbCommandHeader *cmd_header = (UsbCommandHeader*)g_usbTransferBuffer;
u32 cmd_block_size = cmd_header->cmd_block_size;
#if LOG_LEVEL <= LOG_LEVEL_ERROR
u32 cmd = cmd_header->cmd;
#endif
UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer;
u32 status = UsbStatusType_Success;
bool ret = false, zlt_required = false, cmd_block_written = false;
if ((sizeof(UsbCommandHeader) + cmd_block_size) > USB_TRANSFER_BUFFER_SIZE)
{
LOG_MSG("Invalid command size!");
status = UsbStatusType_InvalidCommandSize;
goto end;
}
/* Write command header first. */
if (!usbWrite(cmd_header, sizeof(UsbCommandHeader)))
{
if (!g_usbDetectionThreadExitFlag) LOG_MSG("Failed to write header for type 0x%X command!", cmd);
status = UsbStatusType_WriteCommandFailed;
goto end;
}
/* Check if we need to transfer a command block. */
if (cmd_block_size)
{
/* Move command block data within the transfer buffer to guarantee we'll work with proper alignment. */
memmove(g_usbTransferBuffer, g_usbTransferBuffer + sizeof(UsbCommandHeader), cmd_block_size);
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet after sending the command block. */
zlt_required = IS_ALIGNED(cmd_block_size, g_usbEndpointMaxPacketSize);
if (zlt_required) usbSetZltPacket(true);
/* Write command block. */
cmd_block_written = usbWrite(g_usbTransferBuffer, cmd_block_size);
if (!cmd_block_written)
{
LOG_MSG("Failed to write command block for type 0x%X command!", cmd);
status = UsbStatusType_WriteCommandFailed;
}
/* Disable ZLT if it was previously enabled. */
if (zlt_required) usbSetZltPacket(false);
/* Bail out if we failed to write the command block. */
if (!cmd_block_written) goto end;
}
/* Read status block. */
if (!usbRead(cmd_status, sizeof(UsbStatus)))
{
LOG_MSG("Failed to read 0x%lX bytes long status block for type 0x%X command!", sizeof(UsbStatus), cmd);
status = UsbStatusType_ReadStatusFailed;
goto end;
}
/* Verify magic word in status block. */
if (cmd_status->magic != __builtin_bswap32(USB_CMD_HEADER_MAGIC))
{
status = UsbStatusType_InvalidMagicWord;
goto end;
}
/* Update return value. */
ret = ((status = cmd_status->status) == UsbStatusType_Success);
end:
if (!ret) usbLogStatusDetail(status);
return ret;
}
static void usbLogStatusDetail(u32 status)
{
switch(status)
{
case UsbStatusType_Success:
case UsbStatusType_InvalidCommandSize:
case UsbStatusType_WriteCommandFailed:
case UsbStatusType_ReadStatusFailed:
break;
case UsbStatusType_InvalidMagicWord:
LOG_MSG("Host replied with Invalid Magic Word status code.");
break;
case UsbStatusType_UnsupportedCommand:
LOG_MSG("Host replied with Unsupported Command status code.");
break;
case UsbStatusType_UnsupportedAbiVersion:
LOG_MSG("Host replied with Unsupported ABI Version status code.");
break;
case UsbStatusType_MalformedCommand:
LOG_MSG("Host replied with Malformed Command status code.");
break;
case UsbStatusType_HostIoError:
LOG_MSG("Host replied with I/O Error status code.");
break;
default:
LOG_MSG("Unknown status code: 0x%X.", status);
break;
}
}
NX_INLINE bool usbAllocateTransferBuffer(void)
{
if (g_usbTransferBuffer) return true;
g_usbTransferBuffer = memalign(USB_TRANSFER_ALIGNMENT, USB_TRANSFER_BUFFER_SIZE);
return (g_usbTransferBuffer != NULL);
}
NX_INLINE void usbFreeTransferBuffer(void)
{
if (!g_usbTransferBuffer) return;
free(g_usbTransferBuffer);
g_usbTransferBuffer = NULL;
}
static bool usbInitializeComms(void)
{
Result rc = 0;
bool ret = false, init_dev_if = false;
/* Used on HOS >= 5.0.0. */
struct usb_device_descriptor device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = USB_FS_BCD_REVISION, /* USB 1.1. Updated before setting new device descriptors for USB 2.0 and 3.0. */
.bDeviceClass = 0, /* Defined at interface level. */
.bDeviceSubClass = 0, /* Defined at interface level. */
.bDeviceProtocol = 0, /* Defined at interface level. */
.bMaxPacketSize0 = USB_FS_EP0_MAX_PACKET_SIZE, /* Updated before setting the USB 3.0 device descriptor. */
.idVendor = USB_DEV_VID,
.idProduct = USB_DEV_PID,
.bcdDevice = USB_DEV_BCD_REL,
.iManufacturer = 0, /* Filled at a later time. */
.iProduct = 0, /* Filled at a later time. */
.iSerialNumber = 0, /* Filled at a later time. */
.bNumConfigurations = 1
};
static const u16 supported_langs[] = { USB_LANGID_ENUS };
static const u16 num_supported_langs = (u16)MAX_ELEMENTS(supported_langs);
u8 bos[USB_BOS_SIZE] = {0};
struct usb_bos_descriptor *bos_desc = (struct usb_bos_descriptor*)bos;
struct usb_2_0_extension_descriptor *usb2_ext_desc = (struct usb_2_0_extension_descriptor*)(bos + sizeof(struct usb_bos_descriptor));
struct usb_ss_usb_device_capability_descriptor *usb3_devcap_desc = (struct usb_ss_usb_device_capability_descriptor*)((u8*)usb2_ext_desc + sizeof(struct usb_2_0_extension_descriptor));
bos_desc->bLength = sizeof(struct usb_bos_descriptor);
bos_desc->bDescriptorType = USB_DT_BOS;
bos_desc->wTotalLength = USB_BOS_SIZE;
bos_desc->bNumDeviceCaps = 2; /* USB 2.0 + USB 3.0. No extra capabilities for USB 1.x. */
usb2_ext_desc->bLength = sizeof(struct usb_2_0_extension_descriptor);
usb2_ext_desc->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
usb2_ext_desc->bDevCapabilityType = USB_BT_USB_2_0_EXTENSION;
usb2_ext_desc->bmAttributes = USB_BM_LPM_SUPPORT;
usb3_devcap_desc->bLength = sizeof(struct usb_ss_usb_device_capability_descriptor);
usb3_devcap_desc->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
usb3_devcap_desc->bDevCapabilityType = USB_BT_SS_USB_DEVICE_CAPABILITY;
usb3_devcap_desc->bmAttributes = 0;
usb3_devcap_desc->wSpeedsSupported = (USB_SUPER_SPEED_OPERATION | USB_HIGH_SPEED_OPERATION | USB_FULL_SPEED_OPERATION);
usb3_devcap_desc->bFunctionalitySupport = 1; /* We can fully work under USB 1.x. */
usb3_devcap_desc->bU1DevExitLat = 0;
usb3_devcap_desc->bU2DevExitLat = 0;
/* Used on HOS < 5.0.0. */
static const UsbDsDeviceInfo device_info = {
.idVendor = USB_DEV_VID,
.idProduct = USB_DEV_PID,
.bcdDevice = USB_DEV_BCD_REL,
.Manufacturer = APP_AUTHOR,
.Product = APP_TITLE,
.SerialNumber = APP_VERSION
};
rc = usbDsInitialize();
if (R_FAILED(rc))
{
LOG_MSG("usbDsInitialize failed! (0x%08X).", rc);
goto end;
}
if (hosversionAtLeast(5, 0, 0))
{
/* Set language string descriptor. */
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, num_supported_langs);
if (R_FAILED(rc)) LOG_MSG("usbDsAddUsbLanguageStringDescriptor failed! (0x%08X).", rc);
/* Set manufacturer string descriptor. */
if (R_SUCCEEDED(rc))
{
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iManufacturer), APP_AUTHOR);
if (R_FAILED(rc)) LOG_MSG("usbDsAddUsbStringDescriptor failed! (0x%08X) (manufacturer).", rc);
}
/* Set product string descriptor. */
if (R_SUCCEEDED(rc))
{
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iProduct), APP_TITLE);
if (R_FAILED(rc)) LOG_MSG("usbDsAddUsbStringDescriptor failed! (0x%08X) (product).", rc);
}
/* Set serial number string descriptor. */
if (R_SUCCEEDED(rc))
{
rc = usbDsAddUsbStringDescriptor(&(device_descriptor.iSerialNumber), APP_VERSION);
if (R_FAILED(rc)) LOG_MSG("usbDsAddUsbStringDescriptor failed! (0x%08X) (serial number).", rc);
}
/* Set device descriptors. */
if (R_SUCCEEDED(rc))
{
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); /* Full Speed is USB 1.1. */
if (R_FAILED(rc)) LOG_MSG("usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 1.1).", rc);
}
if (R_SUCCEEDED(rc))
{
/* Update USB revision before proceeding. */
device_descriptor.bcdUSB = USB_HS_BCD_REVISION;
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); /* High Speed is USB 2.0. */
if (R_FAILED(rc)) LOG_MSG("usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 2.0).", rc);
}
if (R_SUCCEEDED(rc))
{
/* Update USB revision and upgrade control endpoint packet size before proceeding. */
device_descriptor.bcdUSB = USB_SS_BCD_REVISION;
device_descriptor.bMaxPacketSize0 = USB_SS_EP0_MAX_PACKET_SIZE;
rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); /* Super Speed is USB 3.0. */
if (R_FAILED(rc)) LOG_MSG("usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 3.0).", rc);
}
/* Set Binary Object Store. */
if (R_SUCCEEDED(rc))
{
rc = usbDsSetBinaryObjectStore(bos, USB_BOS_SIZE);
if (R_FAILED(rc)) LOG_MSG("usbDsSetBinaryObjectStore failed! (0x%08X).", rc);
}
} else {
/* Set VID, PID and BCD. */
rc = usbDsSetVidPidBcd(&device_info);
if (R_FAILED(rc)) LOG_MSG("usbDsSetVidPidBcd failed! (0x%08X).", rc);
}
if (R_FAILED(rc)) goto end;
/* Initialize USB device interface. */
init_dev_if = (hosversionAtLeast(5, 0, 0) ? usbInitializeDeviceInterface5x() : usbInitializeDeviceInterface1x());
if (!init_dev_if)
{
LOG_MSG("Failed to initialize USB device interface!");
goto end;
}
if (hosversionAtLeast(5, 0, 0))
{
rc = usbDsEnable();
if (R_FAILED(rc))
{
LOG_MSG("usbDsEnable failed! (0x%08X).", rc);
goto end;
}
}
ret = true;
end:
if (!ret) usbCloseComms();
return ret;
}
static void usbCloseComms(void)
{
usbDsExit();
g_usbInterface = NULL;
g_usbEndpointIn = NULL;
g_usbEndpointOut = NULL;
}
static bool usbInitializeDeviceInterface5x(void)
{
Result rc = 0;
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = USBDS_DEFAULT_InterfaceNumber,
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceProtocol = USB_CLASS_VENDOR_SPEC,
.iInterface = 0
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = USB_FS_EP_MAX_PACKET_SIZE, /* Updated before setting new device descriptors for USB 2.0 and 3.0. */
.bInterval = 0
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = USB_FS_EP_MAX_PACKET_SIZE, /* Updated before setting new device descriptors for USB 2.0 and 3.0. */
.bInterval = 0
};
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
.bMaxBurst = 0x0F,
.bmAttributes = 0,
.wBytesPerInterval = 0
};
/* Setup interface. */
rc = usbDsRegisterInterface(&g_usbInterface);
if (R_FAILED(rc))
{
LOG_MSG("usbDsRegisterInterface failed! (0x%08X).", rc);
return false;
}
interface_descriptor.bInterfaceNumber = g_usbInterface->interface_index;
endpoint_descriptor_in.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1);
endpoint_descriptor_out.bEndpointAddress += (interface_descriptor.bInterfaceNumber + 1);
/* Full Speed config (USB 1.1). */
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (interface).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (in endpoint).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (out endpoint).", rc);
return false;
}
/* High Speed config (USB 2.0). */
endpoint_descriptor_in.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
endpoint_descriptor_out.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE;
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (interface).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (in endpoint).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (out endpoint).", rc);
return false;
}
/* Super Speed config (USB 3.0). */
endpoint_descriptor_in.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
endpoint_descriptor_out.wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE;
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (interface).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint companion).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint).", rc);
return false;
}
rc = usbDsInterface_AppendConfigurationData(g_usbInterface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint companion).", rc);
return false;
}
/* Setup endpoints. */
rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointIn, endpoint_descriptor_in.bEndpointAddress);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (in endpoint).", rc);
return false;
}
rc = usbDsInterface_RegisterEndpoint(g_usbInterface, &g_usbEndpointOut, endpoint_descriptor_out.bEndpointAddress);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_RegisterEndpoint failed! (0x%08X) (out endpoint).", rc);
return false;
}
rc = usbDsInterface_EnableInterface(g_usbInterface);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc);
return false;
}
return true;
}
static bool usbInitializeDeviceInterface1x(void)
{
Result rc = 0;
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceProtocol = USB_CLASS_VENDOR_SPEC,
.iInterface = 0
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE,
.bInterval = 0
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE,
.bInterval = 0
};
/* Setup interface. */
rc = usbDsGetDsInterface(&g_usbInterface, &interface_descriptor, "usb");
if (R_FAILED(rc))
{
LOG_MSG("usbDsGetDsInterface failed! (0x%08X).", rc);
return false;
}
/* Setup endpoints. */
rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointIn, &endpoint_descriptor_in);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (in endpoint).", rc);
return false;
}
rc = usbDsInterface_GetDsEndpoint(g_usbInterface, &g_usbEndpointOut, &endpoint_descriptor_out);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_GetDsEndpoint failed! (0x%08X) (out endpoint).", rc);
return false;
}
rc = usbDsInterface_EnableInterface(g_usbInterface);
if (R_FAILED(rc))
{
LOG_MSG("usbDsInterface_EnableInterface failed! (0x%08X).", rc);
return false;
}
return true;
}
NX_INLINE bool usbIsHostAvailable(void)
{
UsbState state = UsbState_Detached;
Result rc = usbDsGetState(&state);
return (R_SUCCEEDED(rc) && state == UsbState_Configured);
}
NX_INLINE void usbSetZltPacket(bool enable)
{
usbDsEndpoint_SetZlt(g_usbEndpointIn, enable);
}
NX_INLINE bool usbRead(void *buf, u64 size)
{
return usbTransferData(buf, size, g_usbEndpointOut);
}
NX_INLINE bool usbWrite(void *buf, u64 size)
{
return usbTransferData(buf, size, g_usbEndpointIn);
}
static bool usbTransferData(void *buf, u64 size, UsbDsEndpoint *endpoint)
{
if (!buf || !IS_ALIGNED((u64)buf, USB_TRANSFER_ALIGNMENT) || !size || !endpoint)
{
LOG_MSG("Invalid parameters!");
return false;
}
if (!usbIsHostAvailable())
{
LOG_MSG("USB host unavailable!");
return false;
}
Result rc = 0;
UsbDsReportData report_data = {0};
u32 urb_id = 0, transferred_size = 0;
bool thread_exit = false;
/* Start a USB transfer using the provided endpoint. */
rc = usbDsEndpoint_PostBufferAsync(endpoint, buf, size, &urb_id);
if (R_FAILED(rc))
{
LOG_MSG("usbDsEndpoint_PostBufferAsync failed! (0x%08X) (URB ID %u).", rc, urb_id);
return false;
}
/* Wait for the transfer to finish. */
if (g_usbSessionStarted)
{
/* If the USB session has already been established, then use a regular timeout value. */
rc = eventWait(&(endpoint->CompletionEvent), USB_TRANSFER_TIMEOUT * (u64)1000000000);
} else {
/* If we're starting a USB session, wait indefinitely inside a loop to let the user start the host script. */
int idx = 0;
Waiter completion_event_waiter = waiterForEvent(&(endpoint->CompletionEvent));
Waiter exit_event_waiter = waiterForUEvent(&g_usbDetectionThreadExitEvent);
rc = waitMulti(&idx, -1, completion_event_waiter, exit_event_waiter);
if (R_SUCCEEDED(rc) && idx == 1)
{
/* Exit event triggered. */
rc = MAKERESULT(Module_Kernel, KernelError_TimedOut);
g_usbDetectionThreadExitFlag = thread_exit = true;
}
}
/* Clear the endpoint completion event. */
if (!thread_exit) eventClear(&(endpoint->CompletionEvent));
if (R_FAILED(rc))
{
/* Cancel transfer. */
usbDsEndpoint_Cancel(endpoint);
/* Safety measure: wait until the completion event is triggered again before proceeding. */
eventWait(&(endpoint->CompletionEvent), UINT64_MAX);
eventClear(&(endpoint->CompletionEvent));
/* Signal user-mode USB timeout event if needed. */
/* This will "reset" the USB connection by making the background thread wait until a new session is established. */
if (g_usbSessionStarted) ueventSignal(&g_usbTimeoutEvent);
if (!thread_exit) LOG_MSG("eventWait failed! (0x%08X) (URB ID %u).", rc, urb_id);
return false;
}
rc = usbDsEndpoint_GetReportData(endpoint, &report_data);
if (R_FAILED(rc))
{
LOG_MSG("usbDsEndpoint_GetReportData failed! (0x%08X) (URB ID %u).", rc, urb_id);
return false;
}
rc = usbDsParseReportData(&report_data, urb_id, NULL, &transferred_size);
if (R_FAILED(rc))
{
LOG_MSG("usbDsParseReportData failed! (0x%08X) (URB ID %u).", rc, urb_id);
return false;
}
if (transferred_size != size)
{
LOG_MSG("USB transfer failed! Expected 0x%lX bytes, got 0x%X bytes (URB ID %u).", size, transferred_size, urb_id);
return false;
}
return true;
}