// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/blob/master/LICENSE.md
#include "service-desktop/ServiceDesktop.hpp"
#include "service-desktop/WorkerDesktop.hpp"
#include "service-desktop/DeviceColour.hpp"
#include <endpoints/EndpointFactory.hpp>
#include <service-desktop/DesktopMessages.hpp>
#include <endpoints/message/Sender.hpp>
#include <MessageHandler.hpp>
#include <bsp/usb/usb.hpp>
#include <log/log.hpp>
#include <crc32.h>
#include <utility>
#include <vector>
#include <system/messages/SentinelRegistrationMessage.hpp>
#include <module-utils/EventStore/EventStore.hpp>
WorkerDesktop::WorkerDesktop(sys::Service *ownerServicePtr,
std::function<void()> messageProcessedCallback,
const sdesktop::USBSecurityModel &securityModel,
const std::string &serialNumber,
const std::string &caseColour,
const std::string &rootPath)
: sys::Worker(ownerServicePtr, sdesktop::workerStackSize), securityModel(securityModel), serialNumber(serialNumber),
caseColour(caseColour), rootPath(rootPath), ownerService(ownerServicePtr), parser(ownerServicePtr),
messageProcessedCallback(std::move(messageProcessedCallback))
{}
bool WorkerDesktop::init(std::list<sys::WorkerQueueInfo> queues)
{
if (initialized) {
return true;
}
Worker::init(queues);
irqQueue = Worker::getQueueHandleByName(sdesktop::irqQueueName);
receiveQueue = Worker::getQueueHandleByName(sdesktop::cdcReceiveQueueName);
sdesktop::endpoints::sender::setSendQueueHandle(Worker::getQueueHandleByName(sdesktop::cdcSendQueueName));
cpuSentinel = std::make_shared<sys::CpuSentinel>(cpuSentinelName, ownerService);
auto sentinelRegistrationMsg = std::make_shared<sys::SentinelRegistrationMessage>(cpuSentinel);
ownerService->bus.sendUnicast(std::move(sentinelRegistrationMsg), service::name::system_manager);
const bsp::usbInitParams initParams = {receiveQueue,
irqQueue,
serialNumber,
device_colour::getColourVersion(caseColour),
rootPath,
securityModel.isSecurityEnabled()};
initialized = bsp::usbInit(initParams) == 0;
cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_4);
return initialized;
}
void WorkerDesktop::closeWorker()
{
if (!initialized) {
return;
}
unsigned int maxcount = 10;
while (parser.getCurrentState() != sdesktop::endpoints::State::NoMsg && --maxcount > 0) {
vTaskDelay(300);
}
initialized = false;
/// additional wait to flush on serial - we should wait for data sent...
vTaskDelay(500);
bsp::usbDeinit();
close();
cpuSentinel->ReleaseMinimumFrequency();
auto sentinelRemoveMsg = std::make_shared<sys::SentinelRemovalMessage>(cpuSentinelName);
auto result = ownerService->bus.sendUnicastSync(std::move(sentinelRemoveMsg), service::name::system_manager, 1000);
if (result.first != sys::ReturnCodes::Success) {
LOG_ERROR("Sentinel %s could not be removed!", cpuSentinel->GetName().c_str());
}
cpuSentinel.reset();
}
void WorkerDesktop::reset()
{
initialized = false;
configured = false;
bsp::usbDeinit();
const bsp::usbInitParams initParams = {receiveQueue,
irqQueue,
serialNumber,
device_colour::getColourVersion(caseColour),
rootPath,
securityModel.isSecurityEnabled()};
initialized = bsp::usbInit(initParams) == 0;
if (initialized) {
ownerService->bus.sendMulticast(std::make_shared<sdesktop::usb::USBConnected>(),
sys::BusChannel::USBNotifications);
}
}
void WorkerDesktop::notify(Signal command)
{
if (auto queue = getQueueByName(sdesktop::signallingQueueName); !queue->Overwrite(&command)) {
LOG_ERROR("Unable to overwrite the command in the commands queue '%s'.", sdesktop::signallingQueueName);
}
}
bool WorkerDesktop::handleMessage(std::uint32_t queueID)
{
bool result = false;
auto &queue = queues[queueID];
const auto &qname = queue->GetQueueName();
if (qname == sdesktop::cdcReceiveQueueName) {
result = handleReceiveQueueMessage(queue);
}
else if (qname == sdesktop::cdcSendQueueName) {
result = handleSendQueueMessage(queue);
}
else if (qname == SERVICE_QUEUE_NAME) {
result = handleServiceQueueMessage(queue);
}
else if (qname == sdesktop::irqQueueName) {
result = handleIrqQueueMessage(queue);
}
else if (qname == sdesktop::signallingQueueName) {
result = handleSignallingQueueMessage(queue);
}
else {
LOG_WARN("Unhandled queue '%s'!", qname.c_str());
}
return result;
}
bool WorkerDesktop::handleReceiveQueueMessage(std::shared_ptr<sys::WorkerQueue> &queue)
{
if (!initialized) {
return false;
}
std::string *receivedMsg = nullptr;
if (!queue->Dequeue(&receivedMsg, 0)) {
LOG_ERROR("Failed dequeue for '%s'.", sdesktop::cdcReceiveQueueName);
return false;
}
using namespace sdesktop::endpoints;
auto factory = EndpointFactory::create(securityModel.getEndpointSecurity().access);
auto handler = std::make_unique<MessageHandler>(ownerService, messageProcessedCallback, std::move(factory));
parser.setMessageHandler(std::move(handler));
parser.processMessage(std::move(*receivedMsg));
delete receivedMsg;
return true;
}
bool WorkerDesktop::handleSendQueueMessage(std::shared_ptr<sys::WorkerQueue> &queue)
{
if (!initialized) {
return false;
}
std::string *sendMsg = nullptr;
if (!queue->Dequeue(&sendMsg, 0)) {
LOG_ERROR("Failed dequeue for '%s'.", sdesktop::cdcSendQueueName);
return false;
}
const std::uint32_t dataSent = bsp::usbCDCSend(sendMsg);
if (dataSent != sendMsg->length()) {
LOG_ERROR("Data not sent! (Data sent: %" PRIu32 "B, msg length: %zuB)", dataSent, sendMsg->length());
}
delete sendMsg;
return true;
}
bool WorkerDesktop::handleServiceQueueMessage(std::shared_ptr<sys::WorkerQueue> &queue)
{
if (!initialized) {
return false;
}
auto &serviceQueue = getServiceQueue();
sys::WorkerCommand cmd;
if (serviceQueue.Dequeue(&cmd, 0)) {
LOG_DEBUG("Received command: %d", static_cast<int>(cmd.command));
#if defined(DEBUG)
assert(false);
#endif
}
else {
LOG_ERROR("Failed dequeue for '%s'.", SERVICE_QUEUE_NAME.c_str());
return false;
}
return true;
}
bool WorkerDesktop::handleIrqQueueMessage(std::shared_ptr<sys::WorkerQueue> &queue)
{
bsp::USBDeviceStatus notification = bsp::USBDeviceStatus::Disconnected;
if (!queue->Dequeue(¬ification, 0)) {
LOG_ERROR("Failed dequeue for %s.", sdesktop::irqQueueName);
return false;
}
switch (notification) {
case bsp::USBDeviceStatus::Connected:
handleUsbConnected();
break;
case bsp::USBDeviceStatus::Configured:
handleUsbConfigured();
break;
case bsp::USBDeviceStatus::Disconnected:
handleUsbDisconnected();
break;
case bsp::USBDeviceStatus::DataReceived:
bsp::usbHandleDataReceived();
break;
case bsp::USBDeviceStatus::Reset:
LOG_DEBUG("USB status: Reset");
if (configured) {
reset();
}
break;
default:
LOG_ERROR("Notification not valid.");
return false;
}
return true;
}
void WorkerDesktop::handleUsbConnected()
{
LOG_DEBUG("USB status: Connected");
ownerService->bus.sendMulticast(std::make_shared<sdesktop::usb::USBConnected>(), sys::BusChannel::USBNotifications);
}
void WorkerDesktop::handleUsbDisconnected()
{
LOG_DEBUG("USB status: Disconnected");
Store::Usb::modify().status = Store::Usb::Status::NotConfigured;
ownerService->bus.sendMulticast(std::make_shared<sdesktop::usb::USBDisconnected>(),
sys::BusChannel::USBNotifications);
configured = false;
}
void WorkerDesktop::handleUsbConfigured()
{
LOG_DEBUG("USB status: Configured");
Store::Usb::modify().status = Store::Usb::Status::Configured;
ownerService->bus.sendMulticast(std::make_shared<sdesktop::usb::USBConfigured>(),
sys::BusChannel::USBNotifications);
configured = true;
}
bool WorkerDesktop::handleSignallingQueueMessage(std::shared_ptr<sys::WorkerQueue> &queue)
{
if (!initialized) {
return false;
}
sys::WorkerCommand cmd;
if (!queue->Dequeue(&cmd, 0)) {
LOG_ERROR("Failed dequeue for '%s'.", sdesktop::signallingQueueName);
return false;
}
switch (static_cast<Signal>(cmd.command)) {
case Signal::unlockMTP:
bsp::usbUnlockMTP();
break;
default:
LOG_ERROR("Command not valid.");
return false;
}
return true;
}