mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
692 lines
28 KiB
C
692 lines
28 KiB
C
/*
|
|
* usbhsfs_drive.c
|
|
*
|
|
* Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
|
|
* Copyright (c) 2020-2021, XorTroll.
|
|
*
|
|
* This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
|
|
*/
|
|
|
|
#include "usbhsfs_utils.h"
|
|
#include "usbhsfs_request.h"
|
|
#include "usbhsfs_scsi.h"
|
|
#include "usbhsfs_mount.h"
|
|
|
|
/* Function prototypes. */
|
|
|
|
static bool usbHsFsDriveSetupInterfaceAndEndpointDescriptors(UsbHsFsDriveContext *drive_ctx);
|
|
static bool usbHsFsDriveChangeInterfaceDescriptor(UsbHsClientIfSession *usb_if_session, struct usb_interface_descriptor *interface_desc);
|
|
static bool usbHsFsDriveSetupEndpointDescriptors(UsbHsFsDriveContext *drive_ctx, u8 *config_desc_start, u8 *config_desc_end, u8 **config_desc_ptr);
|
|
static bool usbHsFsDriveGetEndpointSession(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool input, u8 ep_addr);
|
|
|
|
static void usbHsFsDriveGetDeviceStrings(UsbHsFsDriveContext *drive_ctx);
|
|
static void usbHsFsDriveGetUtf8StringFromStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, char **out_buf);
|
|
|
|
static void usbHsFsDriveDestroyLogicalUnitContext(UsbHsFsDriveLogicalUnitContext *lun_ctx, bool stop_lun);
|
|
|
|
bool usbHsFsDriveInitializeContext(UsbHsFsDriveContext *drive_ctx, UsbHsInterface *usb_if)
|
|
{
|
|
if (!drive_ctx || !usb_if)
|
|
{
|
|
USBHSFS_LOG_MSG("Invalid parameters!");
|
|
return false;
|
|
}
|
|
|
|
Result rc = 0;
|
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
|
UsbHsFsDriveLogicalUnitContext **tmp_lun_ctx = NULL;
|
|
bool ret = false;
|
|
|
|
/* Allocate memory for the USB transfer buffer. */
|
|
drive_ctx->xfer_buf = usbHsFsRequestAllocateXferBuffer();
|
|
if (!drive_ctx->xfer_buf)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to allocate USB transfer buffer! (interface %d).", usb_if->inf.ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Open current interface. */
|
|
rc = usbHsAcquireUsbIf(usb_if_session, usb_if);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsAcquireUsbIf failed! (0x%08X) (interface %d).", rc, usb_if->inf.ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Setup interface and endpoint descriptors. */
|
|
if (!usbHsFsDriveSetupInterfaceAndEndpointDescriptors(drive_ctx))
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to setup interface and endpoint descriptors! (interface %d).", usb_if->inf.ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Fill extra device data. */
|
|
drive_ctx->usb_if_id = usb_if_session->ID;
|
|
drive_ctx->uasp = (usb_if_session->inf.inf.interface_desc.bInterfaceProtocol == USB_PROTOCOL_USB_ATTACHED_SCSI);
|
|
drive_ctx->vid = usb_if_session->inf.device_desc.idVendor;
|
|
drive_ctx->pid = usb_if_session->inf.device_desc.idProduct;
|
|
usbHsFsDriveGetDeviceStrings(drive_ctx);
|
|
|
|
/* Retrieve max supported logical units from this storage device. */
|
|
rc = usbHsFsRequestGetMaxLogicalUnits(usb_if_session, &(drive_ctx->max_lun));
|
|
if (R_FAILED(rc))
|
|
{
|
|
/* Fallback to a single logical unit. */
|
|
drive_ctx->max_lun = 1;
|
|
|
|
/* If the request fails (e.g. unsupported by the device), we'll attempt to clear a possible STALL status from the endpoints. */
|
|
if (R_MODULE(rc) != Module_Libnx) usbHsFsDriveClearStallStatus(drive_ctx);
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Max LUN count (interface %d): %u.", drive_ctx->usb_if_id, drive_ctx->max_lun);
|
|
|
|
/* Bail out if we're dealing with a UASP interface (for now). */
|
|
if (drive_ctx->uasp) goto end;
|
|
|
|
/* Allocate memory for LUN context pointer array. */
|
|
drive_ctx->lun_ctx = calloc(drive_ctx->max_lun, sizeof(UsbHsFsDriveLogicalUnitContext*));
|
|
if (!drive_ctx->lun_ctx)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to allocate memory for LUN context pointer array! (interface %d).", drive_ctx->usb_if_id);
|
|
goto end;
|
|
}
|
|
|
|
/* Prepare LUNs using SCSI commands. */
|
|
for(u8 i = 0; i < drive_ctx->max_lun; i++)
|
|
{
|
|
/* Retrieve pointer to the current LUN context. */
|
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[drive_ctx->lun_count];
|
|
if (!lun_ctx)
|
|
{
|
|
/* Allocate memory for a new LUN context. */
|
|
lun_ctx = calloc(1, sizeof(UsbHsFsDriveLogicalUnitContext));
|
|
if (!lun_ctx)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to allocate memory for LUN context entry #%u! (%u / %u).", drive_ctx->lun_count, i + 1, drive_ctx->max_lun);
|
|
goto end;
|
|
}
|
|
|
|
/* Set LUN context entry pointer. */
|
|
drive_ctx->lun_ctx[drive_ctx->lun_count] = lun_ctx;
|
|
}
|
|
|
|
/* Increase LUN context count. */
|
|
(drive_ctx->lun_count)++;
|
|
|
|
/* Set USB interface ID and LUN index. */
|
|
lun_ctx->drive_ctx = drive_ctx;
|
|
lun_ctx->usb_if_id = drive_ctx->usb_if_id;
|
|
lun_ctx->uasp = drive_ctx->uasp;
|
|
lun_ctx->lun = i;
|
|
|
|
/* Start LUN. */
|
|
if (!usbHsFsScsiStartDriveLogicalUnit(lun_ctx))
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to initialize context for LUN #%u! (interface %d).", i, drive_ctx->usb_if_id);
|
|
(drive_ctx->lun_count)--; /* Decrease LUN context count. */
|
|
continue;
|
|
}
|
|
|
|
/* Initialize filesystem contexts for this LUN. */
|
|
if (!usbHsFsMountInitializeLogicalUnitFileSystemContexts(lun_ctx))
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to initialize filesystem contexts for LUN #%u! (interface %d).", i, drive_ctx->usb_if_id);
|
|
usbHsFsDriveDestroyLogicalUnitContext(lun_ctx, true);
|
|
(drive_ctx->lun_count)--; /* Decrease LUN context count. */
|
|
}
|
|
}
|
|
|
|
if (!drive_ctx->lun_count)
|
|
{
|
|
if (drive_ctx->lun_ctx[0]) free(drive_ctx->lun_ctx[0]);
|
|
USBHSFS_LOG_MSG("Failed to initialize any LUN/filesystem contexts! (interface %d).", drive_ctx->usb_if_id);
|
|
goto end;
|
|
}
|
|
|
|
/* Reallocate LUN context pointer array if we're not using all allocated entries. */
|
|
if (drive_ctx->lun_count < drive_ctx->max_lun)
|
|
{
|
|
tmp_lun_ctx = realloc(drive_ctx->lun_ctx, drive_ctx->lun_count * sizeof(UsbHsFsDriveLogicalUnitContext*));
|
|
if (tmp_lun_ctx)
|
|
{
|
|
drive_ctx->lun_ctx = tmp_lun_ctx;
|
|
tmp_lun_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
/* Update return value. */
|
|
ret = true;
|
|
|
|
end:
|
|
/* Destroy drive context if something went wrong. */
|
|
if (!ret) usbHsFsDriveDestroyContext(drive_ctx, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void usbHsFsDriveDestroyContext(UsbHsFsDriveContext *drive_ctx, bool stop_lun)
|
|
{
|
|
if (!drive_ctx) return;
|
|
|
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
|
|
|
UsbHsClientEpSession *usb_in_ep_session_1 = &(drive_ctx->usb_in_ep_session[0]);
|
|
UsbHsClientEpSession *usb_out_ep_session_1 = &(drive_ctx->usb_out_ep_session[0]);
|
|
|
|
UsbHsClientEpSession *usb_in_ep_session_2 = &(drive_ctx->usb_in_ep_session[1]);
|
|
UsbHsClientEpSession *usb_out_ep_session_2 = &(drive_ctx->usb_out_ep_session[1]);
|
|
|
|
if (drive_ctx->lun_ctx)
|
|
{
|
|
/* Destroy LUN contexts. */
|
|
for(u8 i = 0; i < drive_ctx->lun_count; i++)
|
|
{
|
|
UsbHsFsDriveLogicalUnitContext *lun_ctx = drive_ctx->lun_ctx[i];
|
|
if (!lun_ctx) continue;
|
|
|
|
usbHsFsDriveDestroyLogicalUnitContext(lun_ctx, stop_lun);
|
|
free(lun_ctx);
|
|
}
|
|
|
|
/* Free LUN context pointer array. */
|
|
free(drive_ctx->lun_ctx);
|
|
drive_ctx->lun_ctx = NULL;
|
|
}
|
|
|
|
/* Free device strings. */
|
|
if (drive_ctx->manufacturer)
|
|
{
|
|
free(drive_ctx->manufacturer);
|
|
drive_ctx->manufacturer = NULL;
|
|
}
|
|
|
|
if (drive_ctx->product_name)
|
|
{
|
|
free(drive_ctx->product_name);
|
|
drive_ctx->product_name = NULL;
|
|
}
|
|
|
|
if (drive_ctx->serial_number)
|
|
{
|
|
free(drive_ctx->serial_number);
|
|
drive_ctx->serial_number = NULL;
|
|
}
|
|
|
|
/* Close USB interface and endpoint sessions. */
|
|
if (serviceIsActive(&(usb_out_ep_session_2->s))) usbHsEpClose(usb_out_ep_session_2);
|
|
if (serviceIsActive(&(usb_in_ep_session_2->s))) usbHsEpClose(usb_in_ep_session_2);
|
|
|
|
if (serviceIsActive(&(usb_out_ep_session_1->s))) usbHsEpClose(usb_out_ep_session_1);
|
|
if (serviceIsActive(&(usb_in_ep_session_1->s))) usbHsEpClose(usb_in_ep_session_1);
|
|
|
|
if (usbHsIfIsActive(usb_if_session)) usbHsIfClose(usb_if_session);
|
|
|
|
/* Free dedicated USB transfer buffer. */
|
|
if (drive_ctx->xfer_buf)
|
|
{
|
|
free(drive_ctx->xfer_buf);
|
|
drive_ctx->xfer_buf = NULL;
|
|
}
|
|
}
|
|
|
|
void usbHsFsDriveClearStallStatus(UsbHsFsDriveContext *drive_ctx)
|
|
{
|
|
if (!usbHsFsDriveIsValidContext(drive_ctx)) return;
|
|
|
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_in_ep_session[0]));
|
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_out_ep_session[0]));
|
|
|
|
if (drive_ctx->uasp)
|
|
{
|
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_in_ep_session[1]));
|
|
usbHsFsRequestClearEndpointHaltFeature(&(drive_ctx->usb_if_session), &(drive_ctx->usb_out_ep_session[1]));
|
|
}
|
|
}
|
|
|
|
static bool usbHsFsDriveSetupInterfaceAndEndpointDescriptors(UsbHsFsDriveContext *drive_ctx)
|
|
{
|
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session))) return false;
|
|
|
|
Result rc = 0;
|
|
|
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
|
|
|
struct usb_interface_descriptor orig_interface_desc = {0}, *new_interface_desc = NULL;
|
|
|
|
u32 config_desc_size = 0;
|
|
u8 *config_desc_start = NULL, *config_desc_end = NULL, *config_desc_ptr = NULL;
|
|
|
|
bool success = false;
|
|
|
|
/* Copy interface descriptor provided by UsbHsInterface. */
|
|
/* We'll use this to skip this descriptor when we find it within the full configuration descriptor. */
|
|
/* Furthermore, we'll use it as a fallback method if something goes wrong while setting up USB Attached SCSI interface and endpoint descriptors. */
|
|
memcpy(&orig_interface_desc, &(usb_if_session->inf.inf.interface_desc), sizeof(struct usb_interface_descriptor));
|
|
|
|
/* Get full configuration descriptor. The one provided by UsbHsInterface is truncated. */
|
|
/* To simplify things, we won't go beyond index #0. */
|
|
rc = usbHsFsRequestGetConfigurationDescriptor(usb_if_session, 0, &config_desc_start, &config_desc_size);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsFsRequestGetConfigurationDescriptor failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Do not proceed if the configuration descriptor is too small to hold a USB Attached SCSI interface. */
|
|
if (config_desc_size < (sizeof(struct usb_config_descriptor) + sizeof(struct usb_interface_descriptor) + ((sizeof(struct usb_endpoint_descriptor) + sizeof(struct usb_pipe_usage_descriptor)) * 4)))
|
|
{
|
|
USBHSFS_LOG_MSG("Configuration descriptor is too small to hold a UASP interface (interface %d).", usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Update configuration descriptor pointers. */
|
|
config_desc_end = (config_desc_start + config_desc_size);
|
|
config_desc_ptr = config_desc_start;
|
|
|
|
/* Parse configuration descriptor. */
|
|
while(config_desc_ptr < config_desc_end)
|
|
{
|
|
u32 config_desc_offset = (u32)(config_desc_ptr - config_desc_start);
|
|
|
|
/* If somehow we're only a byte away from the end of the configuration descriptor, bail out. */
|
|
if ((config_desc_ptr + 1) >= config_desc_end) break;
|
|
|
|
/* Get descriptor size and type. */
|
|
u8 cur_desc_size = *config_desc_ptr;
|
|
u8 cur_desc_type = *(config_desc_ptr + 1);
|
|
|
|
/* Check descriptor size. */
|
|
if (!cur_desc_size)
|
|
{
|
|
USBHSFS_LOG_MSG("Size for descriptor 0x%02X at offset 0x%X is zero! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
if ((config_desc_offset + cur_desc_size) > config_desc_size)
|
|
{
|
|
USBHSFS_LOG_MSG("Descriptor 0x%02X at offset 0x%X exceeds configuration descriptor size! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Check if we're dealing with a valid USB Attached SCSI interface descriptor. */
|
|
if (cur_desc_type != USB_DT_INTERFACE || cur_desc_size != sizeof(struct usb_interface_descriptor) || \
|
|
(orig_interface_desc.bInterfaceProtocol != USB_PROTOCOL_USB_ATTACHED_SCSI && !memcmp(config_desc_ptr, &orig_interface_desc, sizeof(struct usb_interface_descriptor))) || \
|
|
*(config_desc_ptr + 7) != USB_PROTOCOL_USB_ATTACHED_SCSI || *(config_desc_ptr + 4) != 4)
|
|
{
|
|
config_desc_ptr += cur_desc_size;
|
|
continue;
|
|
}
|
|
|
|
/* Found a USB Attached SCSI descriptor. */
|
|
new_interface_desc = (struct usb_interface_descriptor*)config_desc_ptr;
|
|
USBHSFS_LOG_DATA(new_interface_desc, sizeof(struct usb_interface_descriptor), "Found UASP interface descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
|
|
|
/* Update configuration descriptor pointer. */
|
|
config_desc_ptr += cur_desc_size;
|
|
|
|
/* Switch to the new interface descriptor. */
|
|
if (!usbHsFsDriveChangeInterfaceDescriptor(usb_if_session, new_interface_desc)) continue;
|
|
|
|
/* Setup endpoint descriptors. */
|
|
success = usbHsFsDriveSetupEndpointDescriptors(drive_ctx, config_desc_start, config_desc_end, &config_desc_ptr);
|
|
if (success) break;
|
|
}
|
|
|
|
if (!success) USBHSFS_LOG_MSG("Unable to find and/or set a UASP interface descriptor (interface %d).", usb_if_session->ID);
|
|
|
|
end:
|
|
if (config_desc_start) free(config_desc_start);
|
|
|
|
/* Fallback to the Bulk-Only Transport interface if we must. */
|
|
if (!success && orig_interface_desc.bInterfaceProtocol == USB_PROTOCOL_BULK_ONLY_TRANSPORT && usbHsFsDriveChangeInterfaceDescriptor(usb_if_session, &orig_interface_desc))
|
|
{
|
|
USBHSFS_LOG_MSG("Proceeding with BOT interface descriptor (interface %d).", usb_if_session->ID);
|
|
success = usbHsFsDriveSetupEndpointDescriptors(drive_ctx, NULL, NULL, NULL);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static bool usbHsFsDriveChangeInterfaceDescriptor(UsbHsClientIfSession *usb_if_session, struct usb_interface_descriptor *interface_desc)
|
|
{
|
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !interface_desc) return false;
|
|
|
|
Result rc = 0;
|
|
|
|
u8 cur_if_num = usb_if_session->inf.inf.interface_desc.bInterfaceNumber;
|
|
u8 cur_if_alt_setting = usb_if_session->inf.inf.interface_desc.bAlternateSetting;
|
|
|
|
u8 new_if_num = interface_desc->bInterfaceNumber;
|
|
u8 new_if_alt_setting = interface_desc->bAlternateSetting;
|
|
|
|
/* Check if we're trying to set the same interface. */
|
|
if (new_if_num == cur_if_num && new_if_alt_setting == cur_if_alt_setting)
|
|
{
|
|
USBHSFS_LOG_MSG("Trying to set an interface descriptor that matches the current one (interface %d).", usb_if_session->ID);
|
|
return true;
|
|
}
|
|
|
|
/* Check if we must set a new interface. */
|
|
if (new_if_num != cur_if_num)
|
|
{
|
|
rc = usbHsIfSetInterface(usb_if_session, NULL, new_if_num);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsIfSetInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Check if we must set an alternate interface setting. */
|
|
if (new_if_alt_setting != cur_if_alt_setting)
|
|
{
|
|
rc = usbHsIfGetAlternateInterface(usb_if_session, NULL, new_if_alt_setting);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsIfGetAlternateInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
USBHSFS_LOG_DATA(&(usb_if_session->inf), sizeof(UsbHsInterface), "New interface data (interface %d):", usb_if_session->ID);
|
|
|
|
/*rc = usbHsFsRequestSetInterface(usb_if_session);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsFsRequestSetInterface failed! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
|
return false;
|
|
}*/
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool usbHsFsDriveSetupEndpointDescriptors(UsbHsFsDriveContext *drive_ctx, u8 *config_desc_start, u8 *config_desc_end, u8 **config_desc_ptr)
|
|
{
|
|
UsbHsClientIfSession *usb_if_session = NULL;
|
|
UsbHsClientEpSession *usb_in_ep_session_1 = NULL, *usb_out_ep_session_1 = NULL, *usb_in_ep_session_2 = NULL, *usb_out_ep_session_2 = NULL;
|
|
|
|
u8 *config_desc_ptr_tmp = NULL;
|
|
u32 config_desc_size = 0;
|
|
|
|
struct usb_endpoint_descriptor *ep_desc = NULL;
|
|
struct usb_pipe_usage_descriptor *pipe_usage_desc = NULL;
|
|
|
|
bool success = false, uasp = false;
|
|
|
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session)) || ((uasp = (drive_ctx->usb_if_session.inf.inf.interface_desc.bInterfaceProtocol == USB_PROTOCOL_USB_ATTACHED_SCSI)) && \
|
|
(!config_desc_start || !config_desc_end || !config_desc_ptr || !*config_desc_ptr || config_desc_end <= config_desc_start || *config_desc_ptr >= config_desc_end))) return false;
|
|
|
|
usb_if_session = &(drive_ctx->usb_if_session);
|
|
|
|
usb_in_ep_session_1 = &(drive_ctx->usb_in_ep_session[0]);
|
|
usb_out_ep_session_1 = &(drive_ctx->usb_out_ep_session[0]);
|
|
|
|
usb_in_ep_session_2 = &(drive_ctx->usb_in_ep_session[1]);
|
|
usb_out_ep_session_2 = &(drive_ctx->usb_out_ep_session[1]);
|
|
|
|
if (!uasp)
|
|
{
|
|
/* If we're dealing with a Bulk-Only Transport interface, just setup the endpoints from the interface session right away. */
|
|
success = (usbHsFsDriveGetEndpointSession(usb_if_session, usb_in_ep_session_1, true, 0) && usbHsFsDriveGetEndpointSession(usb_if_session, usb_out_ep_session_1, false, 0));
|
|
goto end;
|
|
}
|
|
|
|
/* Parse endpoint and pipe usage descriptors from the configuration descriptor. */
|
|
config_desc_ptr_tmp = *config_desc_ptr;
|
|
config_desc_size = (u32)(config_desc_end - config_desc_start);
|
|
|
|
while(config_desc_ptr_tmp < config_desc_end)
|
|
{
|
|
u32 config_desc_offset = (u32)(config_desc_ptr_tmp - config_desc_start);
|
|
|
|
/* If somehow we're only a byte away from the end of the configuration descriptor, bail out. */
|
|
if ((config_desc_ptr_tmp + 1) >= config_desc_end) break;
|
|
|
|
/* Get descriptor size and type. */
|
|
u8 cur_desc_size = *config_desc_ptr_tmp;
|
|
u8 cur_desc_type = *(config_desc_ptr_tmp + 1);
|
|
|
|
/* Check descriptor size. */
|
|
if (!cur_desc_size)
|
|
{
|
|
USBHSFS_LOG_MSG("Size for descriptor 0x%02X at offset 0x%X is zero! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
if ((config_desc_offset + cur_desc_size) > config_desc_size)
|
|
{
|
|
USBHSFS_LOG_MSG("Descriptor 0x%02X at offset 0x%X exceeds configuration descriptor size! (interface %d).", cur_desc_type, config_desc_offset, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Check if we're dealing with an endpoint descriptor or a pipe usage descriptor. */
|
|
if ((!ep_desc && (cur_desc_type != USB_DT_ENDPOINT || cur_desc_size != sizeof(struct usb_endpoint_descriptor))) || (ep_desc && (cur_desc_type != USB_DT_PIPE_USAGE || \
|
|
cur_desc_size != sizeof(struct usb_pipe_usage_descriptor))))
|
|
{
|
|
config_desc_ptr_tmp += cur_desc_size;
|
|
continue;
|
|
}
|
|
|
|
if (cur_desc_type == USB_DT_ENDPOINT)
|
|
{
|
|
/* Found an endpoint descriptor. */
|
|
/* Update our current endpoint pointer, then look for its pipe usage descriptor. */
|
|
ep_desc = (struct usb_endpoint_descriptor*)config_desc_ptr_tmp;
|
|
USBHSFS_LOG_DATA(ep_desc, sizeof(struct usb_endpoint_descriptor), "Found endpoint descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
|
|
|
config_desc_ptr_tmp += cur_desc_size;
|
|
continue;
|
|
}
|
|
|
|
/* Found a pipe usage descriptor. */
|
|
pipe_usage_desc = (struct usb_pipe_usage_descriptor*)config_desc_ptr_tmp;
|
|
USBHSFS_LOG_DATA(pipe_usage_desc, sizeof(struct usb_pipe_usage_descriptor), "Found pipe usage descriptor at offset 0x%X (interface %d):", config_desc_offset, usb_if_session->ID);
|
|
|
|
/* Update configuration descriptor pointer. */
|
|
config_desc_ptr_tmp += cur_desc_size;
|
|
|
|
/* Check if the pipe ID matches the endpoint direction. */
|
|
u8 ep_addr = ep_desc->bEndpointAddress, pipe_id = pipe_usage_desc->bPipeID;
|
|
bool input = ((ep_addr & USB_ENDPOINT_IN) != 0);
|
|
|
|
if ((input && pipe_id != USB_PIPE_USAGE_ID_STS && pipe_id != USB_PIPE_USAGE_ID_DATA_IN) || (!input && pipe_id != USB_PIPE_USAGE_ID_CMD && pipe_id != USB_PIPE_USAGE_ID_DATA_OUT))
|
|
{
|
|
USBHSFS_LOG_MSG("Pipe ID 0x%02X doesn't match direction from endpoint 0x%02X! (interface %d).", pipe_id, ep_addr, usb_if_session->ID);
|
|
goto end;
|
|
}
|
|
|
|
/* Finally, setup this endpoint. */
|
|
UsbHsClientEpSession *usb_ep_session = (pipe_id == USB_PIPE_USAGE_ID_CMD ? usb_out_ep_session_1 : (pipe_id == USB_PIPE_USAGE_ID_STS ? usb_in_ep_session_1 : \
|
|
(pipe_id == USB_PIPE_USAGE_ID_DATA_IN ? usb_in_ep_session_2 : usb_out_ep_session_2)));
|
|
|
|
if (!usbHsFsDriveGetEndpointSession(usb_if_session, usb_ep_session, input, ep_addr))
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to retrieve endpoint session! (interface %d, endpoint 0x%02X, pipe ID 0x%02X).", usb_if_session->ID, ep_addr, pipe_id);
|
|
goto end;
|
|
}
|
|
|
|
/* Clear endpoint and pipe usage descriptor pointers. */
|
|
ep_desc = NULL;
|
|
pipe_usage_desc = NULL;
|
|
|
|
/* Check if we're done here. */
|
|
if (serviceIsActive(&(usb_in_ep_session_1->s)) && serviceIsActive(&(usb_out_ep_session_1->s)) && serviceIsActive(&(usb_in_ep_session_2->s)) && serviceIsActive(&(usb_out_ep_session_2->s)))
|
|
{
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
/* Close opened endpoints if something went wrong. */
|
|
if (!success)
|
|
{
|
|
if (serviceIsActive(&(usb_out_ep_session_2->s))) usbHsEpClose(usb_out_ep_session_2);
|
|
if (serviceIsActive(&(usb_in_ep_session_2->s))) usbHsEpClose(usb_in_ep_session_2);
|
|
|
|
if (serviceIsActive(&(usb_out_ep_session_1->s))) usbHsEpClose(usb_out_ep_session_1);
|
|
if (serviceIsActive(&(usb_in_ep_session_1->s))) usbHsEpClose(usb_in_ep_session_1);
|
|
}
|
|
|
|
/* Update configuration descriptor pointer (if needed). */
|
|
if (config_desc_ptr_tmp) *config_desc_ptr = config_desc_ptr_tmp;
|
|
|
|
return success;
|
|
}
|
|
|
|
static bool usbHsFsDriveGetEndpointSession(UsbHsClientIfSession *usb_if_session, UsbHsClientEpSession *usb_ep_session, bool input, u8 ep_addr)
|
|
{
|
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !usb_ep_session) return false;
|
|
|
|
for(u8 i = 0; i < 15; i++)
|
|
{
|
|
struct usb_endpoint_descriptor *ep_desc = (input ? &(usb_if_session->inf.inf.input_endpoint_descs[i]) : &(usb_if_session->inf.inf.output_endpoint_descs[i]));
|
|
|
|
u8 max_burst = (input ? usb_if_session->inf.inf.input_ss_endpoint_companion_descs[i].bMaxBurst : usb_if_session->inf.inf.output_ss_endpoint_companion_descs[i].bMaxBurst);
|
|
max_burst++;
|
|
|
|
if (ep_desc->bLength && ((!ep_addr && ((input && (ep_desc->bEndpointAddress & USB_ENDPOINT_IN)) || (!input && !(ep_desc->bEndpointAddress & USB_ENDPOINT_IN)))) || \
|
|
(ep_addr && ep_desc->bEndpointAddress == ep_addr)) && (ep_desc->bmAttributes & 0x3F) == USB_TRANSFER_TYPE_BULK)
|
|
{
|
|
Result rc = usbHsIfOpenUsbEp(usb_if_session, usb_ep_session, 1, ep_desc->wMaxPacketSize, ep_desc);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("usbHsIfOpenUsbEp failed! (0x%08X) (interface %d, endpoint 0x%02X, %u URB(s)).", rc, usb_if_session->ID, ep_desc->bEndpointAddress, max_burst);
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void usbHsFsDriveGetDeviceStrings(UsbHsFsDriveContext *drive_ctx)
|
|
{
|
|
if (!drive_ctx || !usbHsIfIsActive(&(drive_ctx->usb_if_session))) return;
|
|
|
|
Result rc = 0;
|
|
|
|
UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
|
|
|
|
u16 *lang_ids = NULL, cur_lang_id = 0, sel_lang_id = 0;
|
|
u32 lang_ids_count = 0;
|
|
|
|
/* Set default string language ID to English (US). */
|
|
sel_lang_id = USB_LANGID_ENUS;
|
|
|
|
/* Retrieve string descriptor indexes. Bail out if none of them are valid. */
|
|
u8 manufacturer = usb_if_session->inf.device_desc.iManufacturer;
|
|
u8 product_name = usb_if_session->inf.device_desc.iProduct;
|
|
u8 serial_number = usb_if_session->inf.device_desc.iSerialNumber;
|
|
|
|
if (!manufacturer && !product_name && !serial_number) return;
|
|
|
|
/* Get supported language IDs. */
|
|
rc = usbHsFsRequestGetStringDescriptor(usb_if_session, 0, 0, &lang_ids, &lang_ids_count);
|
|
if (R_FAILED(rc))
|
|
{
|
|
USBHSFS_LOG_MSG("Unable to retrieve supported language IDs! (0x%08X) (interface %d).", rc, usb_if_session->ID);
|
|
return;
|
|
}
|
|
|
|
/* Check if English (US) is supported. Otherwise, just default to the last valid language ID. */
|
|
lang_ids_count /= sizeof(u16);
|
|
for(u32 i = 0; i < lang_ids_count; i++)
|
|
{
|
|
if (!(cur_lang_id = lang_ids[i])) continue;
|
|
|
|
if (cur_lang_id == sel_lang_id) break;
|
|
|
|
if ((i + 1) == lang_ids_count)
|
|
{
|
|
sel_lang_id = cur_lang_id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(lang_ids);
|
|
|
|
/* Retrieve string descriptors. */
|
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, manufacturer, sel_lang_id, &(drive_ctx->manufacturer));
|
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, product_name, sel_lang_id, &(drive_ctx->product_name));
|
|
usbHsFsDriveGetUtf8StringFromStringDescriptor(usb_if_session, serial_number, sel_lang_id, &(drive_ctx->serial_number));
|
|
}
|
|
|
|
static void usbHsFsDriveGetUtf8StringFromStringDescriptor(UsbHsClientIfSession *usb_if_session, u8 idx, u16 lang_id, char **out_buf)
|
|
{
|
|
if (!usb_if_session || !usbHsIfIsActive(usb_if_session) || !idx || !lang_id || !out_buf) return;
|
|
|
|
Result rc = 0;
|
|
|
|
u16 *string_data = NULL;
|
|
u32 string_data_size = 0;
|
|
|
|
ssize_t units = 0;
|
|
char *utf8_str = NULL;
|
|
|
|
/* Get string descriptor. */
|
|
rc = usbHsFsRequestGetStringDescriptor(usb_if_session, idx, lang_id, &string_data, &string_data_size);
|
|
if (R_FAILED(rc)) goto end;
|
|
|
|
/* Get UTF-8 string size. */
|
|
units = utf16_to_utf8(NULL, string_data, 0);
|
|
if (units <= 0)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to get UTF-8 string size for string descriptor! (interface %d, language ID 0x%04X, index %u).", usb_if_session->ID, lang_id, idx);
|
|
goto end;
|
|
}
|
|
|
|
/* Allocate memory for the UTF-8 string. */
|
|
utf8_str = calloc(units + 1, sizeof(char));
|
|
if (!utf8_str)
|
|
{
|
|
USBHSFS_LOG_MSG("Failed to allocate 0x%lX byte-long UTF-8 buffer for string descriptor! (interface %d, language ID 0x%04X, index %u).", units + 1, usb_if_session->ID, lang_id, idx);
|
|
goto end;
|
|
}
|
|
|
|
/* Perform UTF-16 to UTF-8 conversion. */
|
|
units = utf16_to_utf8((u8*)utf8_str, string_data, (size_t)units);
|
|
if (units <= 0)
|
|
{
|
|
USBHSFS_LOG_MSG("UTF-16 to UTF-8 conversion failed for string descriptor! (interface %d, language ID 0x%04X, index %u).", usb_if_session->ID, lang_id, idx);
|
|
goto end;
|
|
}
|
|
|
|
USBHSFS_LOG_MSG("Converted string (interface %d, language ID 0x%04X, index %u): \"%s\".", usb_if_session->ID, lang_id, idx, utf8_str);
|
|
|
|
/* Update output. */
|
|
*out_buf = utf8_str;
|
|
|
|
end:
|
|
if ((R_FAILED(rc) || units <= 0) && utf8_str) free(utf8_str);
|
|
|
|
if (string_data) free(string_data);
|
|
}
|
|
|
|
static void usbHsFsDriveDestroyLogicalUnitContext(UsbHsFsDriveLogicalUnitContext *lun_ctx, bool stop_lun)
|
|
{
|
|
if (!lun_ctx || !usbHsFsDriveIsValidContext((UsbHsFsDriveContext*)lun_ctx->drive_ctx) || lun_ctx->lun >= UMS_MAX_LUN) return;
|
|
|
|
if (lun_ctx->fs_ctx)
|
|
{
|
|
/* Destroy filesystem contexts. */
|
|
for(u32 i = 0; i < lun_ctx->fs_count; i++)
|
|
{
|
|
UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = lun_ctx->fs_ctx[i];
|
|
if (!fs_ctx) continue;
|
|
|
|
usbHsFsMountDestroyLogicalUnitFileSystemContext(fs_ctx);
|
|
free(fs_ctx);
|
|
}
|
|
|
|
/* Free filesystem context pointer array. */
|
|
free(lun_ctx->fs_ctx);
|
|
lun_ctx->fs_ctx = NULL;
|
|
}
|
|
|
|
/* Stop current LUN. */
|
|
if (stop_lun) usbHsFsScsiStopDriveLogicalUnit(lun_ctx);
|
|
}
|