htc: implement htclow listener thread

This commit is contained in:
Michael Scire 2021-02-08 05:45:23 -08:00 committed by SciresM
parent c9c41e0e8d
commit 2341f18edd
21 changed files with 669 additions and 11 deletions

View file

@ -27,4 +27,25 @@ namespace ams::htclow {
ChannelId _channel_id; ChannelId _channel_id;
}; };
enum ChannelState {
ChannelState_Connectable = 0,
ChannelState_Unconnectable = 1,
ChannelState_Connected = 2,
ChannelState_Shutdown = 3,
};
constexpr bool IsStateTransitionAllowed(ChannelState from, ChannelState to) {
switch (from) {
case ChannelState_Connectable:
return to == ChannelState_Unconnectable || to == ChannelState_Connected || to == ChannelState_Shutdown;
case ChannelState_Unconnectable:
return to == ChannelState_Connectable || to == ChannelState_Shutdown;
case ChannelState_Connected:
return to == ChannelState_Shutdown;
case ChannelState_Shutdown:
return to == ChannelState_Shutdown;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
} }

View file

@ -15,6 +15,8 @@
*/ */
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_ctrl_service.hpp" #include "htclow_ctrl_service.hpp"
#include "htclow_ctrl_state.hpp"
#include "htclow_ctrl_state_machine.hpp"
#include "../mux/htclow_mux.hpp" #include "../mux/htclow_mux.hpp"
namespace ams::htclow::ctrl { namespace ams::htclow::ctrl {
@ -81,4 +83,58 @@ namespace ams::htclow::ctrl {
this->UpdateBeaconResponse(this->GetConnectionType(driver_type)); this->UpdateBeaconResponse(this->GetConnectionType(driver_type));
} }
Result HtcctrlService::NotifyDriverConnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
if (m_state_machine->GetHtcctrlState() == HtcctrlState_7) {
R_TRY(this->SetState(HtcctrlState_8));
} else {
R_TRY(this->SetState(HtcctrlState_0));
}
return ResultSuccess();
}
Result HtcctrlService::NotifyDriverDisconnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
if (m_state_machine->GetHtcctrlState() == HtcctrlState_6) {
R_TRY(this->SetState(HtcctrlState_7));
} else {
R_TRY(this->SetState(HtcctrlState_11));
}
return ResultSuccess();
}
Result HtcctrlService::SetState(HtcctrlState state) {
/* Set the state. */
bool did_transition;
R_TRY(m_state_machine->SetHtcctrlState(std::addressof(did_transition), state));
/* Reflect the state transition, if one occurred. */
if (did_transition) {
this->ReflectState();
}
return ResultSuccess();
}
void HtcctrlService::ReflectState() {
/* If our connected status changed, update. */
if (m_state_machine->IsConnectedStatusChanged()) {
m_mux->UpdateChannelState();
}
/* If our sleeping status changed, update. */
if (m_state_machine->IsSleepingStatusChanged()) {
m_mux->UpdateMuxState();
}
/* Broadcast our state transition. */
m_condvar.Broadcast();
}
} }

View file

@ -17,6 +17,7 @@
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_ctrl_settings_holder.hpp" #include "htclow_ctrl_settings_holder.hpp"
#include "htclow_ctrl_send_buffer.hpp" #include "htclow_ctrl_send_buffer.hpp"
#include "htclow_ctrl_state.hpp"
namespace ams::htclow { namespace ams::htclow {
@ -55,10 +56,16 @@ namespace ams::htclow::ctrl {
const char *GetConnectionType(impl::DriverType driver_type) const; const char *GetConnectionType(impl::DriverType driver_type) const;
void UpdateBeaconResponse(const char *connection); void UpdateBeaconResponse(const char *connection);
Result SetState(HtcctrlState state);
void ReflectState();
public: public:
HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux); HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux);
void SetDriverType(impl::DriverType driver_type); void SetDriverType(impl::DriverType driver_type);
Result NotifyDriverConnected();
Result NotifyDriverDisconnected();
}; };
} }

