handle more usb errors, set max for notifications, usb transfer uses unique ptr over vector, show usb speed in usb menu.

This commit is contained in:
ITotalJustice
2025-07-19 20:29:53 +01:00
parent 4421ac1ceb
commit 7fb973c28d
15 changed files with 149 additions and 183 deletions

View File

@@ -595,7 +595,10 @@ enum class SphairaResult : Result {
UsbBadMagic,
UsbBadVersion,
UsbBadCount,
UsbBadBufferAlign,
UsbBadTransferSize,
UsbEmptyTransferSize,
UsbOverflowTransferSize,
UsbBadTotalSize,
UsbUploadBadMagic,
@@ -726,8 +729,10 @@ enum : Result {
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadVersion),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadCount),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadBufferAlign),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbBadTotalSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbEmptyTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbOverflowTransferSize),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadMagic),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadExit),
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCount),

View File

@@ -27,19 +27,16 @@ struct Menu final : MenuBase {
auto GetShortTitle() const -> const char* override { return "USB"; };
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
// this should be private
// private:
void ThreadFunction();
private:
std::unique_ptr<yati::source::Usb> m_usb_source{};
bool m_was_mtp_enabled{};
Thread m_thread{};
Mutex m_mutex{};
// the below are shared across threads, lock with the above mutex!
State m_state{State::None};
std::atomic<State> m_state{State::None};
std::vector<std::string> m_names{};
bool m_usb_has_connection{};
};
} // namespace sphaira::ui::menu::usb

View File

@@ -45,6 +45,7 @@ private:
private:
void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
auto GetEntries(NotifEntry::Side side) -> Entries&;
private:
Entries m_entries_left{};

View File

@@ -2,7 +2,7 @@
#include <vector>
#include <string>
#include <new>
#include <memory>
#include <switch.h>
namespace sphaira::usb {
@@ -39,43 +39,10 @@ struct Base {
ueventSignal(GetCancelEvent());
}
auto& GetTransferBuffer() {
return m_aligned;
}
auto GetTransferTimeout() const {
return m_transfer_timeout;
}
public:
// custom allocator for std::vector that respects alignment.
// https://en.cppreference.com/w/cpp/named_req/Allocator
template <typename T, std::size_t Align>
struct CustomVectorAllocator {
public:
// https://en.cppreference.com/w/cpp/memory/new/operator_new
auto allocate(std::size_t n) -> T* {
n = (n + (Align - 1)) &~ (Align - 1);
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);
::operator delete[] (p, align);
}
private:
static constexpr inline std::align_val_t align{Align};
};
template <typename T>
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
using value_type = T; // used by std::vector
};
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
protected:
enum UsbSessionEndpoint {
UsbSessionEndpoint_In = 0,
@@ -90,7 +57,7 @@ protected:
private:
u64 m_transfer_timeout{};
UEvent m_uevent{};
PageAlignedVector m_aligned{};
std::unique_ptr<u8*> m_aligned{};
};
} // namespace sphaira::usb

View File

@@ -31,6 +31,7 @@ private:
private:
std::unique_ptr<usb::UsbHs> m_usb;
std::vector<u8> m_buf;
};
} // namespace sphaira::usb::upload

View File

