Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

View File

@@ -1,73 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "../htclow_packet.hpp"
namespace ams::htclow::ctrl {
enum HtcctrlPacketType : u16 {
HtcctrlPacketType_ConnectFromHost = 16,
HtcctrlPacketType_ConnectFromTarget = 17,
HtcctrlPacketType_ReadyFromHost = 18,
HtcctrlPacketType_ReadyFromTarget = 19,
HtcctrlPacketType_SuspendFromHost = 20,
HtcctrlPacketType_SuspendFromTarget = 21,
HtcctrlPacketType_ResumeFromHost = 22,
HtcctrlPacketType_ResumeFromTarget = 23,
HtcctrlPacketType_DisconnectFromHost = 24,
HtcctrlPacketType_DisconnectFromTarget = 25,
HtcctrlPacketType_BeaconQuery = 28,
HtcctrlPacketType_BeaconResponse = 29,
HtcctrlPacketType_InformationFromTarget = 33,
};
static constexpr inline u32 HtcctrlSignature = 0x78825637;
struct HtcctrlPacketHeader {
u32 signature;
u32 sequence_id;
u32 reserved;
u32 body_size;
s16 version;
HtcctrlPacketType packet_type;
impl::ChannelInternalType channel;
u64 share;
};
static_assert(util::is_pod<HtcctrlPacketHeader>::value);
static_assert(sizeof(HtcctrlPacketHeader) == 0x20);
static constexpr inline size_t HtcctrlPacketBodySizeMax = 0x1000;
struct HtcctrlPacketBody {
u8 data[HtcctrlPacketBodySizeMax];
};
class HtcctrlPacket : public BasePacket<HtcctrlPacketHeader>, public util::IntrusiveListBaseNode<HtcctrlPacket> {
public:
using BasePacket<HtcctrlPacketHeader>::BasePacket;
};
struct HtcctrlPacketDeleter {
mem::StandardAllocator *m_allocator;
void operator()(HtcctrlPacket *packet) {
std::destroy_at(packet);
m_allocator->Free(packet);
}
};
}

View File

@@ -1,127 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_ctrl_packet_factory.hpp"
namespace ams::htclow::ctrl {
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeSendPacketCommon(int body_size) {
/* Allocate memory for the packet. */
if (void *buffer = m_allocator->Allocate(sizeof(HtcctrlPacket), alignof(HtcctrlPacket)); buffer != nullptr) {
/* Convert the buffer to a packet. */
HtcctrlPacket *packet = static_cast<HtcctrlPacket *>(buffer);
/* Construct the packet. */
std::construct_at(packet, m_allocator, body_size + sizeof(HtcctrlPacketHeader));
/* Create the unique pointer. */
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr(packet, HtcctrlPacketDeleter{m_allocator});
/* Set packet header fields. */
if (ptr && ptr->IsAllocationSucceeded()) {
HtcctrlPacketHeader *header = ptr->GetHeader();
header->signature = HtcctrlSignature;
header->sequence_id = m_sequence_id++;
header->reserved = 0;
header->body_size = body_size;
header->version = 1;
header->channel = {};
header->share = 0;
}
return ptr;
} else {
return std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter>(nullptr, HtcctrlPacketDeleter{m_allocator});
}
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeSuspendPacket() {
auto packet = this->MakeSendPacketCommon(0);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_SuspendFromTarget;
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeResumePacket() {
auto packet = this->MakeSendPacketCommon(0);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_ResumeFromTarget;
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeReadyPacket(const void *body, int body_size) {
auto packet = this->MakeSendPacketCommon(body_size);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_ReadyFromTarget;
std::memcpy(packet->GetBody(), body, packet->GetBodySize());
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeInformationPacket(const void *body, int body_size) {
auto packet = this->MakeSendPacketCommon(body_size);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_InformationFromTarget;
std::memcpy(packet->GetBody(), body, packet->GetBodySize());
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeDisconnectPacket() {
auto packet = this->MakeSendPacketCommon(0);
if (packet) {
packet->GetHeader()->packet_type = HtcctrlPacketType_DisconnectFromTarget;
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeConnectPacket(const void *body, int body_size) {
auto packet = this->MakeSendPacketCommon(body_size);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_ConnectFromTarget;
std::memcpy(packet->GetBody(), body, packet->GetBodySize());
}
return packet;
}
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> HtcctrlPacketFactory::MakeBeaconResponsePacket(const void *body, int body_size) {
auto packet = this->MakeSendPacketCommon(body_size);
if (packet && packet->IsAllocationSucceeded()) {
packet->GetHeader()->packet_type = HtcctrlPacketType_BeaconResponse;
std::memcpy(packet->GetBody(), body, packet->GetBodySize());
}
return packet;
}
void HtcctrlPacketFactory::Delete(HtcctrlPacket *packet) {
HtcctrlPacketDeleter{m_allocator}(packet);
}
}

View File

@@ -1,53 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_ctrl_packet.hpp"
namespace ams::htclow::ctrl {
class HtcctrlPacketFactory {
private:
mem::StandardAllocator *m_allocator;
u32 m_sequence_id;
public:
HtcctrlPacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) {
/* Get the current time. */
const u64 time = os::GetSystemTick().GetInt64Value();
/* Set a random sequence id. */
{
util::TinyMT rng;
rng.Initialize(reinterpret_cast<const u32 *>(std::addressof(time)), sizeof(time) / sizeof(u32));
m_sequence_id = rng.GenerateRandomU32();
}
}
public:
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeSuspendPacket();
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeResumePacket();
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeReadyPacket(const void *body, int body_size);
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeInformationPacket(const void *body, int body_size);
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeDisconnectPacket();
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeConnectPacket(const void *body, int body_size);
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeBeaconResponsePacket(const void *body, int body_size);
void Delete(HtcctrlPacket *packet);
private:
std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> MakeSendPacketCommon(int body_size);
};
}

View File

@@ -1,102 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_ctrl_send_buffer.hpp"
#include "htclow_ctrl_packet_factory.hpp"
namespace ams::htclow::ctrl {
bool HtcctrlSendBuffer::IsPriorPacket(HtcctrlPacketType packet_type) const {
return packet_type == HtcctrlPacketType_DisconnectFromTarget;
}
bool HtcctrlSendBuffer::IsPosteriorPacket(HtcctrlPacketType packet_type) const {
switch (packet_type) {
case HtcctrlPacketType_ConnectFromTarget:
case HtcctrlPacketType_ReadyFromTarget:
case HtcctrlPacketType_SuspendFromTarget:
case HtcctrlPacketType_ResumeFromTarget:
case HtcctrlPacketType_BeaconResponse:
case HtcctrlPacketType_InformationFromTarget:
return true;
default:
return false;
}
}
void HtcctrlSendBuffer::AddPacket(std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr) {
/* Get the packet. */
HtcctrlPacket *packet = ptr.release();
/* Get the packet type. */
const auto packet_type = packet->GetHeader()->packet_type;
/* Add the packet to the appropriate list. */
if (this->IsPriorPacket(packet_type)) {
m_prior_packet_list.push_back(*packet);
} else {
AMS_ABORT_UNLESS(this->IsPosteriorPacket(packet_type));
m_posterior_packet_list.push_back(*packet);
}
}
void HtcctrlSendBuffer::RemovePacket(const HtcctrlPacketHeader &header) {
/* Get the packet type. */
const auto packet_type = header.packet_type;
/* Remove the front from the appropriate list. */
HtcctrlPacket *packet;
if (this->IsPriorPacket(packet_type)) {
packet = std::addressof(m_prior_packet_list.front());
m_prior_packet_list.pop_front();
} else {
AMS_ABORT_UNLESS(this->IsPosteriorPacket(packet_type));
packet = std::addressof(m_posterior_packet_list.front());
m_posterior_packet_list.pop_front();
}
/* Delete the packet. */
m_packet_factory->Delete(packet);
}
bool HtcctrlSendBuffer::QueryNextPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size) {
if (!m_prior_packet_list.empty()) {
this->CopyPacket(header, body, out_body_size, m_prior_packet_list.front());
return true;
} else if (!m_posterior_packet_list.empty()) {
this->CopyPacket(header, body, out_body_size, m_posterior_packet_list.front());
return true;
} else {
return false;
}
}
void HtcctrlSendBuffer::CopyPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size, const HtcctrlPacket &packet) {
/* Get the body size. */
const int body_size = packet.GetBodySize();
AMS_ASSERT(0 <= body_size && body_size <= static_cast<int>(sizeof(*body)));
/* Copy the header. */
std::memcpy(header, packet.GetHeader(), sizeof(*header));
/* Copy the body. */
std::memcpy(body, packet.GetBody(), body_size);
/* Set the output body size. */
*out_body_size = body_size;
}
}

View File

@@ -1,45 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_ctrl_packet.hpp"
namespace ams::htclow::ctrl {
class HtcctrlPacketFactory;
class HtcctrlSendBuffer {
private:
using PacketList = util::IntrusiveListBaseTraits<HtcctrlPacket>::ListType;
private:
HtcctrlPacketFactory *m_packet_factory;
PacketList m_prior_packet_list;
PacketList m_posterior_packet_list;
private:
bool IsPriorPacket(HtcctrlPacketType packet_type) const;
bool IsPosteriorPacket(HtcctrlPacketType packet_type) const;
void CopyPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size, const HtcctrlPacket &packet);
public:
HtcctrlSendBuffer(HtcctrlPacketFactory *pf) : m_packet_factory(pf), m_prior_packet_list(), m_posterior_packet_list() { /* ... */ }
void AddPacket(std::unique_ptr<HtcctrlPacket, HtcctrlPacketDeleter> ptr);
void RemovePacket(const HtcctrlPacketHeader &header);
bool QueryNextPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size);
};
}

View File

@@ -1,502 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_ctrl_service.hpp"
#include "htclow_ctrl_state.hpp"
#include "htclow_ctrl_state_machine.hpp"
#include "htclow_ctrl_packet_factory.hpp"
#include "htclow_service_channel_parser.hpp"
#include "htclow_ctrl_service_channels.hpp"
#include "../mux/htclow_mux.hpp"
namespace ams::htclow::ctrl {
namespace {
constexpr const char BeaconPacketResponseTemplate[] =
"{\r\n"
" \"Spec\" : \"%s\",\r\n"
" \"Conn\" : \"%s\",\r\n"
" \"HW\" : \"%s\",\r\n"
" \"Name\" : \"%s\",\r\n"
" \"SN\" : \"%s\",\r\n"
" \"FW\" : \"%s\",\r\n"
" \"Prot\" : \"%d\"\r\n"
"}\r\n";
}
HtcctrlService::HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux)
: m_settings_holder(), m_beacon_response(), m_information_body(), m_packet_factory(pf), m_state_machine(sm), m_mux(mux), m_event(os::EventClearMode_ManualClear),
m_send_buffer(pf), m_mutex(), m_condvar(), m_service_channels_packet(), m_version(ProtocolVersion)
{
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Set the mux version. */
m_mux->SetVersion(m_version);
/* Update our beacon response. */
this->UpdateBeaconResponse(this->GetConnectionType(impl::DriverType::Unknown));
}
const char *HtcctrlService::GetConnectionType(impl::DriverType driver_type) const {
switch (driver_type) {
case impl::DriverType::Socket: return "TCP";
case impl::DriverType::Usb: return "USB-gen2";
case impl::DriverType::PlainChannel: return "HBPC-gen2";
default: return "Unknown";
}
}
void HtcctrlService::UpdateBeaconResponse(const char *connection) {
/* Load settings into the holder. */
m_settings_holder.LoadSettings();
/* Print our beacon response. */
util::SNPrintf(m_beacon_response, sizeof(m_beacon_response), BeaconPacketResponseTemplate,
m_settings_holder.GetSpec(),
connection,
m_settings_holder.GetHardwareType(),
m_settings_holder.GetTargetName(),
m_settings_holder.GetSerialNumber(),
m_settings_holder.GetFirmwareVersion(),
ProtocolVersion
);
}
void HtcctrlService::UpdateInformationBody(const char *status) {
util::SNPrintf(m_information_body, sizeof(m_information_body), "{\r\n \"Status\" : \"%s\"\r\n}\r\n", status);
}
void HtcctrlService::SetDriverType(impl::DriverType driver_type) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update our beacon response. */
this->UpdateBeaconResponse(this->GetConnectionType(driver_type));
}
Result HtcctrlService::CheckReceivedHeader(const HtcctrlPacketHeader &header) const {
/* Check the packet signature. */
AMS_ASSERT(header.signature == HtcctrlSignature);
/* Validate version. */
R_UNLESS(header.version == 1, htclow::ResultProtocolError());
/* Switch on the packet type. */
switch (header.packet_type) {
case HtcctrlPacketType_ConnectFromHost:
case HtcctrlPacketType_SuspendFromHost:
case HtcctrlPacketType_ResumeFromHost:
case HtcctrlPacketType_DisconnectFromHost:
case HtcctrlPacketType_BeaconQuery:
R_UNLESS(header.body_size == 0, htclow::ResultProtocolError());
break;
case HtcctrlPacketType_ReadyFromHost:
R_UNLESS(header.body_size <= sizeof(HtcctrlPacketBody), htclow::ResultProtocolError());
break;
default:
R_THROW(htclow::ResultProtocolError());
}
R_SUCCEED();
}
Result HtcctrlService::ProcessReceivePacket(const HtcctrlPacketHeader &header, const void *body, size_t body_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
switch (header.packet_type) {
case HtcctrlPacketType_ConnectFromHost:
R_RETURN(this->ProcessReceiveConnectPacket());
case HtcctrlPacketType_ReadyFromHost:
R_RETURN(this->ProcessReceiveReadyPacket(body, body_size));
case HtcctrlPacketType_SuspendFromHost:
R_RETURN(this->ProcessReceiveSuspendPacket());
case HtcctrlPacketType_ResumeFromHost:
R_RETURN(this->ProcessReceiveResumePacket());
case HtcctrlPacketType_DisconnectFromHost:
R_RETURN(this->ProcessReceiveDisconnectPacket());
case HtcctrlPacketType_BeaconQuery:
R_RETURN(this->ProcessReceiveBeaconQueryPacket());
default:
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
}
Result HtcctrlService::ProcessReceiveConnectPacket() {
/* Try to transition to sent connect state. */
if (R_FAILED(this->SetState(HtcctrlState_SentConnectFromHost))) {
/* We couldn't transition to sent connect. */
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
/* Send a connect packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeConnectPacket(m_beacon_response, util::Strnlen(m_beacon_response, sizeof(m_beacon_response)) + 1));
/* Signal our event. */
m_event.Signal();
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveReadyPacket(const void *body, size_t body_size) {
/* Update our service channels. */
this->UpdateServiceChannels(body, body_size);
/* Check that our version is correct. */
if (m_version < ProtocolVersion) {
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
/* Set our version. */
m_version = ProtocolVersion;
m_mux->SetVersion(m_version);
/* Set our state. */
if (R_FAILED(this->SetState(HtcctrlState_SentReadyFromHost))) {
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
/* Ready ourselves. */
this->TryReadyInternal();
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveSuspendPacket() {
/* Try to set our state to enter sleep. */
if (R_FAILED(this->SetState(HtcctrlState_EnterSleep))) {
/* We couldn't transition to sleep. */
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveResumePacket() {
/* If our state is sent-resume, change to readied. */
if (m_state_machine->GetHtcctrlState() != HtcctrlState_SentResumeFromTarget || R_FAILED(this->SetState(HtcctrlState_Ready))) {
/* We couldn't perform a valid resume transition. */
R_RETURN(this->ProcessReceiveUnexpectedPacket());
}
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveDisconnectPacket() {
/* Set our state. */
R_TRY(this->SetState(HtcctrlState_Disconnected));
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveBeaconQueryPacket() {
/* Send a beacon response packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeBeaconResponsePacket(m_beacon_response, util::Strnlen(m_beacon_response, sizeof(m_beacon_response)) + 1));
/* Signal our event. */
m_event.Signal();
R_SUCCEED();
}
Result HtcctrlService::ProcessReceiveUnexpectedPacket() {
/* Set our state. */
R_TRY(this->SetState(HtcctrlState_Error));
/* Send a disconnection packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeDisconnectPacket());
/* Signal our event. */
m_event.Signal();
/* Return unexpected packet error. */
R_THROW(htclow::ResultHtcctrlReceiveUnexpectedPacket());
}
void HtcctrlService::ProcessSendConnectPacket() {
/* Set our state. */
const Result result = this->SetState(HtcctrlState_Connected);
R_ASSERT(result);
}
void HtcctrlService::ProcessSendReadyPacket() {
/* Set our state. */
if (m_state_machine->GetHtcctrlState() == HtcctrlState_SentReadyFromHost) {
const Result result = this->SetState(HtcctrlState_Ready);
R_ASSERT(result);
}
/* Update channel states. */
m_mux->UpdateChannelState();
}
void HtcctrlService::ProcessSendSuspendPacket() {
/* Set our state. */
const Result result = this->SetState(HtcctrlState_SentSuspendFromTarget);
R_ASSERT(result);
}
void HtcctrlService::ProcessSendResumePacket() {
/* Set our state. */
const Result result = this->SetState(HtcctrlState_SentResumeFromTarget);
R_ASSERT(result);
}
void HtcctrlService::ProcessSendDisconnectPacket() {
/* Set our state. */
const Result result = this->SetState(HtcctrlState_Disconnected);
R_ASSERT(result);
}
void HtcctrlService::UpdateServiceChannels(const void *body, size_t body_size) {
/* Copy the packet body to our member. */
std::memcpy(m_service_channels_packet, body, body_size);
/* Parse service channels. */
impl::ChannelInternalType channels[10];
int num_channels;
s16 version = m_version;
ctrl::ParseServiceChannel(std::addressof(version), channels, std::addressof(num_channels), util::size(channels), m_service_channels_packet, body_size);
/* Update version. */
m_version = version;
/* Notify state machine of supported channels. */
m_state_machine->NotifySupportedServiceChannels(channels, num_channels);
}
void HtcctrlService::TryReadyInternal() {
/* If we can send ready, do so. */
if (m_state_machine->IsPossibleToSendReady()) {
/* Print the channels. */
char channel_str[0x100];
this->PrintServiceChannels(channel_str, sizeof(channel_str));
/* Send a ready packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeReadyPacket(channel_str, util::Strnlen(channel_str, sizeof(channel_str)) + 1));
/* Signal our event. */
m_event.Signal();
/* Set connecting checked in state machine. */
m_state_machine->SetConnectingChecked();
}
}
bool HtcctrlService::QuerySendPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_send_buffer.QueryNextPacket(header, body, out_body_size);
}
void HtcctrlService::RemovePacket(const HtcctrlPacketHeader &header) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Remove the packet from our buffer. */
m_send_buffer.RemovePacket(header);
/* Switch on the packet type. */
switch (header.packet_type) {
case HtcctrlPacketType_ConnectFromTarget:
this->ProcessSendConnectPacket();
break;
case HtcctrlPacketType_ReadyFromTarget:
this->ProcessSendReadyPacket();
break;
case HtcctrlPacketType_SuspendFromTarget:
this->ProcessSendSuspendPacket();
break;
case HtcctrlPacketType_ResumeFromTarget:
this->ProcessSendResumePacket();
break;
case HtcctrlPacketType_DisconnectFromTarget:
this->ProcessSendDisconnectPacket();
break;
case HtcctrlPacketType_BeaconResponse:
case HtcctrlPacketType_InformationFromTarget:
break;
default:
AMS_ABORT("Send unsupported packet 0x%04x\n", static_cast<u32>(header.packet_type));
}
}
void HtcctrlService::TryReady() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
this->TryReadyInternal();
}
void HtcctrlService::Disconnect() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
this->DisconnectInternal();
}
void HtcctrlService::DisconnectInternal() {
/* Disconnect, if we need to. */
if (m_state_machine->IsDisconnectionNeeded()) {
/* Send a disconnect packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeDisconnectPacket());
/* Signal our event. */
m_event.Signal();
/* Wait for us to be disconnected. */
while (!m_state_machine->IsDisconnected()) {
m_condvar.Wait(m_mutex);
}
}
}
void HtcctrlService::Resume() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Send resume packet, if we can. */
if (const auto state = m_state_machine->GetHtcctrlState(); state == HtcctrlState_Sleep || state == HtcctrlState_ExitSleep) {
/* Send a resume packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeResumePacket());
/* Signal our event. */
m_event.Signal();
}
}
void HtcctrlService::Suspend() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* If we can, perform a suspend. */
if (m_state_machine->GetHtcctrlState() == HtcctrlState_Ready) {
/* Send a suspend packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeSuspendPacket());
/* Signal our event. */
m_event.Signal();
/* Wait for our state to transition. */
for (auto state = m_state_machine->GetHtcctrlState(); state == HtcctrlState_Ready || state == HtcctrlState_SentSuspendFromTarget; state = m_state_machine->GetHtcctrlState()) {
m_condvar.Wait(m_mutex);
}
} else {
/* Otherwise, just disconnect. */
this->DisconnectInternal();
}
}
void HtcctrlService::NotifyAwake() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update our information. */
this->UpdateInformationBody("Awake");
/* Send information to host. */
this->SendInformation();
}
void HtcctrlService::NotifyAsleep() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update our information. */
this->UpdateInformationBody("Asleep");
/* Send information to host. */
this->SendInformation();
}
void HtcctrlService::SendInformation() {
/* If we need information, send information. */
if (m_state_machine->IsInformationNeeded()) {
/* Send an information packet. */
m_send_buffer.AddPacket(m_packet_factory->MakeInformationPacket(m_information_body, util::Strnlen(m_information_body, sizeof(m_information_body)) + 1));
/* Signal our event. */
m_event.Signal();
}
}
Result HtcctrlService::NotifyDriverConnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
if (m_state_machine->GetHtcctrlState() == HtcctrlState_Sleep) {
R_TRY(this->SetState(HtcctrlState_ExitSleep));
} else {
R_TRY(this->SetState(HtcctrlState_DriverConnected));
}
R_SUCCEED();
}
Result HtcctrlService::NotifyDriverDisconnected() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
if (m_state_machine->GetHtcctrlState() == HtcctrlState_EnterSleep) {
R_TRY(this->SetState(HtcctrlState_Sleep));
} else {
R_TRY(this->SetState(HtcctrlState_DriverDisconnected));
}
R_SUCCEED();
}
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();
}
R_SUCCEED();
}
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();
}
void HtcctrlService::PrintServiceChannels(char *dst, size_t dst_size) {
size_t ofs = 0;
ofs += util::SNPrintf(dst + ofs, dst_size - ofs, "{\r\n \"Chan\" : [\r\n \"%d:%d:%d\"", static_cast<int>(ServiceChannels[0].module_id), ServiceChannels[0].reserved, static_cast<int>(ServiceChannels[0].channel_id));
for (size_t i = 1; i < util::size(ServiceChannels); ++i) {
ofs += util::SNPrintf(dst + ofs, dst_size - ofs, ",\r\n \"%d:%d:%d\"", static_cast<int>(ServiceChannels[i].module_id), ServiceChannels[i].reserved, static_cast<int>(ServiceChannels[i].channel_id));
}
ofs += util::SNPrintf(dst + ofs, dst_size - ofs, "\r\n],\r\n \"Prot\" : %d\r\n}\r\n", ProtocolVersion);
}
}

