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

@ -53,8 +53,9 @@ dist: all
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036 mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034 mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032 mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D
cp fusee/fusee-primary/fusee-primary.bin atmosphere-$(AMSVER)/atmosphere/reboot_payload.bin
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/atmosphere/fusee-secondary.bin cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/atmosphere/fusee-secondary.bin
cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/sept/payload.bin cp fusee/fusee-secondary/fusee-secondary.bin atmosphere-$(AMSVER)/sept/payload.bin
cp sept/sept-primary/sept-primary.bin atmosphere-$(AMSVER)/sept/sept-primary.bin cp sept/sept-primary/sept-primary.bin atmosphere-$(AMSVER)/sept/sept-primary.bin
@ -70,6 +71,7 @@ dist: all
cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro cp troposphere/reboot_to_payload/reboot_to_payload.nro atmosphere-$(AMSVER)/switch/reboot_to_payload.nro
mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags
touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag touch atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags/boot2.flag
cp stratosphere/tma/tma.nsp atmosphere-$(AMSVER)/atmosphere/titles/0100000000000007/exefs.nsp
cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D/exefs.nsp cp stratosphere/dmnt/dmnt.nsp atmosphere-$(AMSVER)/atmosphere/titles/010000000000000D/exefs.nsp
cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../; cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../;
rm -r atmosphere-$(AMSVER) rm -r atmosphere-$(AMSVER)

View file

@ -1,4 +1,4 @@
MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal dmnt MODULES := loader pm sm boot ams_mitm eclct.stub creport fatal dmnt tma
SUBFOLDERS := libstratosphere $(MODULES) SUBFOLDERS := libstratosphere $(MODULES)

@ -1 +1 @@
Subproject commit 0fbc0e2f468762de62d2b14d6495247d04e80bc3 Subproject commit fa37b70b0eca93be04e18636db25c9443e00d03b

159
stratosphere/tma/Makefile Normal file
View file

@ -0,0 +1,159 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source source/test source/settings source/target_io
DATA := data
INCLUDES := include ../../common/include
EXEFS_SRC := exefs_src
DEFINES := -DDISABLE_IPC
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lstratosphere -lnx
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).nsp
ifeq ($(strip $(APP_JSON)),)
$(OUTPUT).nsp : $(OUTPUT).nso
else
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
endif
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View file

@ -0,0 +1,35 @@
# Copyright (c) 2018 Atmosphere-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
from UsbConnection import UsbConnection
import sys, time
from Packet import Packet
import ServiceId
def main(argc, argv):
with UsbConnection(None) as c:
print 'Waiting for connection...'
c.wait_connected()
print 'Connected!'
print 'Reading atmosphere/BCT.ini...'
c.intf.send_packet(Packet().set_service(ServiceId.TARGETIO_SERVICE).set_task(0x01000000).set_cmd(2).write_str('atmosphere/BCT.ini').write_u64(0x109).write_u64(0))
resp = c.intf.read_packet()
res_packet = c.intf.read_packet()
read_res, size_read = resp.read_u32(), resp.read_u32()
print 'Final Result: 0x%x' % res_packet.read_u32()
print 'Size Read: 0x%x' % size_read
print 'Data:\n%s' % resp.body[resp.offset:]
return 0
if __name__ == '__main__':
sys.exit(main(len(sys.argv), sys.argv))

View file

