diff --git a/Makefile b/Makefile index 97c78053d..b280cfdfd 100644 --- a/Makefile +++ b/Makefile @@ -53,8 +53,9 @@ dist: all mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000036 mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000034 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 + 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)/sept/payload.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 mkdir -p atmosphere-$(AMSVER)/atmosphere/titles/0100000000000032/flags 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 cd atmosphere-$(AMSVER); zip -r ../atmosphere-$(AMSVER).zip ./*; cd ../; rm -r atmosphere-$(AMSVER) diff --git a/stratosphere/Makefile b/stratosphere/Makefile index 7bbb09435..7171f7914 100644 --- a/stratosphere/Makefile +++ b/stratosphere/Makefile @@ -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) diff --git a/stratosphere/libstratosphere b/stratosphere/libstratosphere index 0fbc0e2f4..fa37b70b0 160000 --- a/stratosphere/libstratosphere +++ b/stratosphere/libstratosphere @@ -1 +1 @@ -Subproject commit 0fbc0e2f468762de62d2b14d6495247d04e80bc3 +Subproject commit fa37b70b0eca93be04e18636db25c9443e00d03b diff --git a/stratosphere/tma/Makefile b/stratosphere/tma/Makefile new file mode 100644 index 000000000..085537f4a --- /dev/null +++ b/stratosphere/tma/Makefile @@ -0,0 +1,159 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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 +#--------------------------------------------------------------------------------------- diff --git a/stratosphere/tma/client/Main.py b/stratosphere/tma/client/Main.py new file mode 100644 index 000000000..fe4687856 --- /dev/null +++ b/stratosphere/tma/client/Main.py @@ -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)) \ No newline at end of file diff --git a/stratosphere/tma/client/Packet.py b/stratosphere/tma/client/Packet.py new file mode 100644 index 000000000..9f623b28d --- /dev/null +++ b/stratosphere/tma/client/Packet.py @@ -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 . +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('. + +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") + \ No newline at end of file diff --git a/stratosphere/tma/client/UsbConnection.py b/stratosphere/tma/client/UsbConnection.py new file mode 100644 index 000000000..a0ec84e38 --- /dev/null +++ b/stratosphere/tma/client/UsbConnection.py @@ -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 . +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 + diff --git a/stratosphere/tma/client/UsbInterface.py b/stratosphere/tma/client/UsbInterface.py new file mode 100644 index 000000000..69169cf6e --- /dev/null +++ b/stratosphere/tma/client/UsbInterface.py @@ -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 . +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:]) + + diff --git a/stratosphere/tma/source/crc.h b/stratosphere/tma/source/crc.h new file mode 100644 index 000000000..1fc37cffb --- /dev/null +++ b/stratosphere/tma/source/crc.h @@ -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 . + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Code taken from Yazen Ghannam , 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 \ No newline at end of file diff --git a/stratosphere/tma/source/dmnt.c b/stratosphere/tma/source/dmnt.c new file mode 100644 index 000000000..d8124520a --- /dev/null +++ b/stratosphere/tma/source/dmnt.c @@ -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 . + */ + +#include +#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); +} diff --git a/stratosphere/tma/source/dmnt.h b/stratosphere/tma/source/dmnt.h new file mode 100644 index 000000000..2858cd96f --- /dev/null +++ b/stratosphere/tma/source/dmnt.h @@ -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 . + */ + +#pragma once +#include + +#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 \ No newline at end of file diff --git a/stratosphere/tma/source/settings/settings_service.cpp b/stratosphere/tma/source/settings/settings_service.cpp new file mode 100644 index 000000000..8dc5dfa52 --- /dev/null +++ b/stratosphere/tma/source/settings/settings_service.cpp @@ -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 . + */ + +#include +#include + +#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; +} diff --git a/stratosphere/tma/source/settings/settings_service.hpp b/stratosphere/tma/source/settings/settings_service.hpp new file mode 100644 index 000000000..3b21c891b --- /dev/null +++ b/stratosphere/tma/source/settings/settings_service.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; \ No newline at end of file diff --git a/stratosphere/tma/source/settings/settings_task.cpp b/stratosphere/tma/source/settings/settings_task.cpp new file mode 100644 index 000000000..52540e623 --- /dev/null +++ b/stratosphere/tma/source/settings/settings_task.cpp @@ -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 . + */ + +#include +#include + +#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)this->succeeded); + if (this->succeeded) { + packet->Write((u32)this->value_size); + packet->Write(this->value, this->value_size); + } + + this->Complete(); +} \ No newline at end of file diff --git a/stratosphere/tma/source/settings/settings_task.hpp b/stratosphere/tma/source/settings/settings_task.hpp new file mode 100644 index 000000000..5d2bd13be --- /dev/null +++ b/stratosphere/tma/source/settings/settings_task.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; diff --git a/stratosphere/tma/source/target_io/tio_service.cpp b/stratosphere/tma/source/target_io/tio_service.cpp new file mode 100644 index 000000000..84edaba83 --- /dev/null +++ b/stratosphere/tma/source/target_io/tio_service.cpp @@ -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 . + */ + +#include +#include + +#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; +} diff --git a/stratosphere/tma/source/target_io/tio_service.hpp b/stratosphere/tma/source/target_io/tio_service.hpp new file mode 100644 index 000000000..110439468 --- /dev/null +++ b/stratosphere/tma/source/target_io/tio_service.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; \ No newline at end of file diff --git a/stratosphere/tma/source/target_io/tio_task.cpp b/stratosphere/tma/source/target_io/tio_task.cpp new file mode 100644 index 000000000..a8090bd0a --- /dev/null +++ b/stratosphere/tma/source/target_io/tio_task.cpp @@ -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 . + */ + +#include +#include + +#include "tio_task.hpp" + + +void TIOFileReadTask::OnStart(TmaPacket *packet) { + char path[FS_MAX_PATH]; + + packet->ReadString(path, sizeof(path), nullptr); + packet->Read(this->size_remaining); + packet->Read(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(rc); + this->manager->SendPacket(packet); + Complete(); +} + +Result TIOFileReadTask::ProcessPacket(TmaPacket *packet) { + Result rc = 0x196002; + + size_t cur_read = static_cast((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(rc); + packet->Write(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(this->size_remaining); + packet->Read(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(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(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; +} \ No newline at end of file diff --git a/stratosphere/tma/source/target_io/tio_task.hpp b/stratosphere/tma/source/target_io/tio_task.hpp new file mode 100644 index 000000000..7a3307fd3 --- /dev/null +++ b/stratosphere/tma/source/target_io/tio_task.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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(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); +}; + diff --git a/stratosphere/tma/source/test/atmosphere_test_service.cpp b/stratosphere/tma/source/test/atmosphere_test_service.cpp new file mode 100644 index 000000000..7433882fa --- /dev/null +++ b/stratosphere/tma/source/test/atmosphere_test_service.cpp @@ -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 . + */ + +#include +#include + +#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; +} diff --git a/stratosphere/tma/source/test/atmosphere_test_service.hpp b/stratosphere/tma/source/test/atmosphere_test_service.hpp new file mode 100644 index 000000000..c01e7e5cb --- /dev/null +++ b/stratosphere/tma/source/test/atmosphere_test_service.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; \ No newline at end of file diff --git a/stratosphere/tma/source/test/atmosphere_test_task.cpp b/stratosphere/tma/source/test/atmosphere_test_task.cpp new file mode 100644 index 000000000..57cb6078b --- /dev/null +++ b/stratosphere/tma/source/test/atmosphere_test_task.cpp @@ -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 . + */ + +#include +#include + +#include "atmosphere_test_task.hpp" + +void AtmosphereTestTask::OnStart(TmaPacket *packet) { + packet->Read(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('A'); + } + + this->Complete(); +} \ No newline at end of file diff --git a/stratosphere/tma/source/test/atmosphere_test_task.hpp b/stratosphere/tma/source/test/atmosphere_test_task.hpp new file mode 100644 index 000000000..ebbb62b03 --- /dev/null +++ b/stratosphere/tma/source/test/atmosphere_test_task.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; diff --git a/stratosphere/tma/source/tma_conn_connection.cpp b/stratosphere/tma/source/tma_conn_connection.cpp new file mode 100644 index 000000000..3cc571365 --- /dev/null +++ b/stratosphere/tma/source/tma_conn_connection.cpp @@ -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 . + */ + +#include +#include +#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(); +} diff --git a/stratosphere/tma/source/tma_conn_connection.hpp b/stratosphere/tma/source/tma_conn_connection.hpp new file mode 100644 index 000000000..3c5f39bcd --- /dev/null +++ b/stratosphere/tma/source/tma_conn_connection.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; \ No newline at end of file diff --git a/stratosphere/tma/source/tma_conn_packet.hpp b/stratosphere/tma/source/tma_conn_packet.hpp new file mode 100644 index 000000000..20ccce265 --- /dev/null +++ b/stratosphere/tma/source/tma_conn_packet.hpp @@ -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 . + */ + +#pragma once + +#include +#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 buffer = std::make_unique(MaxPacketSize); + u32 offset = 0; + HosMessageQueue *free_queue = nullptr; + + Header *GetHeader() const { + return reinterpret_cast
(buffer.get()); + } + + u8 *GetBody(u32 ofs) const { + return reinterpret_cast(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(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(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(srv); + } + + TmaServiceId GetServiceId() const { + return static_cast(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 + TmaConnResult Write(const T &t) { + return Write(&t, sizeof(T)); + } + + template + 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(GetBody(this->offset)), available, format, va_arg); + + size_t total_written; + if (static_cast(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(*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; + } +}; diff --git a/stratosphere/tma/source/tma_conn_result.hpp b/stratosphere/tma/source/tma_conn_result.hpp new file mode 100644 index 000000000..7d91923af --- /dev/null +++ b/stratosphere/tma/source/tma_conn_result.hpp @@ -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 . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +enum class TmaConnResult : u32 { + Success = 0, + NotImplemented, + GeneralFailure, + ConnectionFailure, + AlreadyConnected, + WrongConnectionVersion, + PacketOverflow, + BufferOverflow, + Disconnected, + ServiceAlreadyRegistered, + ServiceUnknown, + Timeout, + NotInitialized, +}; diff --git a/stratosphere/tma/source/tma_conn_service_ids.hpp b/stratosphere/tma/source/tma_conn_service_ids.hpp new file mode 100644 index 000000000..eb0543d3d --- /dev/null +++ b/stratosphere/tma/source/tma_conn_service_ids.hpp @@ -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 . + */ + +#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; +} diff --git a/stratosphere/tma/source/tma_conn_usb_connection.cpp b/stratosphere/tma/source/tma_conn_usb_connection.cpp new file mode 100644 index 000000000..d7c89199f --- /dev/null +++ b/stratosphere/tma/source/tma_conn_usb_connection.cpp @@ -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 . + */ + +#include +#include +#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(_packet); + if (packet != nullptr) { + this->FreePacket(packet); + } + } +} + +void TmaUsbConnection::SendThreadFunc(void *arg) { + TmaUsbConnection *this_ptr = reinterpret_cast(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(_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(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(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(nullptr)); +} + +void TmaUsbConnection::OnUsbStateChange(void *arg, u32 state) { + TmaUsbConnection *this_ptr = reinterpret_cast(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 lk(this->lock); + + if (this->IsConnected()) { + this->send_queue.Send(reinterpret_cast(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(target_info); + return TmaUsbComms::SendPacket(packet); +} diff --git a/stratosphere/tma/source/tma_conn_usb_connection.hpp b/stratosphere/tma/source/tma_conn_usb_connection.hpp new file mode 100644 index 000000000..2927e6e3f --- /dev/null +++ b/stratosphere/tma/source/tma_conn_usb_connection.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#include "tma_conn_connection.hpp" +#include "tma_usb_comms.hpp" + +class TmaUsbConnection : public TmaConnection { + private: + HosMessageQueue send_queue = HosMessageQueue(64); + std::atomic 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; +}; \ No newline at end of file diff --git a/stratosphere/tma/source/tma_main.cpp b/stratosphere/tma/source/tma_main.cpp new file mode 100644 index 000000000..e0d16571e --- /dev/null +++ b/stratosphere/tma/source/tma_main.cpp @@ -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 . + */ + +#include +#include +#include +#include + +#include +#include +#include + +#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; +} + diff --git a/stratosphere/tma/source/tma_power_manager.cpp b/stratosphere/tma/source/tma_power_manager.cpp new file mode 100644 index 000000000..d71de8592 --- /dev/null +++ b/stratosphere/tma/source/tma_power_manager.cpp @@ -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 . + */ + +#include +#include +#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 */ +} \ No newline at end of file diff --git a/stratosphere/tma/source/tma_power_manager.hpp b/stratosphere/tma/source/tma_power_manager.hpp new file mode 100644 index 000000000..6ca0e4ad8 --- /dev/null +++ b/stratosphere/tma/source/tma_power_manager.hpp @@ -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 . + */ + +#pragma once +#include +#include + +class TmaPowerManager { + public: + static void Initialize(void (*callback)(PscPmState, u32)); + static void Finalize(); +}; diff --git a/stratosphere/tma/source/tma_service.cpp b/stratosphere/tma/source/tma_service.cpp new file mode 100644 index 000000000..8a70701c8 --- /dev/null +++ b/stratosphere/tma/source/tma_service.cpp @@ -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 . + */ + +#include +#include +#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. */ +} \ No newline at end of file diff --git a/stratosphere/tma/source/tma_service.hpp b/stratosphere/tma/source/tma_service.hpp new file mode 100644 index 000000000..e9b0d53e3 --- /dev/null +++ b/stratosphere/tma/source/tma_service.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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(HashServiceName(this->service_name))) { } + virtual ~TmaService() { } + + TmaServiceId GetServiceId() const { return this->id; } + + virtual TmaTask *NewTask(TmaPacket *packet) = 0; + virtual void OnSleep(); + virtual void OnWake(); +}; diff --git a/stratosphere/tma/source/tma_service_manager.cpp b/stratosphere/tma/source/tma_service_manager.cpp new file mode 100644 index 000000000..e684f7e0c --- /dev/null +++ b/stratosphere/tma/source/tma_service_manager.cpp @@ -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 . + */ + +#include +#include +#include "tma_service_manager.hpp" + +TmaServiceManager::TmaServiceManager() { + /* Set up queues */ + for (size_t i = 0; i < TmaServiceManager::PacketQueueDepth; i++) { + TmaPacket *packet = nullptr; + + packet = new TmaPacket(); + packet->SetFreeQueue(&this->free_send_packet_queue); + this->free_send_packet_queue.Send(reinterpret_cast(packet)); + packet = nullptr; + + packet = new TmaPacket(); + packet->SetFreeQueue(&this->free_recv_packet_queue); + this->free_recv_packet_queue.Send(reinterpret_cast(packet)); + packet = nullptr; + } + for (size_t i = 0; i < TmaServiceManager::WorkQueueDepth; i++) { + this->free_work_queue.Send(reinterpret_cast(new TmaWorkItem())); + } +} + +TmaServiceManager::~TmaServiceManager() { + /* Destroy queues. */ + TmaPacket *packet = nullptr; + while (this->free_send_packet_queue.TryReceive(reinterpret_cast(&packet))) { + delete packet; + packet = nullptr; + } + while (this->free_recv_packet_queue.TryReceive(reinterpret_cast(&packet))) { + delete packet; + packet = nullptr; + } + + TmaWorkItem *work_item = nullptr; + while (this->free_work_queue.TryReceive(reinterpret_cast(&work_item))) { + delete work_item; + work_item = nullptr; + } + while (this->work_queue.TryReceive(reinterpret_cast(&work_item))) { + delete work_item; + work_item = nullptr; + } +} + +void TmaServiceManager::Initialize() { + this->initialized = true; + this->work_thread.Initialize(TmaServiceManager::WorkThread, this, 0x4000, 0x26); + this->work_thread.Start(); +} + +void TmaServiceManager::Finalize() { + if (this->initialized) { + this->initialized = false; + if (this->connection && this->connection->IsConnected()) { + this->connection->Disconnect(); + } + + /* Signal to work thread to end. */ + this->work_queue.Send(reinterpret_cast(nullptr)); + this->work_thread.Join(); + + /* TODO: N tells services that they have no manager here. Do we want to do that? */ + } +} + +void TmaServiceManager::AddWork(TmaWorkType type, TmaTask *task, TmaPacket *packet) { + if (!this->initialized) { + std::abort(); + } + + TmaWorkItem *work_item = nullptr; + this->free_work_queue.Receive(reinterpret_cast(&work_item)); + + work_item->task = task; + work_item->packet = packet; + work_item->work_type = type; + this->work_queue.Send(reinterpret_cast(work_item)); +} + +/* Packet management. */ +TmaConnResult TmaServiceManager::SendPacket(TmaPacket *packet) { + TmaConnResult res = TmaConnResult::Disconnected; + + if (this->connection != nullptr) { + res = this->connection->SendPacket(packet); + } + + return res; +} + + +void TmaServiceManager::OnReceivePacket(TmaPacket *packet) { + this->AddWork(TmaWorkType::ReceivePacket, nullptr, packet); +} + +TmaPacket *TmaServiceManager::AllocateSendPacket() { + if (!this->initialized) { + std::abort(); + } + + TmaPacket *packet = nullptr; + this->free_send_packet_queue.Receive(reinterpret_cast(&packet)); + + packet->ClearOffset(); + packet->SetBodyLength(); + + return packet; +} + +TmaPacket *TmaServiceManager::AllocateRecvPacket() { + if (!this->initialized) { + std::abort(); + } + + TmaPacket *packet = nullptr; + this->free_recv_packet_queue.Receive(reinterpret_cast(&packet)); + + packet->ClearOffset(); + + return packet; +} + +void TmaServiceManager::FreePacket(TmaPacket *packet) { + if (!this->initialized) { + std::abort(); + } + + if (packet != nullptr) { + packet->GetFreeQueue()->Send(reinterpret_cast(packet)); + } +} + + +/* Service/task management. */ +TmaService *TmaServiceManager::GetServiceById(TmaServiceId id) { + std::scoped_lock lk(this->lock); + + for (auto srv : this->services) { + if (srv->GetServiceId() == id) { + return srv; + } + } + + return nullptr; +} + +void TmaServiceManager::AddService(TmaService *service) { + std::scoped_lock lk(this->lock); + + this->services.push_back(service); +} + +void TmaServiceManager::AddTask(TmaTask *task, TmaPacket *packet) { + this->AddWork(TmaWorkType::NewTask, task, packet); +} + +void TmaServiceManager::FreeTask(TmaTask *task) { + this->AddWork(TmaWorkType::FreeTask, task, nullptr); +} + +void TmaServiceManager::CancelTask(u32 task_id) { + if (this->initialized) { + this->task_list.Cancel(task_id); + } +} + +void TmaServiceManager::CancelTasks() { + if (this->initialized) { + this->task_list.CancelAll(); + } +} + +u32 TmaServiceManager::GetNextTaskId() { + while (true) { + u32 id; + { + /* N only uses 16 bits for the task id. We'll use 24. */ + std::scoped_lock lk(this->lock); + id = (this->next_task_id++) & 0xFFFFFF; + } + + if (this->task_list.IsIdFree(id)) { + return id; + } + } +} + +/* Connection management. */ +void TmaServiceManager::Tick() { + this->AddWork(TmaWorkType::Tick, nullptr, nullptr); +} + +void TmaServiceManager::SetConnection(TmaConnection *conn) { + this->connection = conn; +} + +void TmaServiceManager::OnDisconnect() { + if (!this->initialized) { + std::abort(); + } + + if (!this->GetAsleep()) { + this->disconnect_signal.Reset(); + + this->AddWork(TmaWorkType::Disconnect, nullptr, nullptr); + + /* TODO: why does N wait with a timeout of zero here? */ + this->disconnect_signal.Wait(); + } +} + +void TmaServiceManager::Sleep() { + if (!this->initialized) { + std::abort(); + } + + if (!this->GetAsleep()) { + this->wake_signal.Reset(); + this->sleep_signal.Reset(); + + /* Tell the work thread to stall, wait for ACK. */ + this->AddWork(TmaWorkType::Sleep, nullptr, nullptr); + this->sleep_signal.Wait(); + + this->SetAsleep(true); + } +} + +void TmaServiceManager::Wake(TmaConnection *conn) { + if (this->connection != nullptr) { + std::abort(); + } + if (this->GetAsleep()) { + this->connection = conn; + this->connection->SetWokenUp(true); + this->connection->SetServiceManager(this); + /* Tell the work thread to resume. */ + this->wake_signal.Signal(); + } +} + +bool TmaServiceManager::GetConnected() const { + return this->connection != nullptr && this->connection->IsConnected(); +} + +/* Work thread. */ +void TmaServiceManager::WorkThread(void *_this) { + TmaServiceManager *this_ptr = reinterpret_cast(_this); + if (!this_ptr->initialized) { + std::abort(); + } + + while (true) { + /* Receive a work item. */ + TmaWorkItem *work_item = nullptr; + this_ptr->work_queue.Receive(reinterpret_cast(&work_item)); + + if (work_item == nullptr) { + /* We're done. */ + this_ptr->task_list.CancelAll(); + break; + } + + switch (work_item->work_type) { + case TmaWorkType::Tick: + /* HandleTickWork called unconditionally. */ + break; + case TmaWorkType::NewTask: + this_ptr->HandleNewTaskWork(work_item); + break; + case TmaWorkType::FreeTask: + this_ptr->HandleFreeTaskWork(work_item); + break; + case TmaWorkType::ReceivePacket: + this_ptr->HandleReceivePacketWork(work_item); + break; + case TmaWorkType::Disconnect: + this_ptr->HandleDisconnectWork(); + break; + case TmaWorkType::Sleep: + this_ptr->HandleSleepWork(); + break; + case TmaWorkType::None: + default: + std::abort(); + break; + } + + this_ptr->free_work_queue.Send(reinterpret_cast(work_item)); + this_ptr->HandleTickWork(); + } +} + +void TmaServiceManager::HandleNewTaskWork(TmaWorkItem *work_item) { + this->task_list.Add(work_item->task); + if (this->GetConnected()) { + if (work_item->packet != nullptr) { + this->SendPacket(work_item->packet); + } + } else { + work_item->task->Cancel(); + this->FreePacket(work_item->packet); + } +} + +void TmaServiceManager::HandleFreeTaskWork(TmaWorkItem *work_item) { + delete work_item->task; +} + +void TmaServiceManager::HandleReceivePacketWork(TmaWorkItem *work_item) { + 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(&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(); +} diff --git a/stratosphere/tma/source/tma_service_manager.hpp b/stratosphere/tma/source/tma_service_manager.hpp new file mode 100644 index 000000000..ac6ccc5e3 --- /dev/null +++ b/stratosphere/tma/source/tma_service_manager.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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 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; +}; diff --git a/stratosphere/tma/source/tma_target.cpp b/stratosphere/tma/source/tma_target.cpp new file mode 100644 index 000000000..2659c2636 --- /dev/null +++ b/stratosphere/tma/source/tma_target.cpp @@ -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 . + */ + +#include +#include + +#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 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... */ +} diff --git a/stratosphere/tma/source/tma_target.hpp b/stratosphere/tma/source/tma_target.hpp new file mode 100644 index 000000000..73b81e857 --- /dev/null +++ b/stratosphere/tma/source/tma_target.hpp @@ -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 . + */ + +#pragma once +#include +#include + +class TmaTarget { + public: + static void Initialize(); + static void Finalize(); +}; diff --git a/stratosphere/tma/source/tma_task.cpp b/stratosphere/tma/source/tma_task.cpp new file mode 100644 index 000000000..4fb10ad2d --- /dev/null +++ b/stratosphere/tma/source/tma_task.cpp @@ -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 . + */ + +#include +#include +#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(); +} \ No newline at end of file diff --git a/stratosphere/tma/source/tma_task.hpp b/stratosphere/tma/source/tma_task.hpp new file mode 100644 index 000000000..7f17ffc7b --- /dev/null +++ b/stratosphere/tma/source/tma_task.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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; +}; diff --git a/stratosphere/tma/source/tma_task_list.cpp b/stratosphere/tma/source/tma_task_list.cpp new file mode 100644 index 000000000..36f54bef9 --- /dev/null +++ b/stratosphere/tma/source/tma_task_list.cpp @@ -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 . + */ +#include + +#include +#include +#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 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 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 lk(this->lock); + return GetById(task_id) == nullptr; +} + +bool TmaTaskList::SendPacket(bool connected, TmaPacket *packet) { + std::scoped_lock 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 lk(this->lock); + + auto task = this->GetById(packet->GetTaskId()); + if (task != nullptr) { + task->OnReceivePacket(packet); + } + return task != nullptr; +} + + +void TmaTaskList::CleanupDoneTasks() { + std::scoped_lock 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 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 lk(this->lock); + + auto task = this->GetById(task_id); + if (task != nullptr) { + task->Cancel(); + } +} + +void TmaTaskList::CancelAll() { + std::scoped_lock 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 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 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); + } + } +} diff --git a/stratosphere/tma/source/tma_task_list.hpp b/stratosphere/tma/source/tma_task_list.hpp new file mode 100644 index 000000000..e29ddb274 --- /dev/null +++ b/stratosphere/tma/source/tma_task_list.hpp @@ -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 . + */ + +#pragma once +#include +#include +#include + +#include "tma_conn_service_ids.hpp" +#include "tma_task.hpp" + +class TmaTaskList { + private: + mutable HosMutex lock; + std::vector tasks[TmaTask::NumPriorities]; + std::vector 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(); +}; \ No newline at end of file diff --git a/stratosphere/tma/source/tma_usb_comms.cpp b/stratosphere/tma/source/tma_usb_comms.cpp new file mode 100644 index 000000000..32390c327 --- /dev/null +++ b/stratosphere/tma/source/tma_usb_comms.cpp @@ -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 . + */ + +#include "tma_usb_comms.hpp" + +/* TODO: Is this actually allowed? */ +#define ATMOSPHERE_INTERFACE_PROTOCOL 0xFC + +static std::atomic 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 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(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 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(); +} \ No newline at end of file diff --git a/stratosphere/tma/source/tma_usb_comms.hpp b/stratosphere/tma/source/tma_usb_comms.hpp new file mode 100644 index 000000000..52f119140 --- /dev/null +++ b/stratosphere/tma/source/tma_usb_comms.hpp @@ -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 . + */ + +#pragma once +#include +#include + +#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); +}; \ No newline at end of file diff --git a/stratosphere/tma/tma.json b/stratosphere/tma/tma.json new file mode 100644 index 000000000..81cbac6c6 --- /dev/null +++ b/stratosphere/tma/tma.json @@ -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 + }] +} \ No newline at end of file