troposphere: add haze MTP server (#2095)
* troposphere: add haze MTP server * haze: adjust banner, new lines between class sections, single-statement if * haze: remove additional newlines between sections * haze: avoid use of reference out parameter * haze: console_main_loop: style * haze: event_reactor, file_system_proxy, ptp: style * haze: ptp_data_builder, ptp_object_database, ptp_object_heap, results, usb_session: style * haze: event_reactor, ptp_data_parser, async_usb_server, ptp_object_database, ptp_object_heap: style * haze: ptp_responder: style * haze: usb_session: style * haze: use svc defs from vapours * haze: misc comments * haze: fix pointer overflow check * haze: fix copy paste error * haze: use alignment, invalidate cached use values in console * haze: fix stray hex constant * haze: misc style * haze: fixes for windows * haze: add GetObjectPropsSupported, GetObjectPropDesc, GetObjectPropValue * haze: add SetObjectPropValue * haze: improve object database API * haze: improve WriteVariableLengthData readability * haze: fix directory renames on windows * haze: ptp_object_database: fix collation * haze: event_reactor: fix size validation * haze: ptp_responder: improve documentation * haze: ptp_responder: avoid unnecessary fs interaction in GetObjectPropValue * haze: ptp_responder: fix object deletion on windows * haze: tear down sessions on suspension * haze: prefer more specific data types * haze: misc * haze: fix usb 3.0 support * haze: improve host-to-device file transfer performance * haze: report device serial * haze: move static_assert to requires, fix alignment
This commit is contained in:
62
troposphere/haze/source/async_usb_server.cpp
Normal file
62
troposphere/haze/source/async_usb_server.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit UsbSession g_usb_session;
|
||||
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::Initialize(const UsbCommsInterfaceInfo *interface_info, u16 id_vendor, u16 id_product, EventReactor *reactor) {
|
||||
m_reactor = reactor;
|
||||
|
||||
/* Set up a new USB session. */
|
||||
R_TRY(g_usb_session.Initialize(interface_info, id_vendor, id_product));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void AsyncUsbServer::Finalize() {
|
||||
g_usb_session.Finalize();
|
||||
}
|
||||
|
||||
Result AsyncUsbServer::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred) const {
|
||||
u32 urb_id;
|
||||
s32 waiter_idx;
|
||||
|
||||
/* If we're not configured yet, wait to become configured first. */
|
||||
if (!g_usb_session.GetConfigured()) {
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(usbDsGetStateChangeEvent())));
|
||||
R_TRY(eventClear(usbDsGetStateChangeEvent()));
|
||||
|
||||
R_THROW(haze::ResultNotConfigured());
|
||||
}
|
||||
|
||||
/* Select the appropriate endpoint and begin a transfer. */
|
||||
UsbSessionEndpoint ep = read ? UsbSessionEndpoint_Read : UsbSessionEndpoint_Write;
|
||||
R_TRY(g_usb_session.TransferAsync(ep, page, size, std::addressof(urb_id)));
|
||||
|
||||
/* Try to wait for the event. */
|
||||
R_TRY(m_reactor->WaitFor(std::addressof(waiter_idx), waiterForEvent(g_usb_session.GetCompletionEvent(ep))));
|
||||
|
||||
/* Return what we transferred. */
|
||||
R_RETURN(g_usb_session.GetTransferResult(ep, urb_id, out_size_transferred));
|
||||
}
|
||||
|
||||
}
|
||||
78
troposphere/haze/source/event_reactor.cpp
Normal file
78
troposphere/haze/source/event_reactor.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
bool EventReactor::AddConsumer(EventConsumer *consumer, Waiter waiter) {
|
||||
HAZE_ASSERT(m_num_wait_objects + 1 <= svc::ArgumentHandleCountMax);
|
||||
|
||||
/* Add to the end of the list. */
|
||||
m_consumers[m_num_wait_objects] = consumer;
|
||||
m_waiters[m_num_wait_objects] = waiter;
|
||||
m_num_wait_objects++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventReactor::RemoveConsumer(EventConsumer *consumer) {
|
||||
s32 output_index = 0;
|
||||
|
||||
/* Remove the consumer. */
|
||||
for (s32 input_index = 0; input_index < m_num_wait_objects; input_index++) {
|
||||
if (m_consumers[input_index] == consumer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output_index != input_index) {
|
||||
m_consumers[output_index] = m_consumers[input_index];
|
||||
m_waiters[output_index] = m_waiters[input_index];
|
||||
}
|
||||
|
||||
output_index++;
|
||||
}
|
||||
|
||||
m_num_wait_objects = output_index;
|
||||
}
|
||||
|
||||
Result EventReactor::WaitForImpl(s32 *out_arg_waiter, const Waiter *arg_waiters, s32 num_arg_waiters) {
|
||||
HAZE_ASSERT(0 < num_arg_waiters && num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
HAZE_ASSERT(m_num_wait_objects + num_arg_waiters <= svc::ArgumentHandleCountMax);
|
||||
|
||||
while (true) {
|
||||
/* Check if we should wait for an event. */
|
||||
R_TRY(m_result);
|
||||
|
||||
/* Insert waiters from argument list. */
|
||||
for (s32 i = 0; i < num_arg_waiters; i++) {
|
||||
m_waiters[i + m_num_wait_objects] = arg_waiters[i];
|
||||
}
|
||||
|
||||
s32 idx;
|
||||
HAZE_R_ABORT_UNLESS(waitObjects(std::addressof(idx), m_waiters, m_num_wait_objects + num_arg_waiters, svc::WaitInfinite));
|
||||
|
||||
/* If a waiter in the argument list was signaled, return it. */
|
||||
if (idx >= m_num_wait_objects) {
|
||||
*out_arg_waiter = idx - m_num_wait_objects;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Otherwise, process the event as normal. */
|
||||
m_consumers[idx]->ProcessEvent();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
troposphere/haze/source/main.cpp
Normal file
25
troposphere/haze/source/main.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
#include <haze/console_main_loop.hpp>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
/* Run the application. */
|
||||
haze::ConsoleMainLoop::RunApplication();
|
||||
|
||||
/* Return to the loader. */
|
||||
return 0;
|
||||
}
|
||||
152
troposphere/haze/source/ptp_object_database.cpp
Normal file
152
troposphere/haze/source/ptp_object_database.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
void PtpObjectDatabase::Initialize(PtpObjectHeap *object_heap) {
|
||||
m_object_heap = object_heap;
|
||||
m_object_heap->Initialize();
|
||||
|
||||
std::construct_at(std::addressof(m_name_tree));
|
||||
std::construct_at(std::addressof(m_object_id_tree));
|
||||
|
||||
m_next_object_id = 1;
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::Finalize() {
|
||||
std::destroy_at(std::addressof(m_object_id_tree));
|
||||
std::destroy_at(std::addressof(m_name_tree));
|
||||
|
||||
m_next_object_id = 0;
|
||||
|
||||
m_object_heap->Finalize();
|
||||
m_object_heap = nullptr;
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateOrFindObject(const char *parent_name, const char *name, u32 parent_id, PtpObject **out_object) {
|
||||
constexpr auto separator = "/";
|
||||
|
||||
/* Calculate length of the new name with null terminator. */
|
||||
const size_t parent_name_len = util::Strlen(parent_name);
|
||||
const size_t separator_len = util::Strlen(separator);
|
||||
const size_t name_len = util::Strlen(name);
|
||||
const size_t terminator_len = 1;
|
||||
const size_t alloc_len = sizeof(PtpObject) + parent_name_len + separator_len + name_len + terminator_len;
|
||||
|
||||
/* Allocate memory for the object. */
|
||||
PtpObject * const object = m_object_heap->Allocate<PtpObject>(alloc_len);
|
||||
R_UNLESS(object != nullptr, haze::ResultOutOfMemory());
|
||||
|
||||
/* Build the object name. */
|
||||
std::strncpy(object->m_name, parent_name, parent_name_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len, separator, separator_len + terminator_len);
|
||||
std::strncpy(object->m_name + parent_name_len + separator_len, name, name_len + terminator_len);
|
||||
|
||||
{
|
||||
/* Ensure we maintain a clean state on failure. */
|
||||
auto guard = SCOPE_GUARD { m_object_heap->Deallocate(object, alloc_len); };
|
||||
|
||||
/* Check if an object with this name already exists. If it does, we can just return it here. */
|
||||
if (auto * const existing = this->GetObjectByName(object->GetName()); existing != nullptr) {
|
||||
*out_object = existing;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
/* Persist the reference to the object. */
|
||||
guard.Cancel();
|
||||
}
|
||||
|
||||
/* Set object properties. */
|
||||
object->m_parent_id = parent_id;
|
||||
object->m_object_id = 0;
|
||||
|
||||
/* Set output. */
|
||||
*out_object = object;
|
||||
|
||||
/* We succeeded. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::RegisterObject(PtpObject *object, u32 desired_id) {
|
||||
/* If the object is already registered, skip registration. */
|
||||
if (object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set desired object ID. */
|
||||
if (desired_id == 0) {
|
||||
desired_id = m_next_object_id++;
|
||||
}
|
||||
|
||||
/* Insert object into trees. */
|
||||
object->Register(desired_id);
|
||||
m_object_id_tree.insert(*object);
|
||||
m_name_tree.insert(*object);
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::UnregisterObject(PtpObject *object) {
|
||||
/* If the object is not registered, skip trying to unregister. */
|
||||
if (!object->GetIsRegistered()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove object from trees. */
|
||||
m_object_id_tree.erase(m_object_id_tree.iterator_to(*object));
|
||||
m_name_tree.erase(m_name_tree.iterator_to(*object));
|
||||
object->Unregister();
|
||||
}
|
||||
|
||||
void PtpObjectDatabase::DeleteObject(PtpObject *object) {
|
||||
/* Unregister the object as required. */
|
||||
this->UnregisterObject(object);
|
||||
|
||||
/* Free the object. */
|
||||
m_object_heap->Deallocate(object, sizeof(PtpObject) + std::strlen(object->GetName()) + 1);
|
||||
}
|
||||
|
||||
Result PtpObjectDatabase::CreateAndRegisterObjectId(const char *parent_name, const char *name, u32 parent_id, u32 *out_object_id) {
|
||||
/* Try to create the object. */
|
||||
PtpObject *object;
|
||||
R_TRY(this->CreateOrFindObject(parent_name, name, parent_id, std::addressof(object)));
|
||||
|
||||
/* We succeeded, so register it. */
|
||||
this->RegisterObject(object);
|
||||
|
||||
/* Set the output ID. */
|
||||
*out_object_id = object->GetObjectId();
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectById(u32 object_id) {
|
||||
/* Find in ID mapping. */
|
||||
if (auto it = m_object_id_tree.find_key(object_id); it != m_object_id_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PtpObject *PtpObjectDatabase::GetObjectByName(const char *name) {
|
||||
/* Find in name mapping. */
|
||||
if (auto it = m_name_tree.find_key(name); it != m_name_tree.end()) {
|
||||
return std::addressof(*it);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
troposphere/haze/source/ptp_object_heap.cpp
Normal file
69
troposphere/haze/source/ptp_object_heap.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
/* Allow 20MiB for use by libnx. */
|
||||
static constexpr size_t LibnxReservedMemorySize = 20_MB;
|
||||
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Initialize() {
|
||||
/* If we're already initialized, skip re-initialization. */
|
||||
if (m_heap_block_size != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Estimate how much memory we can reserve. */
|
||||
size_t mem_used = 0;
|
||||
HAZE_R_ABORT_UNLESS(svcGetInfo(std::addressof(mem_used), InfoType_UsedMemorySize, svc::CurrentProcess, 0));
|
||||
HAZE_ASSERT(mem_used > LibnxReservedMemorySize);
|
||||
mem_used -= LibnxReservedMemorySize;
|
||||
|
||||
/* Calculate size of blocks. */
|
||||
m_heap_block_size = mem_used / NumHeapBlocks;
|
||||
HAZE_ASSERT(m_heap_block_size > 0);
|
||||
|
||||
/* Allocate the memory. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
m_heap_blocks[i] = std::malloc(m_heap_block_size);
|
||||
HAZE_ASSERT(m_heap_blocks[i] != nullptr);
|
||||
}
|
||||
|
||||
/* Set the address to allocate from. */
|
||||
m_next_address = m_heap_blocks[0];
|
||||
}
|
||||
|
||||
void PtpObjectHeap::Finalize() {
|
||||
if (m_heap_block_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tear down the heap, allowing a subsequent call to Initialize() if desired. */
|
||||
for (size_t i = 0; i < NumHeapBlocks; i++) {
|
||||
std::free(m_heap_blocks[i]);
|
||||
m_heap_blocks[i] = nullptr;
|
||||
}
|
||||
|
||||
m_next_address = nullptr;
|
||||
m_heap_block_size = 0;
|
||||
m_current_heap_block = 0;
|
||||
}
|
||||
|
||||
}
|
||||
1039
troposphere/haze/source/ptp_responder.cpp
Normal file
1039
troposphere/haze/source/ptp_responder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
268
troposphere/haze/source/usb_session.cpp
Normal file
268
troposphere/haze/source/usb_session.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <haze.hpp>
|
||||
|
||||
namespace haze {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const u32 DefaultInterfaceNumber = 0;
|
||||
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize1x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x4,
|
||||
};
|
||||
|
||||
/* Set up interface. */
|
||||
R_TRY(usbDsGetDsInterface(std::addressof(m_interface), std::addressof(interface_descriptor), "usb"));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), std::addressof(endpoint_descriptor_in)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), std::addressof(endpoint_descriptor_out)));
|
||||
R_TRY(usbDsInterface_GetDsEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), std::addressof(endpoint_descriptor_interrupt)));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize5x(const UsbCommsInterfaceInfo *info) {
|
||||
struct usb_interface_descriptor interface_descriptor = {
|
||||
.bLength = USB_DT_INTERFACE_SIZE,
|
||||
.bDescriptorType = USB_DT_INTERFACE,
|
||||
.bInterfaceNumber = DefaultInterfaceNumber,
|
||||
.bNumEndpoints = 3,
|
||||
.bInterfaceClass = info->bInterfaceClass,
|
||||
.bInterfaceSubClass = info->bInterfaceSubClass,
|
||||
.bInterfaceProtocol = info->bInterfaceProtocol,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_in = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_out = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_OUT,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_BULK,
|
||||
.wMaxPacketSize = PtpUsbBulkHighSpeedMaxPacketLength,
|
||||
};
|
||||
|
||||
struct usb_endpoint_descriptor endpoint_descriptor_interrupt = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bEndpointAddress = USB_ENDPOINT_IN,
|
||||
.bmAttributes = USB_TRANSFER_TYPE_INTERRUPT,
|
||||
.wMaxPacketSize = 0x18,
|
||||
.bInterval = 0x6,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x0f,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
struct usb_ss_endpoint_companion_descriptor endpoint_companion_interrupt = {
|
||||
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
|
||||
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
|
||||
.bMaxBurst = 0x00,
|
||||
.bmAttributes = 0x00,
|
||||
.wBytesPerInterval = 0x00,
|
||||
};
|
||||
|
||||
R_TRY(usbDsRegisterInterface(std::addressof(m_interface)));
|
||||
|
||||
u8 iInterface;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iInterface), "MTP"));
|
||||
|
||||
interface_descriptor.bInterfaceNumber = m_interface->interface_index;
|
||||
interface_descriptor.iInterface = iInterface;
|
||||
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
|
||||
endpoint_descriptor_interrupt.bEndpointAddress += interface_descriptor.bInterfaceNumber + 2;
|
||||
|
||||
/* High speed config. */
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_High, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
|
||||
/* Super speed config. */
|
||||
endpoint_descriptor_in.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
endpoint_descriptor_out.wMaxPacketSize = PtpUsbBulkSuperSpeedMaxPacketLength;
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(interface_descriptor), USB_DT_INTERFACE_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_in), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_out), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_descriptor_interrupt), USB_DT_ENDPOINT_SIZE));
|
||||
R_TRY(usbDsInterface_AppendConfigurationData(m_interface, UsbDeviceSpeed_Super, std::addressof(endpoint_companion_interrupt), USB_DT_SS_ENDPOINT_COMPANION_SIZE));
|
||||
|
||||
/* Set up endpoints. */
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Write]), endpoint_descriptor_in.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Read]), endpoint_descriptor_out.bEndpointAddress));
|
||||
R_TRY(usbDsInterface_RegisterEndpoint(m_interface, std::addressof(m_endpoints[UsbSessionEndpoint_Interrupt]), endpoint_descriptor_interrupt.bEndpointAddress));
|
||||
|
||||
R_RETURN(usbDsInterface_EnableInterface(m_interface));
|
||||
}
|
||||
|
||||
Result UsbSession::Initialize(const UsbCommsInterfaceInfo *info, u16 id_vendor, u16 id_product) {
|
||||
R_TRY(usbDsInitialize());
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
/* Report language as US English. */
|
||||
static const u16 supported_langs[1] = { 0x0409 };
|
||||
R_TRY(usbDsAddUsbLanguageStringDescriptor(nullptr, supported_langs, util::size(supported_langs)));
|
||||
|
||||
/* Get the device serial number. */
|
||||
SetSysSerialNumber serial;
|
||||
R_TRY(setsysInitialize());
|
||||
R_TRY(setsysGetSerialNumber(std::addressof(serial)));
|
||||
setsysExit();
|
||||
|
||||
/* Report strings. */
|
||||
u8 iManufacturer, iProduct, iSerialNumber;
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iManufacturer), "Nintendo"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iProduct), "Nintendo Switch"));
|
||||
R_TRY(usbDsAddUsbStringDescriptor(std::addressof(iSerialNumber), serial.number));
|
||||
|
||||
/* Send device descriptors */
|
||||
struct usb_device_descriptor device_descriptor = {
|
||||
.bLength = USB_DT_DEVICE_SIZE,
|
||||
.bDescriptorType = USB_DT_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = id_vendor,
|
||||
.idProduct = id_product,
|
||||
.bcdDevice = 0x0100,
|
||||
.iManufacturer = iManufacturer,
|
||||
.iProduct = iProduct,
|
||||
.iSerialNumber = iSerialNumber,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor)));
|
||||
|
||||
device_descriptor.bcdUSB = 0x0300;
|
||||
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor)));
|
||||
|
||||
/* Binary Object Store */
|
||||
u8 bos[0x16] = {
|
||||
0x05, /* .bLength */
|
||||
USB_DT_BOS, /* .bDescriptorType */
|
||||
0x16, 0x00, /* .wTotalLength */
|
||||
0x02, /* .bNumDeviceCaps */
|
||||
|
||||
/* USB 2.0 */
|
||||
0x07, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x02, /* .bDevCapabilityType */
|
||||
0x02, 0x00, 0x00, 0x00, /* .bmAttributes */
|
||||
|
||||
/* USB 3.0 */
|
||||
0x0a, /* .bLength */
|
||||
USB_DT_DEVICE_CAPABILITY, /* .bDescriptorType */
|
||||
0x03, /* .bDevCapabilityType */
|
||||
0x00, /* .bmAttributes */
|
||||
0x0c, 0x00, /* .wSpeedSupported */
|
||||
0x03, /* .bFunctionalitySupport */
|
||||
0x00, /* .bU1DevExitLat */
|
||||
0x00, 0x00 /* .bU2DevExitLat */
|
||||
};
|
||||
R_TRY(usbDsSetBinaryObjectStore(bos, sizeof(bos)));
|
||||
}
|
||||
|
||||
if (hosversionAtLeast(5, 0, 0)) {
|
||||
R_TRY(this->Initialize5x(info));
|
||||
R_TRY(usbDsEnable());
|
||||
} else {
|
||||
R_TRY(this->Initialize1x(info));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UsbSession::Finalize() {
|
||||
usbDsExit();
|
||||
}
|
||||
|
||||
bool UsbSession::GetConfigured() const {
|
||||
UsbState usb_state;
|
||||
|
||||
HAZE_R_ABORT_UNLESS(usbDsGetState(std::addressof(usb_state)));
|
||||
|
||||
return usb_state == UsbState_Configured;
|
||||
}
|
||||
|
||||
Event *UsbSession::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||
}
|
||||
|
||||
Result UsbSession::TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) {
|
||||
R_RETURN(usbDsEndpoint_PostBufferAsync(m_endpoints[ep], buffer, size, out_urb_id));
|
||||
}
|
||||
|
||||
Result UsbSession::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_transferred_size) {
|
||||
UsbDsReportData report_data;
|
||||
|
||||
R_TRY(eventClear(std::addressof(m_endpoints[ep]->CompletionEvent)));
|
||||
R_TRY(usbDsEndpoint_GetReportData(m_endpoints[ep], std::addressof(report_data)));
|
||||
R_TRY(usbDsParseReportData(std::addressof(report_data), urb_id, nullptr, out_transferred_size));
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user