View file

@ -19,7 +19,115 @@
namespace ams::htclow::ctrl { namespace ams::htclow::ctrl {
enum HtcctrlState : u32 { enum HtcctrlState : u32 {
HtcctrlState_Eleven = 11, HtcctrlState_0 = 0,
HtcctrlState_1 = 1,
HtcctrlState_2 = 2,
HtcctrlState_3 = 3,
HtcctrlState_4 = 4,
HtcctrlState_5 = 5,
HtcctrlState_6 = 6,
HtcctrlState_7 = 7,
HtcctrlState_8 = 8,
HtcctrlState_9 = 9,
HtcctrlState_10 = 10,
HtcctrlState_11 = 11,
HtcctrlState_12 = 12,
}; };
constexpr bool IsStateTransitionAllowed(HtcctrlState from, HtcctrlState to) {
switch (from) {
case HtcctrlState_0:
return to == HtcctrlState_1 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_1:
return to == HtcctrlState_2 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_2:
return to == HtcctrlState_3 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_3:
return to == HtcctrlState_4 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_4:
return to == HtcctrlState_5 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_5:
return to == HtcctrlState_6 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_6:
return to == HtcctrlState_7 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_7:
return to == HtcctrlState_8;
case HtcctrlState_8:
return to == HtcctrlState_9 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_9:
return to == HtcctrlState_4 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_10:
return to == HtcctrlState_1 || to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
case HtcctrlState_11:
return to == HtcctrlState_0;
case HtcctrlState_12:
return to == HtcctrlState_10 || to == HtcctrlState_11 || to == HtcctrlState_12;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
constexpr bool IsDisconnected(HtcctrlState state) {
switch (state) {
case HtcctrlState_10:
case HtcctrlState_11:
return true;
default:
return false;
}
}
constexpr bool IsConnecting(HtcctrlState state) {
switch (state) {
case HtcctrlState_0:
case HtcctrlState_1:
case HtcctrlState_10:
return true;
default:
return false;
}
}
constexpr bool IsConnected(HtcctrlState state) {
switch (state) {
case HtcctrlState_2:
case HtcctrlState_3:
case HtcctrlState_4:
case HtcctrlState_5:
case HtcctrlState_6:
case HtcctrlState_7:
case HtcctrlState_8:
case HtcctrlState_9:
return true;
default:
return false;
}
}
constexpr bool IsReadied(HtcctrlState state) {
switch (state) {
case HtcctrlState_4:
case HtcctrlState_5:
case HtcctrlState_6:
case HtcctrlState_7:
case HtcctrlState_8:
case HtcctrlState_9:
return true;
default:
return false;
}
}
constexpr bool IsSleeping(HtcctrlState state) {
switch (state) {
case HtcctrlState_5:
case HtcctrlState_6:
case HtcctrlState_7:
case HtcctrlState_8:
case HtcctrlState_9:
return true;
default:
return false;
}
}
} }

View file

@ -42,7 +42,7 @@ namespace ams::htclow::ctrl {
} }
HtcctrlStateMachine::HtcctrlStateMachine() : m_map(), m_state(HtcctrlState_Eleven), m_prev_state(HtcctrlState_Eleven), m_mutex() { HtcctrlStateMachine::HtcctrlStateMachine() : m_map(), m_state(HtcctrlState_11), m_prev_state(HtcctrlState_11), m_mutex() {
/* Lock ourselves. */ /* Lock ourselves. */
std::scoped_lock lk(m_mutex); std::scoped_lock lk(m_mutex);
@ -55,4 +55,127 @@ namespace ams::htclow::ctrl {
} }
} }
HtcctrlState HtcctrlStateMachine::GetHtcctrlState() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_state;
}
Result HtcctrlStateMachine::SetHtcctrlState(bool *out_transitioned, HtcctrlState state) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that the transition is allowed. */
R_UNLESS(ctrl::IsStateTransitionAllowed(m_state, state), htclow::ResultStateTransitionNotAllowed());
/* Get the state pre-transition. */
const auto old_state = m_state;
/* Set the state. */
this->SetStateWithoutCheckInternal(state);
/* Note whether we transitioned. */
*out_transitioned = state != old_state;
return ResultSuccess();
}
bool HtcctrlStateMachine::IsConnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsConnected(m_state);
}
bool HtcctrlStateMachine::IsReadied() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsReadied(m_state);
}
bool HtcctrlStateMachine::IsUnconnectable() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return !ctrl::IsConnected(m_state);
}
bool HtcctrlStateMachine::IsDisconnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsDisconnected(m_state);
}
bool HtcctrlStateMachine::IsSleeping() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsSleeping(m_state);
}
bool HtcctrlStateMachine::IsConnectedStatusChanged() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsConnected(m_prev_state) ^ ctrl::IsConnected(m_state);
}
bool HtcctrlStateMachine::IsSleepingStatusChanged() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return ctrl::IsSleeping(m_prev_state) ^ ctrl::IsSleeping(m_state);
}
void HtcctrlStateMachine::SetStateWithoutCheckInternal(HtcctrlState state) {
if (m_state != state) {
/* Clear service channel states, if we should. */
if (ctrl::IsDisconnected(state)) {
this->ClearServiceChannelStates();
}
/* Transition our state. */
m_prev_state = m_state;
m_state = state;
}
}
void HtcctrlStateMachine::ClearServiceChannelStates() {
/* Clear all values in our map. */
for (auto &pair : m_map) {
pair.second = {};
}
}
bool HtcctrlStateMachine::IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* TODO: What do these values mean? */
auto it = m_map.find(channel);
return it != m_map.end() && it->second._04 == 2 && it->second._00 == 2;
}
bool HtcctrlStateMachine::IsConnectable(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* TODO: What do these values mean? */
auto it = m_map.find(channel);
return ctrl::IsConnected(m_state) && (!(it != m_map.end()) || it->second._04 != 2);
}
void HtcctrlStateMachine::SetNotConnecting(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* TODO: What do these values mean? */
auto it = m_map.find(channel);
if (it != m_map.end() && it->second._04 != 2) {
it->second._04 = 0;
}
}
} }