@@ -7,6 +7,9 @@ enum { UsbDeviceSpeed_None = 0x0 };
enum { UsbDeviceSpeed_Low = 0x1 };
Result usbDsGetSpeed(UsbDeviceSpeed *out);
auto GetUsbDsStateStr(UsbState state) -> const char*;
auto GetUsbDsSpeedStr(UsbDeviceSpeed speed) -> const char*;
namespace sphaira::usb {
// Device Host

View File

@@ -114,8 +114,10 @@ auto GetCodeMessage(Result rc) -> const char* {
case Result_UsbBadMagic: return "SphairaError_UsbBadMagic";
case Result_UsbBadVersion: return "SphairaError_UsbBadVersion";
case Result_UsbBadCount: return "SphairaError_UsbBadCount";
case Result_UsbBadBufferAlign: return "SphairaError_UsbBadBufferAlign";
case Result_UsbBadTransferSize: return "SphairaError_UsbBadTransferSize";
case Result_UsbBadTotalSize: return "SphairaError_UsbBadTotalSize";
case Result_UsbEmptyTransferSize: return "SphairaError_UsbEmptyTransferSize";
case Result_UsbOverflowTransferSize: return "SphairaError_UsbOverflowTransferSize";
case Result_UsbUploadBadMagic: return "SphairaError_UsbUploadBadMagic";
case Result_UsbUploadExit: return "SphairaError_UsbUploadExit";
case Result_UsbUploadBadCount: return "SphairaError_UsbUploadBadCount";

View File

@@ -41,7 +41,7 @@ Result Stream::ReadChunk(void* buf, s64 size, u64* bytes_read) {
while (!m_token.stop_requested()) {
SCOPED_MUTEX(&m_mutex);
if (m_active && m_buffer.empty()) {
condvarWait(&m_can_read, &m_mutex);
R_TRY(condvarWait(&m_can_read, &m_mutex));
}
if ((!m_active && m_buffer.empty()) || m_token.stop_requested()) {

View File

@@ -8,36 +8,6 @@
#include "haze_helper.hpp"
namespace sphaira::ui::menu::mtp {
namespace {
auto GetUsbStateStr(UsbState state) -> const char* {
switch (state) {
case UsbState_Detached: return "Detached";
case UsbState_Attached: return "Attached";
case UsbState_Powered: return "Powered";
case UsbState_Default: return "Default";
case UsbState_Address: return "Address";
case UsbState_Configured: return "Configured";
case UsbState_Suspended: return "Suspended";
}
return "Unknown";
}
auto GetUsbSpeedStr(UsbDeviceSpeed speed) -> const char* {
// todo: remove this cast when libnx pr is merged.
switch ((u32)speed) {
case UsbDeviceSpeed_None: return "None";
case UsbDeviceSpeed_Low: return "USB 1.0 Low Speed";
case UsbDeviceSpeed_Full: return "USB 1.1 Full Speed";
case UsbDeviceSpeed_High: return "USB 2.0 High Speed";
case UsbDeviceSpeed_Super: return "USB 3.0 Super Speed";
}
return "Unknown";
}
} // namespace
Menu::Menu(u32 flags) : stream::Menu{"MTP Install"_i18n, flags} {
m_was_mtp_enabled = App::GetMtpEnable();
@@ -77,7 +47,7 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
usbDsGetSpeed(&speed);
char buf[128];
std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbStateStr(state)).c_str(), i18n::get(GetUsbSpeedStr(speed)).c_str());
std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbDsStateStr(state)).c_str(), i18n::get(GetUsbDsSpeedStr(speed)).c_str());
SetSubHeading(buf);
}
}

View File

@@ -16,37 +16,7 @@ constexpr u64 FINISHED_TIMEOUT = 1e+9 * 3; // 3 seconds.
void thread_func(void* user) {
auto app = static_cast<Menu*>(user);
for (;;) {
if (app->GetToken().stop_requested()) {
break;
}
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
if (rc == Result_UsbCancelled) {
break;
}
// set connected status
mutexLock(&app->m_mutex);
if (R_SUCCEEDED(rc)) {
app->m_state = State::Connected_WaitForFileList;
} else {
app->m_state = State::None;
}
mutexUnlock(&app->m_mutex);
if (R_SUCCEEDED(rc)) {
std::vector<std::string> names;
if (R_SUCCEEDED(app->m_usb_source->WaitForConnection(CONNECTION_TIMEOUT, names))) {
mutexLock(&app->m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&app->m_mutex));
app->m_state = State::Connected_StartingTransfer;
app->m_names = names;
break;
}
}
}
app->ThreadFunction();
}
} // namespace
@@ -74,8 +44,6 @@ Menu::Menu(u32 flags) : MenuBase{"USB"_i18n, flags} {
m_state = State::Failed;
}
mutexInit(&m_mutex);
if (m_state != State::Failed) {
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, PRIO_PREEMPTIVE, 1);
threadStart(&m_thread);
@@ -102,8 +70,20 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
static TimeStamp poll_ts;
if (poll_ts.GetSeconds() >= 1) {
poll_ts.Update();
UsbState state{UsbState_Detached};
usbDsGetState(&state);
UsbDeviceSpeed speed{(UsbDeviceSpeed)UsbDeviceSpeed_None};
usbDsGetSpeed(&speed);
char buf[128];
std::snprintf(buf, sizeof(buf), "State: %s | Speed: %s", i18n::get(GetUsbDsStateStr(state)).c_str(), i18n::get(GetUsbDsSpeedStr(speed)).c_str());
SetSubHeading(buf);
}
if (m_state == State::Connected_StartingTransfer) {
log_write("set to progress\n");
@@ -152,9 +132,6 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (m_state) {
case State::None:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Waiting for connection..."_i18n.c_str());
@@ -182,8 +159,33 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
}
void Menu::OnFocusGained() {
MenuBase::OnFocusGained();
void Menu::ThreadFunction() {
for (;;) {
if (GetToken().stop_requested()) {
break;
}
const auto rc = m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
if (rc == Result_UsbCancelled) {
break;
}
// set connected status
if (R_SUCCEEDED(rc)) {
m_state = State::Connected_WaitForFileList;
} else {
m_state = State::None;
}
if (R_SUCCEEDED(rc)) {
std::vector<std::string> names;
if (R_SUCCEEDED(m_usb_source->WaitForConnection(CONNECTION_TIMEOUT, names))) {
m_names = names;
m_state = State::Connected_StartingTransfer;
break;
}
}
}
}
} // namespace sphaira::ui::menu::usb

View File

@@ -5,6 +5,11 @@
#include <optional>
namespace sphaira::ui {
namespace {
constexpr u64 MAX_ENTRIES = 9;
} // namespace
NotifEntry::NotifEntry(std::string text, Side side)
: m_text{std::move(text)}
@@ -77,13 +82,12 @@ auto NotifMananger::Push(const NotifEntry& entry) -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (entry.GetSide()) {
case NotifEntry::Side::LEFT:
m_entries_left.emplace_front(entry);
break;
case NotifEntry::Side::RIGHT:
m_entries_right.emplace_front(entry);
break;
auto& entries = GetEntries(entry.GetSide());
entries.emplace_front(entry);
// check if we have too many entries, start removing old ones.
if (entries.size() >= MAX_ENTRIES) {
entries.erase(entries.begin() + MAX_ENTRIES, entries.end());
}
}
@@ -91,32 +95,14 @@ auto NotifMananger::Pop(NotifEntry::Side side) -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (side) {
case NotifEntry::Side::LEFT:
if (!m_entries_left.empty()) {
m_entries_left.clear();
}
break;
case NotifEntry::Side::RIGHT:
if (!m_entries_right.empty()) {
m_entries_right.clear();
}
break;
}
GetEntries(side).pop_back();
}
auto NotifMananger::Clear(NotifEntry::Side side) -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
switch (side) {
case NotifEntry::Side::LEFT:
m_entries_left.clear();
break;
case NotifEntry::Side::RIGHT:
m_entries_right.clear();
break;
}
GetEntries(side).clear();
}
auto NotifMananger::Clear() -> void {
@@ -127,4 +113,12 @@ auto NotifMananger::Clear() -> void {
m_entries_right.clear();
}
auto NotifMananger::GetEntries(NotifEntry::Side side) -> Entries& {
if (side == NotifEntry::Side::LEFT) {
return m_entries_left;
} else {
return m_entries_right;
}
}
} // namespace sphaira::ui