@ -0,0 +1,120 @@
# Copyright (c) 2018 Atmosphere-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/>.
import zlib
import ServiceId
from struct import unpack as up, pack as pk
HEADER_SIZE = 0x28
def crc32(s):
return zlib.crc32(s) & 0xFFFFFFFF
class Packet():
def __init__(self):
self.service = 0
self.task = 0
self.cmd = 0
self.continuation = 0
self.version = 0
self.body_len = 0
self.body = ''
self.offset = 0
def load_header(self, header):
assert len(header) == HEADER_SIZE
self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, \
_, self.body_chk, self.hdr_chk = up('<IIHBBI16sII', header)
if crc32(header[:-4]) != self.hdr_chk:
raise ValueError('Invalid header checksum in received packet!')
def load_body(self, body):
assert len(body) == self.body_len
if crc32(body) != self.body_chk:
raise ValueError('Invalid body checksum in received packet!')
self.body = body
def get_data(self):
assert len(self.body) == self.body_len and self.body_len <= 0xE000
self.body_chk = crc32(self.body)
hdr = pk('<IIHBBIIIIII', self.service, self.task, self.cmd, self.continuation, self.version, self.body_len, 0, 0, 0, 0, self.body_chk)
self.hdr_chk = crc32(hdr)
hdr += pk('<I', self.hdr_chk)
return hdr + self.body
def set_service(self, srv):
if type(srv) is str:
self.service = ServiceId.hash(srv)
else:
self.service = srv
return self
def set_task(self, t):
self.task = t
return self
def set_cmd(self, x):
self.cmd = x
return self
def set_continuation(self, c):
self.continuation = c
return self
def set_version(self, v):
self.version = v
return self
def reset_offset(self):
self.offset = 0
return self
def write_str(self, s):
if s[-1] != '\x00':
s += '\x00'
self.body += s
self.body_len += len(s)
return self
def write_u8(self, x):
self.body += pk('<B', x & 0xFF)
self.body_len += 1
return self
def write_u16(self, x):
self.body += pk('<H', x & 0xFFFF)
self.body_len += 2
return self
def write_u32(self, x):
self.body += pk('<I', x & 0xFFFFFFFF)
self.body_len += 4
return self
def write_u64(self, x):
self.body += pk('<Q', x & 0xFFFFFFFFFFFFFFFF)
self.body_len += 8
return self
def read_str(self):
s = ''
while self.body[self.offset] != '\x00' and self.offset < self.body_len:
s += self.body[self.offset]
self.offset += 1
if self.offset < self.body_len and self.body[self.offset] == '\x00':
self.offset += 1
def read_u8(self):
x, = up('<B', self.body[self.offset:self.offset+1])
self.offset += 1
return x
def read_u16(self):
x, = up('<H', self.body[self.offset:self.offset+2])
self.offset += 2
return x
def read_u32(self):
x, = up('<I', self.body[self.offset:self.offset+4])
self.offset += 4
return x
def read_u64(self):
x, = up('<Q', self.body[self.offset:self.offset+8])
self.offset += 8
return x
def read_struct(self, format, sz):
x = up(format, self.body[self.offset:self.offset+sz])
self.offset += sz
return x

View file

@ -0,0 +1,30 @@
# Copyright (c) 2018 Atmosphere-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/>.
def hash(s):
h = ord(s[0]) & 0xFFFFFFFF
for c in s:
h = ((1000003 * h) ^ ord(c)) & 0xFFFFFFFF
h ^= len(s)
return h
USB_QUERY_TARGET = hash("USBQueryTarget")
USB_SEND_HOST_INFO = hash("USBSendHostInfo")
USB_CONNECT = hash("USBConnect")
USB_DISCONNECT = hash("USBDisconnect")
ATMOSPHERE_TEST_SERVICE = hash("AtmosphereTestService")
SETTINGS_SERVICE = hash("SettingsService")
TARGETIO_SERVICE = hash("TIOService")

View file

