Browse Source

Release

Just lacking documentation extras.
Will come in next commit.
master
luigoalma 6 months ago
commit
3a07896f33
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS. GPG Key ID: 1155216FF5495A42
21 changed files with 1723 additions and 0 deletions
  1. +11
    -0
      .gitignore
  2. +83
    -0
      3ds_gpio.rsf
  3. +16
    -0
      LICENSE.ctrulib.txt
  4. +24
    -0
      LICENSE.txt
  5. +154
    -0
      Makefile
  6. +22
    -0
      README.md
  7. +64
    -0
      include/3ds/errf.h
  8. +49
    -0
      include/3ds/ipc.h
  9. +21
    -0
      include/3ds/os.h
  10. +197
    -0
      include/3ds/result.h
  11. +40
    -0
      include/3ds/srv.h
  12. +94
    -0
      include/3ds/svc.h
  13. +80
    -0
      include/3ds/types.h
  14. +8
    -0
      include/err.h
  15. +71
    -0
      include/gpio.h
  16. +16
    -0
      include/memset.h
  17. +56
    -0
      source/3ds/errf.c
  18. +112
    -0
      source/3ds/srv.c
  19. +78
    -0
      source/3ds/svc.s
  20. +516
    -0
      source/gpio.c
  21. +11
    -0
      source/start.s

+ 11
- 0
.gitignore View File

@@ -0,0 +1,11 @@
*

!.gitignore
!include
!include/**
!source
!source/**
!Makefile
!*.rsf
!*.md
!LICENSE*

+ 83
- 0
3ds_gpio.rsf View File

@@ -0,0 +1,83 @@
BasicInfo:
Title : gpio
CompanyCode : "00"
ProductCode : lennybuilder # I'll join the lennys
ContentType : Application
Logo : None

TitleInfo:
UniqueId : 0x1B
Category : Base
Version : 2

Option:
UseOnSD : false
FreeProductCode : true # Removes limitations on ProductCode
MediaFootPadding : false # If true CCI files are created with padding
EnableCrypt : false # Enables encryption for NCCH and CIA
EnableCompress : true # Compresses exefs code

AccessControlInfo:
IdealProcessor : 1
AffinityMask : 2

Priority : -12

DisableDebug : true
EnableForceDebug : false
CanWriteSharedPage : false
CanUsePrivilegedPriority : false
CanUseNonAlphabetAndNumber : false
PermitMainFunctionArgument : false
CanShareDeviceMemory : false
RunnableOnSleep : true
SpecialMemoryArrange : false
ResourceLimitCategory : Other

CoreVersion : 2
DescVersion : 2

MemoryType : Base # Application / System / Base
HandleTableSize: 0

MemoryMapping:
# none needed

IORegisterMapping:
- 0x1EC47000 # GPIO Registers

SystemCallAccess:
ExitProcess: 3
SleepThread: 10
CloseHandle: 35
ConnectToPort: 45
SendSyncRequest: 50
GetProcessId: 53
AcceptSession: 74
ReplyAndReceive: 79

InterruptNumbers:
- 0x60
- 0x63
- 0x64
- 0x66
- 0x68
- 0x69
- 0x6A
- 0x6B
- 0x6C
- 0x6D
- 0x6E
- 0x6F
- 0x70
- 0x71
- 0x72
- 0x73

ServiceAccessControl:
FileSystemAccess:

SystemControlInfo:
SaveDataSize: 0KB # It doesn't use any save data.
RemasterVersion: 0
StackSize: 0x1000

+ 16
- 0
LICENSE.ctrulib.txt View File

@@ -0,0 +1,16 @@
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.

Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you
must not claim that you wrote the original software. If you use
this software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

+ 24
- 0
LICENSE.txt View File

@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>

+ 154
- 0
Makefile View File

@@ -0,0 +1,154 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------

ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif

TOPDIR ?= $(CURDIR)
include $(DEVKITARM)/3ds_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
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source source/3ds
DATA :=
INCLUDES := include include/3ds

#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=soft -mtp=soft
DEFINES := -DARM11 -D_3DS

CFLAGS := -g -std=gnu11 -Wall -Wextra -Werror -Wno-unused-value -Os -flto -mword-relocations \
-fomit-frame-pointer -ffunction-sections -fdata-sections \
-fno-exceptions -fno-ident -fno-unwind-tables -fno-asynchronous-unwind-tables \
$(ARCH) $(DEFINES)

CFLAGS += $(INCLUDE)

CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11

ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -nostartfiles -nostdlib \
-g $(ARCH) -Wl,-Map,$(notdir $*.map)

LIBS :=

#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(TOPDIR)


#---------------------------------------------------------------------------------
# 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_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))

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)

.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).cxi $(TARGET).elf


#---------------------------------------------------------------------------------
else
.PHONY: all

DEPENDS := $(OFILES:.o=.d)

#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).cxi

$(OUTPUT).cxi : $(OUTPUT).elf $(OUTPUT).rsf
@makerom -f ncch -rsf $(word 2,$^) -o $@ -elf $<
@echo built ... $(notdir $@)

$(OUTPUT).elf : $(OFILES)

%.elf: $(OFILES)
@echo linking $(notdir $@)
$(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
@$(NM) -CSn $@ > $(notdir $*.lst)

$(OFILES_SRC) : $(HFILES_BIN)

#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h: %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.xml.o %_xml.h: %.xml
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)

-include $(DEPENDS)

#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

+ 22
- 0
README.md View File

@@ -0,0 +1,22 @@
# 3ds_gpio

Open source implementation of `gpio` system module.\
With intent of being a documentation supplement, but also working as replacement.\
Also in mind trying to make result binary as small as possible.

## Building

Just run `make`.\
It will create a cxi file, and you can extract `code.bin` and `exheader.bin` with `ctrtool`, or some other tool, to place it in `/luma/titles/0004013000001B02/`.\
This requires game patching to be enabled on luma config.

## License

This code itself is under Unlicense. Read `LICENSE.txt`\
The folders `source/3ds` and `include/3ds` are source files taken from [ctrulib](https://github.com/smealum/ctrulib), with modifications for the purpose of this program.\
Copy of ctrulib's license in `LICENSE.ctrulib.txt`

## Modifications to ctrulib

Ctrulib changed to generate smaller code, slimmed down sources and headers for a quicker read, and not depend on std libraries.\
As well some changes to behavior on result throw.

+ 64
- 0
include/3ds/errf.h View File

@@ -0,0 +1,64 @@
/**
* @file errf.h
* @brief Error Display API
*/

#pragma once

#include <3ds/types.h>

/// Types of errors that can be thrown by err:f.
typedef enum {
ERRF_ERRTYPE_GENERIC = 0, ///< For generic errors. Shows miscellaneous info.
ERRF_ERRTYPE_MEM_CORRUPT = 1, ///< Same output as generic, but informs the user that "the System Memory has been damaged".
ERRF_ERRTYPE_CARD_REMOVED = 2, ///< Displays the "The Game Card was removed." message.
ERRF_ERRTYPE_EXCEPTION = 3, ///< For exceptions, or more specifically 'crashes'. union data should be exception_data.
ERRF_ERRTYPE_FAILURE = 4, ///< For general failure. Shows a message. union data should have a string set in failure_mesg
ERRF_ERRTYPE_LOGGED = 5, ///< Outputs logs to NAND in some cases.
} ERRF_ErrType;

