From 1d0cc9c45bb1c2393c96e813ce249b675c3ed544 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sat, 13 Feb 2021 17:49:05 -0400 Subject: [PATCH] USB changes (breaks ns-usbloader compatibility). * Updated usbSendCommand() to make it send the command header first, and then the command block. Makes it easier for host applications to read and parse command data. * Let usbSendCommand() take care of handling ZLT packets if required by any command block (only SendNspHeader at this moment), as well as logging status data via usbLogStatusDetail(). * Updated USB ABI specs doc. --- source/usb.c | 127 +++++++++++++++++++++++++--------------------- source/usb.h | 2 +- usb_abi_specs.txt | 14 +++-- 3 files changed, 81 insertions(+), 62 deletions(-) diff --git a/source/usb.c b/source/usb.c index 92ec0a3..5545277 100644 --- a/source/usb.c +++ b/source/usb.c @@ -195,7 +195,7 @@ static bool usbStartSession(void); static void usbEndSession(void); NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size); -static u32 usbSendCommand(size_t cmd_size); +static bool usbSendCommand(void); static void usbLogStatusDetail(u32 status); NX_INLINE bool usbAllocateTransferBuffer(void); @@ -311,8 +311,6 @@ bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_s bool ret = false; UsbCommandSendFileProperties *cmd_block = NULL; - size_t cmd_size = 0; - u32 status = UsbStatusType_Success; size_t filename_length = 0; if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || !filename || \ @@ -333,17 +331,12 @@ bool usbSendFileProperties(u64 file_size, const char *filename, u32 nsp_header_s cmd_block->nsp_header_size = nsp_header_size; sprintf(cmd_block->filename, "%s", filename); - cmd_size = (sizeof(UsbCommandHeader) + sizeof(UsbCommandSendFileProperties)); - - status = usbSendCommand(cmd_size); - if (status == UsbStatusType_Success) + ret = usbSendCommand(); + if (ret) { - ret = true; g_usbTransferRemainingSize = file_size; g_usbTransferWrittenSize = 0; if (!g_nspTransferMode) g_nspTransferMode = (file_size && nsp_header_size); - } else { - usbLogStatusDetail(status); } end: @@ -459,9 +452,7 @@ 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; + bool ret = false; if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized || !g_usbHostAvailable || !g_usbSessionStarted || g_usbTransferRemainingSize || \ !g_nspTransferMode || !nsp_header || !nsp_header_size || nsp_header_size > (USB_TRANSFER_BUFFER_SIZE - sizeof(UsbCommandHeader))) @@ -476,22 +467,9 @@ bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size) /* 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); + ret = usbSendCommand(); end: rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); @@ -617,13 +595,12 @@ static void usbDetectionThreadFunc(void *arg) static bool usbStartSession(void) { UsbCommandStartSession *cmd_block = NULL; - size_t cmd_size = 0; - u32 status = UsbStatusType_Success; + bool ret = false; if (!g_usbTransferBuffer || !g_usbDeviceInterfaceInit || !g_usbDeviceInterface.initialized) { LOGFILE("Invalid parameters!"); - return false; + goto end; } usbPrepareCommandHeader(UsbCommandType_StartSession, (u32)sizeof(UsbCommandStartSession)); @@ -636,10 +613,8 @@ static bool usbStartSession(void) cmd_block->app_ver_micro = VERSION_MICRO; cmd_block->abi_version = USB_ABI_VERSION; - cmd_size = (sizeof(UsbCommandHeader) + sizeof(UsbCommandStartSession)); - - status = usbSendCommand(cmd_size); - if (status == UsbStatusType_Success) + 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. */ @@ -651,14 +626,13 @@ static bool usbStartSession(void) LOGFILE("Invalid endpoint max packet size value received from USB host: 0x%04X.", g_usbEndpointMaxPacketSize); /* Reset flags. */ + ret = false; g_usbEndpointMaxPacketSize = 0; - cmd_status->status = status = UsbStatusType_HostIoError; } - } else { - usbLogStatusDetail(status); } - return (status == UsbStatusType_Success); +end: + return ret; } static void usbEndSession(void) @@ -671,7 +645,7 @@ static void usbEndSession(void) usbPrepareCommandHeader(UsbCommandType_EndSession, 0); - if (!usbWrite(g_usbTransferBuffer, sizeof(UsbCommandHeader), true)) LOGFILE("Failed to send EndSession command!"); + if (!usbSendCommand()) LOGFILE("Failed to send EndSession command!"); } NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size) @@ -684,42 +658,79 @@ NX_INLINE void usbPrepareCommandHeader(u32 cmd, u32 cmd_block_size) cmd_header->cmd_block_size = cmd_block_size; } -static u32 usbSendCommand(size_t cmd_size) +static bool usbSendCommand(void) { - u32 cmd = ((UsbCommandHeader*)g_usbTransferBuffer)->cmd; - UsbStatus *cmd_status = NULL; + UsbCommandHeader *cmd_header = (UsbCommandHeader*)g_usbTransferBuffer; + size_t cmd_size = (sizeof(UsbCommandHeader) + cmd_header->cmd_block_size); - if (cmd_size < sizeof(UsbCommandHeader) || cmd_size > USB_TRANSFER_BUFFER_SIZE) + UsbStatus *cmd_status = (UsbStatus*)g_usbTransferBuffer; + u32 status = UsbStatusType_Success; + + /* Log error message only if the USB session has been started, or if thread exit flag hasn't been enabled. */ + bool ret = false, zlt_required = false, cmd_block_written = false, log_rw_errors = (g_usbSessionStarted || !g_usbDetectionThreadExitFlag); + + if (cmd_size > USB_TRANSFER_BUFFER_SIZE) { LOGFILE("Invalid command size!"); - return UsbStatusType_InvalidCommandSize; + status = UsbStatusType_InvalidCommandSize; + goto end; } - if (!usbWrite(g_usbTransferBuffer, cmd_size, true)) + /* Write command header first. */ + if (!usbWrite(cmd_header, sizeof(UsbCommandHeader), true)) { - /* 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); - return UsbStatusType_WriteCommandFailed; + if (log_rw_errors) LOGFILE("Failed to write header for type 0x%X command!", cmd_header->cmd); + status = UsbStatusType_WriteCommandFailed; + goto end; } - u64 read_size = sizeof(UsbStatus); - - if (!usbRead(g_usbTransferBuffer, read_size, true)) + /* Check if we need to transfer a command block. */ + if (cmd_header->cmd_block_size) { - /* 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); - return UsbStatusType_ReadStatusFailed; + /* Determine if we'll need to set a Zero Length Termination (ZLT) packet after sending the command block. */ + zlt_required = (g_usbSessionStarted && IS_ALIGNED(cmd_header->cmd_block_size, g_usbEndpointMaxPacketSize)); + if (zlt_required) usbSetZltPacket(true); + + /* Move command block data within the transfer buffer to guarantee we'll work with a proper alignment. */ + memmove(g_usbTransferBuffer, g_usbTransferBuffer + sizeof(UsbCommandHeader), cmd_header->cmd_block_size); + + /* Write command block. */ + cmd_block_written = usbWrite(g_usbTransferBuffer, cmd_header->cmd_block_size, false); + if (!cmd_block_written) + { + if (log_rw_errors) LOGFILE("Failed to write command block for type 0x%X command!", cmd_header->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; } - cmd_status = (UsbStatus*)g_usbTransferBuffer; + /* Read status block. */ + if (!usbRead(cmd_status, sizeof(UsbStatus), true)) + { + if (log_rw_errors) LOGFILE("Failed to read 0x%lX bytes long status block for type 0x%X command!", sizeof(UsbStatus), cmd_header->cmd); + status = UsbStatusType_ReadStatusFailed; + goto end; + } + /* Verify magic word in status block. */ 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; + status = UsbStatusType_InvalidMagicWord; + goto end; } - return cmd_status->status; + /* Update return value. */ + ret = ((status = cmd_status->status) == UsbStatusType_Success); + +end: + if (!ret) usbLogStatusDetail(status); + + return ret; } static void usbLogStatusDetail(u32 status) diff --git a/source/usb.h b/source/usb.h index d0d4a7e..5f34ba1 100644 --- a/source/usb.h +++ b/source/usb.h @@ -57,7 +57,7 @@ bool usbSendFileData(void *data, u64 data_size); /// Sends NSP header data to the host device, making it rewind the NSP file pointer to write this data, essentially finishing the NSP transfer process. /// Must be called after the data from all NSP file entries has been transferred using both usbSendFileProperties() and usbSendFileData() calls. -/// If the sum of the USB command header and NSP header sizes is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet. +/// If the NSP header size is aligned to the endpoint max packet size, the host device should expect a Zero Length Termination (ZLT) packet. bool usbSendNspHeader(void *nsp_header, u32 nsp_header_size); /// Used to cancel an ongoing file transfer by stalling the input (write) USB endpoint. diff --git a/usb_abi_specs.txt b/usb_abi_specs.txt index 6ce5db2..383d339 100644 --- a/usb_abi_specs.txt +++ b/usb_abi_specs.txt @@ -110,16 +110,24 @@ ________________________________________________________________________________ Highlights: -* All transfers are performed in USB bulk mode. -* A USB status response is always expected to be issued by the USB host device right after each time a USB command is issued by nxdumptool. This includes the SendFileProperties command, right before expecting any file data to be transferred from nxdumptool. +* Vendor ID: 0x057e. +* Product ID: 0x3000. +* Device descriptor strings will always match "nxdumptool" (product) and "DarkMatterCore" (manufacturer). +* Multiple device descriptors are provided to support USB 1.1, USB 2.0 and USB 3.0, each one with slightly different properties. +* A single configuration descriptor is provided per each possible device descriptor. +* A single Binary Object Store (BOS) descriptor is also provided. +* All transfers are performed in USB bulk mode, using 5-second timeouts. +* While handling USB commands, nxdumptool first issues the USB command header (so the USB host device can parse how much data to read next), then sends the USB command block (if required by the command being handled), and finally expects the host device to send a USB status response. + * This includes the SendFileProperties command, which expects a USB status response from the host right before starting the file data transfer. * A USB status response is expected by nxdumptool right after the last file data chunk has been sent. * If, while transferring file data, the last data chunk is aligned to the endpoint max packet size used by the USB host, a Zero Length Termination (ZLT) packet will be issued after transferring the last chunk, in order to comply with the USB bulk transfer specs. * In the case of extracted RomFS transfers, the filename field from the SendFileProperties block may actually contain a filepath - this will always start with a '/'. The client application is free to decide how to handle this (e.g. create full directory tree, etc.). + NSP transfer mode: * If the NSP header size from a SendFileProperties block is greater than zero, the client should enter NSP transfer mode. The file size from this block represents, then, the full NSP size (including the NSP header). * In this mode, the client should immediately create the output file, write "NSP header size" bytes of padding to it, reply with a USB status response and expect additional SendFileProperties commands. No data is transferred for this very first SendFileProperties block. * Each further SendFileProperties block will hold the filename and size for a NSP file entry, and the NSP header size value will always be set to zero. The data received from each one of these file entries must be written to the output file created during the first SendFileProperties command. The sum of all file entry sizes should be equal to the full NSP size minus the NSP header size received during the first SendFileProperties command. * Finally, the client will receive a SendNspHeader command with the NSP header data, which should be written at the start of the output file. The value from the Command Block Size in the command header from this block should match the NSP header size received in the first SendFileProperties command. -* If the sum of the command header length (0x10) plus the NSP header size is aligned to the endpoint max packet size, the client should also expect a ZLT packet after the SendNspHeader command. +* If the NSP header size is aligned to the endpoint max packet size, the client should also expect a ZLT packet after receiving the NSP header data from the SendNspHeader command.