View file

@ -39,6 +39,27 @@ namespace ams::htclow::ctrl {
os::SdkMutex m_mutex; os::SdkMutex m_mutex;
public: public:
HtcctrlStateMachine(); HtcctrlStateMachine();
HtcctrlState GetHtcctrlState();
Result SetHtcctrlState(bool *out_transitioned, HtcctrlState state);
bool IsConnectedStatusChanged();
bool IsSleepingStatusChanged();
bool IsConnected();
bool IsReadied();
bool IsUnconnectable();
bool IsDisconnected();
bool IsSleeping();
bool IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel);
bool IsConnectable(const impl::ChannelInternalType &channel);
void SetNotConnecting(const impl::ChannelInternalType &channel);
private:
void SetStateWithoutCheckInternal(HtcctrlState state);
void ClearServiceChannelStates();
}; };
} }

View file

@ -56,8 +56,35 @@ namespace ams::htclow {
} }
void Listener::ListenThread() { void Listener::ListenThread() {
/* TODO */ /* Check pre-conditions. */
AMS_ABORT("Listener::ListenThread"); AMS_ASSERT(m_driver != nullptr);
AMS_ASSERT(m_worker != nullptr);
/* Set the worker's driver. */
m_worker->SetDriver(m_driver);
/* Loop forever, while we're not cancelled. */
while (!m_cancelled) {
/* Connect. */
if (R_FAILED(m_driver->Connect(m_event.GetBase()))) {
return;
}
/* Notify that we're connected. */
R_ABORT_UNLESS(m_service->NotifyDriverConnected());
/* Start the worker. */
m_worker->Start();
/* Wait for the worker to end. */
m_worker->Wait();
/* Shutdown the driver. */
m_driver->Shutdown();
/* Notify that we're disconnected. */
R_ABORT_UNLESS(m_service->NotifyDriverDisconnected());
}
} }
} }

