ams.tma: restore tma code to debugger_dev branch

This commit is contained in:
Michael Scire
2019-03-05 08:48:21 -08:00
parent a38927ec04
commit 37025258c6
47 changed files with 4511 additions and 3 deletions

View File

@@ -0,0 +1,93 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/* Code taken from Yazen Ghannam <yazen.ghannam@linaro.org>, licensed GPLv2. */
#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
static inline uint16_t __get_unaligned_le16(const uint8_t *p)
{
return p[0] | p[1] << 8;
}
static inline uint32_t __get_unaligned_le32(const uint8_t *p)
{
return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
}
static inline uint64_t __get_unaligned_le64(const uint8_t *p)
{
return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
__get_unaligned_le32(p);
}
static inline uint16_t get_unaligned_le16(const void *p)
{
return __get_unaligned_le16((const uint8_t *)p);
}
static inline uint32_t get_unaligned_le32(const void *p)
{
return __get_unaligned_le32((const uint8_t *)p);
}
static inline uint64_t get_unaligned_le64(const void *p)
{
return __get_unaligned_le64((const uint8_t *)p);
}
static u32 crc32_arm64_le_hw(const u8 *p, unsigned int len) {
u32 crc = 0xFFFFFFFF;
s64 length = len;
while ((length -= sizeof(u64)) >= 0) {
CRC32X(crc, get_unaligned_le64(p));
p += sizeof(u64);
}
/* The following is more efficient than the straight loop */
if (length & sizeof(u32)) {
CRC32W(crc, get_unaligned_le32(p));
p += sizeof(u32);
}
if (length & sizeof(u16)) {
CRC32H(crc, get_unaligned_le16(p));
p += sizeof(u16);
}
if (length & sizeof(u8))
CRC32B(crc, *p);
return crc ^ 0xFFFFFFFF;
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,286 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include "dmnt.h"
static Service g_dmntSrv;
static u64 g_refCnt;
Result dmntInitialize(void) {
atomicIncrement64(&g_refCnt);
if (serviceIsActive(&g_dmntSrv))
return 0;
return smGetService(&g_dmntSrv, "dmnt:-");
}
void dmntExit(void) {
if (atomicDecrement64(&g_refCnt) == 0)
serviceClose(&g_dmntSrv);
}
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
ipcAddRecvBuffer(&c, out, sizeof(*out), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
int flags;
u32 create_option;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 29;
raw->flags = flags;
raw->create_option = create_option;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result dmntTargetIOFileClose(DmntFile *f) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 30;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t *out_read) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
ipcAddRecvBuffer(&c, buf, len, BufferType_Type1);
struct {
u64 magic;
u64 cmd_id;
u64 offset;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 31;
raw->offset = off;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u32 out_read;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && out_read) {
*out_read = resp->out_read;
}
}
return rc;
}
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t *out_written) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, f, sizeof(*f), BufferType_Normal);
ipcAddSendBuffer(&c, buf, len, BufferType_Type1);
struct {
u64 magic;
u64 cmd_id;
u64 offset;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 32;
raw->offset = off;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u32 out_written;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && out_written) {
*out_written = resp->out_written;
}
}
return rc;
}
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, path, FS_MAX_PATH, BufferType_Normal);
ipcAddRecvBuffer(&c, out_info, sizeof(*out_info), BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 34;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
int is_dir;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
*out_is_dir = resp->is_dir != 0;
}
}
return rc;
}
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size) {
DmntFileInformation info;
bool is_dir;
Result rc = dmntTargetIOFileGetInformation(path, &is_dir, &info);
if (R_SUCCEEDED(rc)) {
if (is_dir) {
/* TODO: error code? */
rc = 0x202;
} else {
*out_size = info.size;
}
}
return rc;
}
static Result _dmntTargetIOFileSetSize(const void *arg, size_t arg_size, u64 size) {
IpcCommand c;
ipcInitialize(&c);
ipcAddSendBuffer(&c, arg, arg_size, BufferType_Normal);
struct {
u64 magic;
u64 cmd_id;
u64 size;
} *raw;
raw = serviceIpcPrepareHeader(&g_dmntSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 36;
raw->size = size;
Result rc = serviceIpcDispatch(&g_dmntSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(&g_dmntSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result dmntTargetIOFileSetSize(const char *path, u64 size) {
return _dmntTargetIOFileSetSize(path, FS_MAX_PATH, size);
}
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size) {
/* Atmosphere extension */
return _dmntTargetIOFileSetSize(f, sizeof(*f), size);
}

View File

@@ -0,0 +1,57 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
u64 handle;
} DmntFile;
typedef enum {
DmntTIOCreateOption_CreateNew = 1,
DmntTIOCreateOption_CreateAlways = 2,
DmntTIOCreateOption_OpenExisting = 3,
DmntTIOCreateOption_OpenAlways = 4,
DmntTIOCreateOption_ResetSize = 5,
} DmntTIOCreateOption;
typedef struct {
u64 size;
u64 create_time;
u64 access_time;
u64 modify_time;
} DmntFileInformation;
Result dmntInitialize(void);
void dmntExit(void);
Result dmntTargetIOFileOpen(DmntFile *out, const char *path, int flags, DmntTIOCreateOption create_option);
Result dmntTargetIOFileClose(DmntFile *f);
Result dmntTargetIOFileRead(DmntFile *f, u64 off, void* buf, size_t len, size_t* out_read);
Result dmntTargetIOFileWrite(DmntFile *f, u64 off, const void* buf, size_t len, size_t* out_written);
Result dmntTargetIOFileGetInformation(const char *path, bool *out_is_dir, DmntFileInformation *out_info);
Result dmntTargetIOFileGetSize(const char *path, u64 *out_size);
Result dmntTargetIOFileSetSize(const char *path, u64 size);
Result dmntTargetIOFileSetOpenFileSize(DmntFile *f, u64 size); /* Atmosphere extension */
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,43 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "settings_service.hpp"
#include "settings_task.hpp"
TmaTask *SettingsService::NewTask(TmaPacket *packet) {
TmaTask *new_task = nullptr;
switch (packet->GetCommand()) {
case SettingsServiceCmd_GetSetting:
{
new_task = new GetSettingTask(this->manager);
}
break;
default:
new_task = nullptr;
break;
}
if (new_task != nullptr) {
new_task->SetServiceId(this->GetServiceId());
new_task->SetTaskId(packet->GetTaskId());
new_task->OnStart(packet);
new_task->SetNeedsPackets(true);
}
return new_task;
}

View File

@@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_conn_service_ids.hpp"
#include "../tma_service.hpp"
enum SettingsServiceCmd : u32 {
SettingsServiceCmd_GetSetting = 0,
};
class SettingsService : public TmaService {
public:
SettingsService(TmaServiceManager *m) : TmaService(m, "SettingsService") { }
virtual ~SettingsService() { }
virtual TmaTask *NewTask(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,48 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "settings_task.hpp"
void GetSettingTask::OnStart(TmaPacket *packet) {
size_t length;
packet->ReadString(this->name, sizeof(this->name), &length);
packet->ReadString(this->item_key, sizeof(this->item_key), &length);
if (R_SUCCEEDED(setsysGetSettingsItemValueSize(this->name, this->item_key, &this->value_size))) {
if (this->value_size <= sizeof(this->value)) {
if (R_SUCCEEDED(setsysGetSettingsItemValue(this->name, this->item_key, this->value, this->value_size))) {
this->succeeded = true;
}
}
}
}
void GetSettingTask::OnReceivePacket(TmaPacket *packet) {
std::abort();
}
void GetSettingTask::OnSendPacket(TmaPacket *packet) {
packet->Write<u8>((u8)this->succeeded);
if (this->succeeded) {
packet->Write<u32>((u32)this->value_size);
packet->Write(this->value, this->value_size);
}
this->Complete();
}

View File

@@ -0,0 +1,38 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_task.hpp"
class GetSettingTask : public TmaTask {
private:
char name[0x40] = {0};
char item_key[0x40] = {0};
u8 value[0x40] = {0};
u64 value_size = 0;
bool succeeded = false;
public:
GetSettingTask(TmaServiceManager *m) : TmaTask(m) { }
virtual ~GetSettingTask() { }
virtual void OnStart(TmaPacket *packet) override;
virtual void OnReceivePacket(TmaPacket *packet) override;
virtual void OnSendPacket(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,47 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tio_service.hpp"
#include "tio_task.hpp"
TmaTask *TIOService::NewTask(TmaPacket *packet) {
TmaTask *new_task = nullptr;
switch (packet->GetCommand()) {
case TIOServiceCmd_FileRead:
{
new_task = new TIOFileReadTask(this->manager);
}
break;
case TIOServiceCmd_FileWrite:
{
new_task = new TIOFileWriteTask(this->manager);
}
break;
default:
new_task = nullptr;
break;
}
if (new_task != nullptr) {
new_task->SetServiceId(this->GetServiceId());
new_task->SetTaskId(packet->GetTaskId());
new_task->OnStart(packet);
}
return new_task;
}

View File

@@ -0,0 +1,35 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_conn_service_ids.hpp"
#include "../tma_service.hpp"
enum TIOServiceCmd : u32 {
TIOServiceCmd_FileRead = 2,
TIOServiceCmd_FileWrite = 3,
};
class TIOService : public TmaService {
public:
TIOService(TmaServiceManager *m) : TmaService(m, "TIOService") { }
virtual ~TIOService() { }
virtual TmaTask *NewTask(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,173 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tio_task.hpp"
void TIOFileReadTask::OnStart(TmaPacket *packet) {
char path[FS_MAX_PATH];
packet->ReadString(path, sizeof(path), nullptr);
packet->Read<u64>(this->size_remaining);
packet->Read<u64>(this->cur_offset);
Result rc = 0;
if (strlen(path) == 0) {
rc = 0x202;
}
if (R_SUCCEEDED(rc)) {
u64 file_size;
rc = dmntTargetIOFileGetSize(path, &file_size);
if (R_SUCCEEDED(rc)) {
if (file_size < this->cur_offset + this->size_remaining) {
this->size_remaining = file_size - this->cur_offset;
}
}
}
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
if (R_FAILED(rc)) {
this->SendResult(rc);
return;
} else {
auto packet = this->AllocateSendPacket();
rc = this->ProcessPacket(packet);
if (R_SUCCEEDED(rc)) {
this->manager->SendPacket(packet);
if (this->size_remaining) {
this->SetNeedsPackets(true);
} else {
this->SendResult(rc);
}
} else {
this->manager->FreePacket(packet);
this->SendResult(rc);
}
}
}
void TIOFileReadTask::OnSendPacket(TmaPacket *packet) {
Result rc = this->ProcessPacket(packet);
if (this->size_remaining == 0 || R_FAILED(rc)) {
this->SendResult(rc);
}
}
void TIOFileReadTask::SendResult(Result rc) {
dmntTargetIOFileClose(&this->handle);
this->SetNeedsPackets(false);
auto packet = this->AllocateSendPacket();
packet->Write<Result>(rc);
this->manager->SendPacket(packet);
Complete();
}
Result TIOFileReadTask::ProcessPacket(TmaPacket *packet) {
Result rc = 0x196002;
size_t cur_read = static_cast<u32>((this->size_remaining > MaxDataSize) ? MaxDataSize : this->size_remaining);
u8 *buf = new u8[cur_read];
if (buf != nullptr) {
size_t actual_read = 0;
rc = dmntTargetIOFileRead(&this->handle, this->cur_offset, buf, cur_read, &actual_read);
if (R_SUCCEEDED(rc)) {
packet->Write<Result>(rc);
packet->Write<u32>(actual_read);
packet->Write(buf, actual_read);
this->cur_offset += actual_read;
this->size_remaining -= actual_read;
}
delete buf;
}
return rc;
}
void TIOFileWriteTask::OnStart(TmaPacket *packet) {
char path[FS_MAX_PATH];
packet->ReadString(path, sizeof(path), nullptr);
packet->Read<u64>(this->size_remaining);
packet->Read<u64>(this->cur_offset);
Result rc = 0;
if (strlen(path) == 0) {
rc = 0x202;
}
if (R_SUCCEEDED(rc)) {
u64 file_size;
rc = dmntTargetIOFileGetSize(path, &file_size);
if (R_SUCCEEDED(rc)) {
if (file_size < this->cur_offset + this->size_remaining) {
this->size_remaining = file_size - this->cur_offset;
}
}
}
rc = dmntTargetIOFileOpen(&this->handle, path, FS_OPEN_READ, DmntTIOCreateOption_OpenExisting);
if (R_FAILED(rc)) {
this->SendResult(rc);
}
}
void TIOFileWriteTask::OnReceivePacket(TmaPacket *packet) {
Result rc = this->ProcessPacket(packet);
if (this->size_remaining == 0 || R_FAILED(rc)) {
this->SendResult(rc);
}
}
void TIOFileWriteTask::SendResult(Result rc) {
dmntTargetIOFileClose(&this->handle);
auto packet = this->AllocateSendPacket();
packet->Write<Result>(rc);
this->manager->SendPacket(packet);
Complete();
}
Result TIOFileWriteTask::ProcessPacket(TmaPacket *packet) {
Result rc = 0x196002;
/* Note: N does not bounds check this. We do. */
u32 cur_write = 0;
packet->Read<u32>(cur_write);
size_t actual_written = 0;
if (cur_write < MaxDataSize) {
if (cur_write > this->size_remaining) {
cur_write = this->size_remaining;
}
rc = dmntTargetIOFileWrite(&this->handle, this->cur_offset, packet->GetCurrentBodyPtr(), cur_write, &actual_written);
if (R_SUCCEEDED(rc)) {
this->size_remaining -= actual_written;
this->cur_offset += actual_written;
}
}
return rc;
}

View File

@@ -0,0 +1,85 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_task.hpp"
#include "../tma_service_manager.hpp"
#include "../dmnt.h"
class TIOTask : public TmaTask {
public:
TIOTask(TmaServiceManager *m) : TmaTask(m) { }
virtual ~TIOTask() { }
virtual void SendResult(Result rc) {
TmaPacket *packet = this->AllocateSendPacket();
packet->Write<Result>(rc);
this->manager->SendPacket(packet);
this->Complete();
}
virtual void OnStart(TmaPacket *packet) = 0;
virtual void OnReceivePacket(TmaPacket *packet) override {
this->Complete();
}
virtual void OnSendPacket(TmaPacket *packet) override {
this->Complete();
}
};
class TIOFileReadTask : public TIOTask {
private:
static constexpr size_t HeaderSize = sizeof(Result) + sizeof(u32);
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
private:
DmntFile handle = {0};
u64 size_remaining = 0;
u64 cur_offset = 0;
public:
TIOFileReadTask(TmaServiceManager *m) : TIOTask(m) { }
virtual ~TIOFileReadTask() { }
virtual void OnStart(TmaPacket *packet) override;
virtual void OnSendPacket(TmaPacket *packet) override;
virtual void SendResult(Result rc) override;
Result ProcessPacket(TmaPacket *packet);
};
class TIOFileWriteTask : public TIOTask {
private:
static constexpr size_t HeaderSize = sizeof(u32);
static constexpr size_t MaxDataSize = TmaPacket::MaxBodySize - HeaderSize;
private:
DmntFile handle = {0};
u64 size_remaining = 0;
u64 cur_offset = 0;
public:
TIOFileWriteTask(TmaServiceManager *m) : TIOTask(m) { }
virtual ~TIOFileWriteTask() { }
virtual void OnStart(TmaPacket *packet) override;
virtual void OnReceivePacket(TmaPacket *packet) override;
virtual void SendResult(Result rc) override;
Result ProcessPacket(TmaPacket *packet);
};

View File

@@ -0,0 +1,31 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "atmosphere_test_service.hpp"
#include "atmosphere_test_task.hpp"
TmaTask *AtmosphereTestService::NewTask(TmaPacket *packet) {
auto new_task = new AtmosphereTestTask(this->manager);
new_task->SetServiceId(this->GetServiceId());
new_task->SetTaskId(packet->GetTaskId());
new_task->OnStart(packet);
new_task->SetNeedsPackets(true);
return new_task;
}

View File

@@ -0,0 +1,30 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_conn_service_ids.hpp"
#include "../tma_service.hpp"
class AtmosphereTestService : public TmaService {
public:
AtmosphereTestService(TmaServiceManager *m) : TmaService(m, "AtmosphereTestService") { }
virtual ~AtmosphereTestService() { }
virtual TmaTask *NewTask(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,36 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "atmosphere_test_task.hpp"
void AtmosphereTestTask::OnStart(TmaPacket *packet) {
packet->Read<u32>(this->arg);
}
void AtmosphereTestTask::OnReceivePacket(TmaPacket *packet) {
std::abort();
}
void AtmosphereTestTask::OnSendPacket(TmaPacket *packet) {
for (size_t i = 0; i < this->arg && i < 0x100; i++) {
packet->Write<u8>('A');
}
this->Complete();
}

View File

@@ -0,0 +1,33 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../tma_task.hpp"
class AtmosphereTestTask : public TmaTask {
private:
u32 arg;
public:
AtmosphereTestTask(TmaServiceManager *m) : TmaTask(m) { }
virtual ~AtmosphereTestTask() { }
virtual void OnStart(TmaPacket *packet) override;
virtual void OnReceivePacket(TmaPacket *packet) override;
virtual void OnSendPacket(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,64 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_connection.hpp"
#include "tma_service_manager.hpp"
/* Packet management. */
TmaPacket *TmaConnection::AllocateSendPacket() {
return this->service_manager->AllocateSendPacket();
}
TmaPacket *TmaConnection::AllocateRecvPacket() {
return this->service_manager->AllocateRecvPacket();
}
void TmaConnection::FreePacket(TmaPacket *packet) {
this->service_manager->FreePacket(packet);
}
void TmaConnection::OnReceivePacket(TmaPacket *packet) {
this->service_manager->OnReceivePacket(packet);
}
void TmaConnection::OnDisconnected() {
if (!this->is_initialized) {
std::abort();
}
if (this->service_manager != nullptr) {
this->service_manager->OnDisconnect();
}
this->has_woken_up = false;
this->OnConnectionEvent(ConnectionEvent::Disconnected);
}
void TmaConnection::OnConnectionEvent(ConnectionEvent event) {
if (this->connection_event_callback != nullptr) {
this->connection_event_callback(this->connection_event_arg, event);
}
}
void TmaConnection::CancelTasks() {
this->service_manager->CancelTasks();
}
void TmaConnection::Tick() {
this->service_manager->Tick();
}

View File

@@ -0,0 +1,90 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_result.hpp"
#include "tma_conn_packet.hpp"
enum class ConnectionEvent : u32 {
Connected,
Disconnected
};
class TmaServiceManager;
class TmaConnection {
protected:
HosMutex lock;
void (*connection_event_callback)(void *, ConnectionEvent) = nullptr;
void *connection_event_arg = nullptr;
bool has_woken_up = false;
bool is_initialized = false;
TmaServiceManager *service_manager = nullptr;
protected:
void OnReceivePacket(TmaPacket *packet);
void OnDisconnected();
void OnConnectionEvent(ConnectionEvent event);
void CancelTasks();
void Tick();
public:
/* Setup */
TmaConnection() { }
virtual ~TmaConnection() { }
void Initialize() {
if (this->is_initialized) {
std::abort();
}
this->is_initialized = true;
}
void SetConnectionEventCallback(void (*callback)(void *, ConnectionEvent), void *arg) {
this->connection_event_callback = callback;
this->connection_event_arg = arg;
}
void Finalize() {
if (this->is_initialized) {
this->StopListening();
if (this->IsConnected()) {
this->Disconnect();
}
this->is_initialized = false;
}
}
void SetServiceManager(TmaServiceManager *manager) { this->service_manager = manager; }
/* Packet management. */
TmaPacket *AllocateSendPacket();
TmaPacket *AllocateRecvPacket();
void FreePacket(TmaPacket *packet);
/* Sleep management. */
bool HasWokenUp() const { return this->has_woken_up; }
void SetWokenUp(bool woke) { this->has_woken_up = woke; }
/* For sub-interfaces to implement, connection management. */
virtual void StartListening() { }
virtual void StopListening() { }
virtual bool IsConnected() = 0;
virtual TmaConnResult Disconnect() = 0;
virtual TmaConnResult SendPacket(TmaPacket *packet) = 0;
};

View File

@@ -0,0 +1,279 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdarg>
#include "tma_conn_result.hpp"
#include "tma_conn_service_ids.hpp"
#include "crc.h"
class TmaPacket {
public:
struct Header {
u32 service_id;
u32 task_id;
u16 command;
u8 is_continuation;
u8 version;
u32 body_len;
u32 reserved[4]; /* This is where N's header ends. */
u32 body_checksum;
u32 header_checksum;
};
static_assert(sizeof(Header) == 0x28, "Packet::Header definition!");
static constexpr u32 MaxBodySize = 0xE000;
static constexpr u32 MaxPacketSize = MaxBodySize + sizeof(Header);
private:
std::unique_ptr<u8[]> buffer = std::make_unique<u8[]>(MaxPacketSize);
u32 offset = 0;
HosMessageQueue *free_queue = nullptr;
Header *GetHeader() const {
return reinterpret_cast<Header *>(buffer.get());
}
u8 *GetBody(u32 ofs) const {
return reinterpret_cast<u8 *>(buffer.get() + sizeof(Header) + ofs);
}
public:
TmaPacket() {
memset(buffer.get(), 0, MaxPacketSize);
}
/* Implicit ~TmaPacket() */
/* These allow reading a packet in. */
void CopyHeaderFrom(Header *hdr) {
*GetHeader() = *hdr;
}
TmaConnResult CopyBodyFrom(void *body, size_t size) {
if (size >= MaxBodySize) {
return TmaConnResult::PacketOverflow;
}
memcpy(GetBody(0), body, size);
return TmaConnResult::Success;
}
void CopyHeaderTo(void *out) {
memcpy(out, buffer.get(), sizeof(Header));
}
void CopyBodyTo(void *out) const {
memcpy(out, buffer.get() + sizeof(Header), GetBodyLength());
}
bool IsHeaderValid() {
Header *hdr = GetHeader();
return crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum)) == hdr->header_checksum;
}
bool IsBodyValid() const {
const u32 body_len = GetHeader()->body_len;
if (body_len == 0) {
return GetHeader()->body_checksum == 0;
} else {
return crc32_arm64_le_hw(GetBody(0), body_len) == GetHeader()->body_checksum;
}
}
HosMessageQueue *GetFreeQueue() const {
return this->free_queue;
}
void SetFreeQueue(HosMessageQueue *queue) {
this->free_queue = queue;
}
void SetChecksums() {
Header *hdr = GetHeader();
if (hdr->body_len) {
hdr->body_checksum = crc32_arm64_le_hw(GetBody(0), hdr->body_len);
} else {
hdr->body_checksum = 0;
}
hdr->header_checksum = crc32_arm64_le_hw(reinterpret_cast<const u8 *>(hdr), sizeof(*hdr) - sizeof(hdr->header_checksum));
}
u32 GetBodyLength() const {
return GetHeader()->body_len;
}
u32 GetLength() const {
return GetBodyLength() + sizeof(Header);
}
u32 GetBodyAvailableLength() const {
return MaxPacketSize - this->offset;
}
void SetServiceId(TmaServiceId srv) {
GetHeader()->service_id = static_cast<u32>(srv);
}
TmaServiceId GetServiceId() const {
return static_cast<TmaServiceId>(GetHeader()->service_id);
}
void SetTaskId(u32 id) {
GetHeader()->task_id = id;
}
u32 GetTaskId() const {
return GetHeader()->task_id;
}
void SetCommand(u16 cmd) {
GetHeader()->command = cmd;
}
u16 GetCommand() const {
return GetHeader()->command;
}
void SetContinuation(bool c) {
GetHeader()->is_continuation = c ? 1 : 0;
}
bool GetContinuation() const {
return GetHeader()->is_continuation == 1;
}
void SetVersion(u8 v) {
GetHeader()->version = v;
}
u8 GetVersion() const {
return GetHeader()->version;
}
u8 *GetCurrentBodyPtr() {
return GetBody(this->offset);
}
void ClearOffset() {
this->offset = 0;
}
void SetBodyLength() {
GetHeader()->body_len = this->offset;
}
TmaConnResult Write(const void *data, size_t size) {
if (size > GetBodyAvailableLength()) {
return TmaConnResult::PacketOverflow;
}
memcpy(GetBody(this->offset), data, size);
this->offset += size;
GetHeader()->body_len = this->offset;
return TmaConnResult::Success;
}
TmaConnResult Read(void *data, size_t size) {
if (size > GetBodyAvailableLength()) {
return TmaConnResult::PacketOverflow;
}
memcpy(data, GetBody(this->offset), size);
this->offset += size;
return TmaConnResult::Success;
}
template<typename T>
TmaConnResult Write(const T &t) {
return Write(&t, sizeof(T));
}
template<typename T>
TmaConnResult Read(T &t) {
return Read(&t, sizeof(T));
}
TmaConnResult WriteString(const char *s) {
return Write(s, strlen(s) + 1);
}
size_t WriteFormat(const char *format, ...) {
va_list va_arg;
va_start(va_arg, format);
const size_t available = GetBodyAvailableLength();
const int written = vsnprintf(reinterpret_cast<char *>(GetBody(this->offset)), available, format, va_arg);
size_t total_written;
if (static_cast<size_t>(written) < available) {
this->offset += written;
*GetBody(this->offset++) = 0;
total_written = written + 1;
} else {
this->offset += available;
total_written = available;
}
GetHeader()->body_len = this->offset;
return total_written;
}
TmaConnResult ReadString(char *buf, size_t buf_size, size_t *out_size) {
TmaConnResult res = TmaConnResult::Success;
size_t available = GetBodyAvailableLength();
size_t ofs = 0;
while (ofs < buf_size) {
if (ofs >= available) {
res = TmaConnResult::PacketOverflow;
break;
}
if (ofs == buf_size) {
res = TmaConnResult::BufferOverflow;
break;
}
buf[ofs] = static_cast<char>(*GetBody(this->offset++));
if (buf[ofs++] == '\x00') {
break;
}
}
/* Finish reading the string if the user buffer is too small. */
if (res == TmaConnResult::BufferOverflow) {
u8 cur = *GetBody(this->offset);
while (cur != 0) {
if (ofs >= available) {
res = TmaConnResult::PacketOverflow;
break;
}
cur = *GetBody(this->offset++);
ofs++;
}
}
if (out_size != nullptr) {
*out_size = ofs;
}
return res;
}
};

View File

@@ -0,0 +1,40 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <stratosphere.hpp>
enum class TmaConnResult : u32 {
Success = 0,
NotImplemented,
GeneralFailure,
ConnectionFailure,
AlreadyConnected,
WrongConnectionVersion,
PacketOverflow,
BufferOverflow,
Disconnected,
ServiceAlreadyRegistered,
ServiceUnknown,
Timeout,
NotInitialized,
};

View File

@@ -0,0 +1,53 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "tma_conn_result.hpp"
/* This is just python's hash function, but official TMA code uses it. */
static constexpr u32 HashServiceName(const char *name) {
u32 h = *name;
u32 len = 0;
while (*name) {
h = (1000003 * h) ^ *name;
name++;
len++;
}
return h ^ len;
}
enum class TmaServiceId : u32 {
Invalid = 0,
/* Special nodes, for facilitating connection over USB. */
UsbQueryTarget = HashServiceName("USBQueryTarget"),
UsbSendHostInfo = HashServiceName("USBSendHostInfo"),
UsbConnect = HashServiceName("USBConnect"),
UsbDisconnect = HashServiceName("USBDisconnect"),
TestService = HashServiceName("AtmosphereTestService"), /* Temporary service, will be used to debug communications. */
};
static constexpr bool IsMetaService(TmaServiceId id) {
return id == TmaServiceId::UsbQueryTarget ||
id == TmaServiceId::UsbSendHostInfo ||
id == TmaServiceId::UsbConnect ||
id == TmaServiceId::UsbDisconnect;
}

View File

@@ -0,0 +1,204 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_usb_connection.hpp"
#include "tma_usb_comms.hpp"
static HosThread g_SendThread, g_RecvThread;
TmaConnResult TmaUsbConnection::InitializeComms() {
return TmaUsbComms::Initialize();
}
TmaConnResult TmaUsbConnection::FinalizeComms() {
return TmaUsbComms::Finalize();
}
void TmaUsbConnection::ClearSendQueue() {
uintptr_t _packet;
while (this->send_queue.TryReceive(&_packet)) {
TmaPacket *packet = reinterpret_cast<TmaPacket *>(_packet);
if (packet != nullptr) {
this->FreePacket(packet);
}
}
}
void TmaUsbConnection::SendThreadFunc(void *arg) {
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
TmaConnResult res = TmaConnResult::Success;
TmaPacket *packet = nullptr;
while (res == TmaConnResult::Success) {
/* Receive a packet from the send queue. */
{
uintptr_t _packet;
this_ptr->send_queue.Receive(&_packet);
packet = reinterpret_cast<TmaPacket *>(_packet);
}
if (packet != nullptr) {
/* Send the packet if we're connected. */
if (this_ptr->IsConnected()) {
res = TmaUsbComms::SendPacket(packet);
}
this_ptr->FreePacket(packet);
this_ptr->Tick();
} else {
res = TmaConnResult::Disconnected;
}
}
this_ptr->SetConnected(false);
this_ptr->OnDisconnected();
}
void TmaUsbConnection::RecvThreadFunc(void *arg) {
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
TmaConnResult res = TmaConnResult::Success;
this_ptr->SetConnected(true);
while (res == TmaConnResult::Success) {
TmaPacket *packet = this_ptr->AllocateRecvPacket();
if (packet == nullptr) { std::abort(); }
res = TmaUsbComms::ReceivePacket(packet);
if (res == TmaConnResult::Success) {
if (!IsMetaService(packet->GetServiceId())) {
this_ptr->OnReceivePacket(packet);
} else {
switch (packet->GetServiceId()) {
case TmaServiceId::UsbQueryTarget: {
this_ptr->SetConnected(false);
res = this_ptr->SendQueryReply(packet);
if (!this_ptr->has_woken_up) {
this_ptr->CancelTasks();
}
}
break;
case TmaServiceId::UsbSendHostInfo: {
struct {
u32 version;
u32 sleeping;
} host_info;
packet->Read<decltype(host_info)>(host_info);
if (!this_ptr->has_woken_up || !host_info.sleeping) {
this_ptr->CancelTasks();
}
}
break;
case TmaServiceId::UsbConnect: {
res = this_ptr->SendQueryReply(packet);
if (res == TmaConnResult::Success) {
this_ptr->SetConnected(true);
this_ptr->OnConnectionEvent(ConnectionEvent::Connected);
}
}
break;
case TmaServiceId::UsbDisconnect: {
this_ptr->SetConnected(false);
this_ptr->OnDisconnected();
this_ptr->CancelTasks();
}
break;
default:
break;
}
this_ptr->FreePacket(packet);
}
} else {
this_ptr->FreePacket(packet);
}
}
this_ptr->SetConnected(false);
this_ptr->send_queue.Send(reinterpret_cast<uintptr_t>(nullptr));
}
void TmaUsbConnection::OnUsbStateChange(void *arg, u32 state) {
TmaUsbConnection *this_ptr = reinterpret_cast<TmaUsbConnection *>(arg);
switch (state) {
case 0:
case 6:
this_ptr->StopThreads();
break;
case 5:
this_ptr->StartThreads();
break;
}
}
void TmaUsbConnection::StartThreads() {
g_SendThread.Join();
g_RecvThread.Join();
g_SendThread.Initialize(&TmaUsbConnection::SendThreadFunc, this, 0x4000, 38);
g_RecvThread.Initialize(&TmaUsbConnection::RecvThreadFunc, this, 0x4000, 38);
this->ClearSendQueue();
g_SendThread.Start();
g_RecvThread.Start();
}
void TmaUsbConnection::StopThreads() {
TmaUsbComms::CancelComms();
g_SendThread.Join();
g_RecvThread.Join();
}
bool TmaUsbConnection::IsConnected() {
return this->is_connected;
}
TmaConnResult TmaUsbConnection::Disconnect() {
TmaUsbComms::SetStateChangeCallback(nullptr, nullptr);
this->StopThreads();
return TmaConnResult::Success;
}
TmaConnResult TmaUsbConnection::SendPacket(TmaPacket *packet) {
std::scoped_lock<HosMutex> lk(this->lock);
if (this->IsConnected()) {
this->send_queue.Send(reinterpret_cast<uintptr_t>(packet));
return TmaConnResult::Success;
} else {
this->FreePacket(packet);
this->Tick();
return TmaConnResult::Disconnected;
}
}
TmaConnResult TmaUsbConnection::SendQueryReply(TmaPacket *packet) {
packet->ClearOffset();
struct {
u32 version;
} target_info;
target_info.version = 0;
packet->Write<decltype(target_info)>(target_info);
return TmaUsbComms::SendPacket(packet);
}

View File

@@ -0,0 +1,52 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_connection.hpp"
#include "tma_usb_comms.hpp"
class TmaUsbConnection : public TmaConnection {
private:
HosMessageQueue send_queue = HosMessageQueue(64);
std::atomic<bool> is_connected = false;
private:
static void SendThreadFunc(void *arg);
static void RecvThreadFunc(void *arg);
static void OnUsbStateChange(void *this_ptr, u32 state);
TmaConnResult SendQueryReply(TmaPacket *packet);
void ClearSendQueue();
void StartThreads();
void StopThreads();
void SetConnected(bool c) { this->is_connected = c; }
public:
static TmaConnResult InitializeComms();
static TmaConnResult FinalizeComms();
TmaUsbConnection() {
TmaUsbComms::SetStateChangeCallback(&TmaUsbConnection::OnUsbStateChange, this);
}
virtual ~TmaUsbConnection() {
this->Disconnect();
}
virtual bool IsConnected() override;
virtual TmaConnResult Disconnect() override;
virtual TmaConnResult SendPacket(TmaPacket *packet) override;
};

View File

@@ -0,0 +1,106 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
#include "tma_target.hpp"
#include "dmnt.h"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
#define INNER_HEAP_SIZE 0x400000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
Result rc;
rc = smInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
}
rc = pscInitialize();
if (R_FAILED(rc)) {
fatalSimple(rc);
}
rc = setsysInitialize();
if (R_FAILED(rc)) {
fatalSimple(rc);
}
rc = dmntInitialize();
if (R_FAILED(rc)) {
fatalSimple(rc);
}
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
}
void __appExit(void) {
/* Cleanup services. */
dmntExit();
setsysExit();
pscExit();
smExit();
}
int main(int argc, char **argv)
{
consoleDebugInit(debugDevice_SVC);
/* This will initialize the target. */
TmaTarget::Initialize();
while (true) {
svcSleepThread(10000000UL);
}
TmaTarget::Finalize();
return 0;
}

View File

@@ -0,0 +1,68 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_power_manager.hpp"
static constexpr u16 PscPmModuleId_Usb = 0x04;
static constexpr u16 PscPmModuleId_Pcie = 0x13;
static constexpr u16 PscPmModuleId_Tma = 0x1E;
static const u16 g_tma_pm_dependencies[] = {
PscPmModuleId_Usb,
};
static void (*g_pm_callback)(PscPmState, u32) = nullptr;
static HosThread g_pm_thread;
static void PowerManagerThread(void *arg) {
/* Setup psc module. */
Result rc;
PscPmModule tma_module = {0};
if (R_FAILED((rc = pscGetPmModule(&tma_module, PscPmModuleId_Tma, g_tma_pm_dependencies, sizeof(g_tma_pm_dependencies)/sizeof(u16), true)))) {
fatalSimple(rc);
}
/* For now, just do what dummy tma does -- loop forever, acknowledging everything. */
while (true) {
if (R_FAILED((rc = eventWait(&tma_module.event, U64_MAX)))) {
fatalSimple(rc);
}
PscPmState state;
u32 flags;
if (R_FAILED((rc = pscPmModuleGetRequest(&tma_module, &state, &flags)))) {
fatalSimple(rc);
}
g_pm_callback(state, flags);
if (R_FAILED((rc = pscPmModuleAcknowledge(&tma_module, state)))) {
fatalSimple(rc);
}
}
}
void TmaPowerManager::Initialize(void (*callback)(PscPmState, u32)) {
g_pm_callback = callback;
g_pm_thread.Initialize(PowerManagerThread, nullptr, 0x4000, 0x26);
g_pm_thread.Start();
}
void TmaPowerManager::Finalize() {
/* TODO */
}

View File

@@ -0,0 +1,25 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
class TmaPowerManager {
public:
static void Initialize(void (*callback)(PscPmState, u32));
static void Finalize();
};

View File

@@ -0,0 +1,32 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_service.hpp"
#include "tma_service_manager.hpp"
u32 TmaService::GetNextTaskId() {
return this->manager->GetNextTaskId();
}
void TmaService::OnSleep() {
/* Default service does nothing here. */
}
void TmaService::OnWake() {
/* Default service does nothing here. */
}

View File

@@ -0,0 +1,43 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_service_ids.hpp"
#include "tma_conn_packet.hpp"
#include "tma_task.hpp"
class TmaServiceManager;
class TmaService {
protected:
TmaServiceManager *manager;
const char *service_name;
const TmaServiceId id;
protected:
u32 GetNextTaskId();
public:
TmaService(TmaServiceManager *m, const char *n) : manager(m), service_name(n), id(static_cast<TmaServiceId>(HashServiceName(this->service_name))) { }
virtual ~TmaService() { }
TmaServiceId GetServiceId() const { return this->id; }
virtual TmaTask *NewTask(TmaPacket *packet) = 0;
virtual void OnSleep();
virtual void OnWake();
};

View File

@@ -0,0 +1,406 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#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<uintptr_t>(packet));
packet = nullptr;
packet = new TmaPacket();
packet->SetFreeQueue(&this->free_recv_packet_queue);
this->free_recv_packet_queue.Send(reinterpret_cast<uintptr_t>(packet));
packet = nullptr;
}
for (size_t i = 0; i < TmaServiceManager::WorkQueueDepth; i++) {
this->free_work_queue.Send(reinterpret_cast<uintptr_t>(new TmaWorkItem()));
}
}
TmaServiceManager::~TmaServiceManager() {
/* Destroy queues. */
TmaPacket *packet = nullptr;
while (this->free_send_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
delete packet;
packet = nullptr;
}
while (this->free_recv_packet_queue.TryReceive(reinterpret_cast<uintptr_t *>(&packet))) {
delete packet;
packet = nullptr;
}
TmaWorkItem *work_item = nullptr;
while (this->free_work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&work_item))) {
delete work_item;
work_item = nullptr;
}
while (this->work_queue.TryReceive(reinterpret_cast<uintptr_t *>(&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<uintptr_t>(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<uintptr_t *>(&work_item));
work_item->task = task;
work_item->packet = packet;
work_item->work_type = type;
this->work_queue.Send(reinterpret_cast<uintptr_t>(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<uintptr_t *>(&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<uintptr_t *>(&packet));
packet->ClearOffset();
return packet;
}
void TmaServiceManager::FreePacket(TmaPacket *packet) {
if (!this->initialized) {
std::abort();
}
if (packet != nullptr) {
packet->GetFreeQueue()->Send(reinterpret_cast<uintptr_t>(packet));
}
}
/* Service/task management. */
TmaService *TmaServiceManager::GetServiceById(TmaServiceId id) {
std::scoped_lock<HosMutex> lk(this->lock);
for (auto srv : this->services) {
if (srv->GetServiceId() == id) {
return srv;
}
}
return nullptr;
}
void TmaServiceManager::AddService(TmaService *service) {
std::scoped_lock<HosMutex> 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<HosMutex> 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<TmaServiceManager *>(_this);
if (!this_ptr->initialized) {
std::abort();
}
while (true) {
/* Receive a work item. */
TmaWorkItem *work_item = nullptr;
this_ptr->work_queue.Receive(reinterpret_cast<uintptr_t *>(&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<uintptr_t>(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) {
ON_SCOPE_EXIT { this->FreePacket(work_item->packet); };
/* 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<uintptr_t *>(&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();
}

View File

@@ -0,0 +1,109 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_service_ids.hpp"
#include "tma_conn_packet.hpp"
#include "tma_task.hpp"
#include "tma_service.hpp"
#include "tma_task_list.hpp"
#include "tma_conn_connection.hpp"
enum class TmaWorkType : u32 {
None,
NewTask,
FreeTask,
ReceivePacket,
Tick,
Disconnect,
Sleep,
};
struct TmaWorkItem {
TmaTask *task;
TmaPacket *packet;
TmaWorkType work_type;
};
class TmaServiceManager {
public:
static constexpr size_t PacketQueueDepth = 0x8;
static constexpr size_t WorkQueueDepth = 0x80;
private:
HosMutex lock;
bool initialized = false;
TmaTaskList task_list;
HosThread work_thread;
std::vector<TmaService *> services;
TmaConnection *connection = nullptr;
u32 next_task_id = 0;
/* Work queues. */
HosMessageQueue free_send_packet_queue = HosMessageQueue(PacketQueueDepth);
HosMessageQueue free_recv_packet_queue = HosMessageQueue(PacketQueueDepth);
HosMessageQueue work_queue = HosMessageQueue(WorkQueueDepth);
HosMessageQueue free_work_queue = HosMessageQueue(WorkQueueDepth);
/* Sleep management. */
HosSignal disconnect_signal;
HosSignal wake_signal;
HosSignal sleep_signal;
bool asleep = false;
private:
static void WorkThread(void *arg);
void AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet);
void HandleNewTaskWork(TmaWorkItem *work_item);
void HandleFreeTaskWork(TmaWorkItem *work_item);
void HandleReceivePacketWork(TmaWorkItem *work_item);
void HandleTickWork();
void HandleDisconnectWork();
void HandleSleepWork();
void SetAsleep(bool s) { this->asleep = s; }
public:
TmaServiceManager();
virtual ~TmaServiceManager();
void Initialize();
void Finalize();
/* Packet management. */
TmaConnResult SendPacket(TmaPacket *packet);
void OnReceivePacket(TmaPacket *packet);
TmaPacket *AllocateSendPacket();
TmaPacket *AllocateRecvPacket();
void FreePacket(TmaPacket *packet);
/* Service/task management. */
TmaService *GetServiceById(TmaServiceId id);
void AddService(TmaService *service);
void AddTask(TmaTask *task, TmaPacket *packet);
void FreeTask(TmaTask *task);
void CancelTask(u32 task_id);
void CancelTasks();
u32 GetNextTaskId();
/* Connection management. */
void Tick();
void SetConnection(TmaConnection *conn);
void OnDisconnect();
void Sleep();
void Wake(TmaConnection *conn);
bool GetAsleep() const { return this->asleep; }
bool GetConnected() const;
};

View File

@@ -0,0 +1,234 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_connection.hpp"
#include "tma_conn_usb_connection.hpp"
#include "tma_service_manager.hpp"
#include "tma_power_manager.hpp"
#include "tma_target.hpp"
#include "test/atmosphere_test_service.hpp"
#include "settings/settings_service.hpp"
#include "target_io/tio_service.hpp"
struct TmaTargetConfig {
char configuration_id1[0x80];
char serial_number[0x80];
};
static TmaConnection *g_active_connection = nullptr;
static TmaServiceManager *g_service_manager = nullptr;
static HosMutex g_connection_event_mutex;
static bool g_has_woken_up = false;
static bool g_connected_before_sleep = false;
static bool g_signal_on_disconnect = false;
static TmaUsbConnection *g_usb_connection = nullptr;
static TmaTargetConfig g_target_config = {
"Unknown",
"SerialNumber",
};
static void RefreshTargetConfig() {
setsysInitialize();
/* TODO: setsysGetConfigurationId1(&g_target_config.configuration_id1); */
g_target_config.serial_number[0] = 0;
setsysGetSerialNumber(g_target_config.serial_number);
setsysExit();
}
static void InitializeServices() {
g_service_manager->Initialize();
}
static void FinalizeServices() {
g_service_manager->Finalize();
}
static void SetActiveConnection(TmaConnection *connection) {
if (g_active_connection != connection) {
if (g_active_connection != nullptr) {
FinalizeServices();
g_service_manager->SetConnection(nullptr);
g_active_connection->Disconnect();
g_active_connection = nullptr;
}
if (connection != nullptr) {
g_active_connection = connection;
InitializeServices();
g_service_manager->SetConnection(g_active_connection);
g_active_connection->SetServiceManager(g_service_manager);
}
}
}
static void OnConnectionEvent(void *arg, ConnectionEvent evt) {
std::scoped_lock<HosMutex> lk(g_connection_event_mutex);
switch (evt) {
case ConnectionEvent::Connected:
{
bool has_active_connection = false;
g_has_woken_up = false;
if (arg == g_usb_connection) {
SetActiveConnection(g_usb_connection);
has_active_connection = true;
}
if (has_active_connection) {
/* TODO: Signal connected */
}
}
break;
case ConnectionEvent::Disconnected:
if (g_signal_on_disconnect) {
/* TODO: Signal disconnected */
}
break;
default:
std::abort();
break;
}
}
static void Wake() {
if (g_service_manager->GetAsleep()) {
g_has_woken_up = true;
/* N checks what kind of connection to use here. For now, we only use USB. */
TmaUsbConnection::InitializeComms();
g_usb_connection = new TmaUsbConnection();
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
g_usb_connection->Initialize();
g_active_connection = g_usb_connection;
g_service_manager->Wake(g_active_connection);
}
}
static void Sleep() {
if (!g_service_manager->GetAsleep()) {
if (g_active_connection->IsConnected()) {
g_connected_before_sleep = true;
/* TODO: Send a packet saying we're going to sleep. */
} else {
g_connected_before_sleep = false;
}
g_service_manager->Sleep();
g_service_manager->SetConnection(nullptr);
g_active_connection->Disconnect();
g_active_connection = nullptr;
g_service_manager->CancelTasks();
if (g_usb_connection != nullptr) {
g_usb_connection->Finalize();
delete g_usb_connection;
g_usb_connection = nullptr;
TmaUsbConnection::FinalizeComms();
}
}
}
static void OnPowerManagementEvent(PscPmState state, u32 flags) {
switch (state) {
case PscPmState_Awake:
{
Wake();
}
break;
case PscPmState_ReadyAwaken:
{
if (g_service_manager->GetAsleep()) {
Wake();
if (g_connected_before_sleep)
{
/* Try to restore a connection. */
bool connected = g_service_manager->GetConnected();
/* N uses a seven-second timeout, here. */
TimeoutHelper timeout_helper(7000000000ULL);
while (!connected && !timeout_helper.TimedOut()) {
connected = g_service_manager->GetConnected();
if (!connected) {
/* Sleep for 1ms. */
svcSleepThread(1000000ULL);
}
}
if (!connected) {
/* TODO: Signal disconnected */
}
}
}
}
break;
case PscPmState_ReadySleep:
{
Sleep();
}
break;
default:
/* Don't handle ReadySleepCritical/ReadyAwakenCritical/ReadyShutdown */
break;
}
}
void TmaTarget::Initialize() {
/* Get current thread priority. */
u32 cur_prio;
if (R_FAILED(svcGetThreadPriority(&cur_prio, CUR_THREAD_HANDLE))) {
std::abort();
}
g_active_connection = nullptr;
g_service_manager = new TmaServiceManager();
/* TODO: Make this better. */
g_service_manager->AddService(new AtmosphereTestService(g_service_manager));
g_service_manager->AddService(new SettingsService(g_service_manager));
g_service_manager->AddService(new TIOService(g_service_manager));
RefreshTargetConfig();
/* N checks what kind of connection to use here. For now, we only use USB. */
TmaUsbConnection::InitializeComms();
g_usb_connection = new TmaUsbConnection();
g_usb_connection->SetConnectionEventCallback(OnConnectionEvent, g_usb_connection);
g_usb_connection->Initialize();
SetActiveConnection(g_usb_connection);
/* TODO: Initialize connection events */
/* TODO: Initialize IPC services */
TmaPowerManager::Initialize(OnPowerManagementEvent);
}
void TmaTarget::Finalize() {
/* TODO: Is implementing this actually worthwhile? It will never be called in practice... */
}

View File

@@ -0,0 +1,25 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
class TmaTarget {
public:
static void Initialize();
static void Finalize();
};

View File

@@ -0,0 +1,52 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_task.hpp"
#include "tma_service_manager.hpp"
void TmaTask::SetNeedsPackets(bool n) {
this->needs_packets = n;
this->manager->Tick();
}
TmaPacket *TmaTask::AllocateSendPacket(bool continuation) {
auto packet = this->manager->AllocateSendPacket();
packet->SetServiceId(this->service_id);
packet->SetTaskId(this->task_id);
packet->SetCommand(this->command);
packet->SetContinuation(continuation);
return packet;
}
void TmaTask::FreePacket(TmaPacket *packet) {
this->manager->FreePacket(packet);
}
void TmaTask::Complete() {
SetNeedsPackets(false);
this->state = TmaTaskState::Complete;
this->manager->Tick();
}
void TmaTask::Cancel() {
SetNeedsPackets(false);
this->state = TmaTaskState::Canceled;
this->manager->Tick();
}

View File

@@ -0,0 +1,80 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_service_ids.hpp"
#include "tma_conn_packet.hpp"
enum class TmaTaskState : u32 {
InProgress,
Complete,
Canceled,
};
class TmaServiceManager;
class TmaTask {
public:
static constexpr u32 MaxPriority = 15;
static constexpr u32 NumPriorities = MaxPriority + 1;
protected:
TmaServiceManager *manager;
u32 priority = 0;
TmaServiceId service_id = TmaServiceId::Invalid;
u32 task_id = 0;
u32 command = 0;
TmaTaskState state = TmaTaskState::InProgress;
HosSignal signal;
bool owned_by_task_list = true;
bool sleep_allowed = true;
bool needs_packets = false;
public:
TmaTask(TmaServiceManager *m) : manager(m) { }
virtual ~TmaTask() { }
u32 GetPriority() const { return this->priority; }
TmaServiceId GetServiceId() const { return this->service_id; }
u32 GetTaskId() const { return this->task_id; }
u32 GetCommand() const { return this->command; }
TmaTaskState GetState() const { return this->state; }
bool GetOwnedByTaskList() const { return this->owned_by_task_list; }
bool GetSleepAllowed() const { return this->sleep_allowed; }
bool GetNeedsPackets() const { return this->needs_packets; }
void SetPriority(u32 p) { this->priority = p; }
void SetServiceId(TmaServiceId s) { this->service_id = s; }
void SetTaskId(u32 i) { this->task_id = i; }
void SetCommand(u32 c) { this->command = c; }
void SetOwnedByTaskList(bool o) { this->owned_by_task_list = o; }
void SetSleepAllowed(bool a) { this->sleep_allowed = a; }
void SetNeedsPackets(bool n);
void Signal() { this->signal.Signal(); }
void ResetSignal() { this->signal.Reset(); }
TmaPacket *AllocateSendPacket(bool continuation = true);
void FreePacket(TmaPacket *packet);
void Complete();
void Cancel();
virtual void OnStart(TmaPacket *packet) = 0;
virtual void OnReceivePacket(TmaPacket *packet) = 0;
virtual void OnSendPacket(TmaPacket *packet) = 0;
};

View File

@@ -0,0 +1,218 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <algorithm>
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_task_list.hpp"
TmaTask *TmaTaskList::GetById(u32 task_id) const {
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
for (auto task : this->tasks[i]) {
if (task->GetTaskId() == task_id) {
return task;
}
}
}
return nullptr;
}
u32 TmaTaskList::GetNumTasks() const {
std::scoped_lock<HosMutex> lk(this->lock);
u32 count = 0;
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
count += this->tasks[i].size();
}
return count;
}
u32 TmaTaskList::GetNumSleepingTasks() const {
std::scoped_lock<HosMutex> lk(this->lock);
u32 count = 0;
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
count += this->sleeping_tasks[i].size();
}
return count;
}
bool TmaTaskList::IsIdFree(u32 task_id) const {
std::scoped_lock<HosMutex> lk(this->lock);
return GetById(task_id) == nullptr;
}
bool TmaTaskList::SendPacket(bool connected, TmaPacket *packet) {
std::scoped_lock<HosMutex> lk(this->lock);
TmaTask *target_task = nullptr;
/* This loop both finds a target task, and cleans up finished tasks. */
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
auto it = this->tasks[i].begin();
while (it != this->tasks[i].end()) {
auto task = *it;
switch (task->GetState()) {
case TmaTaskState::InProgress:
it++;
if (target_task == nullptr && task->GetNeedsPackets()) {
if (connected || IsMetaService(task->GetServiceId())) {
target_task = task;
}
}
break;
case TmaTaskState::Complete:
case TmaTaskState::Canceled:
it = this->tasks[i].erase(it);
if (task->GetOwnedByTaskList()) {
delete task;
} else {
task->Signal();
}
break;
default:
/* TODO: Panic to fatal? */
std::abort();
}
}
}
if (target_task) {
/* Setup packet. */
packet->SetContinuation(true);
packet->SetServiceId(target_task->GetServiceId());
packet->SetTaskId(target_task->GetTaskId());
packet->SetCommand(target_task->GetCommand());
packet->ClearOffset();
/* Actually handle packet send. */
target_task->OnSendPacket(packet);
}
return target_task != nullptr;
}
bool TmaTaskList::ReceivePacket(TmaPacket *packet) {
std::scoped_lock<HosMutex> lk(this->lock);
auto task = this->GetById(packet->GetTaskId());
if (task != nullptr) {
task->OnReceivePacket(packet);
}
return task != nullptr;
}
void TmaTaskList::CleanupDoneTasks() {
std::scoped_lock<HosMutex> lk(this->lock);
/* Clean up all tasks in Complete/Canceled state. */
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
auto it = this->tasks[i].begin();
while (it != this->tasks[i].end()) {
auto task = *it;
switch (task->GetState()) {
case TmaTaskState::InProgress:
it++;
break;
case TmaTaskState::Complete:
case TmaTaskState::Canceled:
it = this->tasks[i].erase(it);
if (task->GetOwnedByTaskList()) {
delete task;
} else {
task->Signal();
}
break;
default:
/* TODO: Panic to fatal? */
std::abort();
}
}
}
}
void TmaTaskList::Add(TmaTask *task) {
std::scoped_lock<HosMutex> lk(this->lock);
this->tasks[task->GetPriority()].push_back(task);
}
void TmaTaskList::Remove(TmaTask *task) {
const auto priority = task->GetPriority();
/* Nintendo iterates over all lists instead of just the correct one. */
/* TODO: Is there actually any reason to do that? */
auto ind = std::find(this->tasks[priority].begin(), this->tasks[priority].end(), task);
if (ind != this->tasks[priority].end()) {
this->tasks[priority].erase(ind);
return;
}
/* TODO: Panic to fatal? */
std::abort();
}
void TmaTaskList::Cancel(u32 task_id) {
std::scoped_lock<HosMutex> lk(this->lock);
auto task = this->GetById(task_id);
if (task != nullptr) {
task->Cancel();
}
}
void TmaTaskList::CancelAll() {
std::scoped_lock<HosMutex> lk(this->lock);
for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
for (auto task : this->tasks[i]) {
task->Cancel();
}
}
}
void TmaTaskList::Sleep() {
std::scoped_lock<HosMutex> lk(this->lock);
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
auto it = this->tasks[i].begin();
while (it != this->tasks[i].end()) {
auto task = *it;
if (task->GetSleepAllowed()) {
it = this->tasks[i].erase(it);
this->sleeping_tasks[i].push_back(task);
} else {
it++;
}
}
}
}
void TmaTaskList::Wake() {
std::scoped_lock<HosMutex> lk(this->lock);
for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
auto it = this->sleeping_tasks[i].begin();
while (it != this->sleeping_tasks[i].end()) {
auto task = *it;
it = this->sleeping_tasks[i].erase(it);
this->tasks[i].push_back(task);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <vector>
#include "tma_conn_service_ids.hpp"
#include "tma_task.hpp"
class TmaTaskList {
private:
mutable HosMutex lock;
std::vector<TmaTask *> tasks[TmaTask::NumPriorities];
std::vector<TmaTask *> sleeping_tasks[TmaTask::NumPriorities];
private:
void Remove(TmaTask *task);
TmaTask *GetById(u32 task_id) const;
public:
TmaTaskList() { }
virtual ~TmaTaskList() { }
u32 GetNumTasks() const;
u32 GetNumSleepingTasks() const;
bool IsIdFree(u32 task_id) const;
bool SendPacket(bool connected, TmaPacket *packet);
bool ReceivePacket(TmaPacket *packet);
void CleanupDoneTasks();
void Add(TmaTask *task);
void Cancel(u32 task_id);
void CancelAll();
void Sleep();
void Wake();
};

View File

@@ -0,0 +1,484 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "tma_usb_comms.hpp"
/* TODO: Is this actually allowed? */
#define ATMOSPHERE_INTERFACE_PROTOCOL 0xFC
static std::atomic<bool> g_initialized = false;
static UsbDsInterface *g_interface;
static UsbDsEndpoint *g_endpoint_in, *g_endpoint_out;
/* USB State Change Tracking. */
static HosThread g_state_change_thread;
static WaitableManagerBase *g_state_change_manager = nullptr;
static void (*g_state_change_callback)(void *arg, u32 state);
static void *g_state_change_arg;
/* USB Send/Receive mutexes. */
static HosMutex g_send_mutex;
static HosMutex g_recv_mutex;
/* Static arrays to do USB DMA into. */
static constexpr size_t DmaBufferAlign = 0x1000;
static constexpr size_t HeaderBufferSize = DmaBufferAlign;
static constexpr size_t DataBufferSize = 0x18000;
static __attribute__((aligned(DmaBufferAlign))) u8 g_header_buffer[HeaderBufferSize];
static __attribute__((aligned(DmaBufferAlign))) u8 g_recv_data_buf[DataBufferSize];
static __attribute__((aligned(DmaBufferAlign))) u8 g_send_data_buf[DataBufferSize];
/* Taken from libnx usb comms. */
static Result _usbCommsInterfaceInit1x()
{
Result rc = 0;
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 4,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x200,
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x200,
};
if (R_FAILED(rc)) return rc;
//Setup interface.
rc = usbDsGetDsInterface(&g_interface, &interface_descriptor, "usb");
if (R_FAILED(rc)) return rc;
//Setup endpoints.
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_in, &endpoint_descriptor_in);//device->host
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_GetDsEndpoint(g_interface, &g_endpoint_out, &endpoint_descriptor_out);//host->device
if (R_FAILED(rc)) return rc;
return rc;
}
static Result _usbCommsInterfaceInit5x() {
Result rc = 0;
u8 iManufacturer, iProduct, iSerialNumber;
static const u16 supported_langs[1] = {0x0409};
// Send language descriptor
rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs)/sizeof(u16));
// Send manufacturer
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo");
// Send product
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch");
// Send serial number
if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber");
// Send device descriptors
struct usb_device_descriptor device_descriptor = {
.bLength = USB_DT_DEVICE_SIZE,
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0110,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 0x40,
.idVendor = 0x057e,
.idProduct = 0x3000,
.bcdDevice = 0x0100,
.iManufacturer = iManufacturer,
.iProduct = iProduct,
.iSerialNumber = iSerialNumber,
.bNumConfigurations = 0x01
};
// Full Speed is USB 1.1
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor);
// High Speed is USB 2.0
device_descriptor.bcdUSB = 0x0200;
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor);
// Super Speed is USB 3.0
device_descriptor.bcdUSB = 0x0300;
// Upgrade packet size to 512
device_descriptor.bMaxPacketSize0 = 0x09;
if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor);
// Define Binary Object Store
u8 bos[0x16] = {
0x05, // .bLength
USB_DT_BOS, // .bDescriptorType
0x16, 0x00, // .wTotalLength
0x02, // .bNumDeviceCaps
// USB 2.0
0x07, // .bLength
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
0x02, // .bDevCapabilityType
0x02, 0x00, 0x00, 0x00, // dev_capability_data
// USB 3.0
0x0A, // .bLength
USB_DT_DEVICE_CAPABILITY, // .bDescriptorType
0x03, // .bDevCapabilityType
0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00
};
if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos));
if (R_FAILED(rc)) return rc;
struct usb_interface_descriptor interface_descriptor = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 4,
.bNumEndpoints = 2,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceProtocol = ATMOSPHERE_INTERFACE_PROTOCOL,
};
struct usb_endpoint_descriptor endpoint_descriptor_in = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x40,
};
struct usb_endpoint_descriptor endpoint_descriptor_out = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT,
.bmAttributes = USB_TRANSFER_TYPE_BULK,
.wMaxPacketSize = 0x40,
};
struct usb_ss_endpoint_companion_descriptor endpoint_companion = {
.bLength = sizeof(struct usb_ss_endpoint_companion_descriptor),
.bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION,
.bMaxBurst = 0x0F,
.bmAttributes = 0x00,
.wBytesPerInterval = 0x00,
};
rc = usbDsRegisterInterface(&g_interface);
if (R_FAILED(rc)) return rc;
interface_descriptor.bInterfaceNumber = g_interface->interface_index;
endpoint_descriptor_in.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
endpoint_descriptor_out.bEndpointAddress += interface_descriptor.bInterfaceNumber + 1;
// Full Speed Config
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Full, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
// High Speed Config
endpoint_descriptor_in.wMaxPacketSize = 0x200;
endpoint_descriptor_out.wMaxPacketSize = 0x200;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_High, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
// Super Speed Config
endpoint_descriptor_in.wMaxPacketSize = 0x400;
endpoint_descriptor_out.wMaxPacketSize = 0x400;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &interface_descriptor, USB_DT_INTERFACE_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_in, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_descriptor_out, USB_DT_ENDPOINT_SIZE);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_AppendConfigurationData(g_interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE);
if (R_FAILED(rc)) return rc;
//Setup endpoints.
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_in, endpoint_descriptor_in.bEndpointAddress);
if (R_FAILED(rc)) return rc;
rc = usbDsInterface_RegisterEndpoint(g_interface, &g_endpoint_out, endpoint_descriptor_out.bEndpointAddress);
if (R_FAILED(rc)) return rc;
return rc;
}
/* Actual function implementations. */
TmaConnResult TmaUsbComms::Initialize() {
TmaConnResult res = TmaConnResult::Success;
if (g_initialized) {
std::abort();
}
Result rc = usbDsInitialize();
/* Perform interface setup. */
if (R_SUCCEEDED(rc)) {
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
rc = _usbCommsInterfaceInit5x();
} else {
rc = _usbCommsInterfaceInit1x();
}
}
/* Start the state change thread. */
if (R_SUCCEEDED(rc)) {
rc = g_state_change_thread.Initialize(&TmaUsbComms::UsbStateChangeThreadFunc, nullptr, 0x4000, 38);
if (R_SUCCEEDED(rc)) {
rc = g_state_change_thread.Start();
}
}
/* Enable USB communication. */
if (R_SUCCEEDED(rc)) {
rc = usbDsInterface_EnableInterface(g_interface);
}
if (R_SUCCEEDED(rc) && GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
rc = usbDsEnable();
}
if (R_FAILED(rc)) {
/* TODO: Should I not abort here? */
std::abort();
}
g_initialized = true;
return res;
}
TmaConnResult TmaUsbComms::Finalize() {
Result rc = 0;
/* We must have initialized before calling finalize. */
if (!g_initialized) {
std::abort();
}
/* Kill the state change thread. */
g_state_change_manager->RequestStop();
if (R_FAILED(g_state_change_thread.Join())) {
std::abort();
}
CancelComms();
if (R_SUCCEEDED(rc)) {
usbDsExit();
}
g_state_change_callback = nullptr;
g_interface = nullptr;
g_endpoint_in = nullptr;
g_endpoint_out = nullptr;
g_initialized = false;
return R_SUCCEEDED(rc) ? TmaConnResult::Success : TmaConnResult::ConnectionFailure;
}
void TmaUsbComms::CancelComms() {
if (!g_initialized) {
return;
}
usbDsEndpoint_Cancel(g_endpoint_in);
usbDsEndpoint_Cancel(g_endpoint_out);
}
void TmaUsbComms::SetStateChangeCallback(void (*callback)(void *, u32), void *arg) {
g_state_change_callback = callback;
g_state_change_arg = arg;
}
Result TmaUsbComms::UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size) {
Result rc = 0;
u32 urbId = 0;
u32 total_xferd = 0;
UsbDsReportData reportdata;
if (size) {
/* Start transfer. */
rc = usbDsEndpoint_PostBufferAsync(ep, buf, size, &urbId);
if (R_FAILED(rc)) return rc;
/* Wait for transfer to complete. */
eventWait(&ep->CompletionEvent, U64_MAX);
eventClear(&ep->CompletionEvent);
rc = usbDsEndpoint_GetReportData(ep, &reportdata);
if (R_FAILED(rc)) return rc;
rc = usbDsParseReportData(&reportdata, urbId, NULL, &total_xferd);
if (R_FAILED(rc)) return rc;
}
if (out_xferd) *out_xferd = total_xferd;
return rc;
}
TmaConnResult TmaUsbComms::ReceivePacket(TmaPacket *packet) {
std::scoped_lock<HosMutex> lk{g_recv_mutex};
TmaConnResult res = TmaConnResult::Success;
if (!g_initialized || packet == nullptr) {
return TmaConnResult::GeneralFailure;
}
/* Read the header. */
size_t read = 0;
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_header_buffer, sizeof(TmaPacket::Header)))) {
packet->CopyHeaderFrom(reinterpret_cast<TmaPacket::Header *>(g_header_buffer));
} else {
res = TmaConnResult::GeneralFailure;
}
/* Validate the read header data. */
if (res == TmaConnResult::Success) {
if (read != sizeof(TmaPacket::Header) || !packet->IsHeaderValid()) {
res = TmaConnResult::GeneralFailure;
}
}
/* Read the body! */
if (res == TmaConnResult::Success) {
const u32 body_len = packet->GetBodyLength();
if (0 < body_len) {
if (body_len <= sizeof(g_recv_data_buf)) {
if (R_SUCCEEDED(UsbXfer(g_endpoint_out, &read, g_recv_data_buf, body_len))) {
if (read == body_len) {
res = packet->CopyBodyFrom(g_recv_data_buf, body_len);
} else {
res = TmaConnResult::GeneralFailure;
}
}
} else {
res = TmaConnResult::GeneralFailure;
}
}
}
/* Validate the body. */
if (res == TmaConnResult::Success) {
if (!packet->IsBodyValid()) {
res = TmaConnResult::GeneralFailure;
}
}
if (res == TmaConnResult::Success) {
packet->ClearOffset();
}
return res;
}
TmaConnResult TmaUsbComms::SendPacket(TmaPacket *packet) {
std::scoped_lock<HosMutex> lk{g_send_mutex};
TmaConnResult res = TmaConnResult::Success;
if (!g_initialized || packet == nullptr) {
return TmaConnResult::GeneralFailure;
}
/* Ensure our packets have the correct checksums. */
packet->SetChecksums();
/* Send the packet. */
size_t written = 0;
const u32 body_len = packet->GetBodyLength();
if (body_len <= sizeof(g_send_data_buf)) {
/* Copy header to send buffer. */
packet->CopyHeaderTo(g_send_data_buf);
/* Send the packet header. */
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, sizeof(TmaPacket::Header)))) {
if (written == sizeof(TmaPacket::Header)) {
res = TmaConnResult::Success;
} else {
res = TmaConnResult::GeneralFailure;
}
} else {
res = TmaConnResult::GeneralFailure;
}
if (res == TmaConnResult::Success && 0 < body_len) {
/* Copy body to send buffer. */
packet->CopyBodyTo(g_send_data_buf);
/* Send the packet body. */
if (R_SUCCEEDED(UsbXfer(g_endpoint_in, &written, g_send_data_buf, body_len))) {
if (written == body_len) {
res = TmaConnResult::Success;
} else {
res = TmaConnResult::GeneralFailure;
}
} else {
res = TmaConnResult::GeneralFailure;
}
}
} else {
res = TmaConnResult::GeneralFailure;
}
return res;
}
void TmaUsbComms::UsbStateChangeThreadFunc(void *arg) {
u32 state;
g_state_change_manager = new WaitableManager(1);
auto state_change_event = LoadReadOnlySystemEvent(usbDsGetStateChangeEvent()->revent, [&](u64 timeout) {
if (R_SUCCEEDED(usbDsGetState(&state))) {
if (g_state_change_callback != nullptr) {
g_state_change_callback(g_state_change_arg, state);
}
}
return 0;
}, true);
g_state_change_manager->AddWaitable(state_change_event);
g_state_change_manager->Process();
/* If we get here, we're exiting. */
state_change_event->r_h = 0;
delete g_state_change_manager;
g_state_change_manager = nullptr;
svcExitThread();
}

View File

@@ -0,0 +1,36 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "tma_conn_result.hpp"
#include "tma_conn_packet.hpp"
class TmaUsbComms {
private:
static void UsbStateChangeThreadFunc(void *arg);
static Result UsbXfer(UsbDsEndpoint *ep, size_t *out_xferd, void *buf, size_t size);
public:
static TmaConnResult Initialize();
static TmaConnResult Finalize();
static void CancelComms();
static TmaConnResult ReceivePacket(TmaPacket *packet);
static TmaConnResult SendPacket(TmaPacket *packet);
static void SetStateChangeCallback(void (*callback)(void *, u32), void *arg);
};