View File

@@ -1,112 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_ctrl_settings_holder.hpp"
#include "htclow_ctrl_send_buffer.hpp"
#include "htclow_ctrl_state.hpp"
namespace ams::htclow {
namespace ctrl {
class HtcctrlPacketFactory;
class HtcctrlStateMachine;
}
namespace mux {
class Mux;
}
}
namespace ams::htclow::ctrl {
class HtcctrlService {
private:
SettingsHolder m_settings_holder;
char m_beacon_response[0x1000];
char m_information_body[0x1000];
HtcctrlPacketFactory *m_packet_factory;
HtcctrlStateMachine *m_state_machine;
mux::Mux *m_mux;
os::Event m_event;
HtcctrlSendBuffer m_send_buffer;
os::SdkMutex m_mutex;
os::SdkConditionVariable m_condvar;
char m_service_channels_packet[0x1000];
s16 m_version;
private:
const char *GetConnectionType(impl::DriverType driver_type) const;
void UpdateBeaconResponse(const char *connection);
void UpdateInformationBody(const char *status);
void SendInformation();
Result ProcessReceiveConnectPacket();
Result ProcessReceiveReadyPacket(const void *body, size_t body_size);
Result ProcessReceiveSuspendPacket();
Result ProcessReceiveResumePacket();
Result ProcessReceiveDisconnectPacket();
Result ProcessReceiveBeaconQueryPacket();
Result ProcessReceiveUnexpectedPacket();
void ProcessSendConnectPacket();
void ProcessSendReadyPacket();
void ProcessSendSuspendPacket();
void ProcessSendResumePacket();
void ProcessSendDisconnectPacket();
void UpdateServiceChannels(const void *body, size_t body_size);
void PrintServiceChannels(char *dst, size_t dst_size);
void TryReadyInternal();
void DisconnectInternal();
Result SetState(HtcctrlState state);
void ReflectState();
public:
HtcctrlService(HtcctrlPacketFactory *pf, HtcctrlStateMachine *sm, mux::Mux *mux);
void SetDriverType(impl::DriverType driver_type);
os::EventType *GetSendPacketEvent() { return m_event.GetBase(); }
Result CheckReceivedHeader(const HtcctrlPacketHeader &header) const;
Result ProcessReceivePacket(const HtcctrlPacketHeader &header, const void *body, size_t body_size);
bool QuerySendPacket(HtcctrlPacketHeader *header, HtcctrlPacketBody *body, int *out_body_size);
void RemovePacket(const HtcctrlPacketHeader &header);
void TryReady();
void Disconnect();
void Resume();
void Suspend();
void NotifyAwake();
void NotifyAsleep();
Result NotifyDriverConnected();
Result NotifyDriverDisconnected();
};
}

View File

@@ -1,40 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::ctrl {
constexpr inline const impl::ChannelInternalType ServiceChannels[] = {
{
.channel_id = 0, /* TODO: htcfs::ChannelId? */
.module_id = ModuleId::Htcfs,
},
{
.channel_id = 1, /* TODO: htcmisc::ClientChannelId? */
.module_id = ModuleId::Htcmisc,
},
{
.channel_id = 2, /* TODO: htcmisc::ServerChannelId? */
.module_id = ModuleId::Htcmisc,
},
{
.channel_id = 0, /* TODO: htcs::ChannelId? */
.module_id = ModuleId::Htcs,
},
};
}

View File

@@ -1,59 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_ctrl_settings_holder.hpp"
namespace ams::htclow::ctrl {
void SettingsHolder::LoadSettings() {
/* Load configuration id. */
{
settings::factory::ConfigurationId1 cfg_id;
settings::factory::GetConfigurationId1(std::addressof(cfg_id));
if (cfg_id.str[0]) {
util::Strlcpy(m_hardware_type, cfg_id.str, sizeof(m_hardware_type));
} else {
util::Strlcpy(m_hardware_type, "Unknown", sizeof(m_hardware_type));
}
}
/* Load device name. */
{
char device_name[0x40];
settings::fwdbg::GetSettingsItemValue(device_name, sizeof(device_name), "target_manager", "device_name");
util::Strlcpy(m_target_name, device_name, sizeof(m_target_name));
}
/* Load serial number. */
{
settings::factory::SerialNumber sn;
if (R_SUCCEEDED(settings::factory::GetSerialNumber(std::addressof(sn)))) {
util::Strlcpy(m_serial_number, sn.str, sizeof(m_serial_number));
} else {
m_serial_number[0] = '\x00';
}
}
/* Load firmware version. */
{
settings::system::FirmwareVersion fw_ver;
settings::system::GetFirmwareVersion(std::addressof(fw_ver));
util::Strlcpy(m_firmware_version, fw_ver.display_name, sizeof(m_firmware_version));
}
}
}

View File

@@ -1,39 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::ctrl {
class SettingsHolder {
private:
char m_hardware_type[0x40]{};
char m_target_name[0x40]{};
char m_serial_number[0x40]{};
char m_firmware_version[0x40]{};
public:
constexpr SettingsHolder() = default;
void LoadSettings();
const char *GetSpec() { return "NX"; }
const char *GetHardwareType() { return m_hardware_type; }
const char *GetTargetName() { return m_target_name; }
const char *GetSerialNumber() { return m_serial_number; }
const char *GetFirmwareVersion() { return m_firmware_version; }
};
}

View File

@@ -1,165 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::ctrl {
enum HtcctrlState : u32 {
HtcctrlState_DriverConnected = 0,
HtcctrlState_SentConnectFromHost = 1,
HtcctrlState_Connected = 2,
HtcctrlState_SentReadyFromHost = 3,
HtcctrlState_Ready = 4,
HtcctrlState_SentSuspendFromTarget = 5,
HtcctrlState_EnterSleep = 6,
HtcctrlState_Sleep = 7,
HtcctrlState_ExitSleep = 8,
HtcctrlState_SentResumeFromTarget = 9,
HtcctrlState_Disconnected = 10,
HtcctrlState_DriverDisconnected = 11,
HtcctrlState_Error = 12,
};
constexpr bool IsStateTransitionAllowed(HtcctrlState from, HtcctrlState to) {
switch (from) {
case HtcctrlState_DriverConnected:
return to == HtcctrlState_SentConnectFromHost ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_SentConnectFromHost:
return to == HtcctrlState_Connected ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_Connected:
return to == HtcctrlState_SentReadyFromHost ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_SentReadyFromHost:
return to == HtcctrlState_Ready ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_Ready:
return to == HtcctrlState_SentSuspendFromTarget ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_SentSuspendFromTarget:
return to == HtcctrlState_EnterSleep ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_EnterSleep:
return to == HtcctrlState_Sleep ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_Sleep:
return to == HtcctrlState_ExitSleep;
case HtcctrlState_ExitSleep:
return to == HtcctrlState_SentResumeFromTarget ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_SentResumeFromTarget:
return to == HtcctrlState_Ready ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_Disconnected:
return to == HtcctrlState_SentConnectFromHost ||
to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
case HtcctrlState_DriverDisconnected:
return to == HtcctrlState_DriverConnected;
case HtcctrlState_Error:
return to == HtcctrlState_Disconnected ||
to == HtcctrlState_DriverDisconnected ||
to == HtcctrlState_Error;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
constexpr bool IsDisconnected(HtcctrlState state) {
switch (state) {
case HtcctrlState_Disconnected:
case HtcctrlState_DriverDisconnected:
return true;
default:
return false;
}
}
constexpr bool IsConnecting(HtcctrlState state) {
switch (state) {
case HtcctrlState_DriverConnected:
case HtcctrlState_SentConnectFromHost:
case HtcctrlState_Disconnected:
return true;
default:
return false;
}
}
constexpr bool IsConnected(HtcctrlState state) {
switch (state) {
case HtcctrlState_Connected:
case HtcctrlState_SentReadyFromHost:
case HtcctrlState_Ready:
case HtcctrlState_SentSuspendFromTarget:
case HtcctrlState_EnterSleep:
case HtcctrlState_Sleep:
case HtcctrlState_ExitSleep:
case HtcctrlState_SentResumeFromTarget:
return true;
default:
return false;
}
}
constexpr bool IsReadied(HtcctrlState state) {
switch (state) {
case HtcctrlState_Ready:
case HtcctrlState_SentSuspendFromTarget:
case HtcctrlState_EnterSleep:
case HtcctrlState_Sleep:
case HtcctrlState_ExitSleep:
case HtcctrlState_SentResumeFromTarget:
return true;
default:
return false;
}
}
constexpr bool IsSleeping(HtcctrlState state) {
switch (state) {
case HtcctrlState_SentSuspendFromTarget:
case HtcctrlState_EnterSleep:
case HtcctrlState_Sleep:
case HtcctrlState_ExitSleep:
case HtcctrlState_SentResumeFromTarget:
return true;
default:
return false;
}
}
}

View File

@@ -1,228 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_ctrl_state_machine.hpp"
#include "htclow_ctrl_service_channels.hpp"
namespace ams::htclow::ctrl {
HtcctrlStateMachine::HtcctrlStateMachine() : m_map(), m_state(HtcctrlState_DriverDisconnected), m_prev_state(HtcctrlState_DriverDisconnected), m_mutex() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Initialize our map. */
m_map.Initialize(MaxChannelCount, m_map_buffer, sizeof(m_map_buffer));
/* Insert each service channel the map. */
for (const auto &channel : ServiceChannels) {
m_map.insert(std::make_pair<impl::ChannelInternalType, ServiceChannelState>(impl::ChannelInternalType{channel}, ServiceChannelState{}));
}
}
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::ResultHtcctrlStateTransitionNotAllowed());
/* 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;
R_SUCCEED();
}
bool HtcctrlStateMachine::IsInformationNeeded() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return !ctrl::IsDisconnected(m_state) && m_state != HtcctrlState_DriverConnected;
}
bool HtcctrlStateMachine::IsDisconnectionNeeded() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return !ctrl::IsDisconnected(m_state) && m_state != HtcctrlState_Sleep && m_state != HtcctrlState_DriverConnected;
}
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::IsPossibleToSendReady() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_state == HtcctrlState_SentReadyFromHost && this->AreServiceChannelsConnecting();
}
bool HtcctrlStateMachine::IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
auto it = m_map.find(channel);
return it != m_map.end() && it->second.connect == ServiceChannelConnect_ConnectingChecked && it->second.support == ServiceChannelSupport_Unsupported;
}
bool HtcctrlStateMachine::IsConnectable(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
auto it = m_map.find(channel);
return ctrl::IsConnected(m_state) && (it == m_map.end() || it->second.connect != ServiceChannelConnect_ConnectingChecked);
}
void HtcctrlStateMachine::SetConnecting(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
auto it = m_map.find(channel);
if (it != m_map.end() && it->second.connect != ServiceChannelConnect_ConnectingChecked) {
it->second.connect = ServiceChannelConnect_Connecting;
}
}
void HtcctrlStateMachine::SetNotConnecting(const impl::ChannelInternalType &channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
auto it = m_map.find(channel);
if (it != m_map.end() && it->second.connect != ServiceChannelConnect_ConnectingChecked) {
it->second.connect = ServiceChannelConnect_NotConnecting;
}
}
void HtcctrlStateMachine::SetConnectingChecked() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
for (auto &pair : m_map) {
pair.second.connect = ServiceChannelConnect_ConnectingChecked;
}
}
void HtcctrlStateMachine::NotifySupportedServiceChannels(const impl::ChannelInternalType *channels, int num_channels) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
auto IsSupportedServiceChannel = [](const impl::ChannelInternalType &channel, const impl::ChannelInternalType *supported, int num_supported) ALWAYS_INLINE_LAMBDA -> bool {
for (auto i = 0; i < num_supported; ++i) {
if (channel.module_id == supported[i].module_id && channel.channel_id == supported[i].channel_id) {
return true;
}
}
return false;
};
for (auto &pair : m_map) {
if (IsSupportedServiceChannel(pair.first, channels, num_channels)) {
pair.second.support = ServiceChannelSupport_Suppported;
} else {
pair.second.support = ServiceChannelSupport_Unsupported;
}
}
}
bool HtcctrlStateMachine::AreServiceChannelsConnecting() {
for (auto &pair : m_map) {
if (pair.second.connect != ServiceChannelConnect_Connecting) {
return false;
}
}
return true;
}
}

View File

@@ -1,87 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_ctrl_state.hpp"
namespace ams::htclow::ctrl {
class HtcctrlStateMachine {
private:
enum ServiceChannelSupport {
ServiceChannelSupport_Unknown = 0,
ServiceChannelSupport_Suppported = 1,
ServiceChannelSupport_Unsupported = 2,
};
enum ServiceChannelConnect {
ServiceChannelConnect_NotConnecting = 0,
ServiceChannelConnect_Connecting = 1,
ServiceChannelConnect_ConnectingChecked = 2,
};
struct ServiceChannelState {
ServiceChannelSupport support;
ServiceChannelConnect connect;
};
static constexpr int MaxChannelCount = 10;
using MapType = util::FixedMap<impl::ChannelInternalType, ServiceChannelState>;
static constexpr size_t MapRequiredMemorySize = MapType::GetRequiredMemorySize(MaxChannelCount);
private:
u8 m_map_buffer[MapRequiredMemorySize];
MapType m_map;
HtcctrlState m_state;
HtcctrlState m_prev_state;
os::SdkMutex m_mutex;
public:
HtcctrlStateMachine();
HtcctrlState GetHtcctrlState();
Result SetHtcctrlState(bool *out_transitioned, HtcctrlState state);
bool IsConnectedStatusChanged();
bool IsSleepingStatusChanged();
bool IsInformationNeeded();
bool IsDisconnectionNeeded();
bool IsConnected();
bool IsReadied();
bool IsUnconnectable();
bool IsDisconnected();
bool IsSleeping();
bool IsPossibleToSendReady();
bool IsUnsupportedServiceChannelToShutdown(const impl::ChannelInternalType &channel);
bool IsConnectable(const impl::ChannelInternalType &channel);
void SetConnecting(const impl::ChannelInternalType &channel);
void SetNotConnecting(const impl::ChannelInternalType &channel);
void SetConnectingChecked();
void NotifySupportedServiceChannels(const impl::ChannelInternalType *channels, int num_channels);
private:
void SetStateWithoutCheckInternal(HtcctrlState state);
bool AreServiceChannelsConnecting();
void ClearServiceChannelStates();
};
}

View File

@@ -1,84 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_json.hpp"
namespace ams::htclow::ctrl {
namespace {
constexpr const char ChannelKey[] = "Chan";
constexpr const char ProtocolVersionKey[] = "Prot";
}
bool JsonHandler::Key(const Ch *str, rapidjson::SizeType len, bool copy) {
AMS_UNUSED(len, copy);
if (m_state == State::ParseObject) {
if (!util::Strncmp(str, ChannelKey, sizeof(ChannelKey))) {
m_state = State::ParseServiceChannels;
}
if (!util::Strncmp(str, ProtocolVersionKey, sizeof(ProtocolVersionKey))) {
m_state = State::ParseProtocolVersion;
}
}
return true;
}
bool JsonHandler::Uint(unsigned val) {
if (m_state == State::ParseProtocolVersion) {
*m_version = val;
}
return true;
}
bool JsonHandler::String(const Ch *str, rapidjson::SizeType len, bool copy) {
AMS_UNUSED(len, copy);
if (m_state == State::ParseServiceChannelsArray && *m_num_strings < m_max_strings) {
m_strings[(*m_num_strings)++] = str;
}
return true;
}
bool JsonHandler::StartObject() {
if (m_state == State::Begin) {
m_state = State::ParseObject;
}
return true;
}
bool JsonHandler::EndObject(rapidjson::SizeType) {
m_state = State::End;
return true;
}
bool JsonHandler::StartArray() {
if (m_state == State::ParseServiceChannels) {
m_state = State::ParseServiceChannelsArray;
}
return true;
}
bool JsonHandler::EndArray(rapidjson::SizeType len) {
if (m_state == State::ParseServiceChannelsArray && len) {
m_state = State::ParseObject;
}
return true;
}
}

View File

@@ -1,50 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::ctrl {
class JsonHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<char>, JsonHandler>{
private:
enum class State {
Begin = 0,
ParseObject = 1,
ParseServiceChannels = 2,
ParseServiceChannelsArray = 3,
ParseProtocolVersion = 4,
End = 5,
};
private:
State m_state;
s16 *m_version;
const char **m_strings;
int *m_num_strings;
int m_max_strings;
public:
JsonHandler(s16 *vers, const char **str, int *ns, int max) : m_state(State::Begin), m_version(vers), m_strings(str), m_num_strings(ns), m_max_strings(max) { /* ... */ }
bool Key(const Ch *str, rapidjson::SizeType len, bool copy);
bool Uint(unsigned);
bool String(const Ch *, rapidjson::SizeType, bool);
bool StartObject();
bool EndObject(rapidjson::SizeType);
bool StartArray();
bool EndArray(rapidjson::SizeType);
};
}

View File

@@ -1,160 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_json.hpp"
#include "htclow_service_channel_parser.hpp"
namespace ams::htclow::ctrl {
namespace {
constexpr auto JsonParseFlags = rapidjson::kParseTrailingCommasFlag | rapidjson::kParseInsituFlag;
void ParseBody(s16 *out_version, const char **out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) {
AMS_UNUSED(str_size);
/* Create JSON handler. */
JsonHandler json_handler(out_version, out_channels, out_num_channels, max_channels);
/* Create reader. */
rapidjson::Reader json_reader;
/* Create stream. */
rapidjson::InsituStringStream json_stream(static_cast<char *>(str));
/* Parse the json. */
json_reader.Parse<JsonParseFlags>(json_stream, json_handler);
}
constexpr bool IsNumericCharacter(char c) {
return '0' <= c && c <= '9';
}
constexpr bool IsValidCharacter(char c) {
return IsNumericCharacter(c) || c == ':';
}
bool IntegerToModuleId(ModuleId *out, int id) {
switch (id) {
case 0:
case 1:
case 3:
case 4:
*out = static_cast<ModuleId>(id);
return true;
default:
return false;
}
}
bool StringToChannel(impl::ChannelInternalType *out, char *str, size_t size) {
enum class State {
Begin,
ModuleId,
Sep1,
Reserved,
Sep2,
ChannelId
};
State state = State::Begin;
const char * cur = nullptr;
const char * const str_end = str + size;
while (str < str_end && IsValidCharacter(*str)) {
const char c = *str;
switch (state) {
case State::Begin:
if (IsNumericCharacter(c)) {
cur = str;
state = State::ModuleId;
}
break;
case State::ModuleId:
if (c == ':') {
*str = 0;
if (!IntegerToModuleId(std::addressof(out->module_id), atoi(cur))) {
return false;
}
state = State::Sep1;
} else if (!IsNumericCharacter(c)) {
return false;
}
break;
case State::Sep1:
if (IsNumericCharacter(c)) {
cur = str;
state = State::Reserved;
}
break;
case State::Reserved:
if (c == ':') {
*str = 0;
out->reserved = 0;
state = State::Sep2;
} else if (!IsNumericCharacter(c)) {
return false;
}
break;
case State::Sep2:
if (IsNumericCharacter(c)) {
cur = str;
state = State::ChannelId;
}
break;
case State::ChannelId:
if (!IsNumericCharacter(c)) {
return false;
}
break;
}
++str;
}
if (str != str_end) {
return false;
}
out->channel_id = atoi(cur);
return true;
}
}
void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size) {
/* Parse the JSON. */
const char *channel_strs[0x20];
int num_channels = 0;
ParseBody(out_version, channel_strs, std::addressof(num_channels), util::size(channel_strs), str, str_size);
/* Parse the channel strings. */
char * const str_end = static_cast<char *>(str) + str_size;
int parsed_channels = 0;
for (auto i = 0; i < num_channels && i < max_channels; ++i) {
impl::ChannelInternalType channel;
if (StringToChannel(std::addressof(channel), const_cast<char *>(channel_strs[i]), util::Strnlen(channel_strs[i], str_end - channel_strs[i]))) {
out_channels[parsed_channels++] = channel;
}
}
*out_num_channels = parsed_channels;
}
}