View file

@ -20,6 +20,17 @@ namespace ams::htclow {
class Packet : public util::IntrusiveListBaseNode<Packet> { class Packet : public util::IntrusiveListBaseNode<Packet> {
/* TODO */ /* TODO */
public:
virtual ~Packet();
};
struct PacketDeleter {
mem::StandardAllocator *m_allocator;
void operator()(Packet *packet) {
std::destroy_at(packet);
m_allocator->Free(packet);
}
}; };
} }

View file

@ -0,0 +1,25 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "htclow_packet_factory.hpp"
namespace ams::htclow {
void PacketFactory::Delete(Packet *packet) {
PacketDeleter{m_allocator}(packet);
}
}

View file

@ -15,6 +15,7 @@
*/ */
#pragma once #pragma once
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_packet.hpp"
namespace ams::htclow { namespace ams::htclow {
@ -23,6 +24,8 @@ namespace ams::htclow {
mem::StandardAllocator *m_allocator; mem::StandardAllocator *m_allocator;
public: public:
PacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) { /* ... */ } PacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) { /* ... */ }
void Delete(Packet *packet);
}; };
} }

View file

@ -18,12 +18,77 @@
namespace ams::htclow { namespace ams::htclow {
namespace {
constexpr inline size_t ThreadStackSize = 4_KB;
}
Worker::Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv) Worker::Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv)
: m_thread_stack_size(4_KB), m_allocator(allocator), m_mux(mux), m_service(ctrl_srv), m_driver(nullptr), m_event(os::EventClearMode_ManualClear), m_7C400() : m_thread_stack_size(ThreadStackSize), m_allocator(allocator), m_mux(mux), m_service(ctrl_srv), m_driver(nullptr), m_event(os::EventClearMode_ManualClear), m_cancelled(false)
{ {
/* Allocate stacks. */ /* Allocate stacks. */
m_receive_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment); m_receive_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment);
m_send_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment); m_send_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment);
} }
void Worker::SetDriver(driver::IDriver *driver) {
m_driver = driver;
}
void Worker::Start() {
/* Clear our cancelled state. */
m_cancelled = false;
/* Clear our event. */
m_event.Clear();
/* Create our threads. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowReceive)));
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_send_thread), SendThreadEntry, this, m_send_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowSend)));
/* Set thread names. */
os::SetThreadNamePointer(std::addressof(m_receive_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowReceive));
os::SetThreadNamePointer(std::addressof(m_send_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowSend));
/* Start our threads. */
os::StartThread(std::addressof(m_receive_thread));
os::StartThread(std::addressof(m_send_thread));
}
void Worker::Wait() {
os::WaitThread(std::addressof(m_receive_thread));
os::WaitThread(std::addressof(m_send_thread));
os::DestroyThread(std::addressof(m_receive_thread));
os::DestroyThread(std::addressof(m_send_thread));
}
void Worker::ReceiveThread() {
this->ProcessReceive();
m_driver->CancelSendReceive();
this->Cancel();
}
void Worker::SendThread() {
this->ProcessSend();
m_driver->CancelSendReceive();
this->Cancel();
}
void Worker::Cancel() {
/* Set ourselves as cancelled, and signal. */
m_cancelled = true;
m_event.Signal();
}
Result Worker::ProcessReceive() {
/* TODO */
AMS_ABORT("Worker::ProcessReceive");
}
Result Worker::ProcessSend() {
/* TODO */
AMS_ABORT("Worker::ProcessSend");
}
} }

View file

@ -34,9 +34,30 @@ namespace ams::htclow {
os::Event m_event; os::Event m_event;
void *m_receive_thread_stack; void *m_receive_thread_stack;
void *m_send_thread_stack; void *m_send_thread_stack;
u8 m_7C400; bool m_cancelled;
private:
static void ReceiveThreadEntry(void *arg) {
static_cast<Worker *>(arg)->ReceiveThread();
}
static void SendThreadEntry(void *arg) {
static_cast<Worker *>(arg)->SendThread();
}
void ReceiveThread();
void SendThread();
public: public:
Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv); Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv);
void SetDriver(driver::IDriver *driver);
void Start();
void Wait();
private:
void Cancel();
Result ProcessReceive();
Result ProcessSend();
}; };
} }

