diff --git a/Source/sys-clk/common/include/SaltyNX.h b/Source/sys-clk/common/include/SaltyNX.h new file mode 100644 index 00000000..06ebc50b --- /dev/null +++ b/Source/sys-clk/common/include/SaltyNX.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) MasaGratoR + * + * 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 "ipc.h" + +Handle saltysd_orig; + +Result SaltySD_Connect() { + for (int i = 0; i < 200; i++) { + if (!svcConnectToNamedPort(&saltysd_orig, "SaltySD")) + return 0; + svcSleepThread(1000*1000); + } + return 1; +} + +Result SaltySD_Term() +{ + Result ret; + IpcCommand c; + + ipcInitialize(&c); + ipcSendPid(&c); + + struct input + { + u64 magic; + u64 cmd_id; + u64 zero; + u64 reserved[2]; + } *raw; + + raw = (input*)ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->zero = 0; + + ret = ipcDispatch(saltysd_orig); + + if (R_SUCCEEDED(ret)) + { + IpcParsedCommand r; + ipcParse(&r); + + struct output { + u64 magic; + u64 result; + } *resp = (output*)r.Raw; + + ret = resp->result; + } + + // Session terminated works too. + svcCloseHandle(saltysd_orig); + if (ret == 0xf601) return 0; + + return ret; +} + +Result SaltySD_CheckIfSharedMemoryAvailable(ptrdiff_t *offset, u64 size) +{ + Result ret = 0; + + // Send a command + IpcCommand c; + ipcInitialize(&c); + ipcSendPid(&c); + + struct input { + u64 magic; + u64 cmd_id; + u64 size; + u32 reserved[2]; + } *raw; + + raw = (input*)ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 6; + raw->size = size; + + ret = ipcDispatch(saltysd_orig); + + if (R_SUCCEEDED(ret)) { + IpcParsedCommand r; + ipcParse(&r); + + struct output { + u64 magic; + u64 result; + u64 offset; + } *resp = (output*)r.Raw; + + ret = resp->result; + + if (!ret) + { + *offset = resp->offset; + } + } + + return ret; +} + +Result SaltySD_GetSharedMemoryHandle(Handle *retrieve) +{ + Result ret = 0; + + // Send a command + IpcCommand c; + ipcInitialize(&c); + ipcSendPid(&c); + + struct input { + u64 magic; + u64 cmd_id; + u32 reserved[4]; + } *raw; + + raw = (input*)ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 7; + + ret = ipcDispatch(saltysd_orig); + + if (R_SUCCEEDED(ret)) { + IpcParsedCommand r; + ipcParse(&r); + + struct output { + u64 magic; + u64 result; + u64 reserved[2]; + } *resp = (output*)r.Raw; + + ret = resp->result; + + if (!ret) + { + *retrieve = r.Handles[0]; + } + } + + return ret; +} + +Result SaltySD_GetDisplayRefreshRate(uint8_t* refreshRate) +{ + Result ret = 0; + + // Send a command + IpcCommand c; + ipcInitialize(&c); + ipcSendPid(&c); + + struct input { + u64 magic; + u64 cmd_id; + u64 zero; + u64 reserved; + } *raw; + + raw = (input*)ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 10; + raw->zero = 0; + + ret = ipcDispatch(saltysd_orig); + + if (R_SUCCEEDED(ret)) { + IpcParsedCommand r; + ipcParse(&r); + + struct output { + u64 magic; + u64 result; + u64 refreshRate; + u64 reserved; + } *resp = (output*)r.Raw; + + ret = resp->result; + + if (!ret) + { + *refreshRate = (uint8_t)(resp->refreshRate); + } + } + + return ret; +} + +Result SaltySD_SetDisplayRefreshRate(uint8_t refreshRate) +{ + Result ret = 0; + + // Send a command + IpcCommand c; + ipcInitialize(&c); + ipcSendPid(&c); + + struct input { + u64 magic; + u64 cmd_id; + u64 refreshRate; + u64 reserved; + } *raw; + + raw = (input*)ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 11; + raw->refreshRate = refreshRate; + + ret = ipcDispatch(saltysd_orig); + + if (R_SUCCEEDED(ret)) { + IpcParsedCommand r; + ipcParse(&r); + + struct output { + u64 magic; + u64 result; + u64 reserved[2]; + } *resp = (output*)r.Raw; + + ret = resp->result; + } + + return ret; +} \ No newline at end of file diff --git a/Source/sys-clk/common/include/battery.h b/Source/sys-clk/common/include/battery.h index b2c85253..dff0b08b 100644 --- a/Source/sys-clk/common/include/battery.h +++ b/Source/sys-clk/common/include/battery.h @@ -19,7 +19,6 @@ #include #include #include -// Battery charging flags typedef enum { BatteryFlag_NoHub = BIT(0), // Hub is disconnected BatteryFlag_Rail = BIT(8), // At least one Joy-con is charging from rail @@ -27,7 +26,6 @@ typedef enum { BatteryFlag_ACC = BIT(16) // Accessory } BatteryChargeFlags; -// Power Delivery Controller State (BM92T series) typedef enum { PDState_NewPDO = 1, // Received new Power Data Object PDState_NoPD = 2, // No Power Delivery source is detected @@ -47,14 +45,11 @@ typedef enum { ChargerType_Apple_1000mA = 8, ChargerType_Apple_2000mA = 9 } BatteryChargerType; - -// Power role (USB Power Delivery) typedef enum { PowerRole_Sink = 1, // Device is receiving power PowerRole_Source = 2 // Device is providing power } BatteryPowerRole; -// Complete battery charge information structure typedef struct { int32_t InputCurrentLimit; // Input (Sink) current limit in mA int32_t VBUSCurrentLimit; // Output (Source/VBUS/OTG) current limit in mA @@ -64,7 +59,7 @@ typedef struct { int32_t unk_x14; // Unknown field (possibly flags) BatteryPDControllerState PDControllerState; // PD Controller State int32_t BatteryTemperature; // Battery temperature in milli-Celsius - int32_t RawBatteryCharge; // Battery charge in per cent-mille (100% = 100000) + int32_t RawBatteryCharge; // Battery charge in percentmille int32_t VoltageAvg; // Average voltage in mV int32_t BatteryAge; // Battery health (capacity full/design) in pcm BatteryPowerRole PowerRole; // Current power role @@ -74,36 +69,27 @@ typedef struct { BatteryChargeFlags Flags; // Various status flags } BatteryChargeInfo; -// Helper macro to check if battery charging is enabled #define IS_BATTERY_CHARGING_ENABLED(info) (((info)->unk_x14 >> 8) & 1) -// Initialize the battery info driver Result batteryInfoInitialize(void); -// Cleanup the battery info driver void batteryInfoExit(void); -// Get complete battery charge information Result batteryInfoGetChargeInfo(BatteryChargeInfo *out); -// Get battery charge percentage (0-100) Result batteryInfoGetChargePercentage(u32 *out); -// Check if enough power is being supplied Result batteryInfoIsEnoughPowerSupplied(bool *out); -// Battery charge control functions Result batteryInfoEnableCharging(void); Result batteryInfoDisableCharging(void); Result batteryInfoEnableFastCharging(void); Result batteryInfoDisableFastCharging(void); -// Helper functions to get human-readable strings const char* batteryInfoGetChargerTypeString(BatteryChargerType type); const char* batteryInfoGetPowerRoleString(BatteryPowerRole role); const char* batteryInfoGetPDStateString(BatteryPDControllerState state); -// Convenience functions for common values static inline int batteryInfoGetTemperatureMiliCelsius(BatteryChargeInfo *info) { return info->BatteryTemperature; } @@ -120,7 +106,6 @@ static inline bool batteryInfoIsCharging(BatteryChargeInfo *info) { return IS_BATTERY_CHARGING_ENABLED(info); } -// String lookup tables static const char* s_chargerTypeStrings[] = { "None", "Power Delivery", @@ -187,7 +172,6 @@ static Result psmDisableFastBatteryCharging_internal(void) { return serviceDispatch(&g_psmService, 11); } -// Public API implementations Result batteryInfoInitialize(void) { if (g_batteryInfoInitialized) return 0; diff --git a/Source/sys-clk/common/include/ipc.h b/Source/sys-clk/common/include/ipc.h new file mode 100644 index 00000000..3015af32 --- /dev/null +++ b/Source/sys-clk/common/include/ipc.h @@ -0,0 +1,756 @@ +/** + * @file ipc.h + * @brief Inter-process communication handling + * @author plutoo + * @copyright libnx Authors (ISC License) + */ +#pragma once +#include + +/// IPC input header magic +#define SFCI_MAGIC 0x49434653 +/// IPC output header magic +#define SFCO_MAGIC 0x4f434653 + +/// IPC invalid object ID +#define IPC_INVALID_OBJECT_ID UINT32_MAX + +///@name IPC request building +///@{ + +/// IPC command (request) structure. +#define IPC_MAX_BUFFERS 8 +#define IPC_MAX_OBJECTS 8 + +typedef enum { + BufferType_Normal=0, ///< Regular buffer. + BufferType_Type1=1, ///< Allows ProcessMemory and shared TransferMemory. + BufferType_Invalid=2, + BufferType_Type3=3 ///< Same as Type1 except remote process is not allowed to use device-mapping. +} BufferType; + +typedef enum { + BufferDirection_Send=0, + BufferDirection_Recv=1, + BufferDirection_Exch=2, +} BufferDirection; + +typedef enum { + IpcCommandType_Invalid = 0, + IpcCommandType_LegacyRequest = 1, + IpcCommandType_Close = 2, + IpcCommandType_LegacyControl = 3, + IpcCommandType_Request = 4, + IpcCommandType_Control = 5, + IpcCommandType_RequestWithContext = 6, + IpcCommandType_ControlWithContext = 7, +} IpcCommandType; + +typedef enum { + DomainMessageType_Invalid = 0, + DomainMessageType_SendMessage = 1, + DomainMessageType_Close = 2, +} DomainMessageType; + +/// IPC domain message header. +typedef struct { + u8 Type; + u8 NumObjectIds; + u16 Length; + u32 ThisObjectId; + u32 Pad[2]; +} DomainMessageHeader; + +/// IPC domain response header. +typedef struct { + u32 NumObjectIds; + u32 Pad[3]; +} DomainResponseHeader; + + +typedef struct { + size_t NumSend; // A + size_t NumRecv; // B + size_t NumExch; // W + const void* Buffers[IPC_MAX_BUFFERS]; + size_t BufferSizes[IPC_MAX_BUFFERS]; + BufferType BufferTypes[IPC_MAX_BUFFERS]; + + size_t NumStaticIn; // X + size_t NumStaticOut; // C + const void* Statics[IPC_MAX_BUFFERS]; + size_t StaticSizes[IPC_MAX_BUFFERS]; + u8 StaticIndices[IPC_MAX_BUFFERS]; + + bool SendPid; + size_t NumHandlesCopy; + size_t NumHandlesMove; + Handle Handles[IPC_MAX_OBJECTS]; + + size_t NumObjectIds; + u32 ObjectIds[IPC_MAX_OBJECTS]; +} IpcCommand; + +/** + * @brief Initializes an IPC command structure. + * @param cmd IPC command structure. + */ +static inline void ipcInitialize(IpcCommand* cmd) { + *cmd = (IpcCommand){}; +} + +/// IPC buffer descriptor. +typedef struct { + u32 Size; ///< Size of the buffer. + u32 Addr; ///< Lower 32-bits of the address of the buffer + u32 Packed; ///< Packed data (including higher bits of the address) +} IpcBufferDescriptor; + +/// IPC static send-buffer descriptor. +typedef struct { + u32 Packed; ///< Packed data (including higher bits of the address) + u32 Addr; ///< Lower 32-bits of the address +} IpcStaticSendDescriptor; + +/// IPC static receive-buffer descriptor. +typedef struct { + u32 Addr; ///< Lower 32-bits of the address of the buffer + u32 Packed; ///< Packed data (including higher bits of the address) +} IpcStaticRecvDescriptor; + +/** + * @brief Adds a buffer to an IPC command structure. + * @param cmd IPC command structure. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param type Buffer type. + */ +static inline void ipcAddSendBuffer(IpcCommand* cmd, const void* buffer, size_t size, BufferType type) { + size_t off = cmd->NumSend; + cmd->Buffers[off] = buffer; + cmd->BufferSizes[off] = size; + cmd->BufferTypes[off] = type; + cmd->NumSend++; +} + +/** + * @brief Adds a receive-buffer to an IPC command structure. + * @param cmd IPC command structure. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param type Buffer type. + */ +static inline void ipcAddRecvBuffer(IpcCommand* cmd, void* buffer, size_t size, BufferType type) { + size_t off = cmd->NumSend + cmd->NumRecv; + cmd->Buffers[off] = buffer; + cmd->BufferSizes[off] = size; + cmd->BufferTypes[off] = type; + cmd->NumRecv++; +} + +/** + * @brief Adds an exchange-buffer to an IPC command structure. + * @param cmd IPC command structure. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param type Buffer type. + */ +static inline void ipcAddExchBuffer(IpcCommand* cmd, void* buffer, size_t size, BufferType type) { + size_t off = cmd->NumSend + cmd->NumRecv + cmd->NumExch; + cmd->Buffers[off] = buffer; + cmd->BufferSizes[off] = size; + cmd->BufferTypes[off] = type; + cmd->NumExch++; +} + +/** + * @brief Adds a static-buffer to an IPC command structure. + * @param cmd IPC command structure. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param index Index of buffer. + */ +static inline void ipcAddSendStatic(IpcCommand* cmd, const void* buffer, size_t size, u8 index) { + size_t off = cmd->NumStaticIn; + cmd->Statics[off] = buffer; + cmd->StaticSizes[off] = size; + cmd->StaticIndices[off] = index; + cmd->NumStaticIn++; +} + +/** + * @brief Adds a static-receive-buffer to an IPC command structure. + * @param cmd IPC command structure. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param index Index of buffer. + */ +static inline void ipcAddRecvStatic(IpcCommand* cmd, void* buffer, size_t size, u8 index) { + size_t off = cmd->NumStaticIn + cmd->NumStaticOut; + cmd->Statics[off] = buffer; + cmd->StaticSizes[off] = size; + cmd->StaticIndices[off] = index; + cmd->NumStaticOut++; +} + +/** + * @brief Adds a smart-buffer (buffer + static-buffer pair) to an IPC command structure. + * @param cmd IPC command structure. + * @param pointer_buffer_size Pointer buffer size. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param index Index of buffer. + */ +static inline void ipcAddSendSmart(IpcCommand* cmd, size_t pointer_buffer_size, const void* buffer, size_t size, u8 index) { + if (pointer_buffer_size != 0 && size <= pointer_buffer_size) { + ipcAddSendBuffer(cmd, NULL, 0, BufferType_Normal); + ipcAddSendStatic(cmd, buffer, size, index); + } else { + ipcAddSendBuffer(cmd, buffer, size, BufferType_Normal); + ipcAddSendStatic(cmd, NULL, 0, index); + } +} + +/** + * @brief Adds a smart-receive-buffer (buffer + static-receive-buffer pair) to an IPC command structure. + * @param cmd IPC command structure. + * @param pointer_buffer_size Pointer buffer size. + * @param buffer Address of the buffer. + * @param size Size of the buffer. + * @param index Index of buffer. + */ +static inline void ipcAddRecvSmart(IpcCommand* cmd, size_t pointer_buffer_size, void* buffer, size_t size, u8 index) { + if (pointer_buffer_size != 0 && size <= pointer_buffer_size) { + ipcAddRecvBuffer(cmd, NULL, 0, BufferType_Normal); + ipcAddRecvStatic(cmd, buffer, size, index); + } else { + ipcAddRecvBuffer(cmd, buffer, size, BufferType_Normal); + ipcAddRecvStatic(cmd, NULL, 0, index); + } +} + +/** + * @brief Tags an IPC command structure to send the PID. + * @param cmd IPC command structure. + */ +static inline void ipcSendPid(IpcCommand* cmd) { + cmd->SendPid = true; +} + +/** + * @brief Adds a copy-handle to be sent through an IPC command structure. + * @param cmd IPC command structure. + * @param h Handle to send. + * @remark The receiving process gets a copy of the handle. + */ +static inline void ipcSendHandleCopy(IpcCommand* cmd, Handle h) { + cmd->Handles[cmd->NumHandlesCopy++] = h; +} + +/** + * @brief Adds a move-handle to be sent through an IPC command structure. + * @param cmd IPC command structure. + * @param h Handle to send. + * @remark The sending process loses ownership of the handle, which is transferred to the receiving process. + */ +static inline void ipcSendHandleMove(IpcCommand* cmd, Handle h) { + cmd->Handles[cmd->NumHandlesCopy + cmd->NumHandlesMove++] = h; +} + +/** + * @brief Prepares the header of an IPC command structure. + * @param cmd IPC command structure. + * @param sizeof_raw Size in bytes of the raw data structure to embed inside the IPC request + * @return Pointer to the raw embedded data structure in the request, ready to be filled out. + */ +static inline void* ipcPrepareHeader(IpcCommand* cmd, size_t sizeof_raw) { + u32* buf = (u32*)armGetTls(); + size_t i; + *buf++ = IpcCommandType_Request | (cmd->NumStaticIn << 16) | (cmd->NumSend << 20) | (cmd->NumRecv << 24) | (cmd->NumExch << 28); + + u32* fill_in_size_later = buf; + + if (cmd->NumStaticOut > 0) { + *buf = (cmd->NumStaticOut + 2) << 10; + } + else { + *buf = 0; + } + + if (cmd->SendPid || cmd->NumHandlesCopy > 0 || cmd->NumHandlesMove > 0) { + *buf++ |= 0x80000000; + *buf++ = (!!cmd->SendPid) | (cmd->NumHandlesCopy << 1) | (cmd->NumHandlesMove << 5); + + if (cmd->SendPid) + buf += 2; + + for (i=0; i<(cmd->NumHandlesCopy + cmd->NumHandlesMove); i++) + *buf++ = cmd->Handles[i]; + } + else { + buf++; + } + + for (i=0; iNumStaticIn; i++, buf+=2) { + IpcStaticSendDescriptor* desc = (IpcStaticSendDescriptor*) buf; + + uintptr_t ptr = (uintptr_t) cmd->Statics[i]; + desc->Addr = ptr; + desc->Packed = cmd->StaticIndices[i] | (cmd->StaticSizes[i] << 16) | + (((ptr >> 32) & 15) << 12) | (((ptr >> 36) & 15) << 6); + } + + for (i=0; i<(cmd->NumSend + cmd->NumRecv + cmd->NumExch); i++, buf+=3) { + IpcBufferDescriptor* desc = (IpcBufferDescriptor*) buf; + desc->Size = cmd->BufferSizes[i]; + + uintptr_t ptr = (uintptr_t) cmd->Buffers[i]; + desc->Addr = ptr; + desc->Packed = cmd->BufferTypes[i] | + (((ptr >> 32) & 15) << 28) | ((ptr >> 36) << 2); + } + + u32 padding = ((16 - (((uintptr_t) buf) & 15)) & 15) / 4; + u32* raw = (u32*) (buf + padding); + + size_t raw_size = (sizeof_raw/4) + 4; + buf += raw_size; + + u16* buf_u16 = (u16*) buf; + + for (i=0; iNumStaticOut; i++) { + size_t off = cmd->NumStaticIn + i; + size_t sz = (uintptr_t) cmd->StaticSizes[off]; + + buf_u16[i] = (sz > 0xFFFF) ? 0 : sz; + } + + size_t u16s_size = ((2*cmd->NumStaticOut) + 3)/4; + buf += u16s_size; + raw_size += u16s_size; + + *fill_in_size_later |= raw_size; + + for (i=0; iNumStaticOut; i++, buf+=2) { + IpcStaticRecvDescriptor* desc = (IpcStaticRecvDescriptor*) buf; + size_t off = cmd->NumStaticIn + i; + + uintptr_t ptr = (uintptr_t) cmd->Statics[off]; + desc->Addr = ptr; + desc->Packed = (ptr >> 32) | (cmd->StaticSizes[off] << 16); + } + + return (void*) raw; +} + +/** + * @brief Dispatches an IPC request. + * @param session IPC session handle. + * @return Result code. + */ +static inline Result ipcDispatch(Handle session) { + return svcSendSyncRequest(session); +} + +///@} + +///@name IPC response parsing +///@{ + +/// IPC parsed command (response) structure. +typedef struct { + IpcCommandType CommandType; ///< Type of the command + + bool HasPid; ///< true if the 'Pid' field is filled out. + u64 Pid; ///< PID included in the response (only if HasPid is true) + + size_t NumHandles; ///< Number of handles copied. + Handle Handles[IPC_MAX_OBJECTS]; ///< Handles. + bool WasHandleCopied[IPC_MAX_OBJECTS]; ///< true if the handle was moved, false if it was copied. + + bool IsDomainRequest; ///< true if the the message is a Domain message. + DomainMessageType InMessageType; ///< Type of the domain message. + u32 InMessageLength; ///< Size of rawdata (for domain messages). + u32 InThisObjectId; ///< Object ID to call the command on (for domain messages). + size_t InNumObjectIds; ///< Number of object IDs (for domain messages). + u32 InObjectIds[IPC_MAX_OBJECTS]; ///< Object IDs (for domain messages). + + bool IsDomainResponse; ///< true if the the message is a Domain response. + size_t OutNumObjectIds; ///< Number of object IDs (for domain responses). + u32 OutObjectIds[IPC_MAX_OBJECTS]; ///< Object IDs (for domain responses). + + size_t NumBuffers; ///< Number of buffers in the response. + void* Buffers[IPC_MAX_BUFFERS]; ///< Pointers to the buffers. + size_t BufferSizes[IPC_MAX_BUFFERS]; ///< Sizes of the buffers. + BufferType BufferTypes[IPC_MAX_BUFFERS]; ///< Types of the buffers. + BufferDirection BufferDirections[IPC_MAX_BUFFERS]; ///< Direction of each buffer. + + size_t NumStatics; ///< Number of statics in the response. + void* Statics[IPC_MAX_BUFFERS]; ///< Pointers to the statics. + size_t StaticSizes[IPC_MAX_BUFFERS]; ///< Sizes of the statics. + u8 StaticIndices[IPC_MAX_BUFFERS]; ///< Indices of the statics. + + size_t NumStaticsOut; ///< Number of output statics available in the response. + + void* Raw; ///< Pointer to the raw embedded data structure in the response. + void* RawWithoutPadding; ///< Pointer to the raw embedded data structure, without padding. + size_t RawSize; ///< Size of the raw embedded data. +} IpcParsedCommand; + +/** + * @brief Parse an IPC command response into an IPC parsed command structure. + * @param r IPC parsed command structure to fill in. + * @return Result code. + */ +static inline Result ipcParse(IpcParsedCommand* r) { + u32* buf = (u32*)armGetTls(); + u32 ctrl0 = *buf++; + u32 ctrl1 = *buf++; + size_t i; + + r->IsDomainRequest = false; + r->IsDomainResponse = false; + + r->CommandType = (IpcCommandType) (ctrl0 & 0xffff); + r->HasPid = false; + r->RawSize = (ctrl1 & 0x1ff) * 4; + r->NumHandles = 0; + + r->NumStaticsOut = (ctrl1 >> 10) & 15; + if (r->NumStaticsOut >> 1) r->NumStaticsOut--; // Value 2 -> Single descriptor + if (r->NumStaticsOut >> 1) r->NumStaticsOut--; // Value 3+ -> (Value - 2) descriptors + + if (ctrl1 & 0x80000000) { + u32 ctrl2 = *buf++; + + if (ctrl2 & 1) { + r->HasPid = true; + r->Pid = *buf++; + r->Pid |= ((u64)(*buf++)) << 32; + } + + size_t num_handles_copy = ((ctrl2 >> 1) & 15); + size_t num_handles_move = ((ctrl2 >> 5) & 15); + + size_t num_handles = num_handles_copy + num_handles_move; + u32* buf_after_handles = buf + num_handles; + + if (num_handles > IPC_MAX_OBJECTS) + num_handles = IPC_MAX_OBJECTS; + + for (i=0; iHandles[i] = *(buf+i); + r->WasHandleCopied[i] = (i < num_handles_copy); + } + + r->NumHandles = num_handles; + buf = buf_after_handles; + } + + size_t num_statics = (ctrl0 >> 16) & 15; + u32* buf_after_statics = buf + num_statics*2; + + if (num_statics > IPC_MAX_BUFFERS) + num_statics = IPC_MAX_BUFFERS; + + for (i=0; iPacked; + + r->Statics[i] = (void*) (desc->Addr | (((packed >> 12) & 15) << 32) | (((packed >> 6) & 15) << 36)); + r->StaticSizes[i] = packed >> 16; + r->StaticIndices[i] = packed & 63; + } + + r->NumStatics = num_statics; + buf = buf_after_statics; + + size_t num_bufs_send = (ctrl0 >> 20) & 15; + size_t num_bufs_recv = (ctrl0 >> 24) & 15; + size_t num_bufs_exch = (ctrl0 >> 28) & 15; + + size_t num_bufs = num_bufs_send + num_bufs_recv + num_bufs_exch; + r->Raw = (void*)(((uintptr_t)(buf + num_bufs*3) + 15) &~ 15); + r->RawWithoutPadding = (void*)((uintptr_t)(buf + num_bufs*3)); + + if (num_bufs > IPC_MAX_BUFFERS) + num_bufs = IPC_MAX_BUFFERS; + + for (i=0; iPacked; + + r->Buffers[i] = (void*) (desc->Addr | ((packed >> 28) << 32) | (((packed >> 2) & 15) << 36)); + r->BufferSizes[i] = desc->Size; + r->BufferTypes[i] = (BufferType) (packed & 3); + + if (i < num_bufs_send) + r->BufferDirections[i] = BufferDirection_Send; + else if (i < (num_bufs_send + num_bufs_recv)) + r->BufferDirections[i] = BufferDirection_Recv; + else + r->BufferDirections[i] = BufferDirection_Exch; + } + + r->NumBuffers = num_bufs; + return 0; +} + +/** + * @brief Queries the size of an IPC pointer buffer. + * @param session IPC session handle. + * @param size Output variable in which to store the size. + * @return Result code. + */ +static inline Result ipcQueryPointerBufferSize(Handle session, size_t *size) { + u32* buf = (u32*)armGetTls(); + + buf[0] = IpcCommandType_Control; + buf[1] = 8; + buf[2] = 0; + buf[3] = 0; + buf[4] = SFCI_MAGIC; + buf[5] = 0; + buf[6] = 3; + buf[7] = 0; + + Result rc = ipcDispatch(session); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct ipcQueryPointerBufferSizeResponse { + u64 magic; + u64 result; + u32 size; + } *raw = (struct ipcQueryPointerBufferSizeResponse*)r.Raw; + + rc = raw->result; + + if (R_SUCCEEDED(rc)) { + *size = raw->size & 0xffff; + } + } + + return rc; +} + +/** + * @brief Closes the IPC session with proper clean up. + * @param session IPC session handle. + * @return Result code. + */ +static inline Result ipcCloseSession(Handle session) { + u32* buf = (u32*)armGetTls(); + buf[0] = IpcCommandType_Close; + buf[1] = 0; + return ipcDispatch(session); +} + +/** + * @brief Clones an IPC session. + * @param session IPC session handle. + * @param unk Unknown. + * @param new_session_out Output cloned IPC session handle. + * @return Result code. + */ +static inline Result ipcCloneSession(Handle session, u32 unk, Handle* new_session_out) { + u32* buf = (u32*)armGetTls(); + + buf[0] = IpcCommandType_Control; + buf[1] = 9; + buf[2] = 0; + buf[3] = 0; + buf[4] = SFCI_MAGIC; + buf[5] = 0; + buf[6] = 4; + buf[7] = 0; + buf[8] = unk; + + Result rc = ipcDispatch(session); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct ipcCloneSessionResponse { + u64 magic; + u64 result; + } *raw = (struct ipcCloneSessionResponse*)r.Raw; + + rc = raw->result; + + if (R_SUCCEEDED(rc) && new_session_out) { + *new_session_out = r.Handles[0]; + } + } + + return rc; +} + +///@} + +///@name IPC domain handling +///@{ + +/** + * @brief Converts an IPC session handle into a domain. + * @param session IPC session handle. + * @param object_id_out Output variable in which to store the object ID. + * @return Result code. + */ +static inline Result ipcConvertSessionToDomain(Handle session, u32* object_id_out) { + u32* buf = (u32*)armGetTls(); + + buf[0] = IpcCommandType_Control; + buf[1] = 8; + buf[4] = SFCI_MAGIC; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + + Result rc = ipcDispatch(session); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct ipcConvertSessionToDomainResponse { + u64 magic; + u64 result; + u32 object_id; + } *raw = (struct ipcConvertSessionToDomainResponse*)r.Raw; + + rc = raw->result; + + if (R_SUCCEEDED(rc)) { + *object_id_out = raw->object_id; + } + } + + return rc; +} + +/** + * @brief Adds an object ID to be sent through an IPC domain command structure. + * @param cmd IPC domain command structure. + * @param object_id Object ID to send. + */ +static inline void ipcSendObjectId(IpcCommand* cmd, u32 object_id) { + cmd->ObjectIds[cmd->NumObjectIds++] = object_id; +} + +/** + * @brief Prepares the header of an IPC command structure (domain version). + * @param cmd IPC command structure. + * @param sizeof_raw Size in bytes of the raw data structure to embed inside the IPC request + * @param object_id Domain object ID. + * @return Pointer to the raw embedded data structure in the request, ready to be filled out. + */ +static inline void* ipcPrepareHeaderForDomain(IpcCommand* cmd, size_t sizeof_raw, u32 object_id) { + void* raw = ipcPrepareHeader(cmd, sizeof_raw + sizeof(DomainMessageHeader) + cmd->NumObjectIds*sizeof(u32)); + DomainMessageHeader* hdr = (DomainMessageHeader*) raw; + u32 *object_ids = (u32*)(((uintptr_t) raw) + sizeof(DomainMessageHeader) + sizeof_raw); + + hdr->Type = DomainMessageType_SendMessage; + hdr->NumObjectIds = (u8)cmd->NumObjectIds; + hdr->Length = sizeof_raw; + hdr->ThisObjectId = object_id; + hdr->Pad[0] = hdr->Pad[1] = 0; + + for(size_t i = 0; i < cmd->NumObjectIds; i++) + object_ids[i] = cmd->ObjectIds[i]; + return (void*)(((uintptr_t) raw) + sizeof(DomainMessageHeader)); +} + +/** + * @brief Parse an IPC command request into an IPC parsed command structure (domain version). + * @param r IPC parsed command structure to fill in. + * @return Result code. + */ +static inline Result ipcParseDomainRequest(IpcParsedCommand* r) { + Result rc = ipcParse(r); + DomainMessageHeader *hdr; + u32 *object_ids; + if(R_FAILED(rc)) + return rc; + + hdr = (DomainMessageHeader*) r->Raw; + object_ids = (u32*)(((uintptr_t) hdr) + sizeof(DomainMessageHeader) + hdr->Length); + r->Raw = (void*)(((uintptr_t) r->Raw) + sizeof(DomainMessageHeader)); + + r->IsDomainRequest = true; + r->InMessageType = (DomainMessageType)(hdr->Type); + switch (r->InMessageType) { + case DomainMessageType_SendMessage: + case DomainMessageType_Close: + break; + default: + return MAKERESULT(Module_Libnx, LibnxError_DomainMessageUnknownType); + } + + r->InThisObjectId = hdr->ThisObjectId; + r->InNumObjectIds = hdr->NumObjectIds > 8 ? 8 : hdr->NumObjectIds; + if ((uintptr_t)object_ids + sizeof(u32) * r->InNumObjectIds - (uintptr_t)armGetTls() >= 0x100) { + return MAKERESULT(Module_Libnx, LibnxError_DomainMessageTooManyObjectIds); + } + for(size_t i = 0; i < r->InNumObjectIds; i++) + r->InObjectIds[i] = object_ids[i]; + + return rc; +} + +/** + * @brief Parse an IPC command response into an IPC parsed command structure (domain version). + * @param r IPC parsed command structure to fill in. + * @param sizeof_raw Size in bytes of the raw data structure. + * @return Result code. + */ +static inline Result ipcParseDomainResponse(IpcParsedCommand* r, size_t sizeof_raw) { + Result rc = ipcParse(r); + DomainResponseHeader *hdr; + u32 *object_ids; + if(R_FAILED(rc)) + return rc; + + hdr = (DomainResponseHeader*) r->Raw; + r->Raw = (void*)(((uintptr_t) r->Raw) + sizeof(DomainResponseHeader)); + object_ids = (u32*)(((uintptr_t) r->Raw) + sizeof_raw);//Official sw doesn't align this. + + r->IsDomainResponse = true; + + r->OutNumObjectIds = hdr->NumObjectIds > 8 ? 8 : hdr->NumObjectIds; + if ((uintptr_t)object_ids + sizeof(u32) * r->OutNumObjectIds - (uintptr_t)armGetTls() >= 0x100) { + return MAKERESULT(Module_Libnx, LibnxError_DomainMessageTooManyObjectIds); + } + for(size_t i = 0; i < r->OutNumObjectIds; i++) + r->OutObjectIds[i] = object_ids[i]; + + return rc; +} + +/** + * @brief Closes a domain object by ID. + * @param session IPC session handle. + * @param object_id ID of the object to close. + * @return Result code. + */ +static inline Result ipcCloseObjectById(Handle session, u32 object_id) { + IpcCommand c; + DomainMessageHeader* hdr; + + ipcInitialize(&c); + hdr = (DomainMessageHeader*)ipcPrepareHeader(&c, sizeof(DomainMessageHeader)); + + hdr->Type = DomainMessageType_Close; + hdr->NumObjectIds = 0; + hdr->Length = 0; + hdr->ThisObjectId = object_id; + hdr->Pad[0] = hdr->Pad[1] = 0; + + return ipcDispatch(session); // this command has no associated response +} + +///@} \ No newline at end of file diff --git a/Source/sys-clk/common/include/sysclk/clock_manager.h b/Source/sys-clk/common/include/sysclk/clock_manager.h index 362b6a85..351ca76a 100644 --- a/Source/sys-clk/common/include/sysclk/clock_manager.h +++ b/Source/sys-clk/common/include/sysclk/clock_manager.h @@ -45,6 +45,7 @@ typedef struct u16 iddq[HorizonOCSpeedo_EnumMax]; GpuSchedulingMode gpuSchedulingMode; bool isSysDockInstalled; + bool isSaltyNXInstalled; u8 maxDisplayFreq; u8 dramID; bool isDram8GB; diff --git a/Source/sys-clk/common/include/sysclk/config.h b/Source/sys-clk/common/include/sysclk/config.h index 94e76626..03565282 100644 --- a/Source/sys-clk/common/include/sysclk/config.h +++ b/Source/sys-clk/common/include/sysclk/config.h @@ -66,6 +66,8 @@ typedef enum { HorizonOCConfigValue_RAMVoltUsageDisplayMode, + HorizonOCConfigValue_VRR, + KipConfigValue_custRev, // KipConfigValue_mtcConf, KipConfigValue_hpMode, @@ -250,6 +252,9 @@ static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pr case HorizonOCConfigValue_RAMVoltUsageDisplayMode: return pretty ? "RAM Voltage / Usage Display Mode" : "ram_volt_usage_display_mode"; + case HorizonOCConfigValue_VRR: + return pretty ? "Variable Refresh Rate (VRR)" : "vrr"; + // KIP config values case KipConfigValue_custRev: return pretty ? "Custom Revision" : "kip_cust_rev"; @@ -430,6 +435,7 @@ static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) case HorizonOCConfigValue_GPUScheduling: case HorizonOCConfigValue_LiveCpuUv: case HorizonOCConfigValue_GPUSchedulingMethod: + case HorizonOCConfigValue_VRR: return 0ULL; case HocClkConfigValue_EristaMaxCpuClock: return 1785ULL; @@ -479,6 +485,7 @@ static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t in case HorizonOCConfigValue_EnableExperimentalSettings: case HorizonOCConfigValue_LiveCpuUv: case HorizonOCConfigValue_GPUSchedulingMethod: + case HorizonOCConfigValue_VRR: return (input & 0x1) == input; case KipConfigValue_custRev: diff --git a/Source/sys-clk/common/src/display_refresh_rate.cpp b/Source/sys-clk/common/src/display_refresh_rate.cpp index 1959190b..9c380d83 100644 --- a/Source/sys-clk/common/src/display_refresh_rate.cpp +++ b/Source/sys-clk/common/src/display_refresh_rate.cpp @@ -88,6 +88,7 @@ static const DockedTimings g_dockedTimings1080p[] = { {8, 32, 40, 108, 8, 6, 0, 528880}, //220Hz CVT-RBv2 {8, 32, 40, 114, 8, 6, 0, 555680}, //230Hz CVT-RBv2 {8, 32, 40, 121, 8, 6, 0, 583200}, //240Hz CVT-RBv2 + // technically you can go to 476hz, but in practice, why would you? }; static const HandheldTimings g_handheldTimingsRETRO[] = { diff --git a/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp index ae566cd4..832332cd 100644 --- a/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/about_gui.cpp @@ -25,6 +25,8 @@ tsl::elm::ListItem* SpeedoItem = NULL; tsl::elm::ListItem* IddqItem = NULL; tsl::elm::ListItem* sysdockStatusItem = NULL; +tsl::elm::ListItem* saltyNXStatusItem = NULL; + ImageElement* CatImage = NULL; HideableCategoryHeader* CatHeader = NULL; HideableCustomDrawer* CatSpacer = NULL; @@ -57,6 +59,10 @@ void AboutGui::listUI() new tsl::elm::ListItem("sys-dock status:"); this->listElement->addItem(sysdockStatusItem); + saltyNXStatusItem = + new tsl::elm::ListItem("SaltyNX status:"); + this->listElement->addItem(saltyNXStatusItem); + this->listElement->addItem( new tsl::elm::CategoryHeader("Credits") ); @@ -227,4 +233,5 @@ void AboutGui::refresh() SpeedoItem->setValue(strings[0]); IddqItem->setValue(strings[1]); sysdockStatusItem->setValue(this->context->isSysDockInstalled ? "Installed" : "Not Installed"); + saltyNXStatusItem->setValue(this->context->isSaltyNXInstalled ? "Installed" : "Not Installed"); } \ No newline at end of file diff --git a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp index 568ba7ea..e1167648 100644 --- a/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/base_menu_gui.cpp @@ -58,7 +58,7 @@ void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) { // All constants pre-calculated and cached static constexpr const char* const labels[] = { - "App ID", "Profile", "CPU", "GPU", "MEM", "SoC", "Board", "Skin", "Now", "Avg", "BAT", "PMIC", "FAN", "DISP" + "App ID", "Profile", "CPU", "GPU", "MEM", "SoC", "Board", "Skin", "Now", "Avg", "BAT", "PMIC", "FAN", "DISP", "FPS" }; static constexpr u32 dataPositions[6] = {63-3+3, 200-1, 344-1-3, 200-1, 342-1, 321-1}; @@ -168,8 +168,10 @@ void BaseMenuGui::preDraw(tsl::gfx::Renderer* renderer) { renderer->drawString(displayStrings[21], false, dataPositions[0], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // Bat voltage renderer->drawString(displayStrings[23], false, positions[2] - 2, y, SMALL_TEXT_SIZE, tsl::infoTextColor); // Bat Age - - renderer->drawString(displayStrings[26], false, dataPositions[2], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // disp volt + if(this->context->isSaltyNXInstalled) { + renderer->drawString(labels[14], false, positions[4], y, SMALL_TEXT_SIZE, tsl::sectionTextColor); // FPS label + renderer->drawString(displayStrings[26], false, dataPositions[2], y, SMALL_TEXT_SIZE, tsl::infoTextColor); // FPS + } y+=20; } @@ -285,9 +287,14 @@ void BaseMenuGui::refresh() sprintf(displayStrings[24], "%u%%", context->partLoad[HocClkPartLoad_FAN]); sprintf(displayStrings[25], "%u Hz", context->realFreqs[HorizonOCModule_Display]); - - //sprintf(displayStrings[26], "%u", context->speedos[HorizonOCSpeedo_CPU]); - + if(this->context->isSaltyNXInstalled) { + if(context->fps == 254) { + strcpy(displayStrings[26], "N/A"); + } else { + memset(displayStrings[26], 0, sizeof(displayStrings[26])); + sprintf(displayStrings[26], "%u", context->fps); + } + } } tsl::elm::Element* BaseMenuGui::baseUI() diff --git a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp index 2198f018..0aeebdde 100644 --- a/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp +++ b/Source/sys-clk/overlay/src/ui/gui/misc_gui.cpp @@ -366,6 +366,13 @@ void MiscGui::listUI() addConfigButton(HorizonOCConfigValue_RAMVoltUsageDisplayMode, "RAM Voltage Display Mode", ValueRange(0, 12, 1, "", 0), "RAM Voltage Display Mode", &thresholdsDisabled, {}, ramVoltDispModes, false); + addConfigButton( + HocClkConfigValue_LiteTDPLimit, + "Polling Interval", + ValueRange(50, 1000, 50, "ms", 1), + "Polling Interval", + &thresholdsDisabled + ); tsl::elm::ListItem* safetySubmenu = new tsl::elm::ListItem("Safety Settings"); safetySubmenu->setClickListener([](u64 keys) { if (keys & HidNpadButton_A) { @@ -536,6 +543,7 @@ public: protected: void listUI() override { addConfigToggle(HorizonOCConfigValue_OverwriteRefreshRate, nullptr); + addConfigToggle(HorizonOCConfigValue_VRR, nullptr); tsl::elm::CustomDrawer* warningText = new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { renderer->drawString("\uE150 Enabling unsafe display", false, x + 20, y + 30, 18, tsl::style::color::ColorText); renderer->drawString("refresh rates may cause stress", false, x + 20, y + 50, 18, tsl::style::color::ColorText); diff --git a/Source/sys-clk/sysmodule/perms.json b/Source/sys-clk/sysmodule/perms.json index 5b705463..b5c09894 100644 --- a/Source/sys-clk/sysmodule/perms.json +++ b/Source/sys-clk/sysmodule/perms.json @@ -6,11 +6,11 @@ "main_thread_stack_size": "0x0000C000", "main_thread_priority": 16, "default_cpu_id": 3, - "process_category": 0, + "process_category": 1, "is_retail": true, "pool_partition": 2, "is_64_bit": true, - "address_space_type": 1, + "address_space_type": 3, "filesystem_access": { "permissions": "0xFFFFFFFFFFFFFFFF" }, diff --git a/Source/sys-clk/sysmodule/src/clock_manager.cpp b/Source/sys-clk/sysmodule/src/clock_manager.cpp index 8693ac7d..09bd4e8b 100644 --- a/Source/sys-clk/sysmodule/src/clock_manager.cpp +++ b/Source/sys-clk/sysmodule/src/clock_manager.cpp @@ -55,11 +55,11 @@ bool hasChanged = true; ClockManager *ClockManager::instance = NULL; Thread cpuGovernorTHREAD; Thread gpuGovernorTHREAD; +Thread vrrTHREAD; u32 initialConfigValues[SysClkConfigValue_EnumMax]; // initial config. used for safety checks bool kipAvailable = false; bool isCpuGovernorInBoostMode = false; - ClockManager *ClockManager::GetInstance() { return instance; @@ -100,6 +100,8 @@ ClockManager::ClockManager() this->lastCsvWriteNs = 0; this->sysDockIntegration = new SysDockIntegration; + this->saltyNXIntegration = new SaltyNXIntegration; + memset(&initialConfigValues, 0, sizeof(initialConfigValues)); this->GetKipData(); @@ -123,8 +125,15 @@ ClockManager::ClockManager() -2 ); - threadStart(&cpuGovernorTHREAD); - threadStart(&gpuGovernorTHREAD); + threadCreate( + &vrrTHREAD, + ClockManager::VRRThread, + this, + NULL, + 0x2000, + 0x3F, + -2 + ); for(int i = 0; i < HorizonOCSpeedo_EnumMax; i++) { this->context->speedos[i] = Board::getSpeedo((HorizonOCSpeedo)i); @@ -135,13 +144,27 @@ ClockManager::ClockManager() this->context->isDram8GB = Board::IsDram8GB(); Board::SetGpuSchedulingMode((GpuSchedulingMode)this->config->GetConfigValue(HorizonOCConfigValue_GPUScheduling), (GpuSchedulingOverrideMethod)this->config->GetConfigValue(HorizonOCConfigValue_GPUSchedulingMethod)); this->context->gpuSchedulingMode = (GpuSchedulingMode)this->config->GetConfigValue(HorizonOCConfigValue_GPUScheduling); + this->context->isSysDockInstalled = this->sysDockIntegration->getCurrentSysDockState(); + this->context->isSaltyNXInstalled = this->saltyNXIntegration->getCurrentSaltyNXState(); + if(this->context->isSaltyNXInstalled) { + this->saltyNXIntegration->LoadSaltyNX(); + } + + + threadStart(&cpuGovernorTHREAD); + threadStart(&gpuGovernorTHREAD); + threadStart(&vrrTHREAD); } ClockManager::~ClockManager() { threadClose(&cpuGovernorTHREAD); threadClose(&gpuGovernorTHREAD); + threadClose(&vrrTHREAD); + + delete this->sysDockIntegration; + delete this->saltyNXIntegration; delete this->config; delete this->context; } @@ -459,6 +482,67 @@ void ClockManager::GovernorThread(void* arg) { svcSleepThread(POLL_NS); } } + +void ClockManager::VRRThread(void* arg) { + ClockManager* mgr = static_cast(arg); + u8 tick = 0; + for (;;) { + if (!mgr->running || !mgr->config->GetConfigValue(HorizonOCConfigValue_VRR) || mgr->context->profile == SysClkProfile_Docked) { + continue; + } + + if(Board::GetConsoleType() == HorizonOCConsoleType_Hoag) { + svcSleepThread(~0ULL); + continue; + } + + std::scoped_lock lock{mgr->contextMutex}; + + u8 fps; + + if(mgr->context->isSaltyNXInstalled) { + fps = mgr->saltyNXIntegration->GetFPS(); + } else { + svcSleepThread(~0ULL); // effectively disable the thread if SaltyNX isn't installed, as there's no point in it running + continue; + } + + if(fps == 254) + continue; + // if(appletGetFocusState() != AppletFocusState_InFocus) { + // Board::ResetToStockDisplay(); + // continue; + // } + u8 maxDisplay; + if(Board::GetConsoleType() == HorizonOCConsoleType_Aula) { + maxDisplay = mgr->config->GetConfigValue(HorizonOCConfigValue_EnableUnsafeDisplayFreqs) ? 65 : 60; + } else { + maxDisplay = mgr->config->GetConfigValue(HorizonOCConfigValue_EnableUnsafeDisplayFreqs) ? 72 : 60; + } + + u8 minDisplay = Board::GetConsoleType() == HorizonOCConsoleType_Aula ? 40 : 45; + if(fps >= minDisplay && fps <= maxDisplay) + Board::SetHz(HorizonOCModule_Display, fps); + else { + for(u32 i = 0; i < 10; i++) { + u32 compareHz = fps * i; + if(compareHz >= minDisplay && compareHz <= maxDisplay) { + Board::SetHz(HorizonOCModule_Display, compareHz); + break; + } + } + } + if(++tick > 10) { + Board::ResetToStockDisplay(); + tick = 0; + svcSleepThread(10'000'000); + } + + svcSleepThread(POLL_NS); + } +} + + GovernorState ClockManager::GetEffectiveGovernorState(GovernorState appState, GovernorState tempState) { if (tempState == GovernorState_Disabled) @@ -623,6 +707,12 @@ void ClockManager::HandleFreqReset(SysClkModule module, bool isBoost) { case SysClkModule_MEM: Board::ResetToStockMem(); DVFSReset(); + break; + case HorizonOCModule_Display: + if(this->config->GetConfigValue(HorizonOCConfigValue_OverwriteRefreshRate) && Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { + Board::ResetToStockDisplay(); + } + break; default: break; } @@ -888,7 +978,11 @@ bool ClockManager::RefreshContext() if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) Board::SetDisplayRefreshDockedState(this->context->profile == SysClkProfile_Docked); - + if(this->context->isSaltyNXInstalled) + this->context->fps = saltyNXIntegration->GetFPS(); + else + this->context->fps = 254; // N/A + return hasChanged; } diff --git a/Source/sys-clk/sysmodule/src/clock_manager.h b/Source/sys-clk/sysmodule/src/clock_manager.h index 0294b99a..c287db17 100644 --- a/Source/sys-clk/sysmodule/src/clock_manager.h +++ b/Source/sys-clk/sysmodule/src/clock_manager.h @@ -33,6 +33,7 @@ #include "integrations.h" class SysDockIntegration; +class SaltyNXIntegration; class ClockManager { public: @@ -184,6 +185,13 @@ class ClockManager */ static void GovernorThread(void* arg); + /** + * Runs the VRR Algorithm + * + * @param arg Cast to ClockManager* for context + */ + static void VRRThread(void* arg); + /** * Gets the effective governor state from application/temporary override * @@ -257,4 +265,5 @@ class ClockManager std::uint64_t lastPowerLogNs; std::uint64_t lastCsvWriteNs; SysDockIntegration *sysDockIntegration; + SaltyNXIntegration *saltyNXIntegration; }; \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/integrations.cpp b/Source/sys-clk/sysmodule/src/integrations.cpp index cd55661d..bd53423c 100644 --- a/Source/sys-clk/sysmodule/src/integrations.cpp +++ b/Source/sys-clk/sysmodule/src/integrations.cpp @@ -18,15 +18,93 @@ #include "integrations.h" #include +#include +#include "process_management.h" SysDockIntegration::SysDockIntegration() { } bool SysDockIntegration::getCurrentSysDockState() { struct stat st = {0}; - if (stat("sdmc:/atmosphere/contents/42000000000000A0", &st) == 0 && S_ISDIR(st.st_mode)) { - return true; - } else { - return false; + return stat("sdmc:/atmosphere/contents/42000000000000A0/flags/boot2.flag", &st) == 0; +} + +SaltyNXIntegration::SaltyNXIntegration() { +} + +void SaltyNXIntegration::LoadSaltyNX() { + if (!CheckPort()) + return; + LoadSharedMemory(); +} + +bool SaltyNXIntegration::getCurrentSaltyNXState() { + struct stat st = {0}; + return stat("sdmc:/atmosphere/contents/0000000000534C56/flags/boot2.flag", &st) == 0; +} + +bool SaltyNXIntegration::CheckPort() { + Handle saltysd; + + for (int i = 0; i < 67; i++) { + if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) { + svcCloseHandle(saltysd); + break; + } + if (i == 66) return false; + svcSleepThread(1'000'000); } + + for (int i = 0; i < 67; i++) { + if (R_SUCCEEDED(svcConnectToNamedPort(&saltysd, "InjectServ"))) { + svcCloseHandle(saltysd); + return true; + } + svcSleepThread(1'000'000); + } + + return false; +} + +void SaltyNXIntegration::LoadSharedMemory() { + if (SaltySD_Connect()) + return; + SaltySD_GetSharedMemoryHandle(&remoteSharedMemory); + SaltySD_Term(); + shmemLoadRemote(&_sharedmemory, remoteSharedMemory, 0x1000, Perm_Rw); + if (!shmemMap(&_sharedmemory)) + SharedMemoryUsed = true; +} + +void SaltyNXIntegration::searchSharedMemoryBlock(uintptr_t base) { + ptrdiff_t search_offset = 0; + while (search_offset < 0x1000) { + NxFps = (NxFpsSharedBlock*)(base + search_offset); + if (NxFps->MAGIC == 0x465053) + return; + search_offset += 4; + } + NxFps = 0; +} + +u64 prevTid = 0; +u8 SaltyNXIntegration::GetFPS() { + if (!SharedMemoryUsed) + return 254; + + u64 tid = ProcessManagement::GetCurrentApplicationId(); + if (tid == 0) + return 254; + + if (prevTid != tid) { + NxFps = 0; + prevTid = tid; + } + + if (!NxFps) { + uintptr_t base = (uintptr_t)shmemGetAddr(&_sharedmemory); + searchSharedMemoryBlock(base); + } + + return NxFps ? NxFps->FPS : 254; } \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/integrations.h b/Source/sys-clk/sysmodule/src/integrations.h index 9c2d7ea9..0146bc36 100644 --- a/Source/sys-clk/sysmodule/src/integrations.h +++ b/Source/sys-clk/sysmodule/src/integrations.h @@ -34,4 +34,60 @@ public: SysDockIntegration(); bool getCurrentSysDockState(); +}; + +class SaltyNXIntegration { +public: + struct resolutionCalls { + uint16_t width; + uint16_t height; + uint16_t calls; + }; + + struct NxFpsSharedBlock { + uint32_t MAGIC; + uint8_t FPS; + float FPSavg; + bool pluginActive; + uint8_t FPSlocked; + uint8_t FPSmode; + uint8_t ZeroSync; + uint8_t patchApplied; + uint8_t API; + uint32_t FPSticks[10]; + uint8_t Buffers; + uint8_t SetBuffers; + uint8_t ActiveBuffers; + uint8_t SetActiveBuffers; + union { + struct { + bool handheld: 1; + bool docked: 1; + unsigned int reserved: 6; + } NX_PACKED ds; + uint8_t general; + } displaySync; + resolutionCalls renderCalls[8]; + resolutionCalls viewportCalls[8]; + bool forceOriginalRefreshRate; + bool dontForce60InDocked; + bool forceSuspend; + uint8_t currentRefreshRate; + float readSpeedPerSecond; + uint8_t FPSlockedDocked; + uint64_t frameNumber; + } NX_PACKED; + + NxFpsSharedBlock* NxFps = 0; + SharedMemory _sharedmemory = {}; + bool SharedMemoryUsed = false; + Handle remoteSharedMemory = 1; + SaltyNXIntegration(); + void LoadSaltyNX(); + bool getCurrentSaltyNXState(); + + bool CheckPort(); + void LoadSharedMemory(); + void searchSharedMemoryBlock(uintptr_t base); + u8 GetFPS(); }; \ No newline at end of file diff --git a/Source/sys-clk/sysmodule/src/main.cpp b/Source/sys-clk/sysmodule/src/main.cpp index 9ad50b16..6249172c 100644 --- a/Source/sys-clk/sysmodule/src/main.cpp +++ b/Source/sys-clk/sysmodule/src/main.cpp @@ -38,8 +38,11 @@ #include "ipc_service.h" #define INNER_HEAP_SIZE 0x40000 + extern "C" { + void virtmemSetup(void); + extern std::uint32_t __start__; std::uint32_t __nx_applet_type = AppletType_None; @@ -62,6 +65,8 @@ extern "C" fake_heap_start = (char*)addr; fake_heap_end = (char*)addr + size; + + virtmemSetup(); } void __appInit(void) @@ -88,6 +93,9 @@ extern "C" rc = i2cInitialize(); if (R_FAILED(rc)) diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen)); + rc = appletInitialize(); + if (R_FAILED(rc)) + diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen)); } void __appExit(void) @@ -97,6 +105,7 @@ extern "C" i2cExit(); fsExit(); fsdevUnmountAll(); + appletExit(); } }