View File

@@ -1,23 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::ctrl {
void ParseServiceChannel(s16 *out_version, impl::ChannelInternalType *out_channels, int *out_num_channels, int max_channels, void *str, size_t str_size);
}

View File

@@ -1,94 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_driver_manager.hpp"
namespace ams::htclow::driver {
Result DriverManager::OpenDriver(impl::DriverType driver_type) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that we're not already open. */
R_UNLESS(m_open_driver == nullptr, htclow::ResultDriverOpened());
/* Open the driver. */
switch (driver_type) {
case impl::DriverType::Debug:
R_TRY(m_debug_driver->Open());
m_open_driver = m_debug_driver;
break;
case impl::DriverType::Socket:
m_socket_driver.Open();
m_open_driver = std::addressof(m_socket_driver);
break;
case impl::DriverType::Usb:
m_usb_driver.Open();
m_open_driver = std::addressof(m_usb_driver);
break;
case impl::DriverType::PlainChannel:
//m_plain_channel_driver.Open();
//m_open_driver = std::addressof(m_plain_channel_driver);
//break;
R_THROW(htclow::ResultUnknownDriverType());
default:
R_THROW(htclow::ResultUnknownDriverType());
}
/* Set the driver type. */
m_driver_type = driver_type;
R_SUCCEED();
}
void DriverManager::CloseDriver() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Clear our driver type. */
m_driver_type = util::nullopt;
/* Close our driver. */
if (m_open_driver != nullptr) {
m_open_driver->Close();
m_open_driver = nullptr;
}
}
impl::DriverType DriverManager::GetDriverType() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_driver_type.value_or(impl::DriverType::Unknown);
}
IDriver *DriverManager::GetCurrentDriver() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_open_driver;
}
void DriverManager::Cancel() {
m_open_driver->CancelSendReceive();
}
void DriverManager::SetDebugDriver(IDriver *driver) {
m_debug_driver = driver;
m_driver_type = impl::DriverType::Debug;
}
}

View File

@@ -1,48 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_i_driver.hpp"
#include "htclow_socket_driver.hpp"
#include "htclow_usb_driver.hpp"
namespace ams::htclow::driver {
class DriverManager {
private:
util::optional<htclow::impl::DriverType> m_driver_type{};
IDriver *m_debug_driver{};
SocketDriver m_socket_driver;
UsbDriver m_usb_driver{};
/* TODO: PlainChannelDriver m_plain_channel_driver; */
os::SdkMutex m_mutex{};
IDriver *m_open_driver{};
public:
DriverManager(mem::StandardAllocator *allocator) : m_socket_driver(allocator) { /* ... */ }
Result OpenDriver(impl::DriverType driver_type);
void CloseDriver();
impl::DriverType GetDriverType();
IDriver *GetCurrentDriver();
void Cancel();
void SetDebugDriver(IDriver *driver);
};
}

View File

@@ -1,61 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_driver_memory_management.hpp"
namespace ams::htclow::driver {
namespace {
constexpr inline size_t RequiredAlignment = std::max(os::ThreadStackAlignment, os::MemoryPageSize);
using SocketConfigType = socket::SystemConfigDefault;
/* TODO: If we ever use resolvers, increase this. */
constexpr inline size_t SocketAllocatorSize = 4_KB;
constexpr inline size_t SocketMemoryPoolSize = util::AlignUp(SocketConfigType::PerTcpSocketWorstCaseMemoryPoolSize + SocketConfigType::PerUdpSocketWorstCaseMemoryPoolSize, os::MemoryPageSize);
constexpr inline size_t SocketRequiredSize = util::AlignUp(SocketMemoryPoolSize + SocketAllocatorSize, os::MemoryPageSize);
constexpr inline size_t UsbRequiredSize = 2 * UsbDmaBufferSize + UsbIndicationThreadStackSize;
static_assert(util::IsAligned(UsbDmaBufferSize, RequiredAlignment));
constexpr inline size_t RequiredSize = std::max(SocketRequiredSize, UsbRequiredSize);
static_assert(util::IsAligned(RequiredSize, os::MemoryPageSize));
/* Declare the memory pool. */
alignas(RequiredAlignment) constinit u8 g_driver_memory[RequiredSize];
constexpr inline const socket::SystemConfigDefault SocketConfig(g_driver_memory, RequiredSize, SocketAllocatorSize, 2);
}
void *GetUsbReceiveBuffer() {
return g_driver_memory;
}
void *GetUsbSendBuffer() {
return g_driver_memory + UsbDmaBufferSize;
}
void *GetUsbIndicationThreadStack() {
return g_driver_memory + 2 * UsbDmaBufferSize;
}
void InitializeSocketApiForSocketDriver() {
R_ABORT_UNLESS(socket::Initialize(SocketConfig));
}
}

View File

@@ -1,28 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::driver {
constexpr inline size_t UsbDmaBufferSize = 0x80000;
constexpr inline size_t UsbIndicationThreadStackSize = 16_KB;
void *GetUsbReceiveBuffer();
void *GetUsbSendBuffer();
void *GetUsbIndicationThreadStack();
}

View File

@@ -1,34 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::driver {
class IDriver {
public:
virtual Result Open() = 0;
virtual void Close() = 0;
virtual Result Connect(os::EventType *event) = 0;
virtual void Shutdown() = 0;
virtual Result Send(const void *src, int src_size) = 0;
virtual Result Receive(void *dst, int dst_size) = 0;
virtual void CancelSendReceive() = 0;
virtual void Suspend() = 0;
virtual void Resume() = 0;
};
}

View File

@@ -1,151 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_socket_discovery_manager.hpp"
#include "htclow_socket_discovery_util.hpp"
namespace ams::htclow::driver {
namespace {
constexpr inline u32 BeaconQueryServiceId = 0xB48F5C51;
}
void SocketDiscoveryManager::OnDriverOpen() {
/* Create our socket. */
m_socket = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp);
AMS_ABORT_UNLESS(m_socket != -1);
/* Mark driver open. */
m_driver_closed = false;
/* Create our thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_discovery_thread), ThreadEntry, this, m_thread_stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowDiscovery)));
/* Set our thread name. */
os::SetThreadNamePointer(std::addressof(m_discovery_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowDiscovery));
/* Start our thread. */
os::StartThread(std::addressof(m_discovery_thread));
}
void SocketDiscoveryManager::OnDriverClose() {
/* Mark driver closed. */
m_driver_closed = true;
/* Shutdown our socket. */
socket::Shutdown(m_socket, socket::ShutdownMethod::Shut_RdWr);
/* Close our socket. */
socket::Close(m_socket);
/* Destroy our thread. */
os::WaitThread(std::addressof(m_discovery_thread));
os::DestroyThread(std::addressof(m_discovery_thread));
}
void SocketDiscoveryManager::OnSocketAcceptBegin(u16 port) {
AMS_UNUSED(port);
}
void SocketDiscoveryManager::OnSocketAcceptEnd() {
/* ... */
}
void SocketDiscoveryManager::ThreadFunc() {
for (this->DoDiscovery(); !m_driver_closed; this->DoDiscovery()) {
/* Check if the driver is closed five times. */
for (size_t i = 0; i < 5; ++i) {
os::SleepThread(TimeSpan::FromSeconds(1));
if (m_driver_closed) {
return;
}
}
}
}
Result SocketDiscoveryManager::DoDiscovery() {
/* Ensure we close our socket if we fail. */
auto socket_guard = SCOPE_GUARD { socket::Close(m_socket); };
/* Create sockaddr for our socket. */
const socket::SockAddrIn sockaddr = {
.sin_len = 0,
.sin_family = socket::Family::Af_Inet,
.sin_port = socket::InetHtons(20181),
.sin_addr = { socket::InetHtonl(0) },
};
/* Bind our socket. */
const auto bind_res = socket::Bind(m_socket, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr));
R_UNLESS(bind_res != 0, htclow::ResultSocketBindError());
/* Loop processing beacon queries. */
while (true) {
/* Receive a tmipc query header. */
TmipcHeader header;
socket::SockAddr recv_sockaddr;
socket::SockLenT recv_sockaddr_len = sizeof(recv_sockaddr);
const auto recv_res = socket::RecvFrom(m_socket, std::addressof(header), sizeof(header), socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len));
/* Check that our receive was valid. */
R_UNLESS(recv_res >= 0, htclow::ResultSocketReceiveFromError());
R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError());
/* Check we received a packet header. */
if (recv_res != sizeof(header)) {
continue;
}
/* Check that we received a correctly versioned BeaconQuery packet. */
/* NOTE: Nintendo checks this *after* the following receive, but this seems saner. */
if (header.version != TmipcVersion || header.service_id != BeaconQueryServiceId) {
continue;
}
/* Receive the packet body, if there is one. */
char packet_data[0x120];
/* NOTE: Nintendo does not check this... */
if (header.data_len > sizeof(packet_data)) {
continue;
}
if (header.data_len > 0) {
const auto body_res = socket::RecvFrom(m_socket, packet_data, header.data_len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), std::addressof(recv_sockaddr_len));
R_UNLESS(body_res >= 0, htclow::ResultSocketReceiveFromError());
R_UNLESS(recv_sockaddr_len == sizeof(recv_sockaddr), htclow::ResultSocketReceiveFromError());
if (body_res != header.data_len) {
continue;
}
}
/* Make our beacon response packet. */
const auto len = MakeBeaconResponsePacket(packet_data, sizeof(packet_data));
/* Send the beacon response data. */
const auto send_res = socket::SendTo(m_socket, packet_data, len, socket::MsgFlag::Msg_None, std::addressof(recv_sockaddr), sizeof(recv_sockaddr));
R_UNLESS(send_res >= 0, htclow::ResultSocketSendToError());
}
/* This can never happen, as the above loop should be infinite, but completion logic is here for posterity. */
socket_guard.Cancel();
R_SUCCEED();
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::driver {
class SocketDiscoveryManager {
private:
bool m_driver_closed;
mem::StandardAllocator *m_allocator;
void *m_thread_stack;
os::ThreadType m_discovery_thread;
s32 m_socket;
public:
SocketDiscoveryManager(mem::StandardAllocator *allocator)
: m_driver_closed(false), m_allocator(allocator), m_thread_stack(allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment))
{
/* ... */
}
private:
static void ThreadEntry(void *arg) {
static_cast<SocketDiscoveryManager *>(arg)->ThreadFunc();
}
void ThreadFunc();
Result DoDiscovery();
public:
void OnDriverOpen();
void OnDriverClose();
void OnSocketAcceptBegin(u16 port);
void OnSocketAcceptEnd();
};
}

View File

@@ -1,132 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_socket_discovery_util.hpp"
#include "../ctrl/htclow_ctrl_settings_holder.hpp"
namespace ams::htclow::driver {
namespace {
constexpr inline u32 AutoConnectIpv4RequestServiceId = 0x834C775A;
constexpr inline u32 BeaconResponseServiceId = 0xA6F7FA96;
constexpr const char MakeAutoConnectIpv4RequestPacketFormat[] =
"{\r\n"
" \"Address\" : \"%u.%u.%u.%u\",\r\n"
" \"Port\" : %u,\r\n"
" \"HW\" : \"%s\",\r\n"
" \"SN\" : \"%s\"\r\n"
"}\r\n";
constexpr const char BeaconResponsePacketFormat[] =
"{\r\n"
" \"Gen\" : 2,\r\n"
" \"Spec \": \"%s\",\r\n"
" \"MAC\" : \"00:00:00:00:00:00\",\r\n"
" \"Conn\" : \"TCP\",\r\n"
" \"HW\" : \"%s\",\r\n"
" \"Name\" : \"%s\",\r\n"
" \"SN\" : \"%s\",\r\n"
" \"FW\" : \"%s\"\r\n"
"}\r\n";
constinit os::SdkMutex g_settings_holder_mutex;
constinit bool g_settings_holder_initialized = false;
constinit htclow::ctrl::SettingsHolder g_settings_holder;
void InitializeSettingsHolder() {
std::scoped_lock lk(g_settings_holder_mutex);
if (!g_settings_holder_initialized) {
g_settings_holder.LoadSettings();
g_settings_holder_initialized = true;
}
}
}
s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr) {
/* Initialize the settings holder. */
InitializeSettingsHolder();
/* Create the packet header. */
TmipcHeader header = {
.service_id = AutoConnectIpv4RequestServiceId,
.version = TmipcVersion,
};
/* Create the packet body. */
std::scoped_lock lk(g_settings_holder_mutex);
const auto addr = sockaddr.sin_addr.s_addr;
char packet_body[0x100];
const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), MakeAutoConnectIpv4RequestPacketFormat,
(addr >> 0) & 0xFF, (addr >> 8) & 0xFF, (addr >> 16) & 0xFF, (addr >> 24) & 0xFF,
socket::InetNtohs(sockaddr.sin_port),
g_settings_holder.GetHardwareType(),
"" /* Nintendo passes empty string as serial number here. */
);
/* Determine actual usable body length. */
header.data_len = std::max<u32>(ideal_len, sizeof(packet_body));
/* Check that the packet will fit. */
AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size);
/* Copy the formatted header. */
std::memcpy(dst, std::addressof(header), sizeof(header));
std::memcpy(dst + sizeof(header), packet_body, header.data_len);
return header.data_len;
}
s32 MakeBeaconResponsePacket(char *dst, size_t dst_size) {
/* Initialize the settings holder. */
InitializeSettingsHolder();
/* Create the packet header. */
TmipcHeader header = {
.service_id = BeaconResponseServiceId,
.version = TmipcVersion,
};
/* Create the packet body. */
std::scoped_lock lk(g_settings_holder_mutex);
char packet_body[0x100];
const auto ideal_len = util::SNPrintf(packet_body, sizeof(packet_body), BeaconResponsePacketFormat,
g_settings_holder.GetSpec(),
g_settings_holder.GetHardwareType(),
g_settings_holder.GetTargetName(),
g_settings_holder.GetSerialNumber(),
g_settings_holder.GetFirmwareVersion()
);
/* Determine actual usable body length. */
header.data_len = std::max<u32>(ideal_len, sizeof(packet_body));
/* Check that the packet will fit. */
AMS_ABORT_UNLESS(sizeof(header) + header.data_len <= dst_size);
/* Copy the formatted header. */
std::memcpy(dst, std::addressof(header), sizeof(header));
std::memcpy(dst + sizeof(header), packet_body, header.data_len);
return header.data_len;
}
}

View File

@@ -1,38 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::driver {
constexpr inline u8 TmipcVersion = 5;
struct TmipcHeader {
u32 service_id;
u32 reserved_00;
u16 reserved_01;
u8 reserved_02;
u8 version;
u32 data_len;
u32 reserved[4];
};
static_assert(util::is_pod<TmipcHeader>::value);
static_assert(sizeof(TmipcHeader) == 0x20);
s32 MakeAutoConnectIpv4RequestPacket(char *dst, size_t dst_size, const socket::SockAddrIn &sockaddr);
s32 MakeBeaconResponsePacket(char *dst, size_t dst_size);
}

View File

@@ -1,283 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_socket_driver.hpp"
#include "htclow_socket_discovery_util.hpp"
namespace ams::htclow::driver {
Result SocketDriver::ConnectThread() {
/* Do auto connect, if we should. */
if (m_auto_connect_reserved) {
this->DoAutoConnect();
}
/* Get the socket's name. */
socket::SockAddrIn sockaddr;
socket::SockLenT sockaddr_len = sizeof(sockaddr);
R_UNLESS(socket::GetSockName(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(sockaddr)), std::addressof(sockaddr_len)) == 0, htclow::ResultSocketGetSockNameError());
/* Accept. */
m_discovery_manager.OnSocketAcceptBegin(sockaddr.sin_port);
sockaddr_len = sizeof(m_server_sockaddr);
const auto client_desc = socket::Accept(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(m_server_sockaddr)), std::addressof(sockaddr_len));
m_discovery_manager.OnSocketAcceptEnd();
/* Check accept result. */
R_UNLESS(client_desc >= 0, htclow::ResultSocketAcceptError());
/* Setup client socket. */
R_TRY(this->SetupClientSocket(client_desc));
R_SUCCEED();
}
Result SocketDriver::CreateServerSocket() {
/* Check that we don't have a server socket. */
AMS_ASSERT(!m_server_socket_valid);
/* Create the socket. */
const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Stream, socket::Protocol::IpProto_Tcp);
R_UNLESS(desc != -1, htclow::ResultSocketSocketExemptError());
/* Be sure that we close the socket if we don't succeed. */
auto socket_guard = SCOPE_GUARD { socket::Close(desc); };
/* Create sockaddr for our socket. */
const socket::SockAddrIn sockaddr = {
.sin_len = 0,
.sin_family = socket::Family::Af_Inet,
.sin_port = socket::InetHtons(20180),
.sin_addr = { socket::InetHtonl(0) },
};
/* Enable local address reuse. */
{
u32 enable = 1;
const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Socket, socket::Option::So_ReuseAddr, std::addressof(enable), sizeof(enable));
AMS_ABORT_UNLESS(res == 0);
}
/* Bind the socket. */
const auto bind_res = socket::Bind(desc, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr));
R_UNLESS(bind_res == 0, htclow::ResultSocketBindError());
/* Listen on the socket. */
const auto listen_res = socket::Listen(desc, 1);
R_UNLESS(listen_res == 0, htclow::ResultSocketListenError());
/* We succeeded. */
socket_guard.Cancel();
m_server_socket = desc;
m_server_socket_valid = true;
R_SUCCEED();
}
void SocketDriver::DestroyServerSocket() {
if (m_server_socket_valid) {
socket::Shutdown(m_server_socket, socket::ShutdownMethod::Shut_RdWr);
socket::Close(m_server_socket);
m_server_socket_valid = false;
}
}
Result SocketDriver::SetupClientSocket(s32 desc) {
std::scoped_lock lk(m_mutex);
/* Check that we don't have a client socket. */
AMS_ASSERT(!m_client_socket_valid);
/* Be sure that we close the socket if we don't succeed. */
auto socket_guard = SCOPE_GUARD { socket::Close(desc); };
/* Enable debug logging for the socket. */
u32 debug = 1;
const auto res = socket::SetSockOpt(desc, socket::Level::Sol_Tcp, socket::Option::So_Debug, std::addressof(debug), sizeof(debug));
R_UNLESS(res >= 0, htclow::ResultSocketSetSockOptError());
/* We succeeded. */
socket_guard.Cancel();
m_client_socket = desc;
m_client_socket_valid = true;
R_SUCCEED();
}
bool SocketDriver::IsAutoConnectReserved() {
return m_auto_connect_reserved;
}
void SocketDriver::ReserveAutoConnect() {
std::scoped_lock lk(m_mutex);
if (m_client_socket_valid) {
/* Save our client sockaddr. */
socket::SockLenT sockaddr_len = sizeof(m_saved_client_sockaddr);
if (socket::GetSockName(m_server_socket, reinterpret_cast<socket::SockAddr *>(std::addressof(m_saved_client_sockaddr)), std::addressof(sockaddr_len)) != 0) {
return;
}
/* Save our server sockaddr. */
m_saved_server_sockaddr = m_server_sockaddr;
/* Mark auto-connect reserved. */
m_auto_connect_reserved = true;
}
}
void SocketDriver::DoAutoConnect() {
/* Clear auto-connect reserved. */
m_auto_connect_reserved = false;
/* Create udb socket. */
const auto desc = socket::SocketExempt(socket::Family::Af_Inet, socket::Type::Sock_Dgram, socket::Protocol::IpProto_Udp);
if (desc == -1) {
return;
}
/* Clean up the desc when we're done. */
ON_SCOPE_EXIT { socket::Close(desc); };
/* Create auto-connect packet. */
char auto_connect_packet[0x120];
s32 len;
{
const socket::SockAddrIn sockaddr = {
.sin_family = socket::Family::Af_Inet,
.sin_port = m_saved_client_sockaddr.sin_port,
.sin_addr = m_saved_client_sockaddr.sin_addr,
};
len = htclow::driver::MakeAutoConnectIpv4RequestPacket(auto_connect_packet, sizeof(auto_connect_packet), sockaddr);
}
/* Send the auto-connect packet to the host on port 20181. */
const socket::SockAddrIn sockaddr = {
.sin_family = socket::Family::Af_Inet,
.sin_port = socket::InetHtons(20181),
.sin_addr = m_saved_server_sockaddr.sin_addr,
};
/* Send the auto-connect packet. */
socket::SendTo(desc, auto_connect_packet, len, socket::MsgFlag::Msg_None, reinterpret_cast<const socket::SockAddr *>(std::addressof(sockaddr)), sizeof(sockaddr));
}
Result SocketDriver::Open() {
m_discovery_manager.OnDriverOpen();
R_SUCCEED();
}
void SocketDriver::Close() {
m_discovery_manager.OnDriverClose();
}
Result SocketDriver::Connect(os::EventType *event) {
/* Allocate a temporary thread stack. */
void *stack = m_allocator->Allocate(os::MemoryPageSize, os::ThreadStackAlignment);
ON_SCOPE_EXIT { m_allocator->Free(stack); };
/* Try to create a server socket. */
R_TRY(this->CreateServerSocket());
/* Prepare to run our connect thread. */
m_event.Clear();
/* Run our connect thread. */
{
/* Create the thread. */
os::ThreadType connect_thread;
R_ABORT_UNLESS(os::CreateThread(std::addressof(connect_thread), ConnectThreadEntry, this, stack, os::MemoryPageSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowTcpServer)));
/* Set the thread's name. */
os::SetThreadNamePointer(std::addressof(connect_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowTcpServer));
/* Start the thread. */
os::StartThread(std::addressof(connect_thread));
/* Check if we should cancel the connection. */
if (os::WaitAny(event, m_event.GetBase()) == 0) {
this->DestroyServerSocket();
}
/* Wait for the connect thread to finish. */
os::WaitThread(std::addressof(connect_thread));
/* Destroy the connection thread. */
os::DestroyThread(std::addressof(connect_thread));
/* Destroy the server socket. */
this->DestroyServerSocket();
}
/* Return our connection result. */
R_RETURN(m_connect_result);
}
void SocketDriver::Shutdown() {
std::scoped_lock lk(m_mutex);
/* Shut down our client socket, if we need to. */
if (m_client_socket_valid) {
socket::Shutdown(m_client_socket, socket::ShutdownMethod::Shut_RdWr);
socket::Close(m_client_socket);
m_client_socket_valid = 0;
}
}
Result SocketDriver::Send(const void *src, int src_size) {
/* Check the input size. */
R_UNLESS(src_size >= 0, htclow::ResultInvalidArgument());
/* Repeatedly send data until it's all sent. */
ssize_t cur_sent;
for (ssize_t sent = 0; sent < src_size; sent += cur_sent) {
cur_sent = socket::Send(m_client_socket, static_cast<const u8 *>(src) + sent, src_size - sent, socket::MsgFlag::Msg_None);
R_UNLESS(cur_sent > 0, htclow::ResultSocketSendError());
}
R_SUCCEED();
}
Result SocketDriver::Receive(void *dst, int dst_size) {
/* Check the input size. */
R_UNLESS(dst_size >= 0, htclow::ResultInvalidArgument());
/* Repeatedly receive data until it's all sent. */
ssize_t cur_recv;
for (ssize_t received = 0; received < dst_size; received += cur_recv) {
cur_recv = socket::Recv(m_client_socket, static_cast<u8 *>(dst) + received, dst_size - received, socket::MsgFlag::Msg_None);
R_UNLESS(cur_recv > 0, htclow::ResultSocketReceiveError());
}
R_SUCCEED();
}
void SocketDriver::CancelSendReceive() {
this->Shutdown();
}
void SocketDriver::Suspend() {
this->ReserveAutoConnect();
}
void SocketDriver::Resume() {
/* ... */
}
}