View file

@ -15,6 +15,7 @@
*/ */
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_mux.hpp" #include "htclow_mux.hpp"
#include "../ctrl/htclow_ctrl_state_machine.hpp"
namespace ams::htclow::mux { namespace ams::htclow::mux {
@ -37,4 +38,28 @@ namespace ams::htclow::mux {
} }
} }
void Mux::UpdateChannelState() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update the state of all channels in our map. */
/* NOTE: Nintendo does this highly inefficiently... */
for (auto pair : m_channel_impl_map.GetMap()) {
m_channel_impl_map[pair.first].UpdateState();
}
}
void Mux::UpdateMuxState() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update whether we're sleeping. */
if (m_state_machine->IsSleeping()) {
m_is_sleeping = true;
} else {
m_is_sleeping = false;
m_wake_event.Signal();
}
}
} }

View file

@ -36,6 +36,9 @@ namespace ams::htclow::mux {
Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm); Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm);
void SetVersion(u16 version); void SetVersion(u16 version);
void UpdateChannelState();
void UpdateMuxState();
}; };
} }

View file

@ -15,6 +15,7 @@
*/ */
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_mux_channel_impl.hpp" #include "htclow_mux_channel_impl.hpp"
#include "../ctrl/htclow_ctrl_state_machine.hpp"
namespace ams::htclow::mux { namespace ams::htclow::mux {
@ -27,4 +28,59 @@ namespace ams::htclow::mux {
m_send_buffer.SetVersion(version); m_send_buffer.SetVersion(version);
} }
void ChannelImpl::UpdateState() {
/* Check if shutdown must be forced. */
if (m_state_machine->IsUnsupportedServiceChannelToShutdown(m_channel)) {
this->ShutdownForce();
}
/* Check if we're readied. */
if (m_state_machine->IsReadied()) {
m_task_manager->NotifyConnectReady();
}
/* Update our state transition. */
if (m_state_machine->IsConnectable(m_channel)) {
if (m_state == ChannelState_Unconnectable) {
this->SetState(ChannelState_Connectable);
}
} else if (m_state_machine->IsUnconnectable()) {
if (m_state == ChannelState_Connectable) {
this->SetState(ChannelState_Unconnectable);
m_state_machine->SetNotConnecting(m_channel);
} else if (m_state == ChannelState_Connected) {
this->ShutdownForce();
}
}
}
void ChannelImpl::ShutdownForce() {
/* Clear our send buffer. */
m_send_buffer.Clear();
/* Set our state to shutdown. */
this->SetState(ChannelState_Shutdown);
}
void ChannelImpl::SetState(ChannelState state) {
/* Check that we can perform the transition. */
AMS_ABORT_UNLESS(IsStateTransitionAllowed(m_state, state));
/* Perform the transition. */
this->SetStateWithoutCheck(state);
}
void ChannelImpl::SetStateWithoutCheck(ChannelState state) {
/* Change our state. */
if (m_state != state) {
m_state = state;
m_state_change_event.Signal();
}
/* If relevant, notify disconnect. */
if (m_state == ChannelState_Shutdown) {
m_task_manager->NotifyDisconnect(m_channel);
}
}
} }

View file

@ -45,12 +45,18 @@ namespace ams::htclow::mux {
/* TODO: Channel config */ /* TODO: Channel config */
/* TODO: tracking variables. */ /* TODO: tracking variables. */
std::optional<u64> m_108; std::optional<u64> m_108;
os::Event m_send_packet_event; os::Event m_state_change_event;
/* TODO: Channel state. */ ChannelState m_state;
public: public:
ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev); ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev);
void SetVersion(s16 version); void SetVersion(s16 version);
void UpdateState();
private:
void ShutdownForce();
void SetState(ChannelState state);
void SetStateWithoutCheck(ChannelState state);
}; };
} }

