/* * Copyright (c) 2018 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "tma_service_manager.hpp" TmaServiceManager::TmaServiceManager() { /* Set up queues */ for (size_t i = 0; i < TmaServiceManager::PacketQueueDepth; i++) { TmaPacket *packet = nullptr; packet = new TmaPacket(); packet->SetFreeQueue(&this->free_send_packet_queue); this->free_send_packet_queue.Send(reinterpret_cast(packet)); packet = nullptr; packet = new TmaPacket(); packet->SetFreeQueue(&this->free_recv_packet_queue); this->free_recv_packet_queue.Send(reinterpret_cast(packet)); packet = nullptr; } for (size_t i = 0; i < TmaServiceManager::WorkQueueDepth; i++) { this->free_work_queue.Send(reinterpret_cast(new TmaWorkItem())); } } TmaServiceManager::~TmaServiceManager() { /* Destroy queues. */ TmaPacket *packet = nullptr; while (this->free_send_packet_queue.TryReceive(reinterpret_cast(&packet))) { delete packet; packet = nullptr; } while (this->free_recv_packet_queue.TryReceive(reinterpret_cast(&packet))) { delete packet; packet = nullptr; } TmaWorkItem *work_item = nullptr; while (this->free_work_queue.TryReceive(reinterpret_cast(&work_item))) { delete work_item; work_item = nullptr; } while (this->work_queue.TryReceive(reinterpret_cast(&work_item))) { delete work_item; work_item = nullptr; } } void TmaServiceManager::Initialize() { this->initialized = true; this->work_thread.Initialize(TmaServiceManager::WorkThread, this, 0x4000, 0x26); this->work_thread.Start(); } void TmaServiceManager::Finalize() { if (this->initialized) { this->initialized = false; if (this->connection && this->connection->IsConnected()) { this->connection->Disconnect(); } /* Signal to work thread to end. */ this->work_queue.Send(reinterpret_cast(nullptr)); this->work_thread.Join(); /* TODO: N tells services that they have no manager here. Do we want to do that? */ } } void TmaServiceManager::AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet) { if (!this->initialized) { std::abort(); } TmaWorkItem *work_item = nullptr; this->free_work_queue.Receive(reinterpret_cast(&work_item)); work_item->task = task; work_item->packet = packet; work_item->work_type = type; this->work_queue.Send(reinterpret_cast(work_item)); } /* Packet management. */ TmaConnResult TmaServiceManager::SendPacket(TmaPacket *packet) { TmaConnResult res = TmaConnResult::Disconnected; if (this->connection != nullptr) { res = this->connection->SendPacket(packet); } return res; } void TmaServiceManager::OnReceivePacket(TmaPacket *packet) { this->AddWork(TmaWorkType::ReceivePacket, nullptr, packet); } TmaPacket *TmaServiceManager::AllocateSendPacket() { if (!this->initialized) { std::abort(); } TmaPacket *packet = nullptr; this->free_send_packet_queue.Receive(reinterpret_cast(&packet)); packet->ClearOffset(); packet->SetBodyLength(); return packet; } TmaPacket *TmaServiceManager::AllocateRecvPacket() { if (!this->initialized) { std::abort(); } TmaPacket *packet = nullptr; this->free_recv_packet_queue.Receive(reinterpret_cast(&packet)); packet->ClearOffset(); return packet; } void TmaServiceManager::FreePacket(TmaPacket *packet) { if (!this->initialized) { std::abort(); } if (packet != nullptr) { packet->GetFreeQueue()->Send(reinterpret_cast(packet)); } } /* Service/task management. */ TmaService *TmaServiceManager::GetServiceById(TmaServiceId id) { std::scoped_lock lk(this->lock); for (auto srv : this->services) { if (srv->GetServiceId() == id) { return srv; } } return nullptr; } void TmaServiceManager::AddService(TmaService *service) { std::scoped_lock lk(this->lock); this->services.push_back(service); } void TmaServiceManager::AddTask(TmaTask *task, TmaPacket *packet) { this->AddWork(TmaWorkType::NewTask, task, packet); } void TmaServiceManager::FreeTask(TmaTask *task) { this->AddWork(TmaWorkType::FreeTask, task, nullptr); } void TmaServiceManager::CancelTask(u32 task_id) { if (this->initialized) { this->task_list.Cancel(task_id); } } void TmaServiceManager::CancelTasks() { if (this->initialized) { this->task_list.CancelAll(); } } u32 TmaServiceManager::GetNextTaskId() { while (true) { u32 id; { /* N only uses 16 bits for the task id. We'll use 24. */ std::scoped_lock lk(this->lock); id = (this->next_task_id++) & 0xFFFFFF; } if (this->task_list.IsIdFree(id)) { return id; } } } /* Connection management. */ void TmaServiceManager::Tick() { this->AddWork(TmaWorkType::Tick, nullptr, nullptr); } void TmaServiceManager::SetConnection(TmaConnection *conn) { this->connection = conn; } void TmaServiceManager::OnDisconnect() { if (!this->initialized) { std::abort(); } if (!this->GetAsleep()) { this->disconnect_signal.Reset(); this->AddWork(TmaWorkType::Disconnect, nullptr, nullptr); /* TODO: why does N wait with a timeout of zero here? */ this->disconnect_signal.Wait(); } } void TmaServiceManager::Sleep() { if (!this->initialized) { std::abort(); } if (!this->GetAsleep()) { this->wake_signal.Reset(); this->sleep_signal.Reset(); /* Tell the work thread to stall, wait for ACK. */ this->AddWork(TmaWorkType::Sleep, nullptr, nullptr); this->sleep_signal.Wait(); this->SetAsleep(true); } } void TmaServiceManager::Wake(TmaConnection *conn) { if (this->connection != nullptr) { std::abort(); } if (this->GetAsleep()) { this->connection = conn; this->connection->SetWokenUp(true); this->connection->SetServiceManager(this); /* Tell the work thread to resume. */ this->wake_signal.Signal(); } } bool TmaServiceManager::GetConnected() const { return this->connection != nullptr && this->connection->IsConnected(); } /* Work thread. */ void TmaServiceManager::WorkThread(void *_this) { TmaServiceManager *this_ptr = reinterpret_cast(_this); if (!this_ptr->initialized) { std::abort(); } while (true) { /* Receive a work item. */ TmaWorkItem *work_item = nullptr; this_ptr->work_queue.Receive(reinterpret_cast(&work_item)); if (work_item == nullptr) { /* We're done. */ this_ptr->task_list.CancelAll(); break; } switch (work_item->work_type) { case TmaWorkType::Tick: /* HandleTickWork called unconditionally. */ break; case TmaWorkType::NewTask: this_ptr->HandleNewTaskWork(work_item); break; case TmaWorkType::FreeTask: this_ptr->HandleFreeTaskWork(work_item); break; case TmaWorkType::ReceivePacket: this_ptr->HandleReceivePacketWork(work_item); break; case TmaWorkType::Disconnect: this_ptr->HandleDisconnectWork(); break; case TmaWorkType::Sleep: this_ptr->HandleSleepWork(); break; case TmaWorkType::None: default: std::abort(); break; } this_ptr->free_work_queue.Send(reinterpret_cast(work_item)); this_ptr->HandleTickWork(); } } void TmaServiceManager::HandleNewTaskWork(TmaWorkItem *work_item) { this->task_list.Add(work_item->task); if (this->GetConnected()) { if (work_item->packet != nullptr) { this->SendPacket(work_item->packet); } } else { work_item->task->Cancel(); this->FreePacket(work_item->packet); } } void TmaServiceManager::HandleFreeTaskWork(TmaWorkItem *work_item) { delete work_item->task; } void TmaServiceManager::HandleReceivePacketWork(TmaWorkItem *work_item) { /* Handle continuation packets. */ if (work_item->packet->GetContinuation()) { this->task_list.ReceivePacket(work_item->packet); return; } /* Make a new task for the packet. */ TmaService *srv = this->GetServiceById(work_item->packet->GetServiceId()); if (srv != nullptr) { TmaTask *task = srv->NewTask(work_item->packet); if (task != nullptr) { this->task_list.Add(task); } } } void TmaServiceManager::HandleTickWork() { if (this->connection == nullptr) { std::abort(); } /* N does this kind of manual cleanup if send isn't called. */ /* It's pretty gross, but in lieu of a better idea... */ bool needs_manual_cleanup = true; TmaPacket *packet = nullptr; while (this->connection != nullptr && this->free_send_packet_queue.TryReceive(reinterpret_cast(&packet))) { needs_manual_cleanup = false; if (this->task_list.SendPacket(this->GetConnected(), packet)) { if (this->SendPacket(packet) != TmaConnResult::Success) { break; } } else { this->FreePacket(packet); break; } } if (needs_manual_cleanup) { this->task_list.CleanupDoneTasks(); } } void TmaServiceManager::HandleDisconnectWork() { this->task_list.CancelAll(); this->disconnect_signal.Signal(); } void TmaServiceManager::HandleSleepWork() { /* Put the task list to sleep. */ this->task_list.Sleep(); /* Put services to sleep. */ for (auto srv : this->services) { srv->OnSleep(); } /* Signal to main thread that we're sleeping. */ this->sleep_signal.Signal(); /* Wait for us to wake up. */ this->wake_signal.Wait(); /* We're awake now... */ this->SetAsleep(false); /* Wake up services. */ for (auto srv : this->services) { srv->OnWake(); } /* Wake up the task list. */ this->task_list.Wake(); }