View File

@@ -1,76 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_i_driver.hpp"
#include "htclow_socket_discovery_manager.hpp"
namespace ams::htclow::driver {
class SocketDriver final : public IDriver {
private:
mem::StandardAllocator *m_allocator;
SocketDiscoveryManager m_discovery_manager;
socket::SockAddrIn m_server_sockaddr;
socket::SockAddrIn m_saved_server_sockaddr;
socket::SockAddrIn m_saved_client_sockaddr;
os::Event m_event;
Result m_connect_result;
os::SdkMutex m_mutex;
s32 m_server_socket;
s32 m_client_socket;
bool m_server_socket_valid;
bool m_client_socket_valid;
bool m_auto_connect_reserved;
public:
SocketDriver(mem::StandardAllocator *allocator)
: m_allocator(allocator), m_discovery_manager(m_allocator), m_event(os::EventClearMode_ManualClear),
m_connect_result(), m_mutex(), m_server_socket(), m_client_socket(), m_server_socket_valid(false),
m_client_socket_valid(false), m_auto_connect_reserved(false)
{
/* ... */
}
private:
static void ConnectThreadEntry(void *arg) {
auto * const driver = static_cast<SocketDriver *>(arg);
driver->m_connect_result = driver->ConnectThread();
driver->m_event.Signal();
}
Result ConnectThread();
private:
Result CreateServerSocket();
void DestroyServerSocket();
Result SetupClientSocket(s32 desc);
bool IsAutoConnectReserved();
void ReserveAutoConnect();
void DoAutoConnect();
public:
virtual Result Open() override;
virtual void Close() override;
virtual Result Connect(os::EventType *event) override;
virtual void Shutdown() override;
virtual Result Send(const void *src, int src_size) override;
virtual Result Receive(void *dst, int dst_size) override;
virtual void CancelSendReceive() override;
virtual void Suspend() override;
virtual void Resume() override;
};
}

View File

@@ -1,125 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_usb_driver.hpp"
#include "htclow_usb_impl.hpp"
namespace ams::htclow::driver {
void UsbDriver::OnUsbAvailabilityChange(UsbAvailability availability, void *param) {
/* Convert the argument to a driver. */
UsbDriver *driver = static_cast<UsbDriver *>(param);
/* Handle the change. */
switch (availability) {
case UsbAvailability_Unavailable:
CancelUsbSendReceive();
break;
case UsbAvailability_Available:
driver->m_event.Signal();
break;
case UsbAvailability_Unknown:
driver->CancelSendReceive();
break;
}
}
Result UsbDriver::Open() {
/* Clear our event. */
m_event.Clear();
/* Set the availability change callback. */
SetUsbAvailabilityChangeCallback(OnUsbAvailabilityChange, this);
/* Initialize the interface. */
R_RETURN(InitializeUsbInterface());
}
void UsbDriver::Close() {
/* Finalize the interface. */
FinalizeUsbInterface();
/* Clear the availability callback. */
ClearUsbAvailabilityChangeCallback();
}
Result UsbDriver::Connect(os::EventType *event) {
/* We must not already be connected. */
AMS_ABORT_UNLESS(!m_connected);
/* Perform a wait on our event. */
const int idx = os::WaitAny(m_event.GetBase(), event);
R_UNLESS(idx == 0, htclow::ResultCancelled());
/* Clear our event. */
m_event.Clear();
/* We're connected. */
m_connected = true;
R_SUCCEED();
}
void UsbDriver::Shutdown() {
/* If we're connected, cancel anything we're doing. */
if (m_connected) {
this->CancelSendReceive();
m_connected = false;
}
}
Result UsbDriver::Send(const void *src, int src_size) {
/* Check size. */
R_UNLESS(src_size >= 0, htclow::ResultInvalidArgument());
/* Send until we've sent everything. */
for (auto transferred = 0; transferred < src_size; /* ... */) {
int cur;
R_TRY(SendUsb(std::addressof(cur), reinterpret_cast<const void *>(reinterpret_cast<uintptr_t>(src) + transferred), src_size - transferred));
transferred += cur;
}
R_SUCCEED();
}
Result UsbDriver::Receive(void *dst, int dst_size) {
/* Check size. */
R_UNLESS(dst_size >= 0, htclow::ResultInvalidArgument());
/* Send until we've sent everything. */
for (auto transferred = 0; transferred < dst_size; /* ... */) {
int cur;
R_TRY(ReceiveUsb(std::addressof(cur), reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(dst) + transferred), dst_size - transferred));
transferred += cur;
}
R_SUCCEED();
}
void UsbDriver::CancelSendReceive() {
CancelUsbSendReceive();
}
void UsbDriver::Suspend() {
this->Close();
}
void UsbDriver::Resume() {
R_ABORT_UNLESS(this->Open());
}
}

View File

@@ -1,43 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_i_driver.hpp"
#include "htclow_usb_impl.hpp"
namespace ams::htclow::driver {
class UsbDriver final : public IDriver {
private:
bool m_connected;
os::Event m_event;
public:
UsbDriver() : m_connected(false), m_event(os::EventClearMode_ManualClear) { /* ... */ }
public:
static void OnUsbAvailabilityChange(UsbAvailability availability, void *param);
public:
virtual Result Open() override;
virtual void Close() override;
virtual Result Connect(os::EventType *event) override;
virtual void Shutdown() override;
virtual Result Send(const void *src, int src_size) override;
virtual Result Receive(void *dst, int dst_size) override;
virtual void CancelSendReceive() override;
virtual void Suspend() override;
virtual void Resume() override;
};
}

View File

@@ -1,462 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_usb_impl.hpp"
#include "htclow_driver_memory_management.hpp"
namespace ams::htclow::driver {
namespace {
/* TODO: Should we identify differently than Nintendo does? */
/* It's kind of silly to identify as "NintendoSDK DevKit", but it's also kind of amusing. */
/* TBD */
constinit usb::UsbStringDescriptor LanguageStringDescriptor = { 4, usb::UsbDescriptorType_String, {0x0409}};
constinit usb::UsbStringDescriptor ManufacturerStringDescriptor = {18, usb::UsbDescriptorType_String, {'N', 'i', 'n', 't', 'e', 'n', 'd', 'o'}};
constinit usb::UsbStringDescriptor ProductStringFullSpeedDescriptor = {38, usb::UsbDescriptorType_String, {'N', 'i', 'n', 't', 'e', 'n', 'd', 'o', 'S', 'D', 'K', ' ', 'D', 'e', 'v', 'K', 'i', 't'}};
constinit usb::UsbStringDescriptor SerialNumberStringDescriptor = { 0, usb::UsbDescriptorType_String, {}};
constinit usb::UsbStringDescriptor InterfaceStringDescriptor = {16, usb::UsbDescriptorType_String, {'h', 't', 'c', ' ', 'u', 's', 'b'}};
constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorFullSpeed = {
.bLength = usb::UsbDescriptorSize_Device,
.bDescriptorType = usb::UsbDescriptorType_Device,
.bcdUSB = 0x0110,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 0x40,
.idVendor = 0x057E,
.idProduct = 0x3005,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01,
};
constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorHighSpeed = {
.bLength = usb::UsbDescriptorSize_Device,
.bDescriptorType = usb::UsbDescriptorType_Device,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 0x40,
.idVendor = 0x057E,
.idProduct = 0x3005,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01,
};
constinit usb::UsbDeviceDescriptor UsbDeviceDescriptorSuperSpeed = {
.bLength = usb::UsbDescriptorSize_Device,
.bDescriptorType = usb::UsbDescriptorType_Device,
.bcdUSB = 0x0300,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 0x09,
.idVendor = 0x057E,
.idProduct = 0x3005,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01,
};
constinit u8 BinaryObjectStore[] = {
0x05,
usb::UsbDescriptorType_Bos,
0x16, 0x00,
0x02,
0x07,
usb::UsbDescriptorType_DeviceCapability,
0x02,
0x02, 0x00, 0x00, 0x00,
0x0A,
usb::UsbDescriptorType_DeviceCapability,
0x03,
0x00,
0x0E, 0x00,
0x03,
0x00,
0x00, 0x00,
};
constinit usb::UsbInterfaceDescriptor UsbInterfaceDescriptor = {
.bLength = usb::UsbDescriptorSize_Interface,
.bDescriptorType = usb::UsbDescriptorType_Interface,
.bInterfaceNumber = 0x00,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x02,
.bInterfaceClass = 0xFF,
.bInterfaceSubClass = 0xFF,
.bInterfaceProtocol = 0xFF,
.iInterface = 0x04,
};
constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsFullSpeed[2] = {
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x81,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x40,
.bInterval = 0x00,
},
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x01,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x40,
.bInterval = 0x00,
}
};
constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsHighSpeed[2] = {
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x81,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x200,
.bInterval = 0x00,
},
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x01,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x200,
.bInterval = 0x00,
}
};
constinit usb::UsbEndpointDescriptor UsbEndpointDescriptorsSuperSpeed[2] = {
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x81,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x400,
.bInterval = 0x00,
},
{
.bLength = usb::UsbDescriptorSize_Endpoint,
.bDescriptorType = usb::UsbDescriptorType_Endpoint,
.bEndpointAddress = 0x01,
.bmAttributes = usb::UsbEndpointAttributeMask_XferTypeBulk,
.wMaxPacketSize = 0x400,
.bInterval = 0x00,
}
};
constinit usb::UsbEndpointCompanionDescriptor UsbEndpointCompanionDescriptor = {
.bLength = usb::UsbDescriptorSize_EndpointCompanion,
.bDescriptorType = usb::UsbDescriptorType_EndpointCompanion,
.bMaxBurst = 0x0F,
.bmAttributes = 0x00,
.wBytesPerInterval = 0x0000,
};
constinit void *g_usb_receive_buffer = nullptr;
constinit void *g_usb_send_buffer = nullptr;
constinit UsbAvailabilityChangeCallback g_availability_change_callback = nullptr;
constinit void *g_availability_change_param = nullptr;
constinit bool g_usb_interface_initialized = false;
os::Event g_usb_break_event(os::EventClearMode_ManualClear);
constinit os::ThreadType g_usb_indication_thread = {};
constinit os::SdkMutex g_usb_driver_mutex;
usb::DsClient g_ds_client;
usb::DsInterface g_ds_interface;
usb::DsEndpoint g_ds_endpoints[2];
void InvokeAvailabilityChangeCallback(UsbAvailability availability) {
if (g_availability_change_callback) {
g_availability_change_callback(availability, g_availability_change_param);
}
}
Result ConvertUsbDriverResult(Result result) {
if (result.GetModule() == R_NAMESPACE_MODULE_ID(usb)) {
if (usb::ResultResourceBusy::Includes(result)) {
R_THROW(htclow::ResultUsbDriverBusyError());
} else if (usb::ResultMemAllocFailure::Includes(result)) {
R_THROW(htclow::ResultOutOfMemory());
} else {
R_THROW(htclow::ResultUsbDriverUnknownError());
}
} else {
R_RETURN(result);
}
}
Result InitializeDsClient() {
/* Initialize the client. */
R_TRY(ConvertUsbDriverResult(g_ds_client.Initialize(usb::ComplexId_Tegra21x)));
/* Clear device data. */
R_ABORT_UNLESS(g_ds_client.ClearDeviceData());
/* Add string descriptors. */
u8 index;
R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(LanguageStringDescriptor)));
R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(ManufacturerStringDescriptor)));
R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(ProductStringFullSpeedDescriptor)));
R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(SerialNumberStringDescriptor)));
R_TRY(g_ds_client.AddUsbStringDescriptor(std::addressof(index), std::addressof(InterfaceStringDescriptor)));
/* Add device descriptors. */
R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorFullSpeed), usb::UsbDeviceSpeed_Full));
R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorHighSpeed), usb::UsbDeviceSpeed_High));
R_TRY(g_ds_client.SetUsbDeviceDescriptor(std::addressof(UsbDeviceDescriptorSuperSpeed), usb::UsbDeviceSpeed_Super));
/* Set binary object store. */
R_TRY(g_ds_client.SetBinaryObjectStore(BinaryObjectStore, sizeof(BinaryObjectStore)));
R_SUCCEED();
}
Result InitializeDsInterface() {
/* Initialize the interface. */
R_TRY(ConvertUsbDriverResult(g_ds_interface.Initialize(std::addressof(g_ds_client), 0)));
/* Append the interface descriptors for all speeds. */
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbEndpointDescriptorsFullSpeed[0]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Full, std::addressof(UsbEndpointDescriptorsFullSpeed[1]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbEndpointDescriptorsHighSpeed[0]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_High, std::addressof(UsbEndpointDescriptorsHighSpeed[1]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbInterfaceDescriptor), sizeof(usb::UsbInterfaceDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointDescriptorsSuperSpeed[0]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointCompanionDescriptor), sizeof(usb::UsbEndpointCompanionDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointDescriptorsSuperSpeed[1]), sizeof(usb::UsbEndpointDescriptor)));
R_TRY(g_ds_interface.AppendConfigurationData(usb::UsbDeviceSpeed_Super, std::addressof(UsbEndpointCompanionDescriptor), sizeof(usb::UsbEndpointCompanionDescriptor)));
R_SUCCEED();
}
Result InitializeDsEndpoints() {
R_TRY(g_ds_endpoints[0].Initialize(std::addressof(g_ds_interface), 0x81));
R_TRY(g_ds_endpoints[1].Initialize(std::addressof(g_ds_interface), 0x01));
R_SUCCEED();
}
void UsbIndicationThreadFunction(void *) {
/* Get the state change event. */
os::SystemEventType *state_change_event = g_ds_client.GetStateChangeEvent();
/* Setup multi wait. */
os::MultiWaitType multi_wait;
os::InitializeMultiWait(std::addressof(multi_wait));
/* Link multi wait holders. */
os::MultiWaitHolderType state_change_holder;
os::MultiWaitHolderType break_holder;
os::InitializeMultiWaitHolder(std::addressof(state_change_holder), state_change_event);
os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(state_change_holder));
os::InitializeMultiWaitHolder(std::addressof(break_holder), g_usb_break_event.GetBase());
os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(break_holder));
/* Loop forever. */
while (true) {
/* If we should break, do so. */
if (os::WaitAny(std::addressof(multi_wait)) == std::addressof(break_holder)) {
break;
}
/* Clear the state change event. */
os::ClearSystemEvent(state_change_event);
/* Get the new state. */
usb::UsbState usb_state;
R_ABORT_UNLESS(g_ds_client.GetState(std::addressof(usb_state)));
switch (usb_state) {
case usb::UsbState_Detached:
case usb::UsbState_Suspended:
InvokeAvailabilityChangeCallback(UsbAvailability_Unavailable);
break;
case usb::UsbState_Configured:
InvokeAvailabilityChangeCallback(UsbAvailability_Available);
break;
default:
/* Nothing to do. */
break;
}
}
/* Clear the break event. */
g_usb_break_event.Clear();
/* Unlink all holders. */
os::UnlinkAllMultiWaitHolder(std::addressof(multi_wait));
/* Finalize the multi wait/holders. */
os::FinalizeMultiWaitHolder(std::addressof(break_holder));
os::FinalizeMultiWaitHolder(std::addressof(state_change_holder));
os::FinalizeMultiWait(std::addressof(multi_wait));
}
}
void SetUsbAvailabilityChangeCallback(UsbAvailabilityChangeCallback callback, void *param) {
g_availability_change_callback = callback;
g_availability_change_param = param;
}
void ClearUsbAvailabilityChangeCallback() {
g_availability_change_callback = nullptr;
g_availability_change_param = nullptr;
}
Result InitializeUsbInterface() {
/* Set the interface as initialized. */
g_usb_interface_initialized = true;
/* Get the dma buffers. */
g_usb_receive_buffer = GetUsbReceiveBuffer();
g_usb_send_buffer = GetUsbSendBuffer();
/* If we fail somewhere, finalize. */
auto init_guard = SCOPE_GUARD { FinalizeUsbInterface(); };
/* Get the serial number. */
{
settings::factory::SerialNumber serial_number;
serial_number.str[0] = '\x00';
if (R_FAILED(settings::factory::GetSerialNumber(std::addressof(serial_number))) || serial_number.str[0] == '\x00') {
std::strcpy(serial_number.str, "Corrupted S/N");
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress-of-packed-member"
u16 *dst = SerialNumberStringDescriptor.wData;
u8 *src = reinterpret_cast<u8 *>(serial_number.str);
u8 count = 0;
#pragma GCC diagnostic pop
while (*src) {
*dst++ = static_cast<u16>(*src++);
++count;
}
SerialNumberStringDescriptor.bLength = 2 + (2 * count);
}
/* Initialize the client. */
R_TRY(InitializeDsClient());
/* Initialize the interface. */
R_TRY(InitializeDsInterface());
/* Initialize the endpoints. */
R_TRY(ConvertUsbDriverResult(InitializeDsEndpoints()));
/* Create the indication thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_usb_indication_thread), &UsbIndicationThreadFunction, nullptr, GetUsbIndicationThreadStack(), UsbIndicationThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowUsbIndication)));
/* Set the thread name. */
os::SetThreadNamePointer(std::addressof(g_usb_indication_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowUsbIndication));
/* Start the indication thread. */
os::StartThread(std::addressof(g_usb_indication_thread));
/* Enable the usb device. */
R_TRY(g_ds_client.EnableDevice());
/* We succeeded! */
init_guard.Cancel();
R_SUCCEED();
}
void FinalizeUsbInterface() {
g_usb_break_event.Signal();
os::WaitThread(std::addressof(g_usb_indication_thread));
os::DestroyThread(std::addressof(g_usb_indication_thread));
g_ds_client.DisableDevice();
g_ds_endpoints[1].Finalize();
g_ds_endpoints[0].Finalize();
g_ds_interface.Finalize();
g_ds_client.Finalize();
g_usb_interface_initialized = false;
}
Result SendUsb(int *out_transferred, const void *src, int src_size) {
/* Acquire exclusive access to the driver. */
std::scoped_lock lk(g_usb_driver_mutex);
/* Check that we can send the data. */
R_UNLESS(src_size <= static_cast<int>(UsbDmaBufferSize), htclow::ResultInvalidArgument());
/* Copy the data to the dma buffer. */
std::memcpy(g_usb_send_buffer, src, src_size);
/* Transfer data. */
u32 transferred;
R_UNLESS(R_SUCCEEDED(g_ds_endpoints[0].PostBuffer(std::addressof(transferred), g_usb_send_buffer, src_size)), htclow::ResultUsbDriverSendError());
R_UNLESS(transferred == static_cast<u32>(src_size), htclow::ResultUsbDriverSendError());
/* Set output transferred size. */
*out_transferred = src_size;
R_SUCCEED();
}
Result ReceiveUsb(int *out_transferred, void *dst, int dst_size) {
/* Check that we can send the data. */
R_UNLESS(dst_size <= static_cast<int>(UsbDmaBufferSize), htclow::ResultInvalidArgument());
/* Transfer data. */
u32 transferred;
R_UNLESS(R_SUCCEEDED(g_ds_endpoints[1].PostBuffer(std::addressof(transferred), g_usb_receive_buffer, dst_size)), htclow::ResultUsbDriverReceiveError());
R_UNLESS(transferred == static_cast<u32>(dst_size), htclow::ResultUsbDriverReceiveError());
/* Copy the data. */
std::memcpy(dst, g_usb_receive_buffer, dst_size);
/* Set output transferred size. */
*out_transferred = dst_size;
R_SUCCEED();
}
void CancelUsbSendReceive() {
if (g_usb_interface_initialized) {
g_ds_endpoints[0].Cancel();
g_ds_endpoints[1].Cancel();
}
}
}