View file

@ -15,6 +15,7 @@
*/ */
#include <stratosphere.hpp> #include <stratosphere.hpp>
#include "htclow_mux_channel_impl.hpp" #include "htclow_mux_channel_impl.hpp"
#include "../htclow_packet_factory.hpp"
namespace ams::htclow::mux { namespace ams::htclow::mux {
@ -23,4 +24,12 @@ namespace ams::htclow::mux {
m_version = version; m_version = version;
} }
void SendBuffer::Clear() {
while (!m_packet_list.empty()) {
auto *packet = std::addressof(m_packet_list.front());
m_packet_list.pop_front();
m_packet_factory->Delete(packet);
}
}
} }

View file

@ -41,6 +41,8 @@ namespace ams::htclow::mux {
SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf); SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf);
void SetVersion(s16 version); void SetVersion(s16 version);
void Clear();
}; };
} }

View file

@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "htclow_mux_task_manager.hpp"
namespace ams::htclow::mux {
void TaskManager::NotifyDisconnect(impl::ChannelInternalType channel) {
for (auto i = 0; i < MaxTaskCount; ++i) {
if (m_valid[i] && m_tasks[i].channel == channel) {
this->CompleteTask(i, EventTrigger_Disconnect);
}
}
}
void TaskManager::NotifyConnectReady() {
for (auto i = 0; i < MaxTaskCount; ++i) {
if (m_valid[i] && m_tasks[i].type == TaskType_Connect) {
this->CompleteTask(i, EventTrigger_ConnectReady);
}
}
}
void TaskManager::CompleteTask(int index, EventTrigger trigger) {
/* Check pre-conditions. */
AMS_ASSERT(0 <= index && index < MaxTaskCount);
AMS_ASSERT(m_valid[index]);
/* Complete the task. */
if (!m_tasks[index].has_event_trigger) {
m_tasks[index].has_event_trigger = true;
m_tasks[index].event_trigger = trigger;
os::SignalEvent(std::addressof(m_tasks[index].event));
}
}
}

View file

@ -20,14 +20,26 @@ namespace ams::htclow::mux {
constexpr inline int MaxTaskCount = 0x80; constexpr inline int MaxTaskCount = 0x80;
enum EventTrigger : u8 {
EventTrigger_Disconnect = 1,
EventTrigger_ConnectReady = 11,
};
class TaskManager { class TaskManager {
private: private:
enum TaskType : u8 {
TaskType_Receive = 0,
TaskType_Send = 1,
TaskType_Flush = 6,
TaskType_Connect = 7,
};
struct Task { struct Task {
impl::ChannelInternalType channel; impl::ChannelInternalType channel;
os::EventType event; os::EventType event;
u8 _30; bool has_event_trigger;
u8 _31; EventTrigger event_trigger;
u8 _32; TaskType type;
u64 _38; u64 _38;
}; };
private: private:
@ -35,6 +47,11 @@ namespace ams::htclow::mux {
Task m_tasks[MaxTaskCount]; Task m_tasks[MaxTaskCount];
public: public:
TaskManager() : m_valid() { /* ... */ } TaskManager() : m_valid() { /* ... */ }
void NotifyDisconnect(impl::ChannelInternalType channel);
void NotifyConnectReady();
private:
void CompleteTask(int index, EventTrigger trigger);
}; };
} }

View file

@ -36,4 +36,6 @@ namespace ams::htclow {
R_DEFINE_ERROR_RESULT(UsbDriverReceiveError, 1403); R_DEFINE_ERROR_RESULT(UsbDriverReceiveError, 1403);
R_DEFINE_ERROR_RESULT(UsbDriverSendError, 1404); R_DEFINE_ERROR_RESULT(UsbDriverSendError, 1404);
R_DEFINE_ERROR_RESULT(StateTransitionNotAllowed, 2001);
} }