diff --git a/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp b/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp index b7dacb118..445456e9a 100644 --- a/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/htclow/htclow_types.hpp @@ -34,7 +34,9 @@ namespace ams::htclow { constexpr inline s16 ProtocolVersion = 5; enum ReceiveOption { - /* ... */ + ReceiveOption_NonBlocking = 0, + ReceiveOption_ReceiveAnyData = 1, + ReceiveOption_ReceiveAllData = 2, }; } diff --git a/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp b/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp new file mode 100644 index 000000000..a7bf79be1 --- /dev/null +++ b/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#include +#include "htc_htclow_driver.hpp" + +namespace ams::htc::server::driver { + + namespace { + + constexpr ALWAYS_INLINE htclow::impl::ChannelInternalType GetHtclowChannel(htclow::ChannelId channel_id, htclow::ModuleId module_id) { + return { + .channel_id = channel_id, + .reserved = 0, + .module_id = module_id, + }; + } + + } + + void HtclowDriver::WaitTask(u32 task_id) { + os::WaitEvent(m_manager->GeTaskEvent(task_id)); + } + + void HtclowDriver::SetDisconnectionEmulationEnabled(bool en) { + /* NOTE: Nintendo ignores the input, here. */ + m_disconnection_emulation_enabled = false; + } + + Result HtclowDriver::Open(htclow::ChannelId channel) { + /* Check the channel/module combination. */ + if (m_module_id == htclow::ModuleId::Htcmisc) { + AMS_ABORT_UNLESS(channel == HtcmiscClientChannelId ); + } else if (m_module_id == htclow::ModuleId::Htcs) { + AMS_ABORT_UNLESS(channel == 0); + } else { + AMS_ABORT("Unsupported channel"); + } + + return this->Open(channel, m_default_receive_buffer, sizeof(m_default_receive_buffer), m_default_send_buffer, sizeof(m_default_send_buffer)); + } + + Result HtclowDriver::Open(htclow::ChannelId channel, void *receive_buffer, size_t receive_buffer_size, void *send_buffer, size_t send_buffer_size) { + /* Open the channel. */ + R_TRY(m_manager->Open(GetHtclowChannel(channel, m_module_id))); + + /* Set the send/receive buffers. */ + m_manager->SetReceiveBuffer(receive_buffer, receive_buffer_size); + m_manager->SetSendBuffer(send_buffer, send_buffer_size); + + return ResultSuccess(); + } + + void HtclowDriver::Close(htclow::ChannelId channel) { + /* Close the channel. */ + const auto result = m_manager->Close(GetHtclowChannel(channel, m_module_id)); + R_ASSERT(result); + } + + Result HtclowDriver::Connect(htclow::ChannelId channel) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Begin connecting. */ + u32 task_id; + R_TRY(m_manager->ConnectBegin(std::addressof(task_id), GetHtclowChannel(channel, m_module_id))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish connecting. */ + R_TRY(m_manager->ConnectEnd(GetHtclowChannel(channel, m_module_id), task_id)); + + return ResultSuccess(); + } + + void HtclowDriver::Shutdown(htclow::ChannelId channel) { + /* Shut down the channel. */ + m_manager->Shutdown(GetHtclowChannel(channel, m_module_id)); + } + + Result HtclowDriver::Send(s64 *out, const void *src, s64 src_size, htclow::ChannelId channel) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Validate that dst_size is okay. */ + R_UNLESS(util::IsIntValueRepresentable(src_size), htclow::ResultOverflow()); + + /* Repeatedly send until we're done. */ + size_t cur_send; + size_t sent; + for (sent = 0; sent < static_cast(src_size); sent += cur_send) { + /* Begin sending. */ + u32 task_id; + R_TRY(m_manager->SendBegin(std::addressof(task_id), std::addressof(cur_send), static_cast(src) + sent, static_cast(src_size) - sent, GetHtclowChannel(channel, m_module_id))); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish sending. */ + R_ABORT_UNLESS(m_manager->SendEnd(task_id)); + } + + /* Set the output sent size. */ + *out = static_cast(sent); + + return ResultSuccess(); + } + + Result HtclowDriver::ReceiveInternal(size_t *out, void *dst, size_t dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) { + /* Begin receiving. */ + u32 task_id; + R_TRY(m_manager->ReceiveBegin(std::addressof(task_id), GetHtclowChannel(channel, m_module_id), option != htclow::ReceiveOption_NonBlocking)); + + /* Wait for the task to complete. */ + this->WaitTask(task_id); + + /* Finish receiving. */ + return m_manager->ReceiveEnd(out, dst, dst_size, GetHtclowChannel(channel, m_module_id), task_id); + } + + Result HtclowDriver::Receive(s64 *out, void *dst, s64 dst_size, htclow::ChannelId channel, htclow::ReceiveOption option) { + /* Check if we should emulate disconnection. */ + R_UNLESS(!m_disconnection_emulation_enabled, htclow::ResultConnectionFailure()); + + /* Validate that dst_size is okay. */ + R_UNLESS(util::IsIntValueRepresentable(dst_size), htclow::ResultOverflow()); + + /* Determine the minimum allowable receive size. */ + size_t min_size; + switch (option) { + case htclow::ReceiveOption_NonBlocking: min_size = 0; break; + case htclow::ReceiveOption_ReceiveAnyData: min_size = 1; break; + case htclow::ReceiveOption_ReceiveAllData: min_size = dst_size; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Repeatedly receive. */ + size_t received = 0; + do { + size_t cur_received; + const Result result = this->ReceiveInternal(std::addressof(cur_received), static_cast(dst) + received, static_cast(src_size) - received, channel, option); + + if (R_FAILED(result)) { + if (htclow::ResultChannelReceiveBufferEmpty::Includes(result)) { + R_UNLESS(option != htclow::ReceiveOption_NonBlocking, htclow::ResultNonBlockingReceiveFailed()); + } + if (htclow::ResultChannelNotExist::Includes(result)) { + *out = received; + } + return result; + } + + received += cur_received; + } while (received < min_size); + + /* Set the output received size. */ + *out = static_cast(received); + + return ResultSuccess(); + } + + htclow::ChannelState HtclowDriver::GetChannelState(htclow::ChannelId channel) { + return m_manager->GetChannelState(GetHtclowChannel(channel, m_module_id)); + } + + os::EventType *HtclowDriver::GetChannelStateEvent(htclow::ChannelId channel) { + return m_manager->GetChannelStateEvent(GetHtclowChannel(channel, m_module_id)); + } + +} diff --git a/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp b/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp index 9e600acdd..a4d8588a8 100644 --- a/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp +++ b/libraries/libstratosphere/source/htc/server/driver/htc_htclow_driver.hpp @@ -31,6 +31,9 @@ namespace ams::htc::server::driver { htclow::ModuleId m_module_id; public: HtclowDriver(htclow::HtclowManager *manager, htclow::ModuleId module_id) : m_manager(manager), m_disconnection_emulation_enabled(false), m_module_id(module_id) { /* ... */ } + private: + void WaitTask(u32 task_id); + Result ReceiveInternal(size_t *out, void *dst, size_t dst_size, htclow::ChannelId channel, htclow::ReceiveOption option); public: virtual void SetDisconnectionEmulationEnabled(bool en) override; virtual Result Open(htclow::ChannelId channel) override; diff --git a/libraries/libvapours/include/vapours/results/htclow_results.hpp b/libraries/libvapours/include/vapours/results/htclow_results.hpp index 6294e5977..60e719b79 100644 --- a/libraries/libvapours/include/vapours/results/htclow_results.hpp +++ b/libraries/libvapours/include/vapours/results/htclow_results.hpp @@ -20,13 +20,16 @@ namespace ams::htclow { R_DEFINE_NAMESPACE_RESULT_MODULE(29); - R_DEFINE_ERROR_RESULT(UnknownDriverType, 3); - R_DEFINE_ERROR_RESULT(ChannelNotExist, 10); + R_DEFINE_ERROR_RESULT(ConnectionFailure, 1); + R_DEFINE_ERROR_RESULT(UnknownDriverType, 3); + R_DEFINE_ERROR_RESULT(NonBlockingReceiveFailed, 5); + R_DEFINE_ERROR_RESULT(ChannelNotExist, 10); R_DEFINE_ERROR_RESULT(InvalidChannelState, 200); R_DEFINE_ERROR_RESULT(InvalidChannelStateDisconnected, 201); R_DEFINE_ERROR_RANGE(InternalError, 1000, 2999); + R_DEFINE_ERROR_RESULT(Overflow, 1001); R_DEFINE_ERROR_RESULT(OutOfMemory, 1002); R_DEFINE_ERROR_RESULT(InvalidArgument, 1003); R_DEFINE_ERROR_RESULT(ProtocolError, 1004); diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index f0a9250e1..01cbeb5c5 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/util/util_int_util.hpp b/libraries/libvapours/include/vapours/util/util_int_util.hpp new file mode 100644 index 000000000..77a1739b1 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_int_util.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2020 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 . + */ + +#pragma once +#include +#include + +namespace ams::util { + + namespace impl { + + template + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using ToLimit = std::numeric_limits; + using FromLimit = std::numeric_limits; + if constexpr (ToLimit::min() <= FromLimit::min() && FromLimit::max() <= ToLimit::max()) { + return true; + } else { + return ToLimit::min() <= v && v <= ToLimit::max(); + } + } + + template + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using ToLimit = std::numeric_limits; + using FromLimit = std::numeric_limits; + if constexpr (ToLimit::min() <= FromLimit::min() && FromLimit::max() <= ToLimit::max()) { + return true; + } else { + return ToLimit::min() <= v && v <= ToLimit::max(); + } + } + + template + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using UnsignedFrom = typename std::make_unsigned::type; + + if (v < 0) { + return false; + } else { + return IsIntValueRepresentableImpl(static_cast(v)); + } + } + + template + constexpr ALWAYS_INLINE bool IsIntValueRepresentableImpl(From v) { + using UnsignedTo = typename std::make_unsigned::type; + + return v <= static_cast(std::numeric_limits::max()); + } + + } + + template + constexpr ALWAYS_INLINE bool IsIntValueRepresentable(From v) { + return ::ams::util::impl::IsIntValueRepresentableImpl(v); + } + +}