View File

@@ -1,41 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::driver {
enum UsbAvailability {
UsbAvailability_Unavailable = 1,
UsbAvailability_Available = 2,
UsbAvailability_Unknown = 3,
};
using UsbAvailabilityChangeCallback = void (*)(UsbAvailability availability, void *param);
void SetUsbAvailabilityChangeCallback(UsbAvailabilityChangeCallback callback, void *param);
void ClearUsbAvailabilityChangeCallback();
Result InitializeUsbInterface();
void FinalizeUsbInterface();
Result SendUsb(int *out_transferred, const void *src, int src_size);
Result ReceiveUsb(int *out_transferred, void *dst, int dst_size);
void CancelUsbSendReceive();
}

View File

@@ -1,235 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_channel.hpp"
namespace ams::htclow {
Result Channel::Open(const Module *module, ChannelId id) {
/* Check we're not already initialized. */
AMS_ASSERT(!module->GetBase()->_is_initialized);
/* Set our channel type. */
m_channel = {
._is_initialized = true,
._module_id = module->GetBase()->_id,
._channel_id = id,
};
/* Open the channel. */
R_RETURN(m_manager->Open(impl::ConvertChannelType(m_channel)));
}
void Channel::Close() {
m_manager->Close(impl::ConvertChannelType(m_channel));
}
ChannelState Channel::GetChannelState() {
return m_manager->GetChannelState(impl::ConvertChannelType(m_channel));
}
os::EventType *Channel::GetChannelStateEvent() {
return m_manager->GetChannelStateEvent(impl::ConvertChannelType(m_channel));
}
Result Channel::Connect() {
const auto channel = impl::ConvertChannelType(m_channel);
/* Begin the flush. */
u32 task_id{};
R_TRY(m_manager->ConnectBegin(std::addressof(task_id), channel));
/* Wait for the task to finish. */
this->WaitEvent(m_manager->GetTaskEvent(task_id), false);
/* End the flush. */
R_RETURN(m_manager->ConnectEnd(channel, task_id));
}
Result Channel::Flush() {
/* Begin the flush. */
u32 task_id{};
R_TRY(m_manager->FlushBegin(std::addressof(task_id), impl::ConvertChannelType(m_channel)));
/* Wait for the task to finish. */
this->WaitEvent(m_manager->GetTaskEvent(task_id), true);
/* End the flush. */
R_RETURN(m_manager->FlushEnd(task_id));
}
void Channel::Shutdown() {
m_manager->Shutdown(impl::ConvertChannelType(m_channel));
}
Result Channel::Receive(s64 *out, void *dst, s64 size, ReceiveOption option) {
/* Check pre-conditions. */
AMS_ABORT_UNLESS(util::IsIntValueRepresentable<size_t>(size));
/* Determine the minimum allowable receive size. */
s64 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 = size; break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Repeatedly receive. */
s64 received = 0;
do {
s64 cur_received;
const Result result = this->ReceiveInternal(std::addressof(cur_received), static_cast<u8 *>(dst) + received, size - received, 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;
}
R_RETURN(result);
}
received += static_cast<size_t>(cur_received);
} while (received < min_size);
/* Set output size. */
AMS_ASSERT(received <= size);
*out = received;
R_SUCCEED();
}
Result Channel::Send(s64 *out, const void *src, s64 size) {
/* Check pre-conditions. */
AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size));
/* Convert channel. */
const auto channel = impl::ConvertChannelType(m_channel);
/* Loop sending. */
s64 total_sent;
size_t cur_sent;
for (total_sent = 0; total_sent < size; total_sent += cur_sent) {
AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size - total_sent));
/* Begin the send. */
u32 task_id{};
const auto begin_result = m_manager->SendBegin(std::addressof(task_id), std::addressof(cur_sent), static_cast<const u8 *>(src) + total_sent, size - total_sent, channel);
if (R_FAILED(begin_result)) {
if (total_sent != 0) {
break;
} else {
R_RETURN(begin_result);
}
}
/* Wait for the task to finish. */
this->WaitEvent(m_manager->GetTaskEvent(task_id), true);
/* Finish the send. */
R_ABORT_UNLESS(m_manager->SendEnd(task_id));
}
/* Set output size. */
AMS_ASSERT(total_sent <= size);
*out = total_sent;
R_SUCCEED();
}
void Channel::SetConfig(const ChannelConfig &config) {
m_manager->SetConfig(impl::ConvertChannelType(m_channel), config);
}
void Channel::SetReceiveBuffer(void *buf, size_t size) {
m_manager->SetReceiveBuffer(impl::ConvertChannelType(m_channel), buf, size);
}
void Channel::SetSendBuffer(void *buf, size_t size) {
m_manager->SetSendBuffer(impl::ConvertChannelType(m_channel), buf, size);
}
void Channel::SetSendBufferWithData(const void *buf, size_t size) {
m_manager->SetSendBufferWithData(impl::ConvertChannelType(m_channel), buf, size);
}
Result Channel::WaitReceive(s64 size) {
/* Check pre-conditions. */
AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size));
R_RETURN(this->WaitReceiveInternal(size, nullptr));
}
Result Channel::WaitReceive(s64 size, os::EventType *event) {
/* Check pre-conditions. */
AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size));
AMS_ASSERT(event != nullptr);
R_RETURN(this->WaitReceiveInternal(size, event));
}
void Channel::WaitEvent(os::EventType *event, bool) {
return os::WaitEvent(event);
}
Result Channel::ReceiveInternal(s64 *out, void *dst, s64 size, ReceiveOption option) {
const auto channel = impl::ConvertChannelType(m_channel);
const bool blocking = option != ReceiveOption_NonBlocking;
/* Begin the receive. */
u32 task_id{};
R_TRY(m_manager->ReceiveBegin(std::addressof(task_id), channel, blocking ? 1 : 0));
/* Wait for the task to finish. */
this->WaitEvent(m_manager->GetTaskEvent(task_id), false);
/* Receive the data. */
size_t received;
AMS_ASSERT(util::IsIntValueRepresentable<size_t>(size));
R_TRY(m_manager->ReceiveEnd(std::addressof(received), dst, static_cast<size_t>(size), channel, task_id));
/* Set the output size. */
AMS_ASSERT(util::IsIntValueRepresentable<s64>(received));
*out = static_cast<s64>(received);
R_SUCCEED();
}
Result Channel::WaitReceiveInternal(s64 size, os::EventType *event) {
const auto channel = impl::ConvertChannelType(m_channel);
/* Begin the wait. */
u32 task_id{};
R_TRY(m_manager->WaitReceiveBegin(std::addressof(task_id), channel, size));
/* Perform the wait. */
if (event != nullptr) {
if (os::WaitAny(event, m_manager->GetTaskEvent(task_id)) == 0) {
m_manager->WaitReceiveEnd(task_id);
R_THROW(htclow::ResultChannelWaitCancelled());
}
} else {
this->WaitEvent(m_manager->GetTaskEvent(task_id), false);
}
/* End the wait. */
R_RETURN(m_manager->WaitReceiveEnd(task_id));
}
}

View File

@@ -1,58 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_manager.hpp"
namespace ams::htclow {
class Channel final {
private:
HtclowManager *m_manager;
ChannelType m_channel;
public:
constexpr Channel(HtclowManager *manager) : m_manager(manager), m_channel() { /* ... */ }
constexpr ~Channel() { m_channel = {}; }
ChannelType *GetBase() { return std::addressof(m_channel); }
public:
Result Open(const Module *module, ChannelId id);
void Close();
ChannelState GetChannelState();
os::EventType *GetChannelStateEvent();
Result Connect();
Result Flush();
void Shutdown();
Result Receive(s64 *out, void *dst, s64 size, ReceiveOption option);
Result Send(s64 *out, const void *src, s64 size);
void SetConfig(const ChannelConfig &config);
void SetReceiveBuffer(void *buf, size_t size);
void SetSendBuffer(void *buf, size_t size);
void SetSendBufferWithData(const void *buf, size_t size);
Result WaitReceive(s64 size);
Result WaitReceive(s64 size, os::EventType *event);
private:
void WaitEvent(os::EventType *event, bool);
Result ReceiveInternal(s64 *out, void *dst, s64 size, ReceiveOption option);
Result WaitReceiveInternal(s64 size, os::EventType *event);
};
}

View File

@@ -1,29 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_packet.hpp"
namespace ams::htclow {
constexpr inline const ChannelConfig DefaultChannelConfig = {
.flow_control_enabled = true,
.handshake_enabled = true,
.initial_counter_max_data = 0,
.max_packet_size = 0xE000 + sizeof(PacketHeader),
};
}

View File

@@ -1,118 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_listener.hpp"
namespace ams::htclow {
namespace {
constexpr inline size_t ThreadStackSize = 4_KB;
}
Listener::Listener(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv, Worker *worker)
: m_thread_stack_size(ThreadStackSize), m_allocator(allocator), m_mux(mux), m_service(ctrl_srv), m_worker(worker), m_event(os::EventClearMode_ManualClear), m_driver(nullptr), m_thread_running(false), m_cancelled(false)
{
/* Allocate stack. */
m_listen_thread_stack = m_allocator->Allocate(m_thread_stack_size, os::ThreadStackAlignment);
}
void Listener::Start(driver::IDriver *driver) {
/* Check pre-conditions. */
AMS_ASSERT(!m_thread_running);
/* Create the thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_listen_thread), ListenThreadEntry, this, m_listen_thread_stack, ThreadStackSize, AMS_GET_SYSTEM_THREAD_PRIORITY(htc, HtclowListen)));
/* Set the thread name. */
os::SetThreadNamePointer(std::addressof(m_listen_thread), AMS_GET_SYSTEM_THREAD_NAME(htc, HtclowListen));
/* Set our driver. */
m_driver = driver;
/* Set state. */
m_thread_running = true;
m_cancelled = false;
/* Clear our event. */
m_event.Clear();
/* Start the thread. */
os::StartThread(std::addressof(m_listen_thread));
}
void Listener::Cancel() {
/* Mark ourselves as cancelled. */
m_cancelled = true;
/* Cancel our worker. */
m_worker->Cancel();
/* Signal our event. */
m_event.Signal();
/* Cancel our driver. */
m_driver->CancelSendReceive();
}
void Listener::Wait() {
/* Wait for our listen thread to exit. */
os::WaitThread(std::addressof(m_listen_thread));
/* Destroy our listen thread. */
os::DestroyThread(std::addressof(m_listen_thread));
/* Clear our driver. */
m_driver = nullptr;
/* Mark our thread as not running. */
m_thread_running = false;
}
void Listener::ListenThread() {
/* Check pre-conditions. */
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

@@ -1,49 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_worker.hpp"
namespace ams::htclow {
class Listener {
private:
u32 m_thread_stack_size;
mem::StandardAllocator *m_allocator;
mux::Mux *m_mux;
ctrl::HtcctrlService *m_service;
Worker *m_worker;
os::Event m_event;
os::ThreadType m_listen_thread;
void *m_listen_thread_stack;
driver::IDriver *m_driver;
bool m_thread_running;
bool m_cancelled;
private:
static void ListenThreadEntry(void *arg) {
static_cast<Listener *>(arg)->ListenThread();
}
void ListenThread();
public:
Listener(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv, Worker *worker);
void Start(driver::IDriver *driver);
void Cancel();
void Wait();
};
}

View File

@@ -1,147 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_manager.hpp"
#include "htclow_manager_impl.hpp"
namespace ams::htclow {
HtclowManager::HtclowManager(mem::StandardAllocator *allocator) : m_allocator(allocator), m_impl(static_cast<HtclowManagerImpl *>(allocator->Allocate(sizeof(HtclowManagerImpl), alignof(HtclowManagerImpl)))) {
std::construct_at(m_impl, m_allocator);
}
HtclowManager::~HtclowManager() {
std::destroy_at(m_impl);
m_allocator->Free(m_impl);
}
Result HtclowManager::OpenDriver(impl::DriverType driver_type) {
R_RETURN(m_impl->OpenDriver(driver_type));
}
void HtclowManager::CloseDriver() {
return m_impl->CloseDriver();
}
Result HtclowManager::Open(impl::ChannelInternalType channel) {
R_RETURN(m_impl->Open(channel));
}
Result HtclowManager::Close(impl::ChannelInternalType channel) {
R_RETURN(m_impl->Close(channel));
}
void HtclowManager::Resume() {
return m_impl->Resume();
}
void HtclowManager::Suspend() {
return m_impl->Suspend();
}
Result HtclowManager::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
R_RETURN(m_impl->ConnectBegin(out_task_id, channel));
}
Result HtclowManager::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) {
R_RETURN(m_impl->ConnectEnd(channel, task_id));
}
void HtclowManager::Disconnect() {
return m_impl->Disconnect();
}
Result HtclowManager::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
R_RETURN(m_impl->FlushBegin(out_task_id, channel));
}
Result HtclowManager::FlushEnd(u32 task_id) {
R_RETURN(m_impl->FlushEnd(task_id));
}
ChannelState HtclowManager::GetChannelState(impl::ChannelInternalType channel) {
return m_impl->GetChannelState(channel);
}
os::EventType *HtclowManager::GetChannelStateEvent(impl::ChannelInternalType channel) {
return m_impl->GetChannelStateEvent(channel);
}
impl::DriverType HtclowManager::GetDriverType() {
return m_impl->GetDriverType();
}
os::EventType *HtclowManager::GetTaskEvent(u32 task_id) {
return m_impl->GetTaskEvent(task_id);
}
void HtclowManager::NotifyAsleep() {
return m_impl->NotifyAsleep();
}
void HtclowManager::NotifyAwake() {
return m_impl->NotifyAwake();
}
Result HtclowManager::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
R_RETURN(m_impl->ReceiveBegin(out_task_id, channel, size));
}
Result HtclowManager::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) {
R_RETURN(m_impl->ReceiveEnd(out, dst, dst_size, channel, task_id));
}
Result HtclowManager::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) {
R_RETURN(m_impl->SendBegin(out_task_id, out, src, src_size, channel));
}
Result HtclowManager::SendEnd(u32 task_id) {
R_RETURN(m_impl->SendEnd(task_id));
}
Result HtclowManager::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
R_RETURN(m_impl->WaitReceiveBegin(out_task_id, channel, size));
}
Result HtclowManager::WaitReceiveEnd(u32 task_id) {
R_RETURN(m_impl->WaitReceiveEnd(task_id));
}
void HtclowManager::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) {
return m_impl->SetConfig(channel, config);
}
void HtclowManager::SetDebugDriver(driver::IDriver *driver) {
return m_impl->SetDebugDriver(driver);
}
void HtclowManager::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) {
return m_impl->SetReceiveBuffer(channel, buf, buf_size);
}
void HtclowManager::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) {
return m_impl->SetSendBuffer(channel, buf, buf_size);
}
void HtclowManager::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size) {
return m_impl->SetSendBufferWithData(channel, buf, buf_size);
}
Result HtclowManager::Shutdown(impl::ChannelInternalType channel) {
R_RETURN(m_impl->Shutdown(channel));
}
}

View File

@@ -1,79 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "driver/htclow_i_driver.hpp"
namespace ams::htclow {
class HtclowManagerImpl;
class HtclowManager {
private:
mem::StandardAllocator *m_allocator;
HtclowManagerImpl *m_impl;
public:
HtclowManager(mem::StandardAllocator *allocator);
~HtclowManager();
public:
Result OpenDriver(impl::DriverType driver_type);
void CloseDriver();
Result Open(impl::ChannelInternalType channel);
Result Close(impl::ChannelInternalType channel);
void Resume();
void Suspend();
Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id);
void Disconnect();
Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result FlushEnd(u32 task_id);
ChannelState GetChannelState(impl::ChannelInternalType channel);
os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel);
impl::DriverType GetDriverType();
os::EventType *GetTaskEvent(u32 task_id);
void NotifyAsleep();
void NotifyAwake();
Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id);
Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel);
Result SendEnd(u32 task_id);
Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result WaitReceiveEnd(u32 task_id);
void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config);
void SetDebugDriver(driver::IDriver *driver);
void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size);
void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size);
void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size);
Result Shutdown(impl::ChannelInternalType channel);
};
}

View File

@@ -1,88 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_manager.hpp"
namespace ams::htclow::HtclowManagerHolder {
namespace {
constinit os::SdkMutex g_holder_mutex;
constinit int g_holder_reference_count = 0;
mem::StandardAllocator g_allocator;
constinit HtclowManager *g_manager = nullptr;
constinit htclow::impl::DriverType g_default_driver_type = htclow::impl::DriverType::Socket;
alignas(os::MemoryPageSize) u8 g_heap_buffer[928_KB];
}
void AddReference() {
std::scoped_lock lk(g_holder_mutex);
if ((g_holder_reference_count++) == 0) {
/* Initialize the allocator for the manager. */
g_allocator.Initialize(g_heap_buffer, sizeof(g_heap_buffer));
/* Allocate the manager. */
g_manager = static_cast<HtclowManager *>(g_allocator.Allocate(sizeof(HtclowManager), alignof(HtclowManager)));
/* Construct the manager. */
std::construct_at(g_manager, std::addressof(g_allocator));
/* Open the driver. */
R_ABORT_UNLESS(g_manager->OpenDriver(g_default_driver_type));
}
AMS_ASSERT(g_holder_reference_count > 0);
}
void Release() {
std::scoped_lock lk(g_holder_mutex);
AMS_ASSERT(g_holder_reference_count > 0);
if ((--g_holder_reference_count) == 0) {
/* Disconnect. */
g_manager->Disconnect();
/* Close the driver. */
g_manager->CloseDriver();
/* Destroy the manager. */
std::destroy_at(g_manager);
g_allocator.Free(g_manager);
g_manager = nullptr;
/* Finalize the allocator. */
g_allocator.Finalize();
}
}
HtclowManager *GetHtclowManager() {
std::scoped_lock lk(g_holder_mutex);
return g_manager;
}
void SetDefaultDriver(htclow::impl::DriverType driver_type) {
g_default_driver_type = driver_type;
}
}

View File