/// Types of 'Exceptions' thrown for ERRF_ERRTYPE_EXCEPTION
typedef enum {
ERRF_EXCEPTION_PREFETCH_ABORT = 0, ///< Prefetch Abort
ERRF_EXCEPTION_DATA_ABORT = 1, ///< Data abort
ERRF_EXCEPTION_UNDEFINED = 2, ///< Undefined instruction
ERRF_EXCEPTION_VFP = 3, ///< VFP (floating point) exception.
} ERRF_ExceptionType;

typedef struct {
ERRF_ExceptionType type; ///< Type of the exception. One of the ERRF_EXCEPTION_* values.
u8 reserved[3];
u32 fsr; ///< ifsr (prefetch abort) / dfsr (data abort)
u32 far; ///< pc = ifar (prefetch abort) / dfar (data abort)
u32 fpexc;
u32 fpinst;
u32 fpinst2;
} ERRF_ExceptionInfo;

typedef struct {
ERRF_ExceptionInfo excep; ///< Exception info struct
CpuRegisters regs; ///< CPU register dump.
} ERRF_ExceptionData;

typedef struct {
ERRF_ErrType type; ///< Type, one of the ERRF_ERRTYPE_* enum
u8 revHigh; ///< High revison ID
u16 revLow; ///< Low revision ID
u32 resCode; ///< Result code
u32 pcAddr; ///< PC address at exception
u32 procId; ///< Process ID.
u64 titleId; ///< Title ID.
u64 appTitleId; ///< Application Title ID.
union {
ERRF_ExceptionData exception_data; ///< Data for when type is ERRF_ERRTYPE_EXCEPTION
char failure_mesg[0x60]; ///< String for when type is ERRF_ERRTYPE_FAILURE
} data; ///< The different types of data for errors.
} ERRF_FatalErrInfo;

/// Initializes ERR:f. Unless you plan to call ERRF_Throw yourself, do not use this.
Result errfInit(void);

/// Exits ERR:f. Unless you plan to call ERRF_Throw yourself, do not use this.
void errfExit(void);

void ERRF_ThrowResultNoRet(Result failure) __attribute__((noreturn, noinline));

+ 49
- 0
include/3ds/ipc.h View File

@@ -0,0 +1,49 @@
/**
* @file ipc.h
* @brief Inter Process Communication helpers
*/
#pragma once

#include <3ds/types.h>

/**
* @brief Creates a command header to be used for IPC
* @param command_id ID of the command to create a header for.
* @param normal_params Size of the normal parameters in words. Up to 63.
* @param translate_params Size of the translate parameters in words. Up to 63.
* @return The created IPC header.
*
* Normal parameters are sent directly to the process while the translate parameters might go through modifications and checks by the kernel.
* The translate parameters are described by headers generated with the IPC_Desc_* functions.
*
* @note While #normal_params is equivalent to the number of normal parameters, #translate_params includes the size occupied by the translate parameters headers.
*/
static inline u32 IPC_MakeHeader(u16 command_id, unsigned normal_params, unsigned translate_params)
{
return ((u32) command_id << 16) | (((u32) normal_params & 0x3F) << 6) | (((u32) translate_params & 0x3F) << 0);
}

/**
* @brief Creates a header to share handles
* @param number The number of handles following this header. Max 64.
* @return The created shared handles header.
*
* The #number next values are handles that will be shared between the two processes.
*
* @note Zero values will have no effect.
*/
static inline u32 IPC_Desc_SharedHandles(unsigned number)
{
return ((u32)(number - 1) << 26);
}

/**
* @brief Returns the code to ask the kernel to fill the handle with the current process handle.
* @return The code to request the current process handle.
*
* The next value is a placeholder that will be replaced by the current process handle by the kernel.
*/
static inline u32 IPC_Desc_CurProcessHandle(void)
{
return 0x20;
}

+ 21
- 0
include/3ds/os.h View File

@@ -0,0 +1,21 @@
/**
* @file os.h
* @brief OS related stuff.
*/
#pragma once
#include "svc.h"

/// Packs a system version from its components.
#define SYSTEM_VERSION(major, minor, revision) \
(((major)<<24)|((minor)<<16)|((revision)<<8))

/**
* @brief Gets the system's FIRM version.
* @return The system's FIRM version.
*
* This can be used to compare system versions easily with @ref SYSTEM_VERSION.
*/
static inline u32 osGetFirmVersion(void)
{
return (*(vu32*)0x1FF80060) & ~0xFF;
}

+ 197
- 0
include/3ds/result.h View File

@@ -0,0 +1,197 @@
/**
* @file result.h
* @brief 3DS result code tools
*/
#pragma once
#include "types.h"

/// Checks whether a result code indicates success.
#define R_SUCCEEDED(res) ((res)>=0)
/// Checks whether a result code indicates failure.
#define R_FAILED(res) ((res)<0)
/// Returns the level of a result code.
#define R_LEVEL(res) (((res)>>27)&0x1F)
/// Returns the summary of a result code.
#define R_SUMMARY(res) (((res)>>21)&0x3F)
/// Returns the module ID of a result code.
#define R_MODULE(res) (((res)>>10)&0xFF)
/// Returns the description of a result code.
#define R_DESCRIPTION(res) ((res)&0x3FF)

#define R_DESCRANGE(res,lower,higher) \
(R_DESCRIPTION(res) >= lower && R_DESCRIPTION(res) <= higher)

#define R_MODULEDESCRANGE(res,module,lower,higher) \
(R_MODULE(res) == module && R_DESCRANGE(res,lower,higher))

/// Builds a result code from its constituent components.
#define MAKERESULT(level,summary,module,description) \
((((level)&0x1F)<<27) | (((summary)&0x3F)<<21) | (((module)&0xFF)<<10) | ((description)&0x3FF))

/// Result code level values.
enum
{
// >= 0
RL_SUCCESS = 0,
RL_INFO = 1,

// < 0
RL_FATAL = 0x1F,
RL_RESET = RL_FATAL - 1,
RL_REINITIALIZE = RL_FATAL - 2,
RL_USAGE = RL_FATAL - 3,
RL_PERMANENT = RL_FATAL - 4,
RL_TEMPORARY = RL_FATAL - 5,
RL_STATUS = RL_FATAL - 6,
};

/// Result code summary values.
enum
{
RS_SUCCESS = 0,
RS_NOP = 1,
RS_WOULDBLOCK = 2,
RS_OUTOFRESOURCE = 3,
RS_NOTFOUND = 4,
RS_INVALIDSTATE = 5,
RS_NOTSUPPORTED = 6,
RS_INVALIDARG = 7,
RS_WRONGARG = 8,
RS_CANCELED = 9,
RS_STATUSCHANGED = 10,
RS_INTERNAL = 11,
RS_INVALIDRESVAL = 63,
};

