2020-05-05 16:22:16 +01:00
/*
2020-07-03 10:31:22 +01:00
* usb . c
2020-05-05 16:22:16 +01:00
*
2020-07-03 10:31:22 +01:00
* Heavily based in usb_comms from libnx .
*
2020-07-06 01:10:07 +01:00
* Copyright ( c ) 2018 - 2020 , Switchbrew and libnx contributors .
2020-12-23 17:48:57 +00:00
* Copyright ( c ) 2020 - 2021 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software ; you can redistribute it and / or modify it
2020-05-05 16:22:16 +01:00
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
2020-07-03 10:31:22 +01:00
* nxdumptool is distributed in the hope it will be useful , but WITHOUT
2020-05-05 16:22:16 +01:00
* 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 < http : //www.gnu.org/licenses/>.
*/
# include "utils.h"
2020-07-03 10:31:22 +01:00
# include "usb.h"
2020-05-05 16:22:16 +01:00
2020-05-06 07:01:00 +01:00
# define USB_ABI_VERSION 1
2020-07-06 01:10:07 +01:00
# define USB_CMD_HEADER_MAGIC 0x4E584454 /* "NXDT". */
2020-05-05 16:22:16 +01:00
2020-07-06 01:10:07 +01:00
# define USB_TRANSFER_ALIGNMENT 0x1000 /* 4 KiB. */
2020-08-13 07:01:23 +01:00
# define USB_TRANSFER_TIMEOUT 5 /* 5 seconds. */
2020-05-05 16:22:16 +01:00
2020-08-17 22:30:47 +01:00
# define USB_FS_BCD_REVISION 0x0110
# define USB_FS_EP_MAX_PACKET_SIZE 0x40
# define USB_HS_BCD_REVISION 0x0200
# define USB_HS_EP_MAX_PACKET_SIZE 0x200
# define USB_SS_BCD_REVISION 0x0300
# define USB_SS_EP_MAX_PACKET_SIZE 0x400
2020-08-17 22:31:24 +01:00
2020-05-05 16:22:16 +01:00
/* Type definitions. */
typedef struct {
RwLock lock , lock_in , lock_out ;
bool initialized ;
UsbDsInterface * interface ;
UsbDsEndpoint * endpoint_in , * endpoint_out ;
} usbDeviceInterface ;
typedef enum {
2020-05-08 04:48:22 +01:00
UsbCommandType_StartSession = 0 ,
UsbCommandType_SendFileProperties = 1 ,
2020-10-26 06:39:33 +00:00
UsbCommandType_SendNspHeader = 2 ,
2020-05-08 04:48:22 +01:00
UsbCommandType_EndSession = 3
2020-05-05 16:22:16 +01:00
} UsbCommandType ;
typedef struct {
u32 magic ;
u32 cmd ;
u32 cmd_block_size ;
u8 reserved [ 0x4 ] ;
} UsbCommandHeader ;
typedef struct {
2020-05-06 07:01:00 +01:00
u8 app_ver_major ;
u8 app_ver_minor ;
u8 app_ver_micro ;
u8 abi_version ;
u8 reserved [ 0xC ] ;
2020-05-08 04:48:22 +01:00
} UsbCommandStartSession ;
2020-05-05 16:22:16 +01:00
typedef struct {
u64 file_size ;
u32 filename_length ;
2020-10-26 06:39:33 +00:00
u32 nsp_header_size ;
2020-05-05 16:22:16 +01:00
char filename [ FS_MAX_PATH ] ;
u8 reserved_2 [ 0xF ] ;
} UsbCommandSendFileProperties ;
typedef enum {
2020-07-03 10:31:22 +01:00
///< Expected response code.
2020-05-07 12:08:54 +01:00
UsbStatusType_Success = 0 ,
2020-05-05 16:22:16 +01:00
2020-07-03 10:31:22 +01:00
///< Internal usage.
2020-05-07 12:08:54 +01:00
UsbStatusType_InvalidCommandSize = 1 ,
UsbStatusType_WriteCommandFailed = 2 ,
UsbStatusType_ReadStatusFailed = 3 ,
2020-05-05 16:22:16 +01:00
2020-07-03 10:31:22 +01:00
///< These can be returned by the host device.
2020-05-09 05:48:46 +01:00
UsbStatusType_InvalidMagicWord = 4 ,
UsbStatusType_UnsupportedCommand = 5 ,
2020-05-07 12:08:54 +01:00
UsbStatusType_UnsupportedAbiVersion = 6 ,
2020-05-09 07:32:01 +01:00
UsbStatusType_MalformedCommand = 7 ,
UsbStatusType_HostIoError = 8
2020-05-05 16:22:16 +01:00
} UsbStatusType ;
typedef struct {
u32 magic ;
2020-10-25 15:42:53 +00:00
u32 status ; ///< UsbStatusType.
u16 max_packet_size ; ///< USB host endpoint max packet size.
u8 reserved [ 0x6 ] ;
2020-05-05 16:22:16 +01:00
} UsbStatus ;
/* Global variables. */
static RwLock g_usbDeviceLock = { 0 } ;
2020-05-13 15:09:51 +01:00
static usbDeviceInterface g_usbDeviceInterface = { 0 } ;
2020-12-07 04:26:59 +00:00
static bool g_usbDeviceInterfaceInit = false ;
2020-05-05 16:22:16 +01:00
2020-05-13 15:09:51 +01:00
static Event * g_usbStateChangeEvent = NULL ;
2020-08-18 06:04:13 +01:00
static Thread g_usbDetectionThread = { 0 } ;
2020-07-12 17:37:03 +01:00
static UEvent g_usbDetectionThreadExitEvent = { 0 } , g_usbTimeoutEvent = { 0 } ;
2020-10-26 06:39:33 +00:00
static bool g_usbHostAvailable = false , g_usbSessionStarted = false , g_usbDetectionThreadExitFlag = false , g_nspTransferMode = false ;
2020-07-12 17:37:03 +01:00
static atomic_bool g_usbDetectionThreadCreated = false ;
2020-05-08 04:48:22 +01:00
2020-05-05 16:22:16 +01:00
static u8 * g_usbTransferBuffer = NULL ;
2020-08-13 07:01:23 +01:00
static u64 g_usbTransferRemainingSize = 0 , g_usbTransferWrittenSize = 0 ;
static u32 g_usbUrbId = 0 ;
2020-08-17 22:30:47 +01:00
static u16 g_usbEndpointMaxPacketSize = 0 ;
2020-05-05 16:22:16 +01:00
/* Function prototypes. */
2020-05-13 15:09:51 +01:00
static bool usbCreateDetectionThread ( void ) ;
static void usbDestroyDetectionThread ( void ) ;
2020-08-18 06:04:13 +01:00
static void usbDetectionThreadFunc ( void * arg ) ;
2020-05-13 15:09:51 +01:00
static bool usbStartSession ( void ) ;
static void usbEndSession ( void ) ;
2020-05-07 12:08:54 +01:00
2020-05-05 16:22:16 +01:00
NX_INLINE void usbPrepareCommandHeader ( u32 cmd , u32 cmd_block_size ) ;
static u32 usbSendCommand ( size_t cmd_size ) ;
2020-08-13 07:01:23 +01:00
static void usbLogStatusDetail ( u32 status ) ;
2020-05-05 16:22:16 +01:00
NX_INLINE bool usbAllocateTransferBuffer ( void ) ;
NX_INLINE void usbFreeTransferBuffer ( void ) ;
static bool usbInitializeComms ( void ) ;
static void usbCloseComms ( void ) ;
static void usbFreeDeviceInterface ( void ) ;
NX_INLINE bool usbInitializeDeviceInterface ( void ) ;
static bool usbInitializeDeviceInterface5x ( void ) ;
static bool usbInitializeDeviceInterface1x ( void ) ;
2020-05-07 12:08:54 +01:00
NX_INLINE bool usbIsHostAvailable ( void ) ;
2020-08-13 07:01:23 +01:00
NX_INLINE void usbSetZltPacket ( bool enable ) ;
NX_INLINE bool usbRead ( void * buf , size_t size , bool reset_urb_id ) ;
NX_INLINE bool usbWrite ( void * buf , size_t size , bool reset_urb_id ) ;
2020-05-05 16:22:16 +01:00
static bool usbTransferData ( void * buf , size_t size , UsbDsEndpoint * endpoint ) ;
bool usbInitialize ( void )
{
bool ret = false ;
rwlockWriteLock ( & g_usbDeviceLock ) ;
2020-07-06 01:10:07 +01:00
/* Allocate USB transfer buffer. */
2020-05-05 16:22:16 +01:00
if ( ! usbAllocateTransferBuffer ( ) )
{
LOGFILE ( " Failed to allocate memory for the USB transfer buffer! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* Initialize USB device interface. */
2020-05-05 16:22:16 +01:00
if ( ! usbInitializeComms ( ) )
{
LOGFILE ( " Failed to initialize USB device interface! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* Retrieve USB state change kernel event. */
2020-05-13 15:09:51 +01:00
g_usbStateChangeEvent = usbDsGetStateChangeEvent ( ) ;
if ( ! g_usbStateChangeEvent )
{
LOGFILE ( " Failed to retrieve USB state change kernel event! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-13 15:09:51 +01:00
}
2020-11-28 06:38:53 +00:00
/* Create user-mode exit event. */
2020-07-12 17:37:03 +01:00
ueventCreate ( & g_usbDetectionThreadExitEvent , true ) ;
2020-11-28 06:38:53 +00:00
/* Create user-mode USB timeout event. */
2020-07-03 10:31:22 +01:00
ueventCreate ( & g_usbTimeoutEvent , true ) ;
2020-07-06 01:10:07 +01:00
/* Create USB detection thread. */
2020-07-12 17:37:03 +01:00
atomic_store ( & g_usbDetectionThreadCreated , usbCreateDetectionThread ( ) ) ;
2020-07-13 07:36:17 +01:00
if ( ! atomic_load ( & g_usbDetectionThreadCreated ) ) goto end ;
2020-05-13 15:09:51 +01:00
2020-05-05 16:22:16 +01:00
ret = true ;
2020-07-13 07:36:17 +01:00
end :
2020-05-05 16:22:16 +01:00
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
return ret ;
}
void usbExit ( void )
{
2020-07-12 17:37:03 +01:00
/* Destroy USB detection thread before attempting to lock. */
if ( atomic_load ( & g_usbDetectionThreadCreated ) )
2020-05-13 15:09:51 +01:00
{
usbDestroyDetectionThread ( ) ;
2020-07-12 17:37:03 +01:00
atomic_store ( & g_usbDetectionThreadCreated , false ) ;
2020-05-13 15:09:51 +01:00
}
2020-07-12 17:37:03 +01:00
/* Now we can safely lock. */
2020-07-12 16:29:08 +01:00
rwlockWriteLock ( & g_usbDeviceLock ) ;
2020-07-06 01:10:07 +01:00
/* Clear USB state change kernel event. */
2020-05-13 15:09:51 +01:00
g_usbStateChangeEvent = NULL ;
2020-07-06 01:10:07 +01:00
/* Close USB device interface. */
2020-05-05 16:22:16 +01:00
usbCloseComms ( ) ;
2020-07-06 01:10:07 +01:00
/* Free USB transfer buffer. */
2020-05-05 16:22:16 +01:00
usbFreeTransferBuffer ( ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
}
2020-05-07 12:08:54 +01:00
void * usbAllocatePageAlignedBuffer ( size_t size )
2020-05-05 16:22:16 +01:00
{
2020-05-07 12:08:54 +01:00
if ( ! size ) return NULL ;
return memalign ( USB_TRANSFER_ALIGNMENT , size ) ;
2020-05-05 16:22:16 +01:00
}
2020-05-13 15:09:51 +01:00
bool usbIsReady ( void )
2020-05-05 16:22:16 +01:00
{
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
2020-05-13 15:09:51 +01:00
bool ret = ( g_usbHostAvailable & & g_usbSessionStarted ) ;
2020-05-05 16:22:16 +01:00
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
return ret ;
}
2020-10-26 06:39:33 +00:00
bool usbSendFileProperties ( u64 file_size , const char * filename , u32 nsp_header_size )
2020-05-05 16:22:16 +01:00
{
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
bool ret = false ;
UsbCommandSendFileProperties * cmd_block = NULL ;
size_t cmd_size = 0 ;
u32 status = UsbStatusType_Success ;
2020-10-26 06:39:33 +00:00
size_t filename_length = 0 ;
2020-05-05 16:22:16 +01:00
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized | | ! g_usbHostAvailable | | ! g_usbSessionStarted | | ! filename | | \
2020-10-26 06:39:33 +00:00
! ( 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 ) )
2020-05-05 16:22:16 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
usbPrepareCommandHeader ( UsbCommandType_SendFileProperties , ( u32 ) sizeof ( UsbCommandSendFileProperties ) ) ;
cmd_block = ( UsbCommandSendFileProperties * ) ( g_usbTransferBuffer + sizeof ( UsbCommandHeader ) ) ;
memset ( cmd_block , 0 , sizeof ( UsbCommandSendFileProperties ) ) ;
2020-05-06 15:04:10 +01:00
2020-05-05 16:22:16 +01:00
cmd_block - > file_size = file_size ;
2020-10-26 06:39:33 +00:00
cmd_block - > filename_length = ( u32 ) filename_length ;
cmd_block - > nsp_header_size = nsp_header_size ;
2020-07-03 10:31:22 +01:00
sprintf ( cmd_block - > filename , " %s " , filename ) ;
2020-05-05 16:22:16 +01:00
cmd_size = ( sizeof ( UsbCommandHeader ) + sizeof ( UsbCommandSendFileProperties ) ) ;
status = usbSendCommand ( cmd_size ) ;
if ( status = = UsbStatusType_Success )
{
ret = true ;
g_usbTransferRemainingSize = file_size ;
2020-08-13 07:01:23 +01:00
g_usbTransferWrittenSize = 0 ;
2020-10-26 06:39:33 +00:00
if ( ! g_nspTransferMode ) g_nspTransferMode = ( file_size & & nsp_header_size ) ;
2020-05-05 16:22:16 +01:00
} else {
usbLogStatusDetail ( status ) ;
}
2020-07-13 07:36:17 +01:00
end :
2020-10-26 06:39:33 +00:00
if ( ! ret & & g_nspTransferMode ) g_nspTransferMode = false ;
2020-05-05 16:22:16 +01:00
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
return ret ;
}
2020-05-07 12:08:54 +01:00
bool usbSendFileData ( void * data , u64 data_size )
2020-05-05 16:22:16 +01:00
{
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
2020-05-06 15:04:10 +01:00
void * buf = NULL ;
UsbStatus * cmd_status = NULL ;
2020-08-13 07:01:23 +01:00
bool ret = false , zlt_required = false ;
2020-05-05 16:22:16 +01:00
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized | | ! g_usbHostAvailable | | ! g_usbSessionStarted | | ! g_usbTransferRemainingSize | | ! data | | \
2020-05-13 15:09:51 +01:00
! data_size | | data_size > USB_TRANSFER_BUFFER_SIZE | | data_size > g_usbTransferRemainingSize )
2020-05-05 16:22:16 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* Optimization for buffers that already are page aligned. */
2020-05-11 20:12:03 +01:00
if ( IS_ALIGNED ( ( u64 ) data , USB_TRANSFER_ALIGNMENT ) )
2020-05-06 15:04:10 +01:00
{
buf = data ;
} else {
buf = g_usbTransferBuffer ;
memcpy ( buf , data , data_size ) ;
}
2020-05-05 16:22:16 +01:00
2020-08-13 07:01:23 +01:00
/* 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 )
{
2020-08-17 22:30:47 +01:00
/* Enable ZLT if the last chunk size is aligned to the USB endpoint max packet size. */
if ( IS_ALIGNED ( data_size , g_usbEndpointMaxPacketSize ) )
2020-08-13 07:01:23 +01:00
{
zlt_required = true ;
usbSetZltPacket ( true ) ;
//LOGFILE("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 ) ;
//LOGFILE("ZLT disabled (first chunk).");
}
}
/* Send data chunk. */
/* Make sure to reset the URB ID if this is the first chunk. */
if ( ! usbWrite ( buf , data_size , ! g_usbTransferWrittenSize ) )
2020-05-05 16:22:16 +01:00
{
2020-08-13 07:01:23 +01:00
LOGFILE ( " 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 ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
ret = true ;
g_usbTransferRemainingSize - = data_size ;
2020-08-13 07:01:23 +01:00
g_usbTransferWrittenSize + = data_size ;
2020-05-05 16:22:16 +01:00
2020-07-06 01:10:07 +01:00
/* Check if this is the last chunk. */
2020-05-05 16:22:16 +01:00
if ( ! g_usbTransferRemainingSize )
{
2020-08-13 07:01:23 +01:00
/* Check response from host device. */
if ( ! usbRead ( g_usbTransferBuffer , sizeof ( UsbStatus ) , true ) )
2020-05-05 16:22:16 +01:00
{
2020-05-06 15:04:10 +01:00
LOGFILE ( " Failed to read 0x%lX bytes long status block! " , sizeof ( UsbStatus ) ) ;
2020-05-05 16:22:16 +01:00
ret = false ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
cmd_status = ( UsbStatus * ) g_usbTransferBuffer ;
if ( cmd_status - > magic ! = __builtin_bswap32 ( USB_CMD_HEADER_MAGIC ) )
{
LOGFILE ( " Invalid status block magic word! " ) ;
ret = false ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
ret = ( cmd_status - > status = = UsbStatusType_Success ) ;
if ( ! ret ) usbLogStatusDetail ( cmd_status - > status ) ;
}
2020-07-13 07:36:17 +01:00
end :
2020-08-17 22:30:47 +01:00
/* Disable ZLT if it was previously enabled. */
if ( zlt_required ) usbSetZltPacket ( false ) ;
2020-10-26 06:39:33 +00:00
/* Reset variables in case of errors. */
if ( ! ret )
{
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0 ;
g_nspTransferMode = false ;
}
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
return ret ;
}
bool usbSendNspHeader ( void * nsp_header , u32 nsp_header_size )
{
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
size_t cmd_size = 0 ;
u32 status = UsbStatusType_Success ;
bool ret = false , zlt_required = false ;
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized | | ! g_usbHostAvailable | | ! g_usbSessionStarted | | g_usbTransferRemainingSize | | \
2020-10-26 06:39:33 +00:00
! g_nspTransferMode | | ! nsp_header | | ! nsp_header_size | | nsp_header_size > ( USB_TRANSFER_BUFFER_SIZE - sizeof ( UsbCommandHeader ) ) )
{
LOGFILE ( " Invalid parameters! " ) ;
goto end ;
}
2020-05-05 16:22:16 +01:00
2020-10-26 06:39:33 +00:00
/* 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 ) ;
cmd_size = ( sizeof ( UsbCommandHeader ) + nsp_header_size ) ;
/* Determine if we'll need to set a Zero Length Termination (ZLT) packet. */
zlt_required = IS_ALIGNED ( cmd_size , g_usbEndpointMaxPacketSize ) ;
if ( zlt_required )
{
usbSetZltPacket ( true ) ;
//LOGFILE("ZLT enabled. SendNspHeader command size: 0x%lX.", cmd_size);
}
/* Send command. */
ret = ( ( status = usbSendCommand ( cmd_size ) ) = = UsbStatusType_Success ) ;
if ( ! ret ) usbLogStatusDetail ( status ) ;
/* Disable ZLT if it was previously enabled. */
if ( zlt_required ) usbSetZltPacket ( false ) ;
end :
2020-05-05 16:22:16 +01:00
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
return ret ;
}
2020-08-13 07:01:23 +01:00
void usbCancelFileTransfer ( void )
{
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_in ) ) ;
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized | | ! g_usbHostAvailable | | ! g_usbSessionStarted | | ( ! g_usbTransferRemainingSize & & \
2020-10-26 06:39:33 +00:00
! g_nspTransferMode ) ) goto end ;
/* Reset variables. */
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0 ;
g_nspTransferMode = false ;
2020-08-13 07:01:23 +01:00
/* Disable ZLT, just in case it was previously enabled. */
usbDsEndpoint_SetZlt ( g_usbDeviceInterface . endpoint_in , false ) ;
/* Stall input (write) endpoint. */
/* This will force the client to stop the current session, so a new one will have to be established. */
usbDsEndpoint_Stall ( g_usbDeviceInterface . endpoint_in ) ;
2020-11-28 06:38:53 +00:00
/* Signal user-mode USB timeout event. */
2020-08-13 07:01:23 +01:00
/* This will "reset" the USB connection by making the background thread wait until a new session is established. */
ueventSignal ( & g_usbTimeoutEvent ) ;
end :
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_in ) ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
}
2020-05-13 15:09:51 +01:00
static bool usbCreateDetectionThread ( void )
2020-05-08 04:48:22 +01:00
{
2020-08-18 06:04:13 +01:00
if ( ! utilsCreateThread ( & g_usbDetectionThread , usbDetectionThreadFunc , NULL , 1 ) )
2020-05-08 04:48:22 +01:00
{
2020-05-13 15:09:51 +01:00
LOGFILE ( " Failed to create USB detection thread! " ) ;
return false ;
2020-05-08 04:48:22 +01:00
}
2020-05-13 15:09:51 +01:00
return true ;
}
static void usbDestroyDetectionThread ( void )
{
2020-07-12 17:37:03 +01:00
/* Signal the exit event to terminate the USB detection thread */
ueventSignal ( & g_usbDetectionThreadExitEvent ) ;
2020-07-06 01:10:07 +01:00
/* Wait for the USB detection thread to exit. */
2020-08-18 06:04:13 +01:00
utilsJoinThread ( & g_usbDetectionThread ) ;
2020-05-13 15:09:51 +01:00
}
2020-08-18 06:04:13 +01:00
static void usbDetectionThreadFunc ( void * arg )
2020-05-13 15:09:51 +01:00
{
( void ) arg ;
2020-05-08 04:48:22 +01:00
2020-05-13 15:09:51 +01:00
Result rc = 0 ;
int idx = 0 ;
2020-05-08 04:48:22 +01:00
2020-07-03 10:31:22 +01:00
Waiter usb_change_event_waiter = waiterForEvent ( g_usbStateChangeEvent ) ;
Waiter usb_timeout_event_waiter = waiterForUEvent ( & g_usbTimeoutEvent ) ;
2020-07-12 17:37:03 +01:00
Waiter exit_event_waiter = waiterForUEvent ( & g_usbDetectionThreadExitEvent ) ;
2020-05-13 15:09:51 +01:00
while ( true )
{
2020-10-28 05:03:48 +00:00
/* 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 ;
2020-05-13 15:09:51 +01:00
rwlockWriteLock ( & g_usbDeviceLock ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
2020-07-12 17:37:03 +01:00
/* Exit event triggered. */
if ( idx = = 2 ) break ;
2020-07-07 17:39:20 +01:00
/* Retrieve current USB connection status. */
2020-07-06 01:10:07 +01:00
/* Only proceed if we're dealing with a status change. */
2020-05-13 15:09:51 +01:00
g_usbHostAvailable = usbIsHostAvailable ( ) ;
2020-10-28 05:03:48 +00:00
g_usbSessionStarted = false ;
2020-08-13 07:01:23 +01:00
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0 ;
2020-08-17 22:30:47 +01:00
g_usbEndpointMaxPacketSize = 0 ;
2020-05-13 15:09:51 +01:00
2020-07-12 17:37:03 +01:00
/* Start an 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: */
2020-08-17 22:30:47 +01:00
/* a) A session is successfully established. */
/* b) The console is disconnected from the USB host. */
2020-07-12 17:37:03 +01:00
/* c) The thread exit event is triggered. */
if ( g_usbHostAvailable )
{
/* Wait until a session is established. */
2020-08-27 20:18:31 +01:00
g_usbSessionStarted = usbStartSession ( ) ;
if ( g_usbSessionStarted )
{
2020-10-25 15:42:53 +00:00
LOGFILE ( " USB session successfully established. Endpoint max packet size: 0x%04X. " , g_usbEndpointMaxPacketSize ) ;
2020-08-27 20:18:31 +01:00
} else {
/* Check if the exit event was triggered while waiting for a session to be established. */
if ( g_usbDetectionThreadExitFlag ) break ;
}
2020-07-12 17:37:03 +01:00
}
2020-05-13 15:09:51 +01:00
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
}
2020-07-06 01:10:07 +01:00
/* Close USB session if needed. */
2020-05-13 15:09:51 +01:00
if ( g_usbHostAvailable & & g_usbSessionStarted ) usbEndSession ( ) ;
2020-07-12 17:37:03 +01:00
g_usbHostAvailable = g_usbSessionStarted = g_usbDetectionThreadExitFlag = false ;
2020-08-13 07:01:23 +01:00
g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0 ;
2020-08-17 22:30:47 +01:00
g_usbEndpointMaxPacketSize = 0 ;
2020-05-13 15:09:51 +01:00
2020-07-12 17:37:03 +01:00
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteUnlock ( & g_usbDeviceLock ) ;
2020-08-18 06:04:13 +01:00
threadExit ( ) ;
2020-05-08 04:48:22 +01:00
}
2020-05-13 15:09:51 +01:00
static bool usbStartSession ( void )
2020-05-07 12:08:54 +01:00
{
2020-05-08 04:48:22 +01:00
UsbCommandStartSession * cmd_block = NULL ;
2020-05-07 12:08:54 +01:00
size_t cmd_size = 0 ;
u32 status = UsbStatusType_Success ;
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized )
2020-05-07 12:08:54 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
2020-05-08 04:48:22 +01:00
usbPrepareCommandHeader ( UsbCommandType_StartSession , ( u32 ) sizeof ( UsbCommandStartSession ) ) ;
2020-05-07 12:08:54 +01:00
2020-05-08 04:48:22 +01:00
cmd_block = ( UsbCommandStartSession * ) ( g_usbTransferBuffer + sizeof ( UsbCommandHeader ) ) ;
memset ( cmd_block , 0 , sizeof ( UsbCommandStartSession ) ) ;
2020-05-07 12:08:54 +01:00
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 ;
2020-05-08 04:48:22 +01:00
cmd_size = ( sizeof ( UsbCommandHeader ) + sizeof ( UsbCommandStartSession ) ) ;
2020-05-07 12:08:54 +01:00
status = usbSendCommand ( cmd_size ) ;
2020-10-25 15:42:53 +00:00
if ( status = = UsbStatusType_Success )
{
/* 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. */
UsbStatus * cmd_status = ( UsbStatus * ) g_usbTransferBuffer ;
g_usbEndpointMaxPacketSize = cmd_status - > 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 )
{
LOGFILE ( " Invalid endpoint max packet size value received from USB host: 0x%04X. " , g_usbEndpointMaxPacketSize ) ;
/* Reset flags. */
g_usbEndpointMaxPacketSize = 0 ;
cmd_status - > status = status = UsbStatusType_HostIoError ;
}
} else {
usbLogStatusDetail ( status ) ;
}
2020-05-07 12:08:54 +01:00
return ( status = = UsbStatusType_Success ) ;
}
2020-05-13 15:09:51 +01:00
static void usbEndSession ( void )
{
2020-12-07 04:26:59 +00:00
if ( ! g_usbTransferBuffer | | ! g_usbDeviceInterfaceInit | | ! g_usbDeviceInterface . initialized | | ! g_usbHostAvailable | | ! g_usbSessionStarted )
2020-05-13 15:09:51 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
return ;
}
usbPrepareCommandHeader ( UsbCommandType_EndSession , 0 ) ;
2020-08-13 07:01:23 +01:00
if ( ! usbWrite ( g_usbTransferBuffer , sizeof ( UsbCommandHeader ) , true ) ) LOGFILE ( " Failed to send EndSession command! " ) ;
2020-05-13 15:09:51 +01:00
}
2020-05-05 16:22:16 +01:00
NX_INLINE void usbPrepareCommandHeader ( u32 cmd , u32 cmd_block_size )
{
2020-05-08 04:48:22 +01:00
if ( cmd > UsbCommandType_EndSession ) return ;
2020-05-05 16:22:16 +01:00
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 u32 usbSendCommand ( size_t cmd_size )
{
u32 cmd = ( ( UsbCommandHeader * ) g_usbTransferBuffer ) - > cmd ;
UsbStatus * cmd_status = NULL ;
2020-05-06 15:04:10 +01:00
if ( cmd_size < sizeof ( UsbCommandHeader ) | | cmd_size > USB_TRANSFER_BUFFER_SIZE )
2020-05-05 16:22:16 +01:00
{
LOGFILE ( " Invalid command size! " ) ;
return UsbStatusType_InvalidCommandSize ;
}
2020-08-13 07:01:23 +01:00
if ( ! usbWrite ( g_usbTransferBuffer , cmd_size , true ) )
2020-05-05 16:22:16 +01:00
{
2020-07-23 22:57:43 +01:00
/* Log error message only if the USB session has been started, or if thread exit flag hasn't been enabled. */
if ( g_usbSessionStarted | | ! g_usbDetectionThreadExitFlag ) LOGFILE ( " Failed to write 0x%lX bytes long block for type 0x%X command! " , cmd_size , cmd ) ;
2020-05-05 16:22:16 +01:00
return UsbStatusType_WriteCommandFailed ;
}
2020-08-17 22:30:47 +01:00
u64 read_size = sizeof ( UsbStatus ) ;
if ( ! usbRead ( g_usbTransferBuffer , read_size , true ) )
2020-05-05 16:22:16 +01:00
{
2020-07-23 22:57:43 +01:00
/* Log error message only if the USB session has been started, or if thread exit flag hasn't been enabled. */
if ( g_usbSessionStarted | | ! g_usbDetectionThreadExitFlag ) LOGFILE ( " Failed to read 0x%lX bytes long status block for type 0x%X command! " , sizeof ( UsbStatus ) , cmd ) ;
2020-05-05 16:22:16 +01:00
return UsbStatusType_ReadStatusFailed ;
}
cmd_status = ( UsbStatus * ) g_usbTransferBuffer ;
if ( cmd_status - > magic ! = __builtin_bswap32 ( USB_CMD_HEADER_MAGIC ) )
{
LOGFILE ( " Invalid status block magic word for type 0x%X command! " , cmd ) ;
return UsbStatusType_InvalidMagicWord ;
}
return cmd_status - > status ;
}
2020-08-13 07:01:23 +01:00
static void usbLogStatusDetail ( u32 status )
2020-05-05 16:22:16 +01:00
{
switch ( status )
{
case UsbStatusType_Success :
case UsbStatusType_InvalidCommandSize :
case UsbStatusType_WriteCommandFailed :
case UsbStatusType_ReadStatusFailed :
2020-05-09 05:48:46 +01:00
break ;
2020-05-05 16:22:16 +01:00
case UsbStatusType_InvalidMagicWord :
2020-05-09 05:48:46 +01:00
LOGFILE ( " Host replied with Invalid Magic Word status code. " ) ;
2020-05-05 16:22:16 +01:00
break ;
2020-05-09 05:48:46 +01:00
case UsbStatusType_UnsupportedCommand :
LOGFILE ( " Host replied with Unsupported Command status code. " ) ;
2020-05-05 16:22:16 +01:00
break ;
2020-05-06 07:01:00 +01:00
case UsbStatusType_UnsupportedAbiVersion :
LOGFILE ( " Host replied with Unsupported ABI Version status code. " ) ;
2020-05-05 16:22:16 +01:00
break ;
2020-05-09 07:32:01 +01:00
case UsbStatusType_MalformedCommand :
LOGFILE ( " Host replied with Malformed Command status code. " ) ;
break ;
2020-05-05 16:22:16 +01:00
case UsbStatusType_HostIoError :
LOGFILE ( " Host replied with I/O Error status code. " ) ;
break ;
default :
LOGFILE ( " 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 ;
2020-12-07 04:26:59 +00:00
bool ret = ( g_usbDeviceInterfaceInit & & g_usbDeviceInterface . initialized ) ;
2020-07-13 07:36:17 +01:00
if ( ret ) goto end ;
2020-05-05 16:22:16 +01:00
rc = usbDsInitialize ( ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInitialize failed! (0x%08X). " , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
2020-10-21 05:27:48 +01:00
if ( hosversionAtLeast ( 5 , 0 , 0 ) )
2020-05-05 16:22:16 +01:00
{
u8 manufacturer = 0 , product = 0 , serial_number = 0 ;
static const u16 supported_langs [ 1 ] = { 0x0409 } ;
2020-08-17 22:30:47 +01:00
/* Set language. */
2020-05-05 16:22:16 +01:00
rc = usbDsAddUsbLanguageStringDescriptor ( NULL , supported_langs , sizeof ( supported_langs ) / sizeof ( u16 ) ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsAddUsbLanguageStringDescriptor failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
2020-08-17 22:30:47 +01:00
/* Set manufacturer. */
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsAddUsbStringDescriptor ( & manufacturer , APP_AUTHOR ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsAddUsbStringDescriptor failed! (0x%08X) (manufacturer). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-08-17 22:30:47 +01:00
/* Set product. */
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsAddUsbStringDescriptor ( & product , APP_TITLE ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsAddUsbStringDescriptor failed! (0x%08X) (product). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-08-17 22:30:47 +01:00
/* Set serial number. */
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsAddUsbStringDescriptor ( & serial_number , APP_VERSION ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsAddUsbStringDescriptor failed! (0x%08X) (serial number). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-08-17 22:30:47 +01:00
/* Set device descriptors. */
2020-05-10 10:07:31 +01:00
struct usb_device_descriptor device_descriptor = {
. bLength = USB_DT_DEVICE_SIZE ,
. bDescriptorType = USB_DT_DEVICE ,
2020-08-17 22:30:47 +01:00
. bcdUSB = USB_FS_BCD_REVISION ,
2020-05-10 10:07:31 +01:00
. bDeviceClass = 0x00 ,
. bDeviceSubClass = 0x00 ,
. bDeviceProtocol = 0x00 ,
. bMaxPacketSize0 = 0x40 ,
. idVendor = 0x057e ,
. idProduct = 0x3000 ,
. bcdDevice = 0x0100 ,
. iManufacturer = manufacturer ,
. iProduct = product ,
. iSerialNumber = serial_number ,
. bNumConfigurations = 0x01
} ;
2020-05-05 16:22:16 +01:00
2020-07-06 01:10:07 +01:00
/* Full Speed is USB 1.1. */
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsSetUsbDeviceDescriptor ( UsbDeviceSpeed_Full , & device_descriptor ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 1.1). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* High Speed is USB 2.0. */
2020-08-17 22:30:47 +01:00
device_descriptor . bcdUSB = USB_HS_BCD_REVISION ;
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsSetUsbDeviceDescriptor ( UsbDeviceSpeed_High , & device_descriptor ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 2.0). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* Super Speed is USB 3.0. */
2020-08-13 07:01:23 +01:00
/* Upgrade packet size to 512 (1 << 9). */
2020-08-17 22:30:47 +01:00
device_descriptor . bcdUSB = USB_SS_BCD_REVISION ;
2020-05-05 16:22:16 +01:00
device_descriptor . bMaxPacketSize0 = 0x09 ;
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsSetUsbDeviceDescriptor ( UsbDeviceSpeed_Super , & device_descriptor ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsSetUsbDeviceDescriptor failed! (0x%08X) (USB 3.0). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-07-06 01:10:07 +01:00
/* Define Binary Object Store. */
2020-05-10 10:07:31 +01:00
u8 bos [ 0x16 ] = {
2020-07-06 01:10:07 +01:00
/* USB 1.1. */
0x05 , /* bLength. */
USB_DT_BOS , /* bDescriptorType. */
0x16 , 0x00 , /* wTotalLength. */
0x02 , /* bNumDeviceCaps. */
2020-05-10 10:07:31 +01:00
2020-07-06 01:10:07 +01:00
/* USB 2.0. */
0x07 , /* bLength. */
USB_DT_DEVICE_CAPABILITY , /* bDescriptorType. */
0x02 , /* bDevCapabilityType. */
0x02 , 0x00 , 0x00 , 0x00 , /* dev_capability_data. */
2020-05-10 10:07:31 +01:00
2020-07-06 01:10:07 +01:00
/* USB 3.0. */
0x0A , /* bLength. */
USB_DT_DEVICE_CAPABILITY , /* bDescriptorType. */
0x03 , /* bDevCapabilityType. */
2020-05-10 10:07:31 +01:00
0x00 , 0x0E , 0x00 , 0x03 , 0x00 , 0x00 , 0x00
} ;
2020-05-05 16:22:16 +01:00
if ( R_SUCCEEDED ( rc ) )
{
rc = usbDsSetBinaryObjectStore ( bos , sizeof ( bos ) ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsSetBinaryObjectStore failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-05-07 12:08:54 +01:00
} else {
static const UsbDsDeviceInfo device_info = {
. idVendor = 0x057e ,
. idProduct = 0x3000 ,
. bcdDevice = 0x0100 ,
. Manufacturer = APP_AUTHOR ,
. Product = APP_TITLE ,
. SerialNumber = APP_VERSION
} ;
2020-07-06 01:10:07 +01:00
/* Set VID, PID and BCD. */
2020-05-07 12:08:54 +01:00
rc = usbDsSetVidPidBcd ( & device_info ) ;
2020-07-06 01:10:07 +01:00
if ( R_FAILED ( rc ) ) LOGFILE ( " usbDsSetVidPidBcd failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
}
2020-07-13 07:36:17 +01:00
if ( R_FAILED ( rc ) ) goto end ;
2020-05-05 16:22:16 +01:00
2020-07-06 01:10:07 +01:00
/* Initialize USB device interface. */
2020-05-05 16:22:16 +01:00
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_in ) ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_out ) ) ;
bool dev_iface_init = usbInitializeDeviceInterface ( ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_out ) ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_in ) ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
if ( ! dev_iface_init )
{
LOGFILE ( " Failed to initialize USB device interface! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
2020-10-21 05:27:48 +01:00
if ( hosversionAtLeast ( 5 , 0 , 0 ) )
2020-05-05 16:22:16 +01:00
{
rc = usbDsEnable ( ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsEnable failed! (0x%08X). " , rc ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-05-05 16:22:16 +01:00
}
}
2020-12-07 04:26:59 +00:00
ret = g_usbDeviceInterfaceInit = true ;
2020-05-05 16:22:16 +01:00
2020-07-13 07:36:17 +01:00
end :
2020-05-05 16:22:16 +01:00
if ( ! ret ) usbCloseComms ( ) ;
return ret ;
}
static void usbCloseComms ( void )
{
usbDsExit ( ) ;
2020-12-07 04:26:59 +00:00
g_usbDeviceInterfaceInit = false ;
2020-05-05 16:22:16 +01:00
usbFreeDeviceInterface ( ) ;
}
static void usbFreeDeviceInterface ( void )
{
rwlockWriteLock ( & ( g_usbDeviceInterface . lock ) ) ;
if ( ! g_usbDeviceInterface . initialized ) {
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
return ;
}
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_in ) ) ;
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_out ) ) ;
g_usbDeviceInterface . initialized = false ;
g_usbDeviceInterface . interface = NULL ;
g_usbDeviceInterface . endpoint_in = NULL ;
g_usbDeviceInterface . endpoint_out = NULL ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_out ) ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_in ) ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock ) ) ;
}
NX_INLINE bool usbInitializeDeviceInterface ( void )
{
2020-10-21 05:27:48 +01:00
return ( hosversionAtLeast ( 5 , 0 , 0 ) ? usbInitializeDeviceInterface5x ( ) : usbInitializeDeviceInterface1x ( ) ) ;
2020-05-05 16:22:16 +01:00
}
static bool usbInitializeDeviceInterface5x ( void )
{
Result rc = 0 ;
struct usb_interface_descriptor interface_descriptor = {
. bLength = USB_DT_INTERFACE_SIZE ,
. bDescriptorType = USB_DT_INTERFACE ,
. bInterfaceNumber = 4 ,
. bNumEndpoints = 2 ,
. bInterfaceClass = USB_CLASS_VENDOR_SPEC ,
. bInterfaceSubClass = USB_CLASS_VENDOR_SPEC ,
. bInterfaceProtocol = USB_CLASS_VENDOR_SPEC ,
} ;
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 ,
2020-08-17 22:30:47 +01:00
. wMaxPacketSize = USB_FS_EP_MAX_PACKET_SIZE ,
2020-05-05 16:22:16 +01:00
} ;
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 ,
2020-08-17 22:30:47 +01:00
. wMaxPacketSize = USB_FS_EP_MAX_PACKET_SIZE ,
2020-05-05 16:22:16 +01:00
} ;
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 = 0x00 ,
. wBytesPerInterval = 0x00 ,
} ;
2020-07-06 01:10:07 +01:00
/* Enable device interface. */
2020-05-05 16:22:16 +01:00
g_usbDeviceInterface . initialized = true ;
2020-07-06 01:10:07 +01:00
/* Setup interface. */
2020-05-05 16:22:16 +01:00
rc = usbDsRegisterInterface ( & ( g_usbDeviceInterface . interface ) ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsRegisterInterface failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
interface_descriptor . bInterfaceNumber = g_usbDeviceInterface . interface - > interface_index ;
endpoint_descriptor_in . bEndpointAddress + = ( interface_descriptor . bInterfaceNumber + 1 ) ;
endpoint_descriptor_out . bEndpointAddress + = ( interface_descriptor . bInterfaceNumber + 1 ) ;
2020-07-06 01:10:07 +01:00
/* Full Speed config (USB 1.1). */
2020-05-05 16:22:16 +01:00
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Full , & interface_descriptor , USB_DT_INTERFACE_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (interface). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Full , & endpoint_descriptor_in , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (in endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Full , & endpoint_descriptor_out , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 1.1) (out endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* High Speed config (USB 2.0). */
2020-08-17 22:30:47 +01:00
endpoint_descriptor_in . wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE ;
endpoint_descriptor_out . wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE ;
2020-05-05 16:22:16 +01:00
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_High , & interface_descriptor , USB_DT_INTERFACE_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (interface). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_High , & endpoint_descriptor_in , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (in endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_High , & endpoint_descriptor_out , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 2.0) (out endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* Super Speed config (USB 3.0). */
2020-08-17 22:30:47 +01:00
endpoint_descriptor_in . wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE ;
endpoint_descriptor_out . wMaxPacketSize = USB_SS_EP_MAX_PACKET_SIZE ;
2020-05-05 16:22:16 +01:00
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Super , & interface_descriptor , USB_DT_INTERFACE_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (interface). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Super , & endpoint_descriptor_in , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Super , & endpoint_companion , USB_DT_SS_ENDPOINT_COMPANION_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (in endpoint companion). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Super , & endpoint_descriptor_out , USB_DT_ENDPOINT_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_AppendConfigurationData ( g_usbDeviceInterface . interface , UsbDeviceSpeed_Super , & endpoint_companion , USB_DT_SS_ENDPOINT_COMPANION_SIZE ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_AppendConfigurationData failed! (0x%08X) (USB 3.0) (out endpoint companion). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* Setup endpoints. */
2020-05-05 16:22:16 +01:00
rc = usbDsInterface_RegisterEndpoint ( g_usbDeviceInterface . interface , & ( g_usbDeviceInterface . endpoint_in ) , endpoint_descriptor_in . bEndpointAddress ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_RegisterEndpoint failed! (0x%08X) (in endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_RegisterEndpoint ( g_usbDeviceInterface . interface , & ( g_usbDeviceInterface . endpoint_out ) , endpoint_descriptor_out . bEndpointAddress ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_RegisterEndpoint failed! (0x%08X) (out endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_EnableInterface ( g_usbDeviceInterface . interface ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_EnableInterface failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
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 ,
. bInterfaceClass = USB_CLASS_VENDOR_SPEC ,
. bInterfaceSubClass = USB_CLASS_VENDOR_SPEC ,
. bInterfaceProtocol = USB_CLASS_VENDOR_SPEC ,
} ;
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 ,
2020-08-17 22:30:47 +01:00
. wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE ,
2020-05-05 16:22:16 +01:00
} ;
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 ,
2020-08-17 22:30:47 +01:00
. wMaxPacketSize = USB_HS_EP_MAX_PACKET_SIZE ,
2020-05-05 16:22:16 +01:00
} ;
2020-07-06 01:10:07 +01:00
/* Enable device interface. */
2020-05-05 16:22:16 +01:00
g_usbDeviceInterface . initialized = true ;
2020-07-06 01:10:07 +01:00
/* Setup interface. */
2020-05-05 16:22:16 +01:00
rc = usbDsGetDsInterface ( & ( g_usbDeviceInterface . interface ) , & interface_descriptor , " usb " ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsGetDsInterface failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* Setup endpoints. */
2020-05-05 16:22:16 +01:00
rc = usbDsInterface_GetDsEndpoint ( g_usbDeviceInterface . interface , & ( g_usbDeviceInterface . endpoint_in ) , & endpoint_descriptor_in ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_GetDsEndpoint failed! (0x%08X) (in endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_GetDsEndpoint ( g_usbDeviceInterface . interface , & ( g_usbDeviceInterface . endpoint_out ) , & endpoint_descriptor_out ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_GetDsEndpoint failed! (0x%08X) (out endpoint). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
rc = usbDsInterface_EnableInterface ( g_usbDeviceInterface . interface ) ;
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " usbDsInterface_EnableInterface failed! (0x%08X). " , rc ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
return true ;
}
2020-05-07 12:08:54 +01:00
NX_INLINE bool usbIsHostAvailable ( void )
{
2020-12-06 22:45:30 +00:00
UsbState state = UsbState_Detached ;
2020-05-07 12:08:54 +01:00
Result rc = usbDsGetState ( & state ) ;
2020-12-06 22:45:30 +00:00
return ( R_SUCCEEDED ( rc ) & & state = = UsbState_Configured ) ;
2020-05-07 12:08:54 +01:00
}
2020-08-13 07:01:23 +01:00
NX_INLINE void usbSetZltPacket ( bool enable )
{
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_in ) ) ;
usbDsEndpoint_SetZlt ( g_usbDeviceInterface . endpoint_in , enable ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_in ) ) ;
}
NX_INLINE bool usbRead ( void * buf , u64 size , bool reset_urb_id )
2020-05-05 16:22:16 +01:00
{
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_out ) ) ;
2020-08-13 07:01:23 +01:00
if ( reset_urb_id ) g_usbUrbId = 0 ;
2020-05-05 16:22:16 +01:00
bool ret = usbTransferData ( buf , size , g_usbDeviceInterface . endpoint_out ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_out ) ) ;
return ret ;
}
2020-08-13 07:01:23 +01:00
NX_INLINE bool usbWrite ( void * buf , u64 size , bool reset_urb_id )
2020-05-05 16:22:16 +01:00
{
rwlockWriteLock ( & ( g_usbDeviceInterface . lock_in ) ) ;
2020-08-13 07:01:23 +01:00
if ( reset_urb_id ) g_usbUrbId = 0 ;
2020-05-05 16:22:16 +01:00
bool ret = usbTransferData ( buf , size , g_usbDeviceInterface . endpoint_in ) ;
rwlockWriteUnlock ( & ( g_usbDeviceInterface . lock_in ) ) ;
return ret ;
}
2020-05-07 12:08:54 +01:00
static bool usbTransferData ( void * buf , u64 size , UsbDsEndpoint * endpoint )
2020-05-05 16:22:16 +01:00
{
2020-05-11 20:12:03 +01:00
if ( ! buf | | ! IS_ALIGNED ( ( u64 ) buf , USB_TRANSFER_ALIGNMENT ) | | ! size | | ! endpoint )
2020-05-05 16:22:16 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
return false ;
}
if ( ! usbIsHostAvailable ( ) )
{
LOGFILE ( " USB host unavailable! " ) ;
return false ;
}
Result rc = 0 ;
UsbDsReportData report_data = { 0 } ;
u32 transferred_size = 0 ;
2020-07-12 16:29:08 +01:00
bool thread_exit = false ;
2020-05-05 16:22:16 +01:00
2020-07-06 01:10:07 +01:00
/* Start an USB transfer using the provided endpoint. */
2020-08-13 07:01:23 +01:00
rc = usbDsEndpoint_PostBufferAsync ( endpoint , buf , size , & g_usbUrbId ) ;
2020-05-05 16:22:16 +01:00
if ( R_FAILED ( rc ) )
{
2020-08-13 07:01:23 +01:00
LOGFILE ( " usbDsEndpoint_PostBufferAsync failed! (0x%08X) (URB ID %u). " , rc , g_usbUrbId ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* Wait for the transfer to finish. */
2020-07-12 16:29:08 +01:00
if ( g_usbSessionStarted )
{
/* If the USB transfer session has already been started, then use a regular timeout value. */
rc = eventWait ( & ( endpoint - > CompletionEvent ) , USB_TRANSFER_TIMEOUT * ( u64 ) 1000000000 ) ;
} else {
/* If we're starting an USB transfer session, wait indefinitely inside a loop to let the user start the companion app. */
2020-07-12 17:37:03 +01:00
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 )
2020-07-12 16:29:08 +01:00
{
2020-07-12 17:37:03 +01:00
/* Exit event triggered. */
rc = MAKERESULT ( Module_Kernel , KernelError_TimedOut ) ;
g_usbDetectionThreadExitFlag = thread_exit = true ;
2020-07-12 16:29:08 +01:00
}
}
/* Clear the endpoint completion event. */
if ( ! thread_exit ) eventClear ( & ( endpoint - > CompletionEvent ) ) ;
2020-05-05 16:22:16 +01:00
2020-05-11 13:11:06 +01:00
if ( R_FAILED ( rc ) )
{
2020-07-06 01:10:07 +01:00
/* Cancel transfer. */
2020-05-11 13:11:06 +01:00
usbDsEndpoint_Cancel ( endpoint ) ;
2020-05-11 20:12:03 +01:00
2020-07-06 01:10:07 +01:00
/* Safety measure: wait until the completion event is triggered again before proceeding. */
2020-05-11 20:12:03 +01:00
eventWait ( & ( endpoint - > CompletionEvent ) , UINT64_MAX ) ;
eventClear ( & ( endpoint - > CompletionEvent ) ) ;
2020-11-28 06:38:53 +00:00
/* Signal user-mode USB timeout event if needed. */
2020-07-06 01:10:07 +01:00
/* This will "reset" the USB connection by making the background thread wait until a new session is established. */
2020-07-03 10:31:22 +01:00
if ( g_usbSessionStarted ) ueventSignal ( & g_usbTimeoutEvent ) ;
2020-08-13 07:01:23 +01:00
if ( ! thread_exit ) LOGFILE ( " eventWait failed! (0x%08X) (URB ID %u). " , rc , g_usbUrbId ) ;
2020-05-11 13:11:06 +01:00
return false ;
}
2020-05-05 16:22:16 +01:00
rc = usbDsEndpoint_GetReportData ( endpoint , & report_data ) ;
if ( R_FAILED ( rc ) )
{
2020-08-13 07:01:23 +01:00
LOGFILE ( " usbDsEndpoint_GetReportData failed! (0x%08X) (URB ID %u). " , rc , g_usbUrbId ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
2020-08-13 07:01:23 +01:00
rc = usbDsParseReportData ( & report_data , g_usbUrbId , NULL , & transferred_size ) ;
2020-05-05 16:22:16 +01:00
if ( R_FAILED ( rc ) )
{
2020-08-13 07:01:23 +01:00
LOGFILE ( " usbDsParseReportData failed! (0x%08X) (URB ID %u). " , rc , g_usbUrbId ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
if ( transferred_size ! = size )
{
2020-08-13 07:01:23 +01:00
LOGFILE ( " USB transfer failed! Expected 0x%lX bytes, got 0x%X bytes (URB ID %u). " , size , transferred_size , g_usbUrbId ) ;
2020-05-05 16:22:16 +01:00
return false ;
}
return true ;
}