@@ -1,208 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_manager_impl.hpp"
#include "htclow_default_channel_config.hpp"
namespace ams::htclow {
HtclowManagerImpl::HtclowManagerImpl(mem::StandardAllocator *allocator)
: m_packet_factory(allocator), m_driver_manager(allocator), m_mux(std::addressof(m_packet_factory), std::addressof(m_ctrl_state_machine)),
m_ctrl_packet_factory(allocator), m_ctrl_state_machine(), m_ctrl_service(std::addressof(m_ctrl_packet_factory), std::addressof(m_ctrl_state_machine), std::addressof(m_mux)),
m_worker(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service)),
m_listener(allocator, std::addressof(m_mux), std::addressof(m_ctrl_service), std::addressof(m_worker)),
m_is_driver_open(false)
{
/* ... */
}
HtclowManagerImpl::~HtclowManagerImpl() {
/* ... */
}
Result HtclowManagerImpl::OpenDriver(impl::DriverType driver_type) {
/* Set the driver type. */
m_ctrl_service.SetDriverType(driver_type);
/* Ensure that we don't end up in an invalid state. */
auto drv_guard = SCOPE_GUARD { m_ctrl_service.SetDriverType(impl::DriverType::Unknown); };
/* Try to open the driver. */
R_TRY(m_driver_manager.OpenDriver(driver_type));
/* Start the listener. */
m_listener.Start(m_driver_manager.GetCurrentDriver());
/* Note the driver as open. */
m_is_driver_open = true;
drv_guard.Cancel();
R_SUCCEED();
}
void HtclowManagerImpl::CloseDriver() {
/* Close the driver, if we're open. */
if (m_is_driver_open) {
/* Cancel the driver. */
m_driver_manager.Cancel();
/* Stop our listener. */
m_listener.Cancel();
m_listener.Wait();
/* Close the driver. */
m_driver_manager.CloseDriver();
/* Set the driver type to unknown. */
m_ctrl_service.SetDriverType(impl::DriverType::Unknown);
/* Note the driver as closed. */
m_is_driver_open = false;
}
}
Result HtclowManagerImpl::Open(impl::ChannelInternalType channel) {
R_RETURN(m_mux.Open(channel));
}
Result HtclowManagerImpl::Close(impl::ChannelInternalType channel) {
R_RETURN(m_mux.Close(channel));
}
void HtclowManagerImpl::Resume() {
/* Get our driver. */
auto *driver = m_driver_manager.GetCurrentDriver();
/* Resume our driver. */
driver->Resume();
/* Start the listener. */
m_listener.Start(driver);
/* Resume our control service. */
m_ctrl_service.Resume();
}
void HtclowManagerImpl::Suspend() {
/* Suspend our control service. */
m_ctrl_service.Suspend();
/* Stop our listener. */
m_listener.Cancel();
m_listener.Wait();
/* Suspend our driver. */
m_driver_manager.GetCurrentDriver()->Suspend();
}
Result HtclowManagerImpl::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
/* Begin connecting. */
R_TRY(m_mux.ConnectBegin(out_task_id, channel));
/* Try to ready ourselves. */
m_ctrl_service.TryReady();
R_SUCCEED();
}
Result HtclowManagerImpl::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) {
R_RETURN(m_mux.ConnectEnd(channel, task_id));
}
void HtclowManagerImpl::Disconnect() {
return m_ctrl_service.Disconnect();
}
Result HtclowManagerImpl::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
R_RETURN(m_mux.FlushBegin(out_task_id, channel));
}
Result HtclowManagerImpl::FlushEnd(u32 task_id) {
R_RETURN(m_mux.FlushEnd(task_id));
}
ChannelState HtclowManagerImpl::GetChannelState(impl::ChannelInternalType channel) {
return m_mux.GetChannelState(channel);
}
os::EventType *HtclowManagerImpl::GetChannelStateEvent(impl::ChannelInternalType channel) {
return m_mux.GetChannelStateEvent(channel);
}
impl::DriverType HtclowManagerImpl::GetDriverType() {
return m_driver_manager.GetDriverType();
}
os::EventType *HtclowManagerImpl::GetTaskEvent(u32 task_id) {
return m_mux.GetTaskEvent(task_id);
}
void HtclowManagerImpl::NotifyAsleep() {
return m_ctrl_service.NotifyAsleep();
}
void HtclowManagerImpl::NotifyAwake() {
return m_ctrl_service.NotifyAwake();
}
Result HtclowManagerImpl::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
R_RETURN(m_mux.ReceiveBegin(out_task_id, channel, size));
}
Result HtclowManagerImpl::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) {
R_RETURN(m_mux.ReceiveEnd(out, dst, dst_size, channel, task_id));
}
Result HtclowManagerImpl::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) {
R_RETURN(m_mux.SendBegin(out_task_id, out, src, src_size, channel));
}
Result HtclowManagerImpl::SendEnd(u32 task_id) {
R_RETURN(m_mux.SendEnd(task_id));
}
Result HtclowManagerImpl::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
R_RETURN(m_mux.WaitReceiveBegin(out_task_id, channel, size));
}
Result HtclowManagerImpl::WaitReceiveEnd(u32 task_id) {
R_RETURN(m_mux.WaitReceiveEnd(task_id));
}
void HtclowManagerImpl::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) {
return m_mux.SetConfig(channel, config);
}
void HtclowManagerImpl::SetDebugDriver(driver::IDriver *driver) {
m_driver_manager.SetDebugDriver(driver);
}
void HtclowManagerImpl::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) {
return m_mux.SetReceiveBuffer(channel, buf, buf_size);
}
void HtclowManagerImpl::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) {
return m_mux.SetSendBuffer(channel, buf, buf_size, m_driver_manager.GetDriverType() == impl::DriverType::Usb ? sizeof(PacketBody) : DefaultChannelConfig.max_packet_size);
}
void HtclowManagerImpl::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size) {
return m_mux.SetSendBufferWithData(channel, buf, buf_size, m_driver_manager.GetDriverType() == impl::DriverType::Usb ? sizeof(PacketBody) : DefaultChannelConfig.max_packet_size);
}
Result HtclowManagerImpl::Shutdown(impl::ChannelInternalType channel) {
R_RETURN(m_mux.Shutdown(channel));
}
}

View File

@@ -1,91 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_packet_factory.hpp"
#include "driver/htclow_driver_manager.hpp"
#include "mux/htclow_mux.hpp"
#include "ctrl/htclow_ctrl_packet_factory.hpp"
#include "ctrl/htclow_ctrl_state_machine.hpp"
#include "ctrl/htclow_ctrl_service.hpp"
#include "htclow_worker.hpp"
#include "htclow_listener.hpp"
namespace ams::htclow {
class HtclowManagerImpl {
private:
PacketFactory m_packet_factory;
driver::DriverManager m_driver_manager;
mux::Mux m_mux;
ctrl::HtcctrlPacketFactory m_ctrl_packet_factory;
ctrl::HtcctrlStateMachine m_ctrl_state_machine;
ctrl::HtcctrlService m_ctrl_service;
Worker m_worker;
Listener m_listener;
bool m_is_driver_open;
public:
HtclowManagerImpl(mem::StandardAllocator *allocator);
~HtclowManagerImpl();
public:
Result OpenDriver(impl::DriverType driver_type);
void CloseDriver();
Result Open(impl::ChannelInternalType channel);
Result Close(impl::ChannelInternalType channel);
void Resume();
void Suspend();
Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id);
void Disconnect();
Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result FlushEnd(u32 task_id);
ChannelState GetChannelState(impl::ChannelInternalType channel);
os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel);
impl::DriverType GetDriverType();
os::EventType *GetTaskEvent(u32 task_id);
void NotifyAsleep();
void NotifyAwake();
Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id);
Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel);
Result SendEnd(u32 task_id);
Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result WaitReceiveEnd(u32 task_id);
void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config);
void SetDebugDriver(driver::IDriver *driver);
void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size);
void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size);
void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size);
Result Shutdown(impl::ChannelInternalType channel);
};
}

View File

@@ -1,112 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow {
enum PacketType : u16 {
PacketType_Data = 24,
PacketType_MaxData = 25,
PacketType_Error = 26,
};
static constexpr inline u32 HtcGen2Signature = 0xA79F3540;
struct PacketHeader {
u32 signature;
u32 offset;
u32 reserved;
u32 body_size;
s16 version;
PacketType packet_type;
impl::ChannelInternalType channel;
u64 share;
};
static_assert(util::is_pod<PacketHeader>::value);
static_assert(sizeof(PacketHeader) == 0x20);
static constexpr inline size_t PacketBodySizeMax = 0x3E000;
struct PacketBody {
u8 data[PacketBodySizeMax];
};
template<typename HeaderType>
class BasePacket {
private:
mem::StandardAllocator *m_allocator;
u8 *m_header;
int m_packet_size;
public:
BasePacket(mem::StandardAllocator *allocator, int packet_size) : m_allocator(allocator), m_header(nullptr), m_packet_size(packet_size) {
AMS_ASSERT(packet_size >= static_cast<int>(sizeof(HeaderType)));
m_header = static_cast<u8 *>(m_allocator->Allocate(m_packet_size));
}
virtual ~BasePacket() {
if (m_header != nullptr) {
m_allocator->Free(m_header);
}
}
bool IsAllocationSucceeded() const {
return m_header != nullptr;
}
HeaderType *GetHeader() {
return reinterpret_cast<HeaderType *>(m_header);
}
const HeaderType *GetHeader() const {
return reinterpret_cast<const HeaderType *>(m_header);
}
int GetBodySize() const {
return m_packet_size - sizeof(HeaderType);
}
u8 *GetBody() const {
if (this->GetBodySize() > 0) {
return m_header + sizeof(HeaderType);
} else {
return nullptr;
}
}
void CopyBody(const void *src, int src_size) {
AMS_ASSERT(this->GetBodySize() >= 0);
std::memcpy(this->GetBody(), src, src_size);
}
};
class Packet : public BasePacket<PacketHeader>, public util::IntrusiveListBaseNode<Packet> {
public:
using BasePacket<PacketHeader>::BasePacket;
};
struct PacketDeleter {
mem::StandardAllocator *m_allocator;
void operator()(Packet *packet) {
std::destroy_at(packet);
m_allocator->Free(packet);
}
};
}

View File

@@ -1,97 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_packet_factory.hpp"
namespace ams::htclow {
void PacketFactory::Delete(Packet *packet) {
PacketDeleter{m_allocator}(packet);
}
std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeSendPacketCommon(impl::ChannelInternalType channel, s16 version, int body_size) {
/* Allocate memory for the packet. */
if (void *buffer = m_allocator->Allocate(sizeof(Packet), alignof(Packet)); buffer != nullptr) {
/* Convert the buffer to a packet. */
Packet *packet = static_cast<Packet *>(buffer);
/* Construct the packet. */
std::construct_at(packet, m_allocator, body_size + sizeof(PacketHeader));
/* Create the unique pointer. */
std::unique_ptr<Packet, PacketDeleter> ptr(packet, PacketDeleter{m_allocator});
/* Set packet header fields. */
if (ptr && ptr->IsAllocationSucceeded()) {
PacketHeader *header = ptr->GetHeader();
header->signature = HtcGen2Signature;
header->offset = 0;
header->reserved = 0;
header->body_size = body_size;
header->version = version;
header->channel = channel;
header->share = 0;
}
return ptr;
} else {
return std::unique_ptr<Packet, PacketDeleter>(nullptr, PacketDeleter{m_allocator});
}
}
std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeDataPacket(impl::ChannelInternalType channel, s16 version, const void *body, int body_size, u64 share, u32 offset) {
auto packet = this->MakeSendPacketCommon(channel, version, body_size);
if (packet) {
PacketHeader *header = packet->GetHeader();
header->packet_type = PacketType_Data;
header->offset = offset;
header->share = share;
packet->CopyBody(body, body_size);
AMS_ASSERT(packet->GetBodySize() == body_size);
}
return packet;
}
std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeMaxDataPacket(impl::ChannelInternalType channel, s16 version, u64 share) {
auto packet = this->MakeSendPacketCommon(channel, version, 0);
if (packet) {
PacketHeader *header = packet->GetHeader();
header->packet_type = PacketType_MaxData;
header->share = share;
}
return packet;
}
std::unique_ptr<Packet, PacketDeleter> PacketFactory::MakeErrorPacket(impl::ChannelInternalType channel) {
auto packet = this->MakeSendPacketCommon(channel, 0, 0);
if (packet) {
PacketHeader *header = packet->GetHeader();
header->packet_type = PacketType_Error;
}
return packet;
}
}

View File

@@ -1,37 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_packet.hpp"
namespace ams::htclow {
class PacketFactory {
private:
mem::StandardAllocator *m_allocator;
public:
PacketFactory(mem::StandardAllocator *allocator) : m_allocator(allocator) { /* ... */ }
std::unique_ptr<Packet, PacketDeleter> MakeDataPacket(impl::ChannelInternalType channel, s16 version, const void *body, int body_size, u64 share, u32 offset);
std::unique_ptr<Packet, PacketDeleter> MakeMaxDataPacket(impl::ChannelInternalType channel, s16 version, u64 share);
std::unique_ptr<Packet, PacketDeleter> MakeErrorPacket(impl::ChannelInternalType channel);
void Delete(Packet *packet);
private:
std::unique_ptr<Packet, PacketDeleter> MakeSendPacketCommon(impl::ChannelInternalType channel, s16 version, int body_size);
};
}

View File

@@ -1,181 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_worker.hpp"
namespace ams::htclow {
namespace {
constexpr inline size_t ThreadStackSize = 4_KB;
}
Worker::Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv)
: 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. */
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);
}
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() {
/* Forever receive packets. */
constexpr size_t MaxPacketHeaderSize = std::max(sizeof(PacketHeader), sizeof(ctrl::HtcctrlPacketHeader));
u8 packet_header_storage[MaxPacketHeaderSize];
while (true) {
/* Receive the packet header. */
R_TRY(m_driver->Receive(packet_header_storage, sizeof(packet_header_storage)));
/* Check if the packet is a control packet. */
if (ctrl::HtcctrlPacketHeader *ctrl_header = reinterpret_cast<ctrl::HtcctrlPacketHeader *>(packet_header_storage); ctrl_header->signature == ctrl::HtcctrlSignature) {
/* Process the packet. */
R_TRY(this->ProcessReceive(*ctrl_header));
} else {
/* Otherwise, we must have a normal packet. */
PacketHeader *header = reinterpret_cast<PacketHeader *>(packet_header_storage);
R_UNLESS(header->signature == HtcGen2Signature, htclow::ResultProtocolError());
/* Process the packet. */
R_TRY(this->ProcessReceive(*header));
}
}
}
Result Worker::ProcessReceive(const ctrl::HtcctrlPacketHeader &header) {
/* Check the header. */
R_TRY(m_service->CheckReceivedHeader(header));
/* Receive the body, if we have one. */
if (header.body_size > 0) {
R_TRY(m_driver->Receive(m_receive_packet_body, header.body_size));
}
/* Process the received packet. */
m_service->ProcessReceivePacket(header, m_receive_packet_body, header.body_size);
R_SUCCEED();
}
Result Worker::ProcessReceive(const PacketHeader &header) {
/* Check the header. */
R_TRY(m_mux->CheckReceivedHeader(header));
/* Receive the body, if we have one. */
if (header.body_size > 0) {
R_TRY(m_driver->Receive(m_receive_packet_body, header.body_size));
}
/* Process the received packet. */
m_mux->ProcessReceivePacket(header, m_receive_packet_body, header.body_size);
R_SUCCEED();
}
Result Worker::ProcessSend() {
/* Forever process packets. */
while (true) {
const auto index = os::WaitAny(m_service->GetSendPacketEvent(), m_mux->GetSendPacketEvent(), m_event.GetBase());
if (index == 0) {
/* HtcctrlService packet. */
/* Clear the packet event. */
os::ClearEvent(m_service->GetSendPacketEvent());
/* While we have packets, send them. */
auto *packet_header = reinterpret_cast<ctrl::HtcctrlPacketHeader *>(m_send_buffer);
auto *packet_body = reinterpret_cast<ctrl::HtcctrlPacketBody *>(m_send_buffer + sizeof(*packet_header));
int body_size;
while (m_service->QuerySendPacket(packet_header, packet_body, std::addressof(body_size))) {
m_service->RemovePacket(*packet_header);
R_TRY(m_driver->Send(packet_header, body_size + sizeof(*packet_header)));
}
} else if (index == 1) {
/* Mux packet. */
/* Clear the packet event. */
os::ClearEvent(m_mux->GetSendPacketEvent());
/* While we have packets, send them. */
auto *packet_header = reinterpret_cast<PacketHeader *>(m_send_buffer);
auto *packet_body = reinterpret_cast<PacketBody *>(m_send_buffer + sizeof(*packet_header));
int body_size;
while (m_mux->QuerySendPacket(packet_header, packet_body, std::addressof(body_size))) {
R_TRY(m_driver->Send(packet_header, body_size + sizeof(*packet_header)));
m_mux->RemovePacket(*packet_header);
}
} else {
/* Our event. */
/* Check if we're cancelled. */
if (m_cancelled) {
R_THROW(htclow::ResultCancelled());
}
}
}
}
}

View File

@@ -1,69 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "driver/htclow_i_driver.hpp"
#include "ctrl/htclow_ctrl_service.hpp"
#include "mux/htclow_mux.hpp"
namespace ams::htclow {
class Worker {
private:
static_assert(sizeof(ctrl::HtcctrlPacketHeader) <= sizeof(PacketHeader));
static_assert(sizeof(ctrl::HtcctrlPacketBody) <= sizeof(PacketBody));
private:
u32 m_thread_stack_size;
u8 m_send_buffer[sizeof(PacketHeader) + sizeof(PacketBody)];
u8 m_receive_packet_body[sizeof(PacketBody)];
mem::StandardAllocator *m_allocator;
mux::Mux *m_mux;
ctrl::HtcctrlService *m_service;
driver::IDriver *m_driver;
os::ThreadType m_receive_thread;
os::ThreadType m_send_thread;
os::Event m_event;
void *m_receive_thread_stack;
void *m_send_thread_stack;
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:
Worker(mem::StandardAllocator *allocator, mux::Mux *mux, ctrl::HtcctrlService *ctrl_srv);
void SetDriver(driver::IDriver *driver);
void Start();
void Cancel();
void Wait();
private:
Result ProcessReceive();
Result ProcessSend();
Result ProcessReceive(const ctrl::HtcctrlPacketHeader &header);
Result ProcessReceive(const PacketHeader &header);
};
}

View File