@ -0,0 +1,134 @@
# Copyright (c) 2018 Atmosphere-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/>.
from UsbInterface import UsbInterface
from threading import Thread, Condition
from collections import deque
import time
import ServiceId
from Packet import Packet
class UsbConnection(UsbInterface):
# Auto connect thread func.
def auto_connect(connection):
while not connection.is_connected():
try:
connection.connect(UsbInterface())
except ValueError as e:
continue
def recv_thread(connection):
while connection.is_connected():
try:
connection.recv_packet()
except Exception as e:
print 'An exception occurred:'
print 'Type: '+e.__class__.__name__
print 'Msg: '+str(e)
connection.disconnect()
connection.send_packet(None)
def send_thread(connection):
while connection.is_connected():
try:
next_packet = connection.get_next_send_packet()
if next_packet is not None:
connection.intf.send_packet(next_packet)
else:
connection.disconnect()
except Exception as e:
print 'An exception occurred:'
print 'Type: '+e.__class__.__name__
print 'Msg: '+str(e)
connection.disconnect()
def __init__(self, manager):
self.manager = manager
self.connected = False
self.intf = None
self.conn_lock, self.send_lock = Condition(), Condition()
self.send_queue = deque()
def __enter__(self):
self.conn_thrd = Thread(target=UsbConnection.auto_connect, args=(self,))
self.conn_thrd.daemon = True
self.conn_thrd.start()
return self
def __exit__(self, type, value, traceback):
self.disconnect()
time.sleep(1)
print 'Closing!'
time.sleep(1)
def wait_connected(self):
self.conn_lock.acquire()
if not self.is_connected():
self.conn_lock.wait()
self.conn_lock.release()
def is_connected(self):
return self.connected
def connect(self, intf):
# This indicates we have a connection.
self.conn_lock.acquire()
assert not self.connected
self.intf = intf
try:
# Perform Query + Connection handshake
print 'Performing handshake...'
self.intf.send_packet(Packet().set_service(ServiceId.USB_QUERY_TARGET))
query_resp = self.intf.read_packet()
print 'Found Switch, Protocol version 0x%x' % query_resp.read_u32()
self.intf.send_packet(Packet().set_service(ServiceId.USB_SEND_HOST_INFO).write_u32(0).write_u32(0))
self.intf.send_packet(Packet().set_service(ServiceId.USB_CONNECT))
resp = self.intf.read_packet()
# Spawn threads
self.recv_thrd = Thread(target=UsbConnection.recv_thread, args=(self,))
self.send_thrd = Thread(target=UsbConnection.send_thread, args=(self,))
self.recv_thrd.daemon = True
self.send_thrd.daemon = True
self.recv_thrd.start()
self.send_thrd.start()
self.connected = True
finally:
# Finish connection.
self.conn_lock.notify()
self.conn_lock.release()
def disconnect(self):
self.conn_lock.acquire()
if self.connected:
self.connected = False
self.intf.send_packet(Packet().set_service(ServiceId.USB_DISCONNECT))
self.conn_lock.release()
def recv_packet(self):
packet = self.intf.read_packet()
assert type(packet) is Packet
dat = packet.read_u64()
print('Got Packet: %08x' % dat)
def send_packet(self, packet):
assert type(packet) is Packet
self.send_lock.acquire()
if len(self.send_queue) == 0x40:
self.send_lock.wait()
self.send_queue.append(packet)
if len(self.send_queue) == 1:
self.send_lock.notify()
self.send_lock.release()
def get_next_send_packet(self):
self.send_lock.acquire()
if len(self.send_queue) == 0:
self.send_lock.wait()
packet = self.send_queue.popleft()
if len(self.send_queue) == 0x3F:
self.send_lock.notify()
self.send_lock.release()
return packet

View file

@ -0,0 +1,62 @@
# Copyright (c) 2018 Atmosphere-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/>.
import usb
import Packet
class UsbInterface():
def __init__(self):
self.dev = usb.core.find(idVendor=0x057e, idProduct=0x3000)
if self.dev is None:
raise ValueError('Device not found')
self.dev.set_configuration()
self.cfg = self.dev.get_active_configuration()
self.intf = usb.util.find_descriptor(self.cfg, bInterfaceClass=0xff, bInterfaceSubClass=0xff, bInterfaceProtocol=0xfc)
assert self.intf is not None
self.ep_in = usb.util.find_descriptor(
self.intf,
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_IN)
assert self.ep_in is not None
self.ep_out = usb.util.find_descriptor(
self.intf,
custom_match = \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
assert self.ep_out is not None
def close(self):
usb.util.dispose_resources(self.dev)
def blocking_read(self, size):
return ''.join(chr(x) for x in self.ep_in.read(size, 0xFFFFFFFFFFFFFFFF))
def blocking_write(self, data):
self.ep_out.write(data, 0xFFFFFFFFFFFFFFFF)
def read_packet(self):
packet = Packet.Packet()
hdr = self.blocking_read(Packet.HEADER_SIZE)
packet.load_header(hdr)
if packet.body_len:
packet.load_body(self.blocking_read(packet.body_len))
return packet
def send_packet(self, packet):
data = packet.get_data()
self.blocking_write(data[:Packet.HEADER_SIZE])
if (len(data) > Packet.HEADER_SIZE):
self.blocking_write(data[Packet.HEADER_SIZE:])

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);
};

147
stratosphere/tma/tma.json Normal file
View file