/// Result code module values.
enum
{
RM_COMMON = 0,
RM_KERNEL = 1,
RM_UTIL = 2,
RM_FILE_SERVER = 3,
RM_LOADER_SERVER = 4,
RM_TCB = 5,
RM_OS = 6,
RM_DBG = 7,
RM_DMNT = 8,
RM_PDN = 9,
RM_GSP = 10,
RM_I2C = 11,
RM_GPIO = 12,
RM_DD = 13,
RM_CODEC = 14,
RM_SPI = 15,
RM_PXI = 16,
RM_FS = 17,
RM_DI = 18,
RM_HID = 19,
RM_CAM = 20,
RM_PI = 21,
RM_PM = 22,
RM_PM_LOW = 23,
RM_FSI = 24,
RM_SRV = 25,
RM_NDM = 26,
RM_NWM = 27,
RM_SOC = 28,
RM_LDR = 29,
RM_ACC = 30,
RM_ROMFS = 31,
RM_AM = 32,
RM_HIO = 33,
RM_UPDATER = 34,
RM_MIC = 35,
RM_FND = 36,
RM_MP = 37,
RM_MPWL = 38,
RM_AC = 39,
RM_HTTP = 40,
RM_DSP = 41,
RM_SND = 42,
RM_DLP = 43,
RM_HIO_LOW = 44,
RM_CSND = 45,
RM_SSL = 46,
RM_AM_LOW = 47,
RM_NEX = 48,
RM_FRIENDS = 49,
RM_RDT = 50,
RM_APPLET = 51,
RM_NIM = 52,
RM_PTM = 53,
RM_MIDI = 54,
RM_MC = 55,
RM_SWC = 56,
RM_FATFS = 57,
RM_NGC = 58,
RM_CARD = 59,
RM_CARDNOR = 60,
RM_SDMC = 61,
RM_BOSS = 62,
RM_DBM = 63,
RM_CONFIG = 64,
RM_PS = 65,
RM_CEC = 66,
RM_IR = 67,
RM_UDS = 68,
RM_PL = 69,
RM_CUP = 70,
RM_GYROSCOPE = 71,
RM_MCU = 72,
RM_NS = 73,
RM_NEWS = 74,
RM_RO = 75,
RM_GD = 76,
RM_CARD_SPI = 77,
RM_EC = 78,
RM_WEB_BROWSER = 79,
RM_TEST = 80,
RM_ENC = 81,
RM_PIA = 82,
RM_ACT = 83,
RM_VCTL = 84,
RM_OLV = 85,
RM_NEIA = 86,
RM_NPNS = 87,
RM_AVD = 90,
RM_L2B = 91,
RM_MVD = 92,
RM_NFC = 93,
RM_UART = 94,
RM_SPM = 95,
RM_QTM = 96,
RM_NFP = 97,
RM_APPLICATION = 254,
RM_INVALIDRESVAL = 255,
};

/// Result code generic description values.
enum
{
RD_SUCCESS = 0,
RD_INVALID_RESULT_VALUE = 0x3FF,
RD_TIMEOUT = RD_INVALID_RESULT_VALUE - 1,
RD_OUT_OF_RANGE = RD_INVALID_RESULT_VALUE - 2,
RD_ALREADY_EXISTS = RD_INVALID_RESULT_VALUE - 3,
RD_CANCEL_REQUESTED = RD_INVALID_RESULT_VALUE - 4,
RD_NOT_FOUND = RD_INVALID_RESULT_VALUE - 5,
RD_ALREADY_INITIALIZED = RD_INVALID_RESULT_VALUE - 6,
RD_NOT_INITIALIZED = RD_INVALID_RESULT_VALUE - 7,
RD_INVALID_HANDLE = RD_INVALID_RESULT_VALUE - 8,
RD_INVALID_POINTER = RD_INVALID_RESULT_VALUE - 9,
RD_INVALID_ADDRESS = RD_INVALID_RESULT_VALUE - 10,
RD_NOT_IMPLEMENTED = RD_INVALID_RESULT_VALUE - 11,
RD_OUT_OF_MEMORY = RD_INVALID_RESULT_VALUE - 12,
RD_MISALIGNED_SIZE = RD_INVALID_RESULT_VALUE - 13,
RD_MISALIGNED_ADDRESS = RD_INVALID_RESULT_VALUE - 14,
RD_BUSY = RD_INVALID_RESULT_VALUE - 15,
RD_NO_DATA = RD_INVALID_RESULT_VALUE - 16,
RD_INVALID_COMBINATION = RD_INVALID_RESULT_VALUE - 17,
RD_INVALID_ENUM_VALUE = RD_INVALID_RESULT_VALUE - 18,
RD_INVALID_SIZE = RD_INVALID_RESULT_VALUE - 19,
RD_ALREADY_DONE = RD_INVALID_RESULT_VALUE - 20,
RD_NOT_AUTHORIZED = RD_INVALID_RESULT_VALUE - 21,
RD_TOO_LARGE = RD_INVALID_RESULT_VALUE - 22,
RD_INVALID_SELECTION = RD_INVALID_RESULT_VALUE - 23,
};

+ 40
- 0
include/3ds/srv.h View File

@@ -0,0 +1,40 @@
/**
* @file srv.h
* @brief Service API.
*/
#pragma once

/// Initializes the service API.
Result srvInit(void);

/// Exits the service API.
void srvExit(void);

/// Registers the current process as a client to the service API.
Result srvRegisterClient(void);

/**
* @brief Enables service notificatios, returning a notification semaphore.
* @param semaphoreOut Pointer to output the notification semaphore to.
*/
Result srvEnableNotification(Handle* semaphoreOut);

/**
* @brief Registers the current process as a service.
* @param out Pointer to write the service handle to.
* @param name Name of the service.
* @param maxSessions Maximum number of sessions the service can handle.
*/
Result srvRegisterService(Handle* out, const char* name, int maxSessions);

/**
* @brief Unregisters the current process as a service.
* @param name Name of the service.
*/
Result srvUnregisterService(const char* name);

/**
* @brief Receives a notification.
* @param notificationIdOut Pointer to output the ID of the received notification to.
*/
Result srvReceiveNotification(u32* notificationIdOut);

+ 94
- 0
include/3ds/svc.h View File

@@ -0,0 +1,94 @@
/**
* @file svc.h
* @brief Syscall wrappers.
*/
#pragma once

#include "types.h"

/**
* @brief Gets the thread local storage buffer.
* @return The thread local storage bufger.
*/
static inline void* getThreadLocalStorage(void)
{
void* ret;
__asm__ ("mrc p15, 0, %[data], c13, c0, 3" : [data] "=r" (ret));
return ret;
}

/**
* @brief Gets the thread command buffer.
* @return The thread command bufger.
*/
static inline u32* getThreadCommandBuffer(void)
{
return (u32*)((u8*)getThreadLocalStorage() + 0x80);
}

/**
* @brief Gets the ID of a process.
* @param[out] out Pointer to output the process ID to.
* @param handle Handle of the process to get the ID of.
*/
Result svcGetProcessId(u32 *out, Handle handle);

/**
* @brief Connects to a port.
* @param[out] out Pointer to output the port handle to.
* @param portName Name of the port.
*/
Result svcConnectToPort(volatile Handle* out, const char* portName);

/**
* @brief Puts the current thread to sleep.
* @param ns The minimum number of nanoseconds to sleep for.
*/
void svcSleepThread(s64 ns);

/**
* @brief Sends a synchronized request to a session handle.
* @param session Handle of the session.
*/
Result svcSendSyncRequest(Handle session);

/**
* @brief Accepts a session.
* @param[out] session Pointer to output the created session handle to.
* @param port Handle of the port to accept a session from.
*/
Result svcAcceptSession(Handle* session, Handle port);

/**
* @brief Replies to and receives a new request.
* @param index Pointer to the index of the request.
* @param handles Session handles to receive requests from.
* @param handleCount Number of handles.
* @param replyTarget Handle of the session to reply to.
*/
Result svcReplyAndReceive(s32* index, const Handle* handles, s32 handleCount, Handle replyTarget);