@@ -1,426 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux.hpp"
#include "../htclow_packet_factory.hpp"
#include "../ctrl/htclow_ctrl_state_machine.hpp"
namespace ams::htclow::mux {
Mux::Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm)
: m_packet_factory(pf), m_state_machine(sm), m_task_manager(), m_event(os::EventClearMode_ManualClear),
m_channel_impl_map(pf, sm, std::addressof(m_task_manager), std::addressof(m_event)), m_global_send_buffer(pf),
m_mutex(), m_state(MuxState::Normal), m_version(ProtocolVersion)
{
/* ... */
}
void Mux::SetVersion(u16 version) {
/* Set our version. */
m_version = version;
/* Set all entries in our map. */
/* NOTE: Nintendo does this highly inefficiently... */
for (auto &pair : m_channel_impl_map.GetMap()) {
m_channel_impl_map[pair.second].SetVersion(m_version);
}
}
Result Mux::CheckReceivedHeader(const PacketHeader &header) const {
/* Check the packet signature. */
AMS_ASSERT(header.signature == HtcGen2Signature);
/* Switch on the packet type. */
switch (header.packet_type) {
case PacketType_Data:
R_UNLESS(header.version == m_version, htclow::ResultProtocolError());
R_UNLESS(header.body_size <= sizeof(PacketBody), htclow::ResultProtocolError());
break;
case PacketType_MaxData:
R_UNLESS(header.version == m_version, htclow::ResultProtocolError());
R_UNLESS(header.body_size == 0, htclow::ResultProtocolError());
break;
case PacketType_Error:
R_UNLESS(header.body_size == 0, htclow::ResultProtocolError());
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}
Result Mux::ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Process for the channel. */
if (auto it = m_channel_impl_map.GetMap().find(header.channel); it != m_channel_impl_map.GetMap().end()) {
R_RETURN(m_channel_impl_map[it->second].ProcessReceivePacket(header, body, body_size));
} else {
if (header.packet_type == PacketType_Data || header.packet_type == PacketType_MaxData) {
this->SendErrorPacket(header.channel);
}
R_THROW(htclow::ResultChannelNotExist());
}
}
bool Mux::QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Check for an error packet. */
/* NOTE: Nintendo checks this once per iteration of the below loop. */
/* The extra checks are unnecessary, because we hold our mutex. */
if (auto *error_packet = m_global_send_buffer.GetNextPacket(); error_packet != nullptr) {
std::memcpy(header, error_packet->GetHeader(), sizeof(*header));
*out_body_size = 0;
return true;
}
/* Iterate the map, checking each channel for a valid valid packet. */
for (auto &pair : m_channel_impl_map.GetMap()) {
/* Get the current channel impl. */
/* See if the channel has something for us to send. */
if (m_channel_impl_map[pair.second].QuerySendPacket(header, body, out_body_size)) {
return this->IsSendable(header->packet_type);
}
}
return false;
}
void Mux::RemovePacket(const PacketHeader &header) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Remove the packet from the appropriate source. */
if (header.packet_type == PacketType_Error) {
m_global_send_buffer.RemovePacket();
} else if (auto it = m_channel_impl_map.GetMap().find(header.channel); it != m_channel_impl_map.GetMap().end()) {
m_channel_impl_map[it->second].RemovePacket(header);
}
/* Notify the task manager. */
m_task_manager.NotifySendReady();
}
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.second].UpdateState();
}
}
void Mux::UpdateMuxState() {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Update whether we're sleeping. */
if (m_state_machine->IsSleeping()) {
m_state = MuxState::Sleep;
} else {
m_state = MuxState::Normal;
m_event.Signal();
}
}
Result Mux::CheckChannelExist(impl::ChannelInternalType channel) {
R_UNLESS(m_channel_impl_map.Exists(channel), htclow::ResultChannelNotExist());
R_SUCCEED();
}
Result Mux::SendErrorPacket(impl::ChannelInternalType channel) {
/* Create and send the packet. */
R_TRY(m_global_send_buffer.AddPacket(m_packet_factory->MakeErrorPacket(channel)));
/* Signal our event. */
m_event.Signal();
R_SUCCEED();
}
bool Mux::IsSendable(PacketType packet_type) const {
AMS_UNUSED(packet_type);
switch (m_state) {
case MuxState::Normal:
return true;
case MuxState::Sleep:
return false;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
Result Mux::Open(impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that the channel doesn't already exist. */
R_UNLESS(!m_channel_impl_map.Exists(channel), htclow::ResultChannelAlreadyExist());
/* Add the channel. */
R_TRY(m_channel_impl_map.AddChannel(channel));
/* Set the channel version. */
m_channel_impl_map.GetChannelImpl(channel).SetVersion(m_version);
R_SUCCEED();
}
Result Mux::Close(impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* If we have the channel, close it. */
if (auto it = m_channel_impl_map.GetMap().find(channel); it != m_channel_impl_map.GetMap().end()) {
/* Shut down the channel. */
m_channel_impl_map[it->second].ShutdownForce();
/* Remove the channel. */
R_ABORT_UNLESS(m_channel_impl_map.RemoveChannel(channel));
}
R_SUCCEED();
}
Result Mux::ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the connection. */
R_RETURN(m_channel_impl_map[it->second].DoConnectBegin(out_task_id));
}
Result Mux::ConnectEnd(impl::ChannelInternalType channel, u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Get the trigger for the task. */
const auto trigger = m_task_manager.GetTrigger(task_id);
/* Free the task. */
m_task_manager.FreeTask(task_id);
/* Check that we didn't hit a disconnect. */
R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected());
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the disconnection. */
R_RETURN(m_channel_impl_map[it->second].DoConnectEnd());
}
ChannelState Mux::GetChannelState(impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_channel_impl_map.GetChannelImpl(channel).GetChannelState();
}
os::EventType *Mux::GetChannelStateEvent(impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_channel_impl_map.GetChannelImpl(channel).GetChannelStateEvent();
}
Result Mux::FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the connection. */
R_RETURN(m_channel_impl_map[it->second].DoFlush(out_task_id));
}
Result Mux::FlushEnd(u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Get the trigger for the task. */
const auto trigger = m_task_manager.GetTrigger(task_id);
/* Free the task. */
m_task_manager.FreeTask(task_id);
/* Check that we didn't hit a disconnect. */
R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected());
R_SUCCEED();
}
os::EventType *Mux::GetTaskEvent(u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
return m_task_manager.GetTaskEvent(task_id);
}
Result Mux::ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the connection. */
R_RETURN(m_channel_impl_map[it->second].DoReceiveBegin(out_task_id, size));
}
Result Mux::ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Free the task. */
m_task_manager.FreeTask(task_id);
/* If we have data, perform the receive. */
if (dst_size > 0) {
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the receive. */
R_RETURN(m_channel_impl_map[it->second].DoReceiveEnd(out, dst, dst_size));
} else {
*out = 0;
R_SUCCEED();
}
}
Result Mux::SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the connection. */
R_RETURN(m_channel_impl_map[it->second].DoSend(out_task_id, out, src, src_size));
}
Result Mux::SendEnd(u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Get the trigger for the task. */
const auto trigger = m_task_manager.GetTrigger(task_id);
/* Free the task. */
m_task_manager.FreeTask(task_id);
/* Check that we didn't hit a disconnect. */
R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected());
R_SUCCEED();
}
Result Mux::WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size) {
R_RETURN(this->ReceiveBegin(out_task_id, channel, size));
}
Result Mux::WaitReceiveEnd(u32 task_id) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Get the trigger for the task. */
const auto trigger = m_task_manager.GetTrigger(task_id);
/* Free the task. */
m_task_manager.FreeTask(task_id);
/* Check that we didn't hit a disconnect. */
R_UNLESS(trigger != EventTrigger_Disconnect, htclow::ResultInvalidChannelStateDisconnected());
R_SUCCEED();
}
void Mux::SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end());
/* Perform the connection. */
return m_channel_impl_map[it->second].SetConfig(config);
}
void Mux::SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size, size_t max_packet_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end());
/* Set the send buffer. */
m_channel_impl_map[it->second].SetSendBuffer(buf, buf_size, max_packet_size);
}
void Mux::SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size, size_t max_packet_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end());
/* Set the send buffer. */
m_channel_impl_map[it->second].SetSendBufferWithData(buf, buf_size, max_packet_size);
}
void Mux::SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
AMS_ABORT_UNLESS(it != m_channel_impl_map.GetMap().end());
/* Set the send buffer. */
m_channel_impl_map[it->second].SetReceiveBuffer(buf, buf_size);
}
Result Mux::Shutdown(impl::ChannelInternalType channel) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Find the channel. */
auto it = m_channel_impl_map.GetMap().find(channel);
R_UNLESS(it != m_channel_impl_map.GetMap().end(), htclow::ResultChannelNotExist());
/* Perform the shutdown. */
R_RETURN(m_channel_impl_map[it->second].DoShutdown());
}
}

View File

@@ -1,95 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_mux_task_manager.hpp"
#include "htclow_mux_channel_impl_map.hpp"
#include "htclow_mux_global_send_buffer.hpp"
namespace ams::htclow::mux {
enum class MuxState {
Normal,
Sleep,
};
class Mux {
private:
private:
PacketFactory *m_packet_factory;
ctrl::HtcctrlStateMachine *m_state_machine;
TaskManager m_task_manager;
os::Event m_event;
ChannelImplMap m_channel_impl_map;
GlobalSendBuffer m_global_send_buffer;
os::SdkMutex m_mutex;
MuxState m_state;
s16 m_version;
public:
Mux(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm);
void SetVersion(u16 version);
os::EventType *GetSendPacketEvent() { return m_event.GetBase(); }
Result CheckReceivedHeader(const PacketHeader &header) const;
Result ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size);
bool QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size);
void RemovePacket(const PacketHeader &header);
void UpdateChannelState();
void UpdateMuxState();
public:
Result Open(impl::ChannelInternalType channel);
Result Close(impl::ChannelInternalType channel);
Result ConnectBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result ConnectEnd(impl::ChannelInternalType channel, u32 task_id);
ChannelState GetChannelState(impl::ChannelInternalType channel);
os::EventType *GetChannelStateEvent(impl::ChannelInternalType channel);
Result FlushBegin(u32 *out_task_id, impl::ChannelInternalType channel);
Result FlushEnd(u32 task_id);
os::EventType *GetTaskEvent(u32 task_id);
Result ReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result ReceiveEnd(size_t *out, void *dst, size_t dst_size, impl::ChannelInternalType channel, u32 task_id);
Result SendBegin(u32 *out_task_id, size_t *out, const void *src, size_t src_size, impl::ChannelInternalType channel);
Result SendEnd(u32 task_id);
Result WaitReceiveBegin(u32 *out_task_id, impl::ChannelInternalType channel, size_t size);
Result WaitReceiveEnd(u32 task_id);
void SetConfig(impl::ChannelInternalType channel, const ChannelConfig &config);
void SetSendBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size, size_t max_packet_size);
void SetReceiveBuffer(impl::ChannelInternalType channel, void *buf, size_t buf_size);
void SetSendBufferWithData(impl::ChannelInternalType channel, const void *buf, size_t buf_size, size_t max_packet_size);
Result Shutdown(impl::ChannelInternalType channel);
private:
Result CheckChannelExist(impl::ChannelInternalType channel);
Result SendErrorPacket(impl::ChannelInternalType channel);
bool IsSendable(PacketType packet_type) const;
};
}

View File

@@ -1,473 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_channel_impl.hpp"
#include "../ctrl/htclow_ctrl_state_machine.hpp"
#include "../htclow_default_channel_config.hpp"
#include "../htclow_packet_factory.hpp"
namespace ams::htclow::mux {
ChannelImpl::ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev)
: m_channel(channel), m_packet_factory(pf), m_state_machine(sm), m_task_manager(tm), m_event(ev),
m_send_buffer(m_channel, pf), m_receive_buffer(), m_version(ProtocolVersion), m_config(DefaultChannelConfig),
m_offset(0), m_total_send_size(0), m_cur_max_data(0), m_prev_max_data(0), m_share(),
m_state_change_event(os::EventClearMode_ManualClear), m_state(ChannelState_Unconnectable)
{
this->UpdateState();
}
void ChannelImpl::SetVersion(s16 version) {
/* Sanity check the version. */
AMS_ASSERT(version <= ProtocolVersion);
/* Set version. */
m_version = version;
m_send_buffer.SetVersion(version);
}
Result ChannelImpl::CheckState(std::initializer_list<ChannelState> states) const {
/* Determine if we have a matching state. */
bool match = false;
for (const auto &state : states) {
match |= m_state == state;
}
/* If we do, we're good. */
R_SUCCEED_IF(match);
/* Otherwise, return appropriate failure error. */
if (m_state == ChannelState_Disconnected) {
R_THROW(htclow::ResultInvalidChannelStateDisconnected());
} else {
R_THROW(htclow::ResultInvalidChannelState());
}
}
Result ChannelImpl::CheckPacketVersion(s16 version) const {
R_UNLESS(version == m_version, htclow::ResultChannelVersionNotMatched());
R_SUCCEED();
}
Result ChannelImpl::ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size) {
switch (header.packet_type) {
case PacketType_Data:
R_RETURN(this->ProcessReceiveDataPacket(header.version, header.share, header.offset, body, body_size));
case PacketType_MaxData:
R_RETURN(this->ProcessReceiveMaxDataPacket(header.version, header.share));
case PacketType_Error:
R_RETURN(this->ProcessReceiveErrorPacket());
default:
R_THROW(htclow::ResultProtocolError());
}
}
Result ChannelImpl::ProcessReceiveDataPacket(s16 version, u64 share, u32 offset, const void *body, size_t body_size) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connectable, ChannelState_Connected}));
/* Check the packet version. */
R_TRY(this->CheckPacketVersion(version));
/* Check that offset matches. */
R_UNLESS(offset == static_cast<u32>(m_offset), htclow::ResultProtocolError());
/* Check for flow control, if we should. */
if (m_config.flow_control_enabled) {
/* Check that the share increases monotonically. */
if (m_share.has_value()) {
R_UNLESS(m_share.value() <= share, htclow::ResultProtocolError());
}
/* Update our share. */
m_share = share;
/* Signal our event. */
this->SignalSendPacketEvent();
}
/* Update our offset. */
m_offset += body_size;
/* Write the packet body. */
R_ABORT_UNLESS(m_receive_buffer.Write(body, body_size));
/* Notify the data was received. */
m_task_manager->NotifyReceiveData(m_channel, m_receive_buffer.GetDataSize());
R_SUCCEED();
}
Result ChannelImpl::ProcessReceiveMaxDataPacket(s16 version, u64 share) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connectable, ChannelState_Connected}));
/* Check the packet version. */
R_TRY(this->CheckPacketVersion(version));
/* Check for flow control, if we should. */
if (m_config.flow_control_enabled) {
/* Check that the share increases monotonically. */
if (m_share.has_value()) {
R_UNLESS(m_share.value() <= share, htclow::ResultProtocolError());
}
/* Update our share. */
m_share = share;
/* Signal our event. */
this->SignalSendPacketEvent();
}
R_SUCCEED();
}
Result ChannelImpl::ProcessReceiveErrorPacket() {
if (m_state == ChannelState_Connected || m_state == ChannelState_Disconnected) {
this->ShutdownForce();
}
R_SUCCEED();
}
bool ChannelImpl::QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size) {
/* Check our send buffer. */
if (m_send_buffer.QueryNextPacket(header, body, out_body_size, m_cur_max_data, m_total_send_size, m_share.has_value(), m_share.value_or(0))) {
/* Update tracking variables. */
if (header->packet_type == PacketType_Data) {
m_prev_max_data = m_cur_max_data;
}
return true;
} else {
return false;
}
}
void ChannelImpl::RemovePacket(const PacketHeader &header) {
/* Remove the packet. */
m_send_buffer.RemovePacket(header);
/* Check if the send buffer is now empty. */
if (m_send_buffer.Empty()) {
m_task_manager->NotifySendBufferEmpty(m_channel);
}
}
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_Disconnected);
}
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_Disconnected) {
m_task_manager->NotifyDisconnect(m_channel);
}
}
void ChannelImpl::SignalSendPacketEvent() {
if (m_event != nullptr) {
m_event->Signal();
}
}
Result ChannelImpl::DoConnectBegin(u32 *out_task_id) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connectable}));
/* Set ourselves as connecting. */
m_state_machine->SetConnecting(m_channel);
/* Allocate a task. */
u32 task_id{};
R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel));
/* Configure the task. */
m_task_manager->ConfigureConnectTask(task_id);
/* If we're ready, complete the task immediately. */
if (m_state_machine->IsReadied()) {
m_task_manager->CompleteTask(task_id, EventTrigger_ConnectReady);
}
/* Set the output task id. */
*out_task_id = task_id;
R_SUCCEED();
}
Result ChannelImpl::DoConnectEnd() {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connectable}));
/* Perform handshake, if we should. */
if (m_config.handshake_enabled) {
/* Set our current max data. */
m_cur_max_data = m_receive_buffer.GetBufferSize();
/* Make a max data packet. */
auto packet = m_packet_factory->MakeMaxDataPacket(m_channel, m_version, m_cur_max_data);
R_UNLESS(packet, htclow::ResultOutOfMemory());
/* Send the packet. */
m_send_buffer.AddPacket(std::move(packet));
/* Signal that we have an packet to send. */
this->SignalSendPacketEvent();
/* Set our prev max data. */
m_prev_max_data = m_cur_max_data;
} else {
/* Set our share. */
m_share = m_config.initial_counter_max_data;
/* If we're not empty, signal. */
if (!m_send_buffer.Empty()) {
this->SignalSendPacketEvent();
}
}
/* Set our state as connected. */
this->SetState(ChannelState_Connected);
R_SUCCEED();
}
Result ChannelImpl::DoFlush(u32 *out_task_id) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connected}));
/* Allocate a task. */
u32 task_id{};
R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel));
/* Configure the task. */
m_task_manager->ConfigureFlushTask(task_id);
/* If we're already flushed, complete the task immediately. */
if (m_send_buffer.Empty()) {
m_task_manager->CompleteTask(task_id, EventTrigger_SendBufferEmpty);
}
/* Set the output task id. */
*out_task_id = task_id;
R_SUCCEED();
}
Result ChannelImpl::DoReceiveBegin(u32 *out_task_id, size_t size) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connected, ChannelState_Disconnected}));
/* Allocate a task. */
u32 task_id{};
R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel));
/* Configure the task. */
m_task_manager->ConfigureReceiveTask(task_id, size);
/* Check if the task is already complete. */
if (m_receive_buffer.GetDataSize() >= size) {
m_task_manager->CompleteTask(task_id, EventTrigger_ReceiveData);
} else if (m_state == ChannelState_Disconnected) {
m_task_manager->CompleteTask(task_id, EventTrigger_Disconnect);
}
/* Set the output task id. */
*out_task_id = task_id;
R_SUCCEED();
}
Result ChannelImpl::DoReceiveEnd(size_t *out, void *dst, size_t dst_size) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connected, ChannelState_Disconnected}));
/* If we have nowhere to receive, we're done. */
if (dst_size == 0) {
*out = 0;
R_SUCCEED();
}
/* Get the amount of receivable data. */
const size_t receivable = m_receive_buffer.GetDataSize();
const size_t received = std::min(dst_size, receivable);
/* Read the data. */
R_ABORT_UNLESS(m_receive_buffer.Read(dst, received));
/* Handle flow control, if we should. */
if (m_config.flow_control_enabled) {
/* Read our fields. */
const auto prev_max_data = m_prev_max_data;
const auto next_max_data = m_cur_max_data + received;
const auto max_packet_size = m_config.max_packet_size;
const auto offset = m_offset;
/* Update our current max data. */
m_cur_max_data = next_max_data;
/* If we can, send a max data packet. */
if (prev_max_data - offset < max_packet_size + sizeof(PacketHeader)) {
/* Make a max data packet. */
auto packet = m_packet_factory->MakeMaxDataPacket(m_channel, m_version, next_max_data);
R_UNLESS(packet, htclow::ResultOutOfMemory());
/* Send the packet. */
m_send_buffer.AddPacket(std::move(packet));
/* Signal that we have an packet to send. */
this->SignalSendPacketEvent();
/* Set our prev max data. */
m_prev_max_data = m_cur_max_data;
}
}
/* Set the output size. */
*out = received;
R_SUCCEED();
}
Result ChannelImpl::DoSend(u32 *out_task_id, size_t *out, const void *src, size_t src_size) {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connected}));
/* Allocate a task. */
u32 task_id{};
R_TRY(m_task_manager->AllocateTask(std::addressof(task_id), m_channel));
/* Send the data. */
const size_t sent = m_send_buffer.AddData(src, src_size);
/* Add the size to our total. */
m_total_send_size += sent;
/* Signal our event. */
this->SignalSendPacketEvent();
/* Configure the task. */
m_task_manager->ConfigureSendTask(task_id);
/* If we sent all the data, we're done. */
if (sent == src_size) {
m_task_manager->CompleteTask(task_id, EventTrigger_SendComplete);
}
/* Set the output. */
*out_task_id = task_id;
*out = sent;
R_SUCCEED();
}
Result ChannelImpl::DoShutdown() {
/* Check our state. */
R_TRY(this->CheckState({ChannelState_Connected}));
/* Set our state. */
this->SetState(ChannelState_Disconnected);
R_SUCCEED();
}
void ChannelImpl::SetConfig(const ChannelConfig &config) {
/* Check our state. */
R_ABORT_UNLESS(this->CheckState({ChannelState_Unconnectable, ChannelState_Connectable}));
/* Set our config. */
m_config = config;
/* Set flow control for our send buffer. */
m_send_buffer.SetFlowControlEnabled(m_config.flow_control_enabled);
}
void ChannelImpl::SetSendBuffer(void *buf, size_t buf_size, size_t max_packet_size) {
/* Set buffer. */
m_send_buffer.SetBuffer(buf, buf_size);
/* Determine true max packet size. */
if (m_config.flow_control_enabled) {
max_packet_size = std::min(max_packet_size, m_config.max_packet_size);
}
/* Set max packet size. */
m_send_buffer.SetMaxPacketSize(max_packet_size);
}
void ChannelImpl::SetReceiveBuffer(void *buf, size_t buf_size) {
/* Set the buffer. */
m_receive_buffer.Initialize(buf, buf_size);
}
void ChannelImpl::SetSendBufferWithData(const void *buf, size_t buf_size, size_t max_packet_size) {
/* Set buffer. */
m_send_buffer.SetReadOnlyBuffer(buf, buf_size);
/* Determine true max packet size. */
if (m_config.flow_control_enabled) {
max_packet_size = std::min(max_packet_size, m_config.max_packet_size);
}
/* Set max packet size. */
m_send_buffer.SetMaxPacketSize(max_packet_size);
/* Set our total send size. */
m_total_send_size = buf_size;
}
}

View File

@@ -1,102 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_mux_task_manager.hpp"
#include "htclow_mux_send_buffer.hpp"
namespace ams::htclow {
class PacketFactory;
namespace ctrl {
class HtcctrlStateMachine;
}
}
namespace ams::htclow::mux {
class ChannelImpl {
private:
impl::ChannelInternalType m_channel;
PacketFactory *m_packet_factory;
ctrl::HtcctrlStateMachine *m_state_machine;
TaskManager *m_task_manager;
os::Event *m_event;
SendBuffer m_send_buffer;
RingBuffer m_receive_buffer;
s16 m_version;
ChannelConfig m_config;
u64 m_offset;
u64 m_total_send_size;
u64 m_cur_max_data;
u64 m_prev_max_data;
util::optional<u64> m_share;
os::Event m_state_change_event;
ChannelState m_state;
public:
ChannelImpl(impl::ChannelInternalType channel, PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev);
void SetVersion(s16 version);
ChannelState GetChannelState() { return m_state; }
os::EventType *GetChannelStateEvent() { return m_state_change_event.GetBase(); }
Result ProcessReceivePacket(const PacketHeader &header, const void *body, size_t body_size);
bool QuerySendPacket(PacketHeader *header, PacketBody *body, int *out_body_size);
void RemovePacket(const PacketHeader &header);
void ShutdownForce();
void UpdateState();
public:
Result DoConnectBegin(u32 *out_task_id);
Result DoConnectEnd();
Result DoFlush(u32 *out_task_id);
Result DoReceiveBegin(u32 *out_task_id, size_t size);
Result DoReceiveEnd(size_t *out, void *dst, size_t dst_size);
Result DoSend(u32 *out_task_id, size_t *out, const void *src, size_t src_size);
Result DoShutdown();
void SetConfig(const ChannelConfig &config);
void SetSendBuffer(void *buf, size_t buf_size, size_t max_packet_size);
void SetReceiveBuffer(void *buf, size_t buf_size);
void SetSendBufferWithData(const void *buf, size_t buf_size, size_t max_packet_size);
private:
void SetState(ChannelState state);
void SetStateWithoutCheck(ChannelState state);
void SignalSendPacketEvent();
Result CheckState(std::initializer_list<ChannelState> states) const;
Result CheckPacketVersion(s16 version) const;
Result ProcessReceiveDataPacket(s16 version, u64 share, u32 offset, const void *body, size_t body_size);
Result ProcessReceiveMaxDataPacket(s16 version, u64 share);
Result ProcessReceiveErrorPacket();
};
}

