From da33b9a6b9eff74d59b910da8cd6ef0f6c4638fe Mon Sep 17 00:00:00 2001 From: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com> Date: Wed, 21 May 2025 14:43:18 +0100 Subject: [PATCH] add usbds speed / max packet detection, add zlt support for usbds, fix game usb transfer bug (see below). due to the previous commit, i broke dumping multiple games via usb as the stream offset wasn't reset. because of this, the first transfer would complete, but the 2nd one would fail. --- sphaira/include/usb/base.hpp | 8 ++-- sphaira/include/usb/usbds.hpp | 6 ++- sphaira/include/usb/usbhs.hpp | 2 +- sphaira/include/yati/source/stream.hpp | 4 ++ sphaira/source/ui/menus/game_menu.cpp | 5 ++ sphaira/source/ui/menus/gc_menu.cpp | 1 + sphaira/source/usb/base.cpp | 6 +-- sphaira/source/usb/usbds.cpp | 66 +++++++++++++++++++++----- sphaira/source/usb/usbhs.cpp | 2 +- sphaira/source/yati/source/usb.cpp | 1 + 10 files changed, 79 insertions(+), 22 deletions(-) diff --git a/sphaira/include/usb/base.hpp b/sphaira/include/usb/base.hpp index 3a4b800..7794692 100644 --- a/sphaira/include/usb/base.hpp +++ b/sphaira/include/usb/base.hpp @@ -24,9 +24,9 @@ struct Base { virtual Result IsUsbConnected(u64 timeout) = 0; // transfers a chunk of data, check out_size_transferred for how much was transferred. - Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout); - Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) { - return TransferPacketImpl(read, page, size, out_size_transferred, m_transfer_timeout); + Result TransferPacketImpl(bool read, void *page, u32 remaining, u32 size, u32 *out_size_transferred, u64 timeout); + Result TransferPacketImpl(bool read, void *page, u32 remaining, u32 size, u32 *out_size_transferred) { + return TransferPacketImpl(read, page, remaining, size, out_size_transferred, m_transfer_timeout); } // transfers all data. @@ -90,7 +90,7 @@ protected: virtual Event *GetCompletionEvent(UsbSessionEndpoint ep) = 0; virtual Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) = 0; - virtual Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) = 0; + virtual Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_xfer_id) = 0; virtual Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) = 0; private: diff --git a/sphaira/include/usb/usbds.hpp b/sphaira/include/usb/usbds.hpp index c0eb0f9..5d56fe8 100644 --- a/sphaira/include/usb/usbds.hpp +++ b/sphaira/include/usb/usbds.hpp @@ -11,17 +11,19 @@ struct UsbDs final : Base { Result Init() override; Result IsUsbConnected(u64 timeout) override; - Result GetSpeed(UsbDeviceSpeed* out); + Result GetSpeed(UsbDeviceSpeed* out, u16* max_packet_size); private: + Result WaitUntilConfigured(u64 timeout); Event *GetCompletionEvent(UsbSessionEndpoint ep) override; Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override; - Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) override; + Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) override; Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) override; private: UsbDsInterface* m_interface{}; UsbDsEndpoint* m_endpoints[2]{}; + u16 m_max_packet_size{}; }; } // namespace sphaira::usb diff --git a/sphaira/include/usb/usbhs.hpp b/sphaira/include/usb/usbhs.hpp index 630d0f6..fc2bf3e 100644 --- a/sphaira/include/usb/usbhs.hpp +++ b/sphaira/include/usb/usbhs.hpp @@ -14,7 +14,7 @@ struct UsbHs final : Base { private: Event *GetCompletionEvent(UsbSessionEndpoint ep) override; Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) override; - Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) override; + Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_xfer_id) override; Result GetTransferResult(UsbSessionEndpoint ep, u32 xfer_id, u32 *out_requested_size, u32 *out_transferred_size) override; Result Connect(); diff --git a/sphaira/include/yati/source/stream.hpp b/sphaira/include/yati/source/stream.hpp index 00535d9..395314a 100644 --- a/sphaira/include/yati/source/stream.hpp +++ b/sphaira/include/yati/source/stream.hpp @@ -18,6 +18,10 @@ struct Stream : Base { return true; } + void Reset() { + m_offset = 0; + } + protected: Result m_open_result{}; diff --git a/sphaira/source/ui/menus/game_menu.cpp b/sphaira/source/ui/menus/game_menu.cpp index d99ee3c..7466b46 100644 --- a/sphaira/source/ui/menus/game_menu.cpp +++ b/sphaira/source/ui/menus/game_menu.cpp @@ -277,6 +277,7 @@ struct UsbTest final : usb::upload::Usb, yati::source::Stream { m_path = path; m_progress = 0; m_pull_offset = 0; + Stream::Reset(); m_size = m_source->GetSize(path); m_pbox->SetTitle(m_source->GetName(path)); m_pbox->NewTransfer(m_path); @@ -422,7 +423,11 @@ Result DumpNspToUsbS2S(ProgressBox* pbox, std::span entries) { // wait for exit command. if (R_SUCCEEDED(rc)) { + log_write("waiting for exit command\n"); rc = usb->PollCommands(); + log_write("finished polling for exit command\n"); + } else { + log_write("skipped polling for exit command\n"); } if (rc == usb->Result_Exit) { diff --git a/sphaira/source/ui/menus/gc_menu.cpp b/sphaira/source/ui/menus/gc_menu.cpp index 4e613d4..f54cb2f 100644 --- a/sphaira/source/ui/menus/gc_menu.cpp +++ b/sphaira/source/ui/menus/gc_menu.cpp @@ -260,6 +260,7 @@ struct UsbTest final : usb::upload::Usb, yati::source::Stream { m_path = path; m_progress = 0; m_pull_offset = 0; + Stream::Reset(); m_size = m_entry.GetSize(path); m_pbox->SetTitle(m_entry.GetName(path)); m_pbox->NewTransfer(m_path); diff --git a/sphaira/source/usb/base.cpp b/sphaira/source/usb/base.cpp index 28b0b3d..8c2c5c3 100644 --- a/sphaira/source/usb/base.cpp +++ b/sphaira/source/usb/base.cpp @@ -38,7 +38,7 @@ Base::~Base() { App::SetAutoSleepDisabled(false); } -Result Base::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) { +Result Base::TransferPacketImpl(bool read, void *page, u32 remaining, u32 size, u32 *out_size_transferred, u64 timeout) { u32 xfer_id; /* If we're not configured yet, wait to become configured first. */ @@ -46,7 +46,7 @@ Result Base::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_t /* Select the appropriate endpoint and begin a transfer. */ const auto ep = read ? UsbSessionEndpoint_Out : UsbSessionEndpoint_In; - R_TRY(TransferAsync(ep, page, size, std::addressof(xfer_id))); + R_TRY(TransferAsync(ep, page, remaining, size, std::addressof(xfer_id))); /* Try to wait for the event. */ R_TRY(WaitTransferCompletion(ep, timeout)); @@ -84,7 +84,7 @@ Result Base::TransferAll(bool read, void *data, u32 size, u64 timeout) { } u32 out_size_transferred; - R_TRY(TransferPacketImpl(read, transfer_buf, size, &out_size_transferred, timeout)); + R_TRY(TransferPacketImpl(read, transfer_buf, size, size, &out_size_transferred, timeout)); if (!alias && read) { std::memcpy(buf, transfer_buf, out_size_transferred); diff --git a/sphaira/source/usb/usbds.cpp b/sphaira/source/usb/usbds.cpp index bc10e61..543561b 100644 --- a/sphaira/source/usb/usbds.cpp +++ b/sphaira/source/usb/usbds.cpp @@ -7,8 +7,19 @@ namespace sphaira::usb { namespace { -// untested, should work tho. // TODO: pr missing speed fields to libnx. +enum { UsbDeviceSpeed_None = 0x0 }; +enum { UsbDeviceSpeed_Low = 0x1 }; + +constexpr u16 DEVICE_SPEED[] = { + [UsbDeviceSpeed_None] = 0x0, + [UsbDeviceSpeed_Low] = 0x0, + [UsbDeviceSpeed_Full] = 0x40, + [UsbDeviceSpeed_High] = 0x200, + [UsbDeviceSpeed_Super] = 0x400, +}; + +// TODO: pr this to libnx. Result usbDsGetSpeed(u32 *out) { if (hosversionBefore(8,0,0)) { return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); @@ -140,22 +151,22 @@ Result UsbDs::Init() { endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1; // Full Speed Config - endpoint_descriptor_in.wMaxPacketSize = 0x40; - endpoint_descriptor_out.wMaxPacketSize = 0x40; + endpoint_descriptor_in.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_Full]; + endpoint_descriptor_out.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_Full]; R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE)); // High Speed Config - endpoint_descriptor_in.wMaxPacketSize = 0x200; - endpoint_descriptor_out.wMaxPacketSize = 0x200; + endpoint_descriptor_in.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_High]; + endpoint_descriptor_out.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_High]; R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE)); // Super Speed Config - endpoint_descriptor_in.wMaxPacketSize = 0x400; - endpoint_descriptor_out.wMaxPacketSize = 0x400; + endpoint_descriptor_in.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_Super]; + endpoint_descriptor_out.wMaxPacketSize = DEVICE_SPEED[UsbDeviceSpeed_Super]; R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE)); R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE)); @@ -174,7 +185,7 @@ Result UsbDs::Init() { } // the below code is taken from libnx, with the addition of a uevent to cancel. -Result UsbDs::IsUsbConnected(u64 timeout) { +Result UsbDs::WaitUntilConfigured(u64 timeout) { Result rc; UsbState state = UsbState_Detached; @@ -218,8 +229,33 @@ Result UsbDs::IsUsbConnected(u64 timeout) { return rc; } -Result UsbDs::GetSpeed(UsbDeviceSpeed* out) { - return usbDsGetSpeed((u32*)out); +Result UsbDs::IsUsbConnected(u64 timeout) { + const auto rc = WaitUntilConfigured(timeout); + if (R_FAILED(rc)) { + m_max_packet_size = 0; + return rc; + } + + if (!m_max_packet_size) { + UsbDeviceSpeed speed; + R_TRY(GetSpeed(&speed, &m_max_packet_size)); + log_write("[USBDS] speed: %u max_packet: 0x%X\n", speed, m_max_packet_size); + } + + R_SUCCEED(); +} + +Result UsbDs::GetSpeed(UsbDeviceSpeed* out, u16* max_packet_size) { + if (hosversionAtLeast(8,0,0)) { + R_TRY(usbDsGetSpeed((u32*)out)); + } else { + // assume USB 2.0 speed (likely the case anyway). + *out = UsbDeviceSpeed_High; + } + + *max_packet_size = DEVICE_SPEED[*out]; + R_UNLESS(*max_packet_size > 0, 0x1); + R_SUCCEED(); } Event *UsbDs::GetCompletionEvent(UsbSessionEndpoint ep) { @@ -242,6 +278,7 @@ Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) { rc = Result_Cancelled; } else if (R_SUCCEEDED(rc) && idx == waiters.size() - 2) { log_write("got usbDsGetStateChangeEvent() event\n"); + m_max_packet_size = 0; rc = KERNELRESULT(TimedOut); } @@ -255,7 +292,14 @@ Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) { return rc; } -Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) { +Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) { + if (remaining == size && size == m_max_packet_size) { + log_write("[USBDS] SetZlt(true)\n"); + R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], true)); + } else { + R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], false)); + } + return usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id); } diff --git a/sphaira/source/usb/usbhs.cpp b/sphaira/source/usb/usbhs.cpp index c3eacee..ecadad6 100644 --- a/sphaira/source/usb/usbhs.cpp +++ b/sphaira/source/usb/usbhs.cpp @@ -184,7 +184,7 @@ Result UsbHs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) { return rc; } -Result UsbHs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_xfer_id) { +Result UsbHs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_xfer_id) { return usbHsEpPostBufferAsync(&m_endpoints[ep], buffer, size, 0, out_xfer_id); } diff --git a/sphaira/source/yati/source/usb.cpp b/sphaira/source/yati/source/usb.cpp index 4ed9503..38afbe3 100644 --- a/sphaira/source/yati/source/usb.cpp +++ b/sphaira/source/yati/source/usb.cpp @@ -80,6 +80,7 @@ Result Usb::SendFileRangeCmd(u64 off, u64 size, u64 timeout) { } Result Usb::Finished(u64 timeout) { + log_write("[USB] sending finished command\n"); return SendCmdHeader(tinfoil::USBCmdId::EXIT, 0, timeout); }