/**
* @brief Binds an event or semaphore handle to an ARM11 interrupt.
* @param interruptId Interrupt identfier (see https://www.3dbrew.org/wiki/ARM11_Interrupts).
* @param eventOrSemaphore Event or semaphore handle to bind to the given interrupt.
* @param priority Priority of the interrupt for the current process.
* @param isManualClear Indicates whether the interrupt has to be manually cleared or not (= level-high active).
*/
Result svcBindInterrupt(u32 interruptId, Handle eventOrSemaphore, s32 priority, bool isManualClear);

/**
* @brief Unbinds an event or semaphore handle from an ARM11 interrupt.
* @param interruptId Interrupt identfier, see (see https://www.3dbrew.org/wiki/ARM11_Interrupts).
* @param eventOrSemaphore Event or semaphore handle to unbind from the given interrupt.
*/
Result svcUnbindInterrupt(u32 interruptId, Handle eventOrSemaphore);

/**
* @brief Closes a handle.
* @param handle Handle to close.
*/
Result svcCloseHandle(Handle handle);

/// Stop point, does nothing if the process is not attached (as opposed to 'bkpt' instructions)
#define SVC_STOP_POINT __asm__ volatile("svc 0xFF");

+ 80
- 0
include/3ds/types.h View File

@@ -0,0 +1,80 @@
/**
* @file types.h
* @brief Various system types.
*/
#pragma once

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

/// The maximum value of a u64.
#define U64_MAX UINT64_MAX

/// would be nice if newlib had this already
#ifndef SSIZE_MAX
#ifdef SIZE_MAX
#define SSIZE_MAX ((SIZE_MAX) >> 1)
#endif
#endif

typedef uint8_t u8; ///< 8-bit unsigned integer
typedef uint16_t u16; ///< 16-bit unsigned integer
typedef uint32_t u32; ///< 32-bit unsigned integer
typedef uint64_t u64; ///< 64-bit unsigned integer
typedef uintptr_t uptr; ///< 32-bit unsigned integer

typedef int8_t s8; ///< 8-bit signed integer
typedef int16_t s16; ///< 16-bit signed integer
typedef int32_t s32; ///< 32-bit signed integer
typedef int64_t s64; ///< 64-bit signed integer

typedef volatile u8 vu8; ///< 8-bit volatile unsigned integer.
typedef volatile u16 vu16; ///< 16-bit volatile unsigned integer.
typedef volatile u32 vu32; ///< 32-bit volatile unsigned integer.
typedef volatile u64 vu64; ///< 64-bit volatile unsigned integer.

typedef volatile s8 vs8; ///< 8-bit volatile signed integer.
typedef volatile s16 vs16; ///< 16-bit volatile signed integer.
typedef volatile s32 vs32; ///< 32-bit volatile signed integer.
typedef volatile s64 vs64; ///< 64-bit volatile signed integer.

typedef u32 Handle; ///< Resource handle.
typedef s32 Result; ///< Function result.
typedef void (*ThreadFunc)(void *); ///< Thread entrypoint function.
typedef void (*voidfn)(void);

/// Creates a bitmask from a bit number.
#define BIT(n) (1U<<(n))

/// Aligns a struct (and other types?) to m, making sure that the size of the struct is a multiple of m.
#define ALIGN(m) __attribute__((aligned(m)))
/// Packs a struct (and other types?) so it won't include padding bytes.
#define PACKED __attribute__((packed))

#ifndef LIBCTRU_NO_DEPRECATION
/// Flags a function as deprecated.
#define DEPRECATED __attribute__ ((deprecated))
#else
/// Flags a function as deprecated.
#define DEPRECATED
#endif

/// Structure representing CPU registers
typedef struct {
u32 r[13]; ///< r0-r12.
u32 sp; ///< sp.
u32 lr; ///< lr.
u32 pc; ///< pc. May need to be adjusted.
u32 cpsr; ///< cpsr.
} CpuRegisters;

/// Structure representing FPU registers
typedef struct {
union {
struct PACKED { double d[16]; }; ///< d0-d15.
float s[32]; ///< s0-s31.
};
u32 fpscr; ///< fpscr.
u32 fpexc; ///< fpexc.
} FpuRegisters;

+ 8
- 0
include/err.h View File

@@ -0,0 +1,8 @@
#pragma once
#include <3ds/errf.h>
#include <3ds/types.h>
#include <3ds/result.h>

#define Err_Throw(failure) ERRF_ThrowResultNoRet(failure)

#define Err_FailedThrow(failure) do {Result __tmp = failure; if (R_FAILED(__tmp)) Err_Throw(__tmp);} while(0)

+ 71
- 0
include/gpio.h View File

@@ -0,0 +1,71 @@
#pragma once
#include <3ds/types.h>

// GPIO IO Memory data regions
#define GPIO_REG0 (*(vu16*)0x1EC47000)
#define GPIO_REG1 (*(vu32*)0x1EC47010)
#define GPIO_REG2 (*(vu16*)0x1EC47014)
#define GPIO_REG3 (*(vu32*)0x1EC47020)
#define GPIO_REG4 (*(vu32*)0x1EC47024)
#define GPIO_REG5 (*(vu16*)0x1EC47028)

// Single access bit masks
#define GPIO_MASK0 BIT(0)
#define GPIO_MASK1 BIT(1)
#define GPIO_MASK2 BIT(2)
#define GPIO_MASK3 BIT(3)
#define GPIO_MASK4 BIT(4)
#define GPIO_MASK5 BIT(5)
#define GPIO_MASK6 BIT(6)
#define GPIO_MASK7 BIT(7)
#define GPIO_MASK8 BIT(8)
#define GPIO_MASK9 BIT(9)
#define GPIO_MASK10 BIT(10)
#define GPIO_MASK11 BIT(11)
#define GPIO_MASK12 BIT(12)
#define GPIO_MASK13 BIT(13)
#define GPIO_MASK14 BIT(14)
#define GPIO_MASK15 BIT(15)
#define GPIO_MASK16 BIT(16)
#define GPIO_MASK17 BIT(17)
#define GPIO_MASK18 BIT(18)

// Need to define names per bit mask, depending of what's the true purpose of the bit when reading GPIO IO
// and use them instead of GPIO_MASKX defines directly

// here some based on 3dbrew information
#define GPIO_HID_PAD0 GPIO_MASK0
#define GPIO_IR_SEND GPIO_MASK10
#define GPIO_IR_RECEIVE GPIO_MASK11
#define GPIO_HID_PAD1 GPIO_MASK14
#define GPIO_WIFI_STATE GPIO_MASK18

// Max possible binds
#define GPIO_BIND_MAX 19

// Service allowed access bits
#define GPIO_CDC_MASK (GPIO_MASK6 | GPIO_MASK3)
#define GPIO_MCU_MASK (GPIO_WIFI_STATE | GPIO_MASK15 | GPIO_MASK5)
#define GPIO_HID_MASK (GPIO_HID_PAD1 | GPIO_MASK9 | GPIO_MASK8 | GPIO_HID_PAD0)
#define GPIO_NWM_MASK (GPIO_WIFI_STATE | GPIO_MASK5)
#define GPIO_IR_MASK_GE_V2048 (GPIO_IR_RECEIVE | GPIO_IR_SEND | GPIO_MASK9 | GPIO_MASK7 | GPIO_MASK6)
#define GPIO_IR_MASK_GE_V0 (GPIO_IR_RECEIVE | GPIO_IR_SEND | GPIO_MASK7)
#define GPIO_NFC_MASK (GPIO_MASK16 | GPIO_MASK13 | GPIO_MASK12)
#define GPIO_QTM_MASK (GPIO_MASK17)