@ -0,0 +1,147 @@
{
"name": "tma",
"title_id": "0x0100000000000007",
"title_id_range_min": "0x0100000000000007",
"title_id_range_max": "0x0100000000000007",
"main_thread_stack_size": "0x00004000",
"main_thread_priority": 38,
"default_cpu_id": 3,
"process_category": 0,
"is_retail": true,
"pool_partition": 2,
"is_64_bit": true,
"address_space_type": 1,
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
"service_access": [
"bsd:s",
"bsdcfg",
"dmnt:-",
"fatal:u",
"i2c",
"pcie",
"psc:m",
"set:cal",
"set:fd",
"set:sys",
"sfdnsres",
"spl:",
"usb:ds"
],
"service_host": [
"file_io",
"gds",
"htc",
"htcs",
"tma_log",
"tmagent"
],
"kernel_capabilities": [{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 24,
"lowest_cpu_id": 0,
"highest_cpu_id": 3
}
}, {
"type": "syscalls",
"value": {
"svcSetHeapSize": "0x01",
"svcSetMemoryPermission": "0x02",
"svcSetMemoryAttribute": "0x03",
"svcMapMemory": "0x04",
"svcUnmapMemory": "0x05",
"svcQueryMemory": "0x06",
"svcExitProcess": "0x07",
"svcCreateThread": "0x08",
"svcStartThread": "0x09",
"svcExitThread": "0x0a",
"svcSleepThread": "0x0b",
"svcGetThreadPriority": "0x0c",
"svcSetThreadPriority": "0x0d",
"svcGetThreadCoreMask": "0x0e",
"svcSetThreadCoreMask": "0x0f",
"svcGetCurrentProcessorNumber": "0x10",
"svcSignalEvent": "0x11",
"svcClearEvent": "0x12",
"svcMapSharedMemory": "0x13",
"svcUnmapSharedMemory": "0x14",
"svcCreateTransferMemory": "0x15",
"svcCloseHandle": "0x16",
"svcResetSignal": "0x17",
"svcWaitSynchronization": "0x18",
"svcCancelSynchronization": "0x19",
"svcArbitrateLock": "0x1a",
"svcArbitrateUnlock": "0x1b",
"svcWaitProcessWideKeyAtomic": "0x1c",
"svcSignalProcessWideKey": "0x1d",
"svcGetSystemTick": "0x1e",
"svcConnectToNamedPort": "0x1f",
"svcSendSyncRequestLight": "0x20",
"svcSendSyncRequest": "0x21",
"svcSendSyncRequestWithUserBuffer": "0x22",
"svcSendAsyncRequestWithUserBuffer": "0x23",
"svcGetProcessId": "0x24",
"svcGetThreadId": "0x25",
"svcBreak": "0x26",
"svcOutputDebugString": "0x27",
"svcReturnFromException": "0x28",
"svcGetInfo": "0x29",
"svcWaitForAddress": "0x34",
"svcSignalToAddress": "0x35",
"svcCreateSession": "0x40",
"svcAcceptSession": "0x41",
"svcReplyAndReceiveLight": "0x42",
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcReadWriteRegister": "0x4E",
"svcCreateSharedMemory": "0x50",
"svcMapTransferMemory": "0x51",
"svcUnmapTransferMemory": "0x52",
"svcCreateInterruptEvent": "0x53",
"svcQueryIoMapping": "0x55",
"svcCreateDeviceAddressSpace": "0x56",
"svcAttachDeviceAddressSpace": "0x57",
"svcDetachDeviceAddressSpace": "0x58",
"svcMapDeviceAddressSpaceByForce": "0x59",
"svcMapDeviceAddressSpaceAligned": "0x5a",
"svcMapDeviceAddressSpace": "0x5b",
"svcUnmapDeviceAddressSpace": "0x5c",
"svcInvalidateProcessDataCache": "0x5d",
"svcStoreProcessDataCache": "0x5e",
"svcFlushProcessDataCache": "0x5f",
"svcCallSecureMonitor": "0x7f"
}
}, {
"type": "map",
"value": {
"address": "0x02000000",
"is_ro": false,
"size": "0x05000000",
"is_io": true
}
}, {
"type": "map",
"value": {
"address": "0x10000000",
"is_ro": false,
"size": "0x04000000",
"is_io": true
}
}, {
"type": "irq_pair",
"value": [130, null]
}, {
"type": "irq_pair",
"value": [131, 132]
}, {
"type": "min_kernel_version",
"value": "0x0030"
}, {
"type": "handle_table_size",
"value": 256
}]
}