View File

@@ -24,14 +24,20 @@
#include <cstring>
namespace sphaira::usb {
namespace {
constexpr u64 TRANSFER_ALIGN = 0x1000;
constexpr u64 TRANSFER_MAX = 1024*1024*16;
static_assert(!(TRANSFER_MAX % TRANSFER_ALIGN));
} // namespace
Base::Base(u64 transfer_timeout) {
App::SetAutoSleepDisabled(true);
m_transfer_timeout = transfer_timeout;
ueventCreate(GetCancelEvent(), true);
// this avoids allocations during transfers.
m_aligned.reserve(1024 * 1024 * 16);
m_aligned = std::make_unique<u8*>(new(std::align_val_t{TRANSFER_ALIGN}) u8[TRANSFER_MAX]);
}
Base::~Base() {
@@ -63,39 +69,28 @@ Result Base::TransferPacketImpl(bool read, void *page, u32 remaining, u32 size,
// an changes are made.
// yati already goes to great lengths to be zero-copy during installing
// by swapping buffers and inflating in-place.
// NOTE: it is now possible to request the transfer buffer using GetTransferBuffer(),
// which will always be aligned and have the size aligned.
// this allows for zero-copy transferrs to take place.
// this is used in usb_upload.cpp.
// do note that this relies of the host sending / receiving buffers of an aligned size.
Result Base::TransferAll(bool read, void *data, u32 size, u64 timeout) {
auto buf = static_cast<u8*>(data);
auto transfer_buf = m_aligned.data();
const auto alias = buf == transfer_buf;
auto transfer_buf = *m_aligned;
if (!alias) {
m_aligned.resize(size);
}
R_UNLESS(!((u64)transfer_buf & 0xFFF), Result_UsbBadBufferAlign);
R_UNLESS(size <= TRANSFER_MAX, Result_UsbBadTransferSize);
while (size) {
if (!alias && !read) {
if (!read) {
std::memcpy(transfer_buf, buf, size);
}
u32 out_size_transferred;
R_TRY(TransferPacketImpl(read, transfer_buf, size, size, &out_size_transferred, timeout));
R_UNLESS(out_size_transferred > 0, Result_UsbEmptyTransferSize);
R_UNLESS(out_size_transferred <= size, Result_UsbOverflowTransferSize);
if (!alias && read) {
if (read) {
std::memcpy(buf, transfer_buf, out_size_transferred);
}
if (alias) {
transfer_buf += out_size_transferred;
} else {
buf += out_size_transferred;
}
buf += out_size_transferred;
size -= out_size_transferred;
}

View File

@@ -92,9 +92,7 @@ Result Usb::FileRangeCmd(u64 data_size) {
s64 end_off = header.size;
s64 read_size = header.size;
// use transfer buffer directly to avoid copy overhead.
auto& buf = m_usb->GetTransferBuffer();
buf.resize(header.size);
m_buf.resize(header.size);
while (curr_off < end_off) {
if (curr_off + read_size >= end_off) {
@@ -102,8 +100,8 @@ Result Usb::FileRangeCmd(u64 data_size) {
}
u64 bytes_read;
R_TRY(Read(path, buf.data(), header.offset + curr_off, read_size, &bytes_read));
R_TRY(m_usb->TransferAll(false, buf.data(), bytes_read));
R_TRY(Read(path, m_buf.data(), header.offset + curr_off, read_size, &bytes_read));
R_TRY(m_usb->TransferAll(false, m_buf.data(), bytes_read));
curr_off += bytes_read;
}

View File

@@ -13,6 +13,33 @@ Result usbDsGetSpeed(UsbDeviceSpeed *out) {
return serviceDispatchOut(usbDsGetServiceSession(), hosversionAtLeast(11,0,0) ? 11 : 12, *out);
}
auto GetUsbDsStateStr(UsbState state) -> const char* {
switch (state) {
case UsbState_Detached: return "Detached";
case UsbState_Attached: return "Attached";
case UsbState_Powered: return "Powered";
case UsbState_Default: return "Default";
case UsbState_Address: return "Address";
case UsbState_Configured: return "Configured";
case UsbState_Suspended: return "Suspended";
}
return "Unknown";
}
auto GetUsbDsSpeedStr(UsbDeviceSpeed speed) -> const char* {
// todo: remove this cast when libnx pr is merged.
switch ((u32)speed) {
case UsbDeviceSpeed_None: return "None";
case UsbDeviceSpeed_Low: return "USB 1.0 Low Speed";
case UsbDeviceSpeed_Full: return "USB 1.1 Full Speed";
case UsbDeviceSpeed_High: return "USB 2.0 High Speed";
case UsbDeviceSpeed_Super: return "USB 3.0 Super Speed";
}
return "Unknown";
}
namespace sphaira::usb {
namespace {
@@ -210,9 +237,12 @@ Result UsbDs::WaitUntilConfigured(u64 timeout) {
eventClear(usbDsGetStateChangeEvent());
// check if we got one of the cancel events.
if (R_SUCCEEDED(rc) && idx == waiters.size() - 1) {
rc = Result_UsbCancelled;
break;
if (R_SUCCEEDED(rc)) {
if (waiters[idx].handle == waiterForUEvent(GetCancelEvent()).handle) {
log_write("got usb cancel event\n");
rc = Result_UsbCancelled;
break;
}
}
rc = usbDsGetState(&state);
@@ -268,18 +298,19 @@ Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
auto rc = waitObjects(&idx, waiters.data(), waiters.size(), timeout);
// check if we got one of the cancel events.
if (R_SUCCEEDED(rc) && idx == waiters.size() - 1) {
log_write("got usb cancel event\n");
rc = Result_UsbCancelled;
} else if (R_SUCCEEDED(rc) && idx == waiters.size() - 2) {
log_write("got usbDsGetStateChangeEvent() event\n");
m_max_packet_size = 0;
rc = KERNELRESULT(TimedOut);
if (R_SUCCEEDED(rc)) {
if (waiters[idx].handle == waiterForEvent(usbDsGetStateChangeEvent()).handle) {
log_write("got usbDsGetStateChangeEvent() event\n");
m_max_packet_size = 0;
rc = KERNELRESULT(TimedOut);
} else if (waiters[idx].handle == waiterForUEvent(GetCancelEvent()).handle) {
log_write("got usb cancel event\n");
rc = Result_UsbCancelled;
}
}
if (R_FAILED(rc)) {
R_TRY(usbDsEndpoint_Cancel(m_endpoints[ep]));
usbDsEndpoint_Cancel(m_endpoints[ep]);
eventClear(GetCompletionEvent(ep));
eventClear(usbDsGetStateChangeEvent());
}
@@ -289,7 +320,7 @@ Result UsbDs::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
Result UsbDs::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 remaining, u32 size, u32 *out_urb_id) {
if (ep == UsbSessionEndpoint_In) {
if (remaining == size && !(size % (u32)m_max_packet_size)) {
if (size && remaining == size && !(size % (u32)m_max_packet_size)) {
log_write("[USBDS] SetZlt(true)\n");
R_TRY(usbDsEndpoint_SetZlt(m_endpoints[ep], true));
} else {

View File

@@ -71,7 +71,7 @@ Result Usb::SendFileRangeCmd(u64 off, u64 size, u64 timeout) {
R_TRY(SendCmdHeader(tinfoil::USBCmdId::FILE_RANGE, sizeof(fRangeHeader) + fRangeHeader.nspNameLen, timeout));
R_TRY(m_usb->TransferAll(false, &fRangeHeader, sizeof(fRangeHeader), timeout));
R_TRY(m_usb->TransferAll(false, m_transfer_file_name.data(), m_transfer_file_name.size(), timeout));
R_TRY(m_usb->TransferAll(false, m_transfer_file_name.data(), fRangeHeader.nspNameLen, timeout));
tinfoil::USBCmdHeader responseHeader;
R_TRY(m_usb->TransferAll(true, &responseHeader, sizeof(responseHeader), timeout));