// Accessable categories of bits when accessing IO
#define GPIO_ACCESS_REG0 (GPIO_MASK2 | GPIO_MASK1 | GPIO_HID_PAD0)
#define GPIO_ACCESS_REG1 (GPIO_MASK4 | GPIO_MASK3)
#define GPIO_ACCESS_REG2 (GPIO_MASK5)
#define GPIO_ACCESS_REG3 (GPIO_MASK17 | GPIO_MASK16 | GPIO_MASK15 | GPIO_HID_PAD1 | GPIO_MASK13 | GPIO_MASK12 | GPIO_IR_RECEIVE | GPIO_IR_SEND | GPIO_MASK9 | GPIO_MASK8 | GPIO_MASK7 | GPIO_MASK6)
#define GPIO_ACCESS_REG4 (GPIO_ACCESS_REG3)
#define GPIO_ACCESS_REG5 (GPIO_MASK18)

// Result values
#define GPIO_NOT_AUTHORIZED MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_GPIO, RD_NOT_AUTHORIZED)
#define GPIO_BUSY MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_GPIO, RD_BUSY)
#define GPIO_NOT_FOUND MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_GPIO, RD_NOT_FOUND)

// Result values, my additions edition:tm:
#define GPIO_INTERNAL_RANGE MAKERESULT(RL_FATAL, RS_INTERNAL, RM_GPIO, RD_OUT_OF_RANGE)
#define GPIO_CANCELED_RANGE MAKERESULT(RL_FATAL, RS_CANCELED, RM_GPIO, RD_OUT_OF_RANGE)

+ 16
- 0
include/memset.h View File

@@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
#include <stddef.h>

inline static void _memset32_aligned(void* dest, uint32_t c, size_t size) {
uint32_t *_dest = (uint32_t*)dest;
for (; size >= 4; size -= 4) {
*_dest = c;
_dest++;
}
uint8_t *_dest8 = (uint8_t*)_dest;
for (; size > 0; size--) {
*_dest8 = c;
_dest8++;
}
}

+ 56
- 0
source/3ds/errf.c View File

@@ -0,0 +1,56 @@
#include <memset.h>
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/errf.h>
#include <3ds/ipc.h>

static Handle errfHandle;
static int errfRefCount;

Result errfInit(void)
{
Result rc = 0;

if (errfRefCount++) return 0;

rc = svcConnectToPort(&errfHandle, "err:f");

if (R_FAILED(rc)) errfExit();
return rc;
}

void errfExit(void)
{
if (--errfRefCount)
return;
svcCloseHandle(errfHandle);
}

static inline void getCommonErrorData(ERRF_FatalErrInfo* error, Result failure)
{
error->resCode = failure;
svcGetProcessId(&error->procId, 0xFFFF8001);
}

void ERRF_ThrowResultNoRet(Result failure)
{
while (R_FAILED(errfInit())) {
svcSleepThread(100000000LLU);
}

// manually inlined ERRF_Throw and adjusted to make smaller code output
uint32_t *cmdbuf = getThreadCommandBuffer();
_memset32_aligned(&cmdbuf[1], 0, sizeof(ERRF_FatalErrInfo));
ERRF_FatalErrInfo* error = (ERRF_FatalErrInfo*)&cmdbuf[1];
error->type = ERRF_ERRTYPE_GENERIC;
error->pcAddr = (u32)__builtin_extract_return_addr(__builtin_return_address(0));
getCommonErrorData(error, failure);

svcSendSyncRequest(errfHandle);
errfExit();

for (;;) {
svcSleepThread(10000000000LLU); // lighter loop
}
}

+ 112
- 0
source/3ds/srv.c View File

@@ -0,0 +1,112 @@
#include <3ds/types.h>
#include <3ds/result.h>
#include <3ds/srv.h>
#include <3ds/svc.h>
#include <3ds/ipc.h>

static inline int str_len_and_copy(char* out, const char* name) {
int s = 0;
while (*name) {
*out = *name;
if(++s == 8)
return s;
name++;
out++;
}
*out = 0;
return s;
}

static Handle srvHandle;
static int srvRefCount;

Result srvInit(void)
{
Result rc = 0;

if (srvRefCount++) return 0;

rc = svcConnectToPort(&srvHandle, "srv:");
if (R_SUCCEEDED(rc)) rc = srvRegisterClient();

if (R_FAILED(rc)) srvExit();
return rc;
}

void srvExit(void)
{
if (--srvRefCount) return;

if (srvHandle != 0) svcCloseHandle(srvHandle);
srvHandle = 0;
}

Result srvRegisterClient(void)
{
Result rc = 0;
u32* cmdbuf = getThreadCommandBuffer();

cmdbuf[0] = IPC_MakeHeader(0x1,0,2); // 0x10002
cmdbuf[1] = IPC_Desc_CurProcessHandle();

if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc;

return cmdbuf[1];
}

Result srvEnableNotification(Handle* semaphoreOut)
{
Result rc = 0;
u32* cmdbuf = getThreadCommandBuffer();

cmdbuf[0] = IPC_MakeHeader(0x2,0,0); // 0x20000

if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc;

if(semaphoreOut) *semaphoreOut = cmdbuf[3];

return cmdbuf[1];
}

Result srvRegisterService(Handle* out, const char* name, int maxSessions)
{
Result rc = 0;
u32* cmdbuf = getThreadCommandBuffer();

cmdbuf[0] = IPC_MakeHeader(0x3,4,0); // 0x30100
cmdbuf[3] = str_len_and_copy((char*) &cmdbuf[1], name);
cmdbuf[4] = maxSessions;

if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc;

if(out) *out = cmdbuf[3];

return cmdbuf[1];
}

Result srvUnregisterService(const char* name)
{
Result rc = 0;
u32* cmdbuf = getThreadCommandBuffer();

cmdbuf[0] = IPC_MakeHeader(0x4,3,0); // 0x400C0
cmdbuf[3] = str_len_and_copy((char*) &cmdbuf[1], name);

if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc;

return cmdbuf[1];
}

Result srvReceiveNotification(u32* notificationIdOut)
{
Result rc = 0;
u32* cmdbuf = getThreadCommandBuffer();

cmdbuf[0] = IPC_MakeHeader(0xB,0,0); // 0xB0000

if(R_FAILED(rc = svcSendSyncRequest(srvHandle)))return rc;

if(notificationIdOut) *notificationIdOut = cmdbuf[2];

return cmdbuf[1];
}

+ 78
- 0
source/3ds/svc.s View File

@@ -0,0 +1,78 @@
.arch armv6k
.arm

.macro SVC_BEGIN name
.section .text.\name, "ax", %progbits
.global \name
.type \name, %function
.align 2
\name:
.endm

.macro SVC_END name
.size \name, .-\name
.endm

SVC_BEGIN svcSleepThread
svc 0x0A
bx lr
SVC_END svcSleepThread

SVC_BEGIN svcCloseHandle
svc 0x23
bx lr
SVC_END svcCloseHandle