View File

@@ -1,94 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_channel_impl_map.hpp"
namespace ams::htclow::mux {
ChannelImplMap::ChannelImplMap(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev)
: m_packet_factory(pf), m_state_machine(sm), m_task_manager(tm), m_event(ev), m_map()
{
/* Initialize the map. */
m_map.Initialize(MaxChannelCount, m_map_buffer, sizeof(m_map_buffer));
/* Set all storages as invalid. */
for (auto i = 0; i < MaxChannelCount; ++i) {
m_storage_valid[i] = false;
}
}
ChannelImpl &ChannelImplMap::GetChannelImpl(int index) {
return GetReference(m_channel_storage[index]);
}
ChannelImpl &ChannelImplMap::GetChannelImpl(impl::ChannelInternalType channel) {
/* Find the channel. */
auto it = m_map.find(channel);
AMS_ASSERT(it != m_map.end());
/* Return the implementation object. */
return this->GetChannelImpl(it->second);
}
Result ChannelImplMap::AddChannel(impl::ChannelInternalType channel) {
/* Find a free storage. */
int idx;
for (idx = 0; idx < MaxChannelCount; ++idx) {
if (!m_storage_valid[idx]) {
break;
}
}
/* Validate that the storage is free. */
R_UNLESS(idx < MaxChannelCount, htclow::ResultOutOfChannel());
/* Create the channel impl. */
util::ConstructAt(m_channel_storage[idx], channel, m_packet_factory, m_state_machine, m_task_manager, m_event);
/* Mark the storage valid. */
m_storage_valid[idx] = true;
/* Insert into our map. */
m_map.insert(std::pair<const impl::ChannelInternalType, int>{channel, idx});
R_SUCCEED();
}
Result ChannelImplMap::RemoveChannel(impl::ChannelInternalType channel) {
/* Find the storage. */
auto it = m_map.find(channel);
AMS_ASSERT(it != m_map.end());
/* Get the channel index. */
const auto index = it->second;
AMS_ASSERT(0 <= index && index < MaxChannelCount);
/* Get the channel impl. */
auto *channel_impl = GetPointer(m_channel_storage[index]);
/* Mark the storage as invalid. */
m_storage_valid[index] = false;
/* Erase the channel from the map. */
m_map.erase(channel);
/* Destroy the channel. */
std::destroy_at(channel_impl);
R_SUCCEED();
}
}

View File

@@ -1,63 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "htclow_mux_channel_impl.hpp"
namespace ams::htclow::mux {
class ChannelImplMap {
NON_COPYABLE(ChannelImplMap);
NON_MOVEABLE(ChannelImplMap);
public:
static constexpr int MaxChannelCount = 64;
using MapType = util::FixedMap<impl::ChannelInternalType, int>;
static constexpr size_t MapRequiredMemorySize = MapType::GetRequiredMemorySize(MaxChannelCount);
private:
PacketFactory *m_packet_factory;
ctrl::HtcctrlStateMachine *m_state_machine;
TaskManager *m_task_manager;
os::Event *m_event;
u8 m_map_buffer[MapRequiredMemorySize];
MapType m_map;
util::TypedStorage<ChannelImpl> m_channel_storage[MaxChannelCount];
bool m_storage_valid[MaxChannelCount];
public:
ChannelImplMap(PacketFactory *pf, ctrl::HtcctrlStateMachine *sm, TaskManager *tm, os::Event *ev);
ChannelImpl &GetChannelImpl(int index);
ChannelImpl &GetChannelImpl(impl::ChannelInternalType channel);
bool Exists(impl::ChannelInternalType channel) const {
return m_map.find(channel) != m_map.end();
}
Result AddChannel(impl::ChannelInternalType channel);
Result RemoveChannel(impl::ChannelInternalType channel);
private:
public:
MapType &GetMap() {
return m_map;
}
ChannelImpl &operator[](int index) {
return this->GetChannelImpl(index);
}
};
}

View File

@@ -1,52 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_global_send_buffer.hpp"
#include "../htclow_packet_factory.hpp"
namespace ams::htclow::mux {
Packet *GlobalSendBuffer::GetNextPacket() {
if (!m_packet_list.empty()) {
return std::addressof(m_packet_list.front());
} else {
return nullptr;
}
}
Result GlobalSendBuffer::AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr) {
/* Global send buffer only supports adding error packets. */
R_UNLESS(ptr->GetHeader()->packet_type == PacketType_Error, htclow::ResultInvalidArgument());
/* Check if we already have an error packet for the channel. */
for (const auto &packet : m_packet_list) {
R_SUCCEED_IF(packet.GetHeader()->channel == ptr->GetHeader()->channel);
}
/* We don't, so push back a new one. */
m_packet_list.push_back(*(ptr.release()));
R_SUCCEED();
}
void GlobalSendBuffer::RemovePacket() {
auto *packet = std::addressof(m_packet_list.front());
m_packet_list.pop_front();
m_packet_factory->Delete(packet);
}
}

View File

@@ -1,43 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "../htclow_packet.hpp"
namespace ams::htclow {
class PacketFactory;
}
namespace ams::htclow::mux {
class GlobalSendBuffer {
private:
using PacketList = util::IntrusiveListBaseTraits<Packet>::ListType;
private:
PacketFactory *m_packet_factory;
PacketList m_packet_list;
public:
GlobalSendBuffer(PacketFactory *pf) : m_packet_factory(pf), m_packet_list() { /* ... */ }
Packet *GetNextPacket();
Result AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr);
void RemovePacket();
};
}

View File

@@ -1,133 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_ring_buffer.hpp"
namespace ams::htclow::mux {
void RingBuffer::Initialize(void *buffer, size_t buffer_size) {
/* Validate pre-conditions. */
AMS_ASSERT(m_buffer == nullptr);
AMS_ASSERT(m_read_only_buffer == nullptr);
/* Set our fields. */
m_buffer = buffer;
m_buffer_size = buffer_size;
m_is_read_only = false;
}
void RingBuffer::InitializeForReadOnly(const void *buffer, size_t buffer_size) {
/* Validate pre-conditions. */
AMS_ASSERT(m_buffer == nullptr);
AMS_ASSERT(m_read_only_buffer == nullptr);
/* Set our fields. */
m_read_only_buffer = const_cast<void *>(buffer);
m_buffer_size = buffer_size;
m_data_size = buffer_size;
m_is_read_only = true;
}
void RingBuffer::Clear() {
m_data_size = 0;
m_offset = 0;
m_can_discard = false;
}
Result RingBuffer::Read(void *dst, size_t size) {
/* Copy the data. */
R_TRY(this->Copy(dst, size));
/* Discard. */
R_TRY(this->Discard(size));
R_SUCCEED();
}
Result RingBuffer::Write(const void *data, size_t size) {
/* Validate pre-conditions. */
AMS_ASSERT(!m_is_read_only);
/* Check that our buffer can hold the data. */
R_UNLESS(m_buffer != nullptr, htclow::ResultChannelBufferOverflow());
R_UNLESS(m_data_size + size <= m_buffer_size, htclow::ResultChannelBufferOverflow());
/* Determine position and copy sizes. */
const size_t pos = (m_data_size + m_offset) % m_buffer_size;
const size_t left = std::min(m_buffer_size - pos, size);
const size_t over = size - left;
/* Copy. */
if (left != 0) {
std::memcpy(static_cast<u8 *>(m_buffer) + pos, data, left);
}
if (over != 0) {
std::memcpy(m_buffer, static_cast<const u8 *>(data) + left, over);
}
/* Update our data size. */
m_data_size += size;
R_SUCCEED();
}
Result RingBuffer::Copy(void *dst, size_t size) {
/* Select buffer to discard from. */
void *buffer = m_is_read_only ? m_read_only_buffer : m_buffer;
R_UNLESS(buffer != nullptr, htclow::ResultChannelBufferHasNotEnoughData());
/* Verify that we have enough data. */
R_UNLESS(m_data_size >= size, htclow::ResultChannelBufferHasNotEnoughData());
/* Determine position and copy sizes. */
const size_t pos = m_offset;
const size_t left = std::min(m_buffer_size - pos, size);
const size_t over = size - left;
/* Copy. */
if (left != 0) {
std::memcpy(dst, static_cast<const u8 *>(buffer) + pos, left);
}
if (over != 0) {
std::memcpy(static_cast<u8 *>(dst) + left, buffer, over);
}
/* Mark that we can discard. */
m_can_discard = true;
R_SUCCEED();
}
Result RingBuffer::Discard(size_t size) {
/* Select buffer to discard from. */
void *buffer = m_is_read_only ? m_read_only_buffer : m_buffer;
R_UNLESS(buffer != nullptr, htclow::ResultChannelBufferHasNotEnoughData());
/* Verify that the data we're discarding has been read. */
R_UNLESS(m_can_discard, htclow::ResultChannelCannotDiscard());
/* Verify that we have enough data. */
R_UNLESS(m_data_size >= size, htclow::ResultChannelBufferHasNotEnoughData());
/* Discard. */
m_offset = (m_offset + size) % m_buffer_size;
m_data_size -= size;
m_can_discard = false;
R_SUCCEED();
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::mux {
class RingBuffer {
private:
void *m_buffer;
void *m_read_only_buffer;
bool m_is_read_only;
size_t m_buffer_size;
size_t m_data_size;
size_t m_offset;
bool m_can_discard;
public:
RingBuffer() : m_buffer(), m_read_only_buffer(), m_is_read_only(true), m_buffer_size(), m_data_size(), m_offset(), m_can_discard(false) { /* ... */ }
void Initialize(void *buffer, size_t buffer_size);
void InitializeForReadOnly(const void *buffer, size_t buffer_size);
void Clear();
size_t GetBufferSize() { return m_buffer_size; }
size_t GetDataSize() { return m_data_size; }
Result Read(void *dst, size_t size);
Result Write(const void *data, size_t size);
Result Copy(void *dst, size_t size);
Result Discard(size_t size);
};
}

View File

@@ -1,182 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_channel_impl.hpp"
#include "../htclow_packet_factory.hpp"
#include "../htclow_default_channel_config.hpp"
namespace ams::htclow::mux {
SendBuffer::SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf)
: m_channel(channel), m_packet_factory(pf), m_ring_buffer(), m_packet_list(),
m_version(ProtocolVersion), m_flow_control_enabled(true), m_max_packet_size(DefaultChannelConfig.max_packet_size)
{
/* ... */
}
SendBuffer::~SendBuffer() {
m_ring_buffer.Clear();
this->Clear();
}
bool SendBuffer::IsPriorPacket(PacketType packet_type) const {
return packet_type == PacketType_MaxData;
}
void SendBuffer::SetVersion(s16 version) {
/* Set version. */
m_version = version;
}
void SendBuffer::SetFlowControlEnabled(bool en) {
/* Set flow control enabled. */
m_flow_control_enabled = en;
}
void SendBuffer::MakeDataPacketHeader(PacketHeader *header, int body_size, s16 version, u64 share, u32 offset) const {
/* Set all packet fields. */
header->signature = HtcGen2Signature;
header->offset = offset;
header->reserved = 0;
header->version = version;
header->body_size = body_size;
header->channel = m_channel;
header->packet_type = PacketType_Data;
header->share = share;
}
void SendBuffer::CopyPacket(PacketHeader *header, PacketBody *body, int *out_body_size, const Packet &packet) {
/* Get the body size. */
const int body_size = packet.GetBodySize();
AMS_ASSERT(0 <= body_size && body_size <= static_cast<int>(sizeof(*body)));
/* Copy the header. */
std::memcpy(header, packet.GetHeader(), sizeof(*header));
/* Copy the body. */
std::memcpy(body, packet.GetBody(), body_size);
/* Set the output body size. */
*out_body_size = body_size;
}
bool SendBuffer::QueryNextPacket(PacketHeader *header, PacketBody *body, int *out_body_size, u64 max_data, u64 total_send_size, bool has_share, u64 share) {
/* Check for a max data packet. */
if (!m_packet_list.empty()) {
this->CopyPacket(header, body, out_body_size, m_packet_list.front());
return true;
}
/* Check that we have data. */
const auto ring_buffer_data_size = m_ring_buffer.GetDataSize();
if (ring_buffer_data_size == 0) {
return false;
}
/* Check that we're valid for flow control. */
if (m_flow_control_enabled && !has_share) {
return false;
}
/* Determine the sendable size. */
const auto offset = total_send_size - ring_buffer_data_size;
const auto sendable_size = m_flow_control_enabled ? std::min<size_t>(share - offset, ring_buffer_data_size) : ring_buffer_data_size;
if (sendable_size == 0) {
return false;
}
/* We're additionally bound by the actual packet size. */
const auto data_size = std::min(sendable_size, m_max_packet_size);
/* Make data packet header. */
this->MakeDataPacketHeader(header, data_size, m_version, max_data, offset);
/* Copy the data. */
R_ABORT_UNLESS(m_ring_buffer.Copy(body, data_size));
/* Set output body size. */
*out_body_size = data_size;
return true;
}
void SendBuffer::AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr) {
/* Get the packet. */
auto *packet = ptr.release();
/* Check the packet type. */
AMS_ABORT_UNLESS(this->IsPriorPacket(packet->GetHeader()->packet_type));
/* Add the packet. */
m_packet_list.push_back(*packet);
}
void SendBuffer::RemovePacket(const PacketHeader &header) {
/* Get the packet type. */
const auto packet_type = header.packet_type;
if (this->IsPriorPacket(packet_type)) {
/* Packet will be using our list. */
auto *packet = std::addressof(m_packet_list.front());
m_packet_list.pop_front();
m_packet_factory->Delete(packet);
} else {
/* Packet managed by ring buffer. */
AMS_ABORT_UNLESS(packet_type == PacketType_Data);
/* Discard the packet's data. */
const Result result = m_ring_buffer.Discard(header.body_size);
if (!htclow::ResultChannelCannotDiscard::Includes(result)) {
R_ABORT_UNLESS(result);
}
}
}
size_t SendBuffer::AddData(const void *data, size_t size) {
/* Determine how much to actually add. */
size = std::min(size, m_ring_buffer.GetBufferSize() - m_ring_buffer.GetDataSize());
/* Write the data. */
R_ABORT_UNLESS(m_ring_buffer.Write(data, size));
/* Return the size we wrote. */
return size;
}
void SendBuffer::SetBuffer(void *buffer, size_t buffer_size) {
m_ring_buffer.Initialize(buffer, buffer_size);
}
void SendBuffer::SetReadOnlyBuffer(const void *buffer, size_t buffer_size) {
m_ring_buffer.InitializeForReadOnly(buffer, buffer_size);
}
void SendBuffer::SetMaxPacketSize(size_t max_packet_size) {
m_max_packet_size = max_packet_size;
}
bool SendBuffer::Empty() {
return m_packet_list.empty() && m_ring_buffer.GetDataSize() == 0;
}
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

@@ -1,69 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "../htclow_packet.hpp"
#include "htclow_mux_ring_buffer.hpp"
namespace ams::htclow {
class PacketFactory;
}
namespace ams::htclow::mux {
class SendBuffer {
private:
using PacketList = util::IntrusiveListBaseTraits<Packet>::ListType;
private:
impl::ChannelInternalType m_channel;
PacketFactory *m_packet_factory;
RingBuffer m_ring_buffer;
PacketList m_packet_list;
s16 m_version;
bool m_flow_control_enabled;
size_t m_max_packet_size;
private:
bool IsPriorPacket(PacketType packet_type) const;
void MakeDataPacketHeader(PacketHeader *header, int body_size, s16 version, u64 share, u32 offset) const;
void CopyPacket(PacketHeader *header, PacketBody *body, int *out_body_size, const Packet &packet);
public:
SendBuffer(impl::ChannelInternalType channel, PacketFactory *pf);
~SendBuffer();
void SetVersion(s16 version);
void SetFlowControlEnabled(bool en);
bool QueryNextPacket(PacketHeader *header, PacketBody *body, int *out_body_size, u64 max_data, u64 total_send_size, bool has_share, u64 share);
void AddPacket(std::unique_ptr<Packet, PacketDeleter> ptr);
void RemovePacket(const PacketHeader &header);
size_t AddData(const void *data, size_t size);
void SetBuffer(void *buffer, size_t buffer_size);
void SetReadOnlyBuffer(const void *buffer, size_t buffer_size);
void SetMaxPacketSize(size_t max_packet_size);
bool Empty();
void Clear();
};
}

View File

@@ -1,165 +0,0 @@
/*
* 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 <stratosphere.hpp>
#include "htclow_mux_task_manager.hpp"
namespace ams::htclow::mux {
os::EventType *TaskManager::GetTaskEvent(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
return std::addressof(m_tasks[task_id].event);
}
EventTrigger TaskManager::GetTrigger(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
return m_tasks[task_id].event_trigger;
}
Result TaskManager::AllocateTask(u32 *out_task_id, impl::ChannelInternalType channel) {
/* Find a free task. */
u32 task_id = 0;
for (task_id = 0; task_id < util::size(m_tasks); ++task_id) {
if (!m_valid[task_id]) {
break;
}
}
/* Verify the task is free. */
R_UNLESS(task_id < util::size(m_tasks), htclow::ResultOutOfTask());
/* Mark the task as allocated. */
m_valid[task_id] = true;
/* Setup the task. */
m_tasks[task_id].channel = channel;
m_tasks[task_id].has_event_trigger = false;
os::InitializeEvent(std::addressof(m_tasks[task_id].event), false, os::EventClearMode_ManualClear);
/* Return the task id. */
*out_task_id = task_id;
R_SUCCEED();
}
void TaskManager::FreeTask(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
/* Invalidate the task. */
if (m_valid[task_id]) {
os::FinalizeEvent(std::addressof(m_tasks[task_id].event));
m_valid[task_id] = false;
}
}
void TaskManager::ConfigureConnectTask(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
/* Set the task type. */
m_tasks[task_id].type = TaskType_Connect;
}
void TaskManager::ConfigureFlushTask(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
/* Set the task type. */
m_tasks[task_id].type = TaskType_Flush;
}
void TaskManager::ConfigureReceiveTask(u32 task_id, size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
/* Set the task type. */
m_tasks[task_id].type = TaskType_Receive;
/* Set the task size. */
m_tasks[task_id].size = size;
}
void TaskManager::ConfigureSendTask(u32 task_id) {
/* Check pre-conditions. */
AMS_ASSERT(task_id < MaxTaskCount);
AMS_ASSERT(m_valid[task_id]);
/* Set the task type. */
m_tasks[task_id].type = TaskType_Send;
}
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::NotifyReceiveData(impl::ChannelInternalType channel, size_t size) {
for (auto i = 0; i < MaxTaskCount; ++i) {
if (m_valid[i] && m_tasks[i].channel == channel && m_tasks[i].type == TaskType_Receive && m_tasks[i].size <= size) {
this->CompleteTask(i, EventTrigger_ReceiveData);
}
}
}
void TaskManager::NotifySendReady() {
for (auto i = 0; i < MaxTaskCount; ++i) {
if (m_valid[i] && m_tasks[i].type == TaskType_Send) {
this->CompleteTask(i, EventTrigger_SendReady);
}
}
}
void TaskManager::NotifySendBufferEmpty(impl::ChannelInternalType channel) {
for (auto i = 0; i < MaxTaskCount; ++i) {
if (m_valid[i] && m_tasks[i].channel == channel && m_tasks[i].type == TaskType_Flush) {
this->CompleteTask(i, EventTrigger_SendBufferEmpty);
}
}
}
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

@@ -1,75 +0,0 @@
/*
* 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/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::htclow::mux {
constexpr inline int MaxTaskCount = 0x80;
enum EventTrigger : u8 {
EventTrigger_Disconnect = 1,
EventTrigger_ReceiveData = 2,
EventTrigger_SendComplete = 4,
EventTrigger_SendReady = 5,
EventTrigger_SendBufferEmpty = 10,
EventTrigger_ConnectReady = 11,
};
class TaskManager {
private:
enum TaskType : u8 {
TaskType_Receive = 0,
TaskType_Send = 1,
TaskType_Flush = 6,
TaskType_Connect = 7,
};
struct Task {
impl::ChannelInternalType channel;
os::EventType event;
bool has_event_trigger;
EventTrigger event_trigger;
TaskType type;
size_t size;
};
private:
bool m_valid[MaxTaskCount];
Task m_tasks[MaxTaskCount];
public:
TaskManager() : m_valid() { /* ... */ }
Result AllocateTask(u32 *out_task_id, impl::ChannelInternalType channel);
void FreeTask(u32 task_id);
os::EventType *GetTaskEvent(u32 task_id);
EventTrigger GetTrigger(u32 task_id);
void ConfigureConnectTask(u32 task_id);
void ConfigureFlushTask(u32 task_id);
void ConfigureReceiveTask(u32 task_id, size_t size);
void ConfigureSendTask(u32 task_id);
void NotifyDisconnect(impl::ChannelInternalType channel);
void NotifyReceiveData(impl::ChannelInternalType channel, size_t size);
void NotifySendReady();
void NotifySendBufferEmpty(impl::ChannelInternalType channel);
void NotifyConnectReady();
void CompleteTask(int index, EventTrigger trigger);
};
}