diff --git a/sphaira/include/yati/source/usb.hpp b/sphaira/include/yati/source/usb.hpp index 7970e33..9ba64cc 100644 --- a/sphaira/include/yati/source/usb.hpp +++ b/sphaira/include/yati/source/usb.hpp @@ -2,6 +2,8 @@ #include "base.hpp" #include "fs.hpp" +#include +#include #include namespace sphaira::yati::source { @@ -21,20 +23,47 @@ struct Usb final : Base { ~Usb(); Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override; - Result Finished() const; + Result Finished(); Result Init(); Result WaitForConnection(u64 timeout, u32& speed, u32& count); Result GetFileInfo(std::string& name_out, u64& size_out); +public: + // custom allocator for std::vector that respects alignment. + // https://en.cppreference.com/w/cpp/named_req/Allocator + template + struct CustomVectorAllocator { + public: + // https://en.cppreference.com/w/cpp/memory/new/operator_new + auto allocate(std::size_t n) -> T* { + return new(align) T[n]; + } + + // https://en.cppreference.com/w/cpp/memory/new/operator_delete + auto deallocate(T* p, std::size_t n) noexcept -> void { + ::operator delete[] (p, n, align); + } + + private: + static constexpr inline std::align_val_t align{Align}; + }; + + template + struct PageAllocator : CustomVectorAllocator { + using value_type = T; // used by std::vector + }; + + using PageAlignedVector = std::vector>; + private: enum UsbSessionEndpoint { UsbSessionEndpoint_In = 0, UsbSessionEndpoint_Out = 1, }; - Result SendCommand(s64 off, s64 size) const; - Result InternalRead(void* buf, s64 off, s64 size) const; + Result SendCommand(s64 off, s64 size); + Result InternalRead(void* buf, s64 off, s64 size); bool GetConfigured() const; Event *GetCompletionEvent(UsbSessionEndpoint ep) const; @@ -42,11 +71,15 @@ private: Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) const; Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) const; Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) const; + Result TransferAll(bool read, void *data, u32 size, u64 timeout); private: UsbDsInterface* m_interface{}; UsbDsEndpoint* m_endpoints[2]{}; u64 m_transfer_timeout{}; + // aligned buffer that transfer data is copied to and from. + // a vector is used to avoid multiple alloc within the transfer loop. + PageAlignedVector m_aligned{}; }; } // namespace sphaira::yati::source diff --git a/sphaira/source/ui/menus/main_menu.cpp b/sphaira/source/ui/menus/main_menu.cpp index f5a4afe..761f802 100644 --- a/sphaira/source/ui/menus/main_menu.cpp +++ b/sphaira/source/ui/menus/main_menu.cpp @@ -11,6 +11,7 @@ #include "download.hpp" #include "defines.hpp" #include "i18n.hpp" +#include "ui/menus/usb_menu.hpp" #include #include @@ -208,6 +209,9 @@ MainMenu::MainMenu() { this->SetActions( std::make_pair(Button::START, Action{App::Exit}), + std::make_pair(Button::SELECT, Action{[this](){ + App::Push(std::make_shared()); + }}), std::make_pair(Button::Y, Action{"Menu"_i18n, [this](){ auto options = std::make_shared("Menu Options"_i18n, "v" APP_VERSION_HASH, Sidebar::Side::LEFT); ON_SCOPE_EXIT(App::Push(options)); diff --git a/sphaira/source/ui/menus/usb_menu.cpp b/sphaira/source/ui/menus/usb_menu.cpp index f84bf59..bc0eae5 100644 --- a/sphaira/source/ui/menus/usb_menu.cpp +++ b/sphaira/source/ui/menus/usb_menu.cpp @@ -128,6 +128,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) { if (result) { App::Notify("Usb install success!"_i18n); m_state = State::Done; + SetPop(); } else { App::Notify("Usb install failed!"_i18n); m_state = State::Failed; diff --git a/sphaira/source/yati/source/usb.cpp b/sphaira/source/yati/source/usb.cpp index 21b7bf1..908a7c9 100644 --- a/sphaira/source/yati/source/usb.cpp +++ b/sphaira/source/yati/source/usb.cpp @@ -24,11 +24,6 @@ namespace { constexpr u32 MAGIC = 0x53504841; constexpr u32 VERSION = 2; -struct SendHeader { - u32 magic; - u32 version; -}; - struct RecvHeader { u32 magic; u32 version; @@ -41,6 +36,8 @@ struct RecvHeader { Usb::Usb(u64 transfer_timeout) { m_open_result = usbDsInitialize(); m_transfer_timeout = transfer_timeout; + // this avoids allocations during transfers. + m_aligned.reserve(1024 * 1024 * 16); } Usb::~Usb() { @@ -195,24 +192,17 @@ Result Usb::Init() { } Result Usb::WaitForConnection(u64 timeout, u32& speed, u32& count) { - const SendHeader send_header{ - .magic = MAGIC, - .version = VERSION, - }; - - alignas(0x1000) u8 aligned[0x1000]{}; - std::memcpy(aligned, std::addressof(send_header), sizeof(send_header)); + struct { + u32 magic; + u32 version; + } send_header{MAGIC, VERSION}; // send header. - u32 transferredSize; - R_TRY(TransferPacketImpl(false, aligned, sizeof(send_header), &transferredSize, timeout)); + R_TRY(TransferAll(false, &send_header, sizeof(send_header), timeout)); // receive header. - struct RecvHeader recv_header{}; - R_TRY(TransferPacketImpl(true, aligned, sizeof(recv_header), &transferredSize, timeout)); - - // copy data into header struct. - std::memcpy(&recv_header, aligned, sizeof(recv_header)); + struct RecvHeader recv_header; + R_TRY(TransferAll(true, &recv_header, sizeof(recv_header), timeout)); // validate received header. R_UNLESS(recv_header.magic == MAGIC, Result_BadMagic); @@ -230,17 +220,11 @@ Result Usb::GetFileInfo(std::string& name_out, u64& size_out) { u64 name_length; } file_info_meta; - alignas(0x1000) u8 aligned[0x1000]{}; - // receive meta. - u32 transferredSize; - R_TRY(TransferPacketImpl(true, aligned, sizeof(file_info_meta), &transferredSize, m_transfer_timeout)); - std::memcpy(&file_info_meta, aligned, sizeof(file_info_meta)); - R_UNLESS(file_info_meta.name_length < sizeof(aligned), 0x1); + R_TRY(TransferAll(true, &file_info_meta, sizeof(file_info_meta), m_transfer_timeout)); - R_TRY(TransferPacketImpl(true, aligned, file_info_meta.name_length, &transferredSize, m_transfer_timeout)); name_out.resize(file_info_meta.name_length); - std::memcpy(name_out.data(), aligned, name_out.size()); + R_TRY(TransferAll(true, name_out.data(), file_info_meta.name_length, m_transfer_timeout)); size_out = file_info_meta.size; R_SUCCEED(); @@ -304,7 +288,38 @@ Result Usb::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_tr return GetTransferResult(ep, urb_id, nullptr, out_size_transferred); } -Result Usb::SendCommand(s64 off, s64 size) const { +// while it may seem like a bad idea to transfer data to a buffer and copy it +// in practice, this has no impact on performance. +// the switch is *massively* bottlenecked by slow io (nand and sd). +// so making usb transfers zero-copy provides no benefit other than increased +// code complexity and the increase of future bugs if/when sphaira is forked +// an changes are made. +// yati already goes to great lengths to be zero-copy during installing +// by swapping buffers and inflating in-place. +Result Usb::TransferAll(bool read, void *data, u32 size, u64 timeout) { + auto buf = static_cast(data); + m_aligned.resize((size + 0xFFF) & ~0xFFF); + + while (size) { + if (!read) { + std::memcpy(m_aligned.data(), buf, size); + } + + u32 out_size_transferred; + R_TRY(TransferPacketImpl(read, m_aligned.data(), size, &out_size_transferred, timeout)); + + if (read) { + std::memcpy(buf, m_aligned.data(), out_size_transferred); + } + + buf += out_size_transferred; + size -= out_size_transferred; + } + + R_SUCCEED(); +} + +Result Usb::SendCommand(s64 off, s64 size) { struct { u32 hash; u32 magic; @@ -312,64 +327,16 @@ Result Usb::SendCommand(s64 off, s64 size) const { s64 size; } meta{0, 0, off, size}; - alignas(0x1000) static u8 aligned[0x1000]{}; - std::memcpy(aligned, std::addressof(meta), sizeof(meta)); - - u32 transferredSize; - return TransferPacketImpl(false, aligned, sizeof(meta), &transferredSize, m_transfer_timeout); + return TransferAll(false, &meta, sizeof(meta), m_transfer_timeout); } -Result Usb::Finished() const { +Result Usb::Finished() { return SendCommand(0, 0); } -Result Usb::InternalRead(void* _buf, s64 off, s64 size) const { - u8* buf = (u8*)_buf; - alignas(0x1000) u8 aligned[0x1000]{}; - const auto stored_size = size; - s64 total = 0; - - while (size) { - auto read_size = size; - auto read_buf = buf; - - if (u64(buf) & 0xFFF) { - read_size = std::min(size, sizeof(aligned) - (u64(buf) & 0xFFF)); - read_buf = aligned; - log_write("unaligned read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); - } else if (read_size & 0xFFF) { - if (read_size <= 0xFFF) { - log_write("unaligned small read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); - read_buf = aligned; - } else { - log_write("unaligned big read %zd %zd read_size: %zd align: %zd\n", off, size, read_size, u64(buf) & 0xFFF); - // read as much as possible into buffer, the rest will - // be handled in a second read which will be aligned size aligned. - read_size = read_size & ~0xFFF; - } - } - - R_TRY(SendCommand(off, read_size)); - - u32 transferredSize{}; - R_TRY(TransferPacketImpl(true, read_buf, read_size, &transferredSize, m_transfer_timeout)); - R_UNLESS(transferredSize <= read_size, Result_BadTransferSize); - - if (read_buf == aligned) { - std::memcpy(buf, aligned, transferredSize); - } - - if (transferredSize < read_size) { - log_write("reading less than expected! %u vs %zd stored: %zd\n", transferredSize, read_size, stored_size); - } - - off += transferredSize; - buf += transferredSize; - size -= transferredSize; - total += transferredSize; - } - - R_UNLESS(total == stored_size, Result_BadTotalSize); +Result Usb::InternalRead(void* buf, s64 off, s64 size) { + R_TRY(SendCommand(off, size)); + R_TRY(TransferAll(true, buf, size, m_transfer_timeout)); R_SUCCEED(); }