SVC_BEGIN svcConnectToPort
str r0, [sp, #-0x4]!
svc 0x2D
ldr r3, [sp], #4
str r1, [r3]
bx lr
SVC_END svcConnectToPort

SVC_BEGIN svcSendSyncRequest
svc 0x32
bx lr
SVC_END svcSendSyncRequest

SVC_BEGIN svcGetProcessId
str r0, [sp, #-0x4]!
svc 0x35
ldr r3, [sp], #4
str r1, [r3]
bx lr
SVC_END svcGetProcessId

SVC_BEGIN svcBreak
svc 0x3C
bx lr
SVC_END svcBreak

SVC_BEGIN svcAcceptSession
str r0, [sp, #-4]!
svc 0x4A
ldr r2, [sp]
str r1, [r2]
add sp, sp, #4
bx lr
SVC_END svcAcceptSession

SVC_BEGIN svcReplyAndReceive
str r0, [sp, #-4]!
svc 0x4F
ldr r2, [sp]
str r1, [r2]
add sp, sp, #4
bx lr
SVC_END svcReplyAndReceive

SVC_BEGIN svcBindInterrupt
svc 0x50
bx lr
SVC_END svcBindInterrupt

SVC_BEGIN svcUnbindInterrupt
svc 0x51
bx lr
SVC_END svcUnbindInterrupt

+ 516
- 0
source/gpio.c View File

@@ -0,0 +1,516 @@
#include <3ds/ipc.h>
#include <3ds/os.h>
#include <3ds/result.h>
#include <3ds/svc.h>
#include <3ds/srv.h>
#include <3ds/types.h>
#include <gpio.h>
#include <err.h>
#include <memset.h>

#define OS_REMOTE_SESSION_CLOSED MAKERESULT(RL_STATUS, RS_CANCELED, RM_OS, 26)
#define OS_INVALID_HEADER MAKERESULT(RL_PERMANENT, RS_WRONGARG, RM_OS, 47)
#define OS_INVALID_IPC_PARAMATER MAKERESULT(RL_PERMANENT, RS_WRONGARG, RM_OS, 48)

static const char* const GPIO_ServiceNames[] = {"gpio:CDC", "gpio:MCU", "gpio:HID", "gpio:NWM", "gpio:IR", "gpio:NFC", "gpio:QTM"};
static const u32 GPIO_ServiceBitmasks_V2048[] = {GPIO_CDC_MASK, GPIO_MCU_MASK, GPIO_HID_MASK, GPIO_NWM_MASK, GPIO_IR_MASK_GE_V2048, GPIO_NFC_MASK, GPIO_QTM_MASK};
static const u32 GPIO_ServiceBitmasks_V0[] = {GPIO_CDC_MASK, GPIO_MCU_MASK, GPIO_HID_MASK, GPIO_NWM_MASK, GPIO_IR_MASK_GE_V0};
static __attribute__((section(".data.TerminationFlag"))) bool TerminationFlag = false;

inline static void HandleSRVNotification() {
u32 id;
Err_FailedThrow(srvReceiveNotification(&id));
if (id == 0x100)
TerminationFlag = true;
}

// reset maybe?
// but also this only existed since v2048, >= 8.0.0-18
// in any sense related to NFC or QTM?
// or perhaps a fix in GPIO behaviour?
// BIT(7) is only referenced and allowed access to IR
inline static void GPIO_InitIO() {
GPIO_REG3 |= BIT(23);
GPIO_REG3 &= ~BIT(7);
}

#define IF_MASK_INTERRUPT(_mask, _interrupt) else if (mask == _mask) *interrupt = _interrupt
// interrupts that can never happen due to access bits, using the following definition
// mainly left for documentary reason
// gcc will optimize it out initially
#define IF_DEADMASK_INTERRUPT(_mask, _interrupt) else if (0) *interrupt = _interrupt

Result GPIO_MaskToInterrupt(u32 mask, u8* interrupt) {
if(0) {} //dummy for the else if chain
IF_DEADMASK_INTERRUPT(GPIO_MASK1, 0x63); // Touchscreen
IF_DEADMASK_INTERRUPT(GPIO_MASK2, 0x60); // Shell opened
IF_MASK_INTERRUPT (GPIO_MASK3, 0x64); // Headphone jack plugged in/out
IF_DEADMASK_INTERRUPT(GPIO_MASK4, 0x66);
IF_MASK_INTERRUPT (GPIO_MASK6, 0x68); // IR
IF_MASK_INTERRUPT (GPIO_MASK7, 0x69);
IF_MASK_INTERRUPT (GPIO_MASK8, 0x6A);
IF_MASK_INTERRUPT (GPIO_MASK9, 0x6B);
IF_MASK_INTERRUPT (GPIO_MASK10, 0x6C);
IF_MASK_INTERRUPT (GPIO_MASK11, 0x6D);
IF_MASK_INTERRUPT (GPIO_MASK12, 0x6E);
IF_MASK_INTERRUPT (GPIO_MASK13, 0x6F);
IF_MASK_INTERRUPT (GPIO_MASK14, 0x70);
IF_MASK_INTERRUPT (GPIO_MASK15, 0x71); // MCU (HOME/POWER pressed/released or WiFi switch pressed)
IF_MASK_INTERRUPT (GPIO_MASK16, 0x72);
IF_MASK_INTERRUPT (GPIO_MASK17, 0x73); // related to QTM? only allowed binding on gpio:QTM
else return GPIO_NOT_FOUND;
return 0;
}

static Handle GPIO_BindHandles[GPIO_BIND_MAX] = {0};
static u32 GPIO_BindHandleStoreUsage = 0;

inline static bool GPIO_IsBindFree(u32 mask) {
return !(GPIO_BindHandleStoreUsage & mask);
}

inline static void GPIO_ReleaseBind(u8 bit) {
GPIO_BindHandleStoreUsage &= ~BIT(bit);
Err_FailedThrow(svcCloseHandle(GPIO_BindHandles[bit]));
}

inline static void GPIO_StoreBind(Handle bind, u8 bit) {
GPIO_BindHandles[bit] = bind;
GPIO_BindHandleStoreUsage |= BIT(bit);
}

inline static u32 Read_GPIO16(vu16* io, u32 mask, u32 access_mask, s8 left_shift) {
u32 value = *io;
mask &= access_mask;
if (left_shift < 0)
value >>= -left_shift;
else
value <<= left_shift;
return value & mask;
}

inline static void Write_GPIO16(vu16* io, u32 value, u32 mask, u32 access_mask, s8 left_shift) {
mask &= access_mask;
if (left_shift < 0) {
value >>= -left_shift;
mask >>= -left_shift;
} else {
value <<= left_shift;
mask <<= left_shift;
}
*io = (*io & ~mask) | (value & mask);
}

inline static u32 Read_GPIO32(vu32* io, u32 mask, u32 access_mask, s8 left_shift) {
u32 value = *io;
mask &= access_mask;
if (left_shift < 0)
value >>= -left_shift;
else
value <<= left_shift;
return value & mask;
}

inline static void Write_GPIO32(vu32* io, u32 value, u32 mask, u32 access_mask, s8 left_shift) {
mask &= access_mask;
if (left_shift < 0) {
value >>= -left_shift;
mask >>= -left_shift;
} else {
value <<= left_shift;
mask <<= left_shift;
}
*io = (*io & ~mask) | (value & mask);
}

// names for the IPC functions based off on 3dbrew named them

static Result GPIO_GetRegPart1(u32 service_bitmask, u32 mask, u32* value) {
*value = 0;
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG3))
return GPIO_NOT_FOUND;
if (mask & GPIO_ACCESS_REG1) {
*value |= Read_GPIO32(&GPIO_REG1, mask, GPIO_ACCESS_REG1, -5);
}
if (mask & GPIO_ACCESS_REG3) {
*value |= Read_GPIO32(&GPIO_REG3, mask, GPIO_ACCESS_REG3, -10);
}

return 0;
}

static Result GPIO_SetRegPart1(u32 service_bitmask, u32 mask, u32 value) {
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG3))
return GPIO_NOT_FOUND;
if (mask & GPIO_ACCESS_REG1) {
Write_GPIO32(&GPIO_REG1, value, mask, GPIO_ACCESS_REG1, 5);
}
if (mask & GPIO_ACCESS_REG3) {
Write_GPIO32(&GPIO_REG3, value, mask, GPIO_ACCESS_REG3, 10);
}

return 0;
}

static Result GPIO_GetRegPart2(u32 service_bitmask, u32 mask, u32* value) {
*value = 0;
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG4))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG1) {
*value |= Read_GPIO32(&GPIO_REG1, mask, GPIO_ACCESS_REG1, -13);
}
if (mask & GPIO_ACCESS_REG4) {
*value |= Read_GPIO32(&GPIO_REG4, mask, GPIO_ACCESS_REG4, 6);
}

return 0;
}

static Result GPIO_SetRegPart2(u32 service_bitmask, u32 mask, u32 value) {
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG4))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG1) {
Write_GPIO32(&GPIO_REG1, value, mask, GPIO_ACCESS_REG1, 13);
}
if (mask & GPIO_ACCESS_REG4) {
Write_GPIO32(&GPIO_REG4, value, mask, GPIO_ACCESS_REG4, -6);
}

return 0;
}

static Result GPIO_GetInterruptMask(u32 service_bitmask, u32 mask, u32* value) {
*value = 0;
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG4))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG1) {
*value |= Read_GPIO32(&GPIO_REG1, mask, GPIO_ACCESS_REG1, -21);
}
if (mask & GPIO_ACCESS_REG4) {
*value |= Read_GPIO32(&GPIO_REG4, mask, GPIO_ACCESS_REG4, -10);
}

return 0;
}

static Result GPIO_SetInterruptMask(u32 service_bitmask, u32 mask, u32 value) {
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG4))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG1) {
Write_GPIO32(&GPIO_REG1, value, mask, GPIO_ACCESS_REG1, 21);
}
if (mask & GPIO_ACCESS_REG4) {
Write_GPIO32(&GPIO_REG4, value, mask, GPIO_ACCESS_REG4, 10);
}

return 0;
}

static Result GPIO_GetGPIOData(u32 service_bitmask, u32 mask, u32* value) {
*value = 0;
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG0 | GPIO_ACCESS_REG1 | GPIO_ACCESS_REG2 | GPIO_ACCESS_REG3 | GPIO_ACCESS_REG5))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG0) {
*value |= Read_GPIO16(&GPIO_REG0, mask, GPIO_ACCESS_REG0, 0);
}
if (mask & GPIO_ACCESS_REG1) {
*value |= Read_GPIO32(&GPIO_REG1, mask, GPIO_ACCESS_REG1, 3);
}
if (mask & GPIO_ACCESS_REG2) {
*value |= Read_GPIO16(&GPIO_REG2, mask, GPIO_ACCESS_REG2, 5);
}
if (mask & GPIO_ACCESS_REG3) {
*value |= Read_GPIO32(&GPIO_REG3, mask, GPIO_ACCESS_REG3, 6);
}
if (mask & GPIO_ACCESS_REG5) {
*value |= Read_GPIO16(&GPIO_REG5, mask, GPIO_ACCESS_REG5, 18);
}

return 0;
}

static Result GPIO_SetGPIOData(u32 service_bitmask, u32 mask, u32 value) {
if (mask & ~service_bitmask)
return GPIO_NOT_AUTHORIZED;
if (mask & ~(GPIO_ACCESS_REG1 | GPIO_ACCESS_REG2 | GPIO_ACCESS_REG3 | GPIO_ACCESS_REG5))
return GPIO_NOT_FOUND;

if (mask & GPIO_ACCESS_REG1) {
Write_GPIO32(&GPIO_REG1, value, mask, GPIO_ACCESS_REG1, -3);
}
if (mask & GPIO_ACCESS_REG2) {
Write_GPIO16(&GPIO_REG2, value, mask, GPIO_ACCESS_REG2, -5);
}
if (mask & GPIO_ACCESS_REG3) {
Write_GPIO32(&GPIO_REG3, value, mask, GPIO_ACCESS_REG3, -6);
}
if (mask & GPIO_ACCESS_REG5) {
Write_GPIO16(&GPIO_REG5, value, mask, GPIO_ACCESS_REG5, -18);
}

return 0;
}

static Result GPIO_BindInterrupt(u32 service_bitmask, u32 mask, Handle bind, s32 priority) {
if (!GPIO_IsBindFree(mask)) {
Err_FailedThrow(svcCloseHandle(bind));
return GPIO_BUSY;
}

if (mask & ~service_bitmask) {
Err_FailedThrow(svcCloseHandle(bind));
return GPIO_NOT_AUTHORIZED;
}

u8 interrupt;
Result res = GPIO_MaskToInterrupt(mask, &interrupt);
if (R_FAILED(res)) {
Err_FailedThrow(svcCloseHandle(bind));
return res;
}

res = svcBindInterrupt(interrupt, bind, priority, false);
Err_FailedThrow(res);

u8 bit;
for (bit = 0; mask != BIT(bit); bit++) {}

GPIO_StoreBind(bind, bit);

return res;
}

// v2048 -> v3073: They added svcCloseHandle calls on failed exits
static Result GPIO_UnbindInterrupt(u32 service_bitmask, u32 mask, Handle bind) {
if (GPIO_IsBindFree(mask)) {
Err_FailedThrow(svcCloseHandle(bind));
return GPIO_BUSY;
}

if (mask & ~service_bitmask) {
Err_FailedThrow(svcCloseHandle(bind));
return GPIO_NOT_AUTHORIZED;
}

u8 interrupt;
Result res = GPIO_MaskToInterrupt(mask, &interrupt);
if (R_FAILED(res)) {
Err_FailedThrow(svcCloseHandle(bind));
return res;
}

res = svcUnbindInterrupt(interrupt, bind);
Err_FailedThrow(res);

u8 bit;
for (bit = 0; mask != BIT(bit); bit++) {}

GPIO_ReleaseBind(bit);
Err_FailedThrow(svcCloseHandle(bind));

return res;
}

static void GPIO_IPCSession(u32 service_bitmask) {
u32* cmdbuf = getThreadCommandBuffer();
u32 value;

switch (cmdbuf[0] >> 16) {
case 0x1:
cmdbuf[0] = IPC_MakeHeader(0x1, 2, 0);
cmdbuf[1] = GPIO_GetRegPart1(service_bitmask, cmdbuf[1], &value);
cmdbuf[2] = value;
break;
case 0x2:
cmdbuf[0] = IPC_MakeHeader(0x2, 1, 0);
cmdbuf[1] = GPIO_SetRegPart1(service_bitmask, cmdbuf[2], cmdbuf[1]);
break;
case 0x3:
cmdbuf[0] = IPC_MakeHeader(0x3, 2, 0);
cmdbuf[1] = GPIO_GetRegPart2(service_bitmask, cmdbuf[1], &value);
cmdbuf[2] = value;
break;
case 0x4:
cmdbuf[0] = IPC_MakeHeader(0x4, 1, 0);
cmdbuf[1] = GPIO_SetRegPart2(service_bitmask, cmdbuf[2], cmdbuf[1]);
break;
case 0x5:
cmdbuf[0] = IPC_MakeHeader(0x5, 2, 0);
cmdbuf[1] = GPIO_GetInterruptMask(service_bitmask, cmdbuf[1], &value);
cmdbuf[2] = value;
break;
case 0x6:
cmdbuf[0] = IPC_MakeHeader(0x6, 1, 0);
cmdbuf[1] = GPIO_SetInterruptMask(service_bitmask, cmdbuf[2], cmdbuf[1]);
break;
case 0x7:
cmdbuf[0] = IPC_MakeHeader(0x7, 2, 0);
cmdbuf[1] = GPIO_GetGPIOData(service_bitmask, cmdbuf[1], &value);
cmdbuf[2] = value;
break;
case 0x8:
cmdbuf[0] = IPC_MakeHeader(0x8, 1, 0);
cmdbuf[1] = GPIO_SetGPIOData(service_bitmask, cmdbuf[2], cmdbuf[1]);
break;
case 0x9:
if (cmdbuf[0] != IPC_MakeHeader(0x9, 2, 2) || cmdbuf[3] != IPC_Desc_SharedHandles(1)) {
cmdbuf[0] = IPC_MakeHeader(0x0, 1, 0);
cmdbuf[1] = OS_INVALID_IPC_PARAMATER;
break;
}
cmdbuf[0] = IPC_MakeHeader(0x9, 1, 0);
cmdbuf[1] = GPIO_BindInterrupt(service_bitmask, cmdbuf[1], cmdbuf[4], (s32)cmdbuf[2]);
break;
case 0xA:
if (cmdbuf[0] != IPC_MakeHeader(0xA, 1, 2) || cmdbuf[2] != IPC_Desc_SharedHandles(1)) {
cmdbuf[0] = IPC_MakeHeader(0x0, 1, 0);
cmdbuf[1] = OS_INVALID_IPC_PARAMATER;
break;
}
cmdbuf[0] = IPC_MakeHeader(0xA, 1, 0);
cmdbuf[1] = GPIO_UnbindInterrupt(service_bitmask, cmdbuf[1], cmdbuf[3]);
break;
default:
cmdbuf[0] = IPC_MakeHeader(0x0, 1, 0);
cmdbuf[1] = OS_INVALID_HEADER;
}
}

static void GPIO_BindClosedSessionClean(u32 service_bitmask) {
for(u8 i = 0; i < GPIO_BIND_MAX; i++) {
u8 interrupt;
u32 mask = BIT(i);
if (!(service_bitmask & mask) || GPIO_IsBindFree(mask) || R_FAILED(GPIO_MaskToInterrupt(mask, &interrupt)))
continue;

Err_FailedThrow(svcUnbindInterrupt(interrupt, GPIO_BindHandles[i]));
GPIO_ReleaseBind(i);
}
}

static inline void initBSS() {
extern void* __bss_start__;
extern void* __bss_end__;
_memset32_aligned(__bss_start__, 0, (size_t)__bss_end__ - (size_t)__bss_start__);
}

void GPIOMain() {
initBSS();
GPIO_InitIO();

bool is_pre_8x = osGetFirmVersion() < SYSTEM_VERSION(2, 44, 6);
const u32* GPIO_ServiceBitmasks = is_pre_8x ? GPIO_ServiceBitmasks_V0 : GPIO_ServiceBitmasks_V2048;
const s32 SERVICE_COUNT = is_pre_8x ? 5 : 7;
const s32 INDEX_MAX = SERVICE_COUNT * 2 + 1; // 11 pre 8.0, 15 post 8.0
const s32 REMOTE_SESSION_INDEX = SERVICE_COUNT + 1; // 6 pre 8.0, 8 post 8.0

Handle session_handles[15];

u32 service_indexes[7];

s32 handle_count = SERVICE_COUNT + 1;

Err_FailedThrow(srvInit());

for (int i = 0; i < SERVICE_COUNT; i++)
Err_FailedThrow(srvRegisterService(&session_handles[i + 1], GPIO_ServiceNames[i], 1));

Err_FailedThrow(srvEnableNotification(&session_handles[0]));

Handle target = 0;
s32 target_index = -1;
for (;;) {
s32 index;

if (!target) {
if (TerminationFlag && handle_count == REMOTE_SESSION_INDEX)
break;
else
*getThreadCommandBuffer() = 0xFFFF0000;
}

Result res = svcReplyAndReceive(&index, session_handles, handle_count, target);
s32 last_target_index = target_index;
target = 0;
target_index = -1;

if (R_FAILED(res)) {

if (res != OS_REMOTE_SESSION_CLOSED)
Err_Throw(res);

else if (index == -1) {
if (last_target_index == -1)
Err_Throw(GPIO_CANCELED_RANGE);
else
index = last_target_index;
}

else if (index >= handle_count)
Err_Throw(GPIO_CANCELED_RANGE);

svcCloseHandle(session_handles[index]);
GPIO_BindClosedSessionClean(GPIO_ServiceBitmasks[service_indexes[index - REMOTE_SESSION_INDEX]]);
handle_count--;
for (s32 i = index - REMOTE_SESSION_INDEX; i < handle_count - REMOTE_SESSION_INDEX; i++) {
session_handles[REMOTE_SESSION_INDEX + i] = session_handles[REMOTE_SESSION_INDEX + i + 1];
service_indexes[i] = service_indexes[i + 1];
}

continue;
}

if (index == 0)
HandleSRVNotification();

else if (index >= 1 && index < REMOTE_SESSION_INDEX) {
Handle newsession = 0;
Err_FailedThrow(svcAcceptSession(&newsession, session_handles[index]));

if (handle_count >= INDEX_MAX) {
svcCloseHandle(newsession);
continue;
}

session_handles[handle_count] = newsession;
service_indexes[handle_count - REMOTE_SESSION_INDEX] = index - 1;
handle_count++;

} else if (index >= REMOTE_SESSION_INDEX && index < INDEX_MAX) {
GPIO_IPCSession(GPIO_ServiceBitmasks[service_indexes[index - REMOTE_SESSION_INDEX]]);
target = session_handles[index];
target_index = index;

} else {
Err_Throw(GPIO_INTERNAL_RANGE);
}
}

for (int i = 0; i < SERVICE_COUNT; i++) {
Err_FailedThrow(srvUnregisterService(GPIO_ServiceNames[i]));
svcCloseHandle(session_handles[i + 1]);
}

svcCloseHandle(session_handles[0]);

srvExit();
}

+ 11
- 0
source/start.s View File

@@ -0,0 +1,11 @@
.arch armv6k
.section .init, "ax", %progbits
.arm
.align 2
.global _start
.syntax unified
.type _start, %function
_start:
bl GPIOMain
svc 0x03
.size _start, .-_start

Loading…
Cancel
Save