From 3e75bd5b95bf4c6354970840049c7ef350da2e18 Mon Sep 17 00:00:00 2001 From: Lightos1 <124387232+Lightos1@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:46:42 +0100 Subject: [PATCH] Start hoc-clk rewrite --- Source/rewrite-hoc-clk/.gitignore | 5 + Source/rewrite-hoc-clk/.gitlab-ci.yml | 17 + Source/rewrite-hoc-clk/.gitmodules | 3 + Source/rewrite-hoc-clk/LICENSE | 7 + Source/rewrite-hoc-clk/README.md | 6 + Source/rewrite-hoc-clk/bitmap.py | 53 + Source/rewrite-hoc-clk/build.sh | 42 + .../rewrite-hoc-clk/common/include/SaltyNX.h | 248 +++ .../rewrite-hoc-clk/common/include/battery.h | 252 ++++ .../common/include/cpp_util.hpp | 53 + Source/rewrite-hoc-clk/common/include/crc32.h | 56 + .../common/include/display_refresh_rate.h | 126 ++ Source/rewrite-hoc-clk/common/include/i2c.h | 62 + Source/rewrite-hoc-clk/common/include/ipc.h | 756 ++++++++++ .../rewrite-hoc-clk/common/include/memmem.h | 41 + .../common/include/notification.h | 44 + .../common/include/pcv_types.h | 113 ++ Source/rewrite-hoc-clk/common/include/pwm.h | 39 + .../common/include/registers.h | 527 +++++++ Source/rewrite-hoc-clk/common/include/rgltr.h | 38 + .../common/include/rgltr_services.h | 32 + .../common/include/service_guard.h | 82 + .../rewrite-hoc-clk/common/include/sysclk.h | 55 + .../common/include/sysclk/apm.h | 39 + .../common/include/sysclk/board.h | 270 ++++ .../common/include/sysclk/client/ipc.h | 57 + .../common/include/sysclk/client/types.h | 46 + .../common/include/sysclk/clock_manager.h | 66 + .../common/include/sysclk/config.h | 593 ++++++++ .../common/include/sysclk/errors.h | 39 + .../common/include/sysclk/ipc.h | 72 + .../common/include/sysclk/psm_ext.h | 94 ++ .../common/src/apm_profile_table.c | 49 + .../rewrite-hoc-clk/common/src/client/ipc.c | 169 +++ .../common/src/display_refresh_rate.cpp | 720 +++++++++ Source/rewrite-hoc-clk/common/src/i2c.cpp | 202 +++ Source/rewrite-hoc-clk/common/src/memmem.c | 83 + Source/rewrite-hoc-clk/common/src/psm_ext.c | 67 + Source/rewrite-hoc-clk/common/src/pwm.c | 52 + .../common/src/rgltr_services.cpp | 66 + Source/rewrite-hoc-clk/config.ini.template | 19 + Source/rewrite-hoc-clk/sysmodule/.gitignore | 2 + Source/rewrite-hoc-clk/sysmodule/Makefile | 164 ++ .../sysmodule/lib/minIni/.gitignore | 13 + .../sysmodule/lib/minIni/.gitrepo | 12 + .../sysmodule/lib/minIni/LICENSE | 189 +++ .../sysmodule/lib/minIni/Makefile | 133 ++ .../sysmodule/lib/minIni/NOTICE | 12 + .../sysmodule/lib/minIni/README.md | 170 +++ .../sysmodule/lib/minIni/dev/minGlue-FatFs.h | 37 + .../sysmodule/lib/minIni/dev/minGlue-ccs.h | 64 + .../sysmodule/lib/minIni/dev/minGlue-efsl.h | 63 + .../sysmodule/lib/minIni/dev/minGlue-ffs.h | 26 + .../sysmodule/lib/minIni/dev/minGlue-mdd.h | 58 + .../sysmodule/lib/minIni/dev/minGlue-stdio.h | 31 + .../sysmodule/lib/minIni/dev/minGlue.h | 35 + .../sysmodule/lib/minIni/dev/minIni.c | 1009 +++++++++++++ .../sysmodule/lib/minIni/dev/minIni.h | 68 + .../sysmodule/lib/minIni/dev/test.c | 117 ++ .../sysmodule/lib/minIni/dev/test.ini | 8 + .../sysmodule/lib/minIni/dev/test2.cc | 80 + .../sysmodule/lib/minIni/dev/testplain.ini | 3 + .../sysmodule/lib/minIni/dev/wxMinIni.h | 101 ++ .../sysmodule/lib/minIni/doc/minIni.pdf | Bin 0 -> 153827 bytes .../sysmodule/lib/minIni/include/minIni.h | 38 + .../sysmodule/lib/nxExt/.gitignore | 13 + .../sysmodule/lib/nxExt/Makefile | 132 ++ .../sysmodule/lib/nxExt/include/nxExt.h | 36 + .../lib/nxExt/include/nxExt/apm_ext.h | 58 + .../nxExt/include/nxExt/cpp/lockable_mutex.h | 81 + .../sysmodule/lib/nxExt/include/nxExt/i2c.h | 41 + .../lib/nxExt/include/nxExt/ipc_server.h | 79 + .../lib/nxExt/include/nxExt/max17050.h | 44 + .../sysmodule/lib/nxExt/include/nxExt/t210.h | 45 + .../lib/nxExt/include/nxExt/tmp451.h | 44 + .../sysmodule/lib/nxExt/src/apm_ext.c | 83 + .../sysmodule/lib/nxExt/src/i2c.c | 45 + .../sysmodule/lib/nxExt/src/ipc_server.c | 221 +++ .../sysmodule/lib/nxExt/src/max17050.c | 124 ++ .../sysmodule/lib/nxExt/src/t210.c | 290 ++++ .../sysmodule/lib/nxExt/src/tmp451.c | 102 ++ Source/rewrite-hoc-clk/sysmodule/perms.json | 240 +++ .../sysmodule/src/board/board.hpp | 39 + .../sysmodule/src/board/board_fuse.cpp | 120 ++ .../sysmodule/src/board/board_fuse.hpp | 44 + .../sysmodule/src/board/board_init.cpp | 195 +++ .../sysmodule/src/board/board_load.cpp | 66 + .../sysmodule/src/board/board_load.hpp | 35 + .../sysmodule/src/clock_manager.cpp | 1299 ++++++++++++++++ .../sysmodule/src/clock_manager.h | 261 ++++ .../rewrite-hoc-clk/sysmodule/src/config.cpp | 520 +++++++ Source/rewrite-hoc-clk/sysmodule/src/config.h | 91 ++ .../rewrite-hoc-clk/sysmodule/src/errors.cpp | 54 + Source/rewrite-hoc-clk/sysmodule/src/errors.h | 52 + .../sysmodule/src/file_utils.cpp | 217 +++ .../sysmodule/src/file_utils.h | 55 + .../sysmodule/src/integrations.cpp | 137 ++ .../sysmodule/src/integrations.h | 94 ++ .../sysmodule/src/ipc_service.cpp | 374 +++++ .../sysmodule/src/ipc_service.h | 66 + Source/rewrite-hoc-clk/sysmodule/src/kip.h | 450 ++++++ Source/rewrite-hoc-clk/sysmodule/src/main.cpp | 168 +++ .../sysmodule/src/old_board.cpp | 1329 +++++++++++++++++ .../rewrite-hoc-clk/sysmodule/src/old_board.h | 80 + .../sysmodule/src/process_management.cpp | 85 ++ .../sysmodule/src/process_management.h | 41 + Source/rewrite-hoc-clk/sysmodule/toolbox.json | 5 + 107 files changed, 15348 insertions(+) create mode 100644 Source/rewrite-hoc-clk/.gitignore create mode 100644 Source/rewrite-hoc-clk/.gitlab-ci.yml create mode 100644 Source/rewrite-hoc-clk/.gitmodules create mode 100644 Source/rewrite-hoc-clk/LICENSE create mode 100644 Source/rewrite-hoc-clk/README.md create mode 100644 Source/rewrite-hoc-clk/bitmap.py create mode 100644 Source/rewrite-hoc-clk/build.sh create mode 100644 Source/rewrite-hoc-clk/common/include/SaltyNX.h create mode 100644 Source/rewrite-hoc-clk/common/include/battery.h create mode 100644 Source/rewrite-hoc-clk/common/include/cpp_util.hpp create mode 100644 Source/rewrite-hoc-clk/common/include/crc32.h create mode 100644 Source/rewrite-hoc-clk/common/include/display_refresh_rate.h create mode 100644 Source/rewrite-hoc-clk/common/include/i2c.h create mode 100644 Source/rewrite-hoc-clk/common/include/ipc.h create mode 100644 Source/rewrite-hoc-clk/common/include/memmem.h create mode 100644 Source/rewrite-hoc-clk/common/include/notification.h create mode 100644 Source/rewrite-hoc-clk/common/include/pcv_types.h create mode 100644 Source/rewrite-hoc-clk/common/include/pwm.h create mode 100644 Source/rewrite-hoc-clk/common/include/registers.h create mode 100644 Source/rewrite-hoc-clk/common/include/rgltr.h create mode 100644 Source/rewrite-hoc-clk/common/include/rgltr_services.h create mode 100644 Source/rewrite-hoc-clk/common/include/service_guard.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/apm.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/board.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/client/ipc.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/client/types.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/clock_manager.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/config.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/errors.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/ipc.h create mode 100644 Source/rewrite-hoc-clk/common/include/sysclk/psm_ext.h create mode 100644 Source/rewrite-hoc-clk/common/src/apm_profile_table.c create mode 100644 Source/rewrite-hoc-clk/common/src/client/ipc.c create mode 100644 Source/rewrite-hoc-clk/common/src/display_refresh_rate.cpp create mode 100644 Source/rewrite-hoc-clk/common/src/i2c.cpp create mode 100644 Source/rewrite-hoc-clk/common/src/memmem.c create mode 100644 Source/rewrite-hoc-clk/common/src/psm_ext.c create mode 100644 Source/rewrite-hoc-clk/common/src/pwm.c create mode 100644 Source/rewrite-hoc-clk/common/src/rgltr_services.cpp create mode 100644 Source/rewrite-hoc-clk/config.ini.template create mode 100644 Source/rewrite-hoc-clk/sysmodule/.gitignore create mode 100644 Source/rewrite-hoc-clk/sysmodule/Makefile create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitignore create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitrepo create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/LICENSE create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/Makefile create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/NOTICE create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/README.md create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-FatFs.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ccs.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-efsl.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ffs.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-mdd.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-stdio.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.ini create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test2.cc create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/testplain.ini create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/wxMinIni.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/doc/minIni.pdf create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/minIni/include/minIni.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/.gitignore create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/Makefile create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/apm_ext.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/i2c.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/ipc_server.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/max17050.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/t210.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/tmp451.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/apm_ext.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/i2c.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/ipc_server.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/max17050.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/t210.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/tmp451.c create mode 100644 Source/rewrite-hoc-clk/sysmodule/perms.json create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board.hpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.hpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board_init.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board_load.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/board/board_load.hpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/clock_manager.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/clock_manager.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/config.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/config.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/errors.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/errors.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/file_utils.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/file_utils.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/integrations.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/integrations.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/ipc_service.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/ipc_service.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/kip.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/main.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/old_board.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/old_board.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/process_management.cpp create mode 100644 Source/rewrite-hoc-clk/sysmodule/src/process_management.h create mode 100644 Source/rewrite-hoc-clk/sysmodule/toolbox.json diff --git a/Source/rewrite-hoc-clk/.gitignore b/Source/rewrite-hoc-clk/.gitignore new file mode 100644 index 00000000..cc543c0e --- /dev/null +++ b/Source/rewrite-hoc-clk/.gitignore @@ -0,0 +1,5 @@ +/dist +.DS_Store +Thumbs.db +desktop.ini +.vscode \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/.gitlab-ci.yml b/Source/rewrite-hoc-clk/.gitlab-ci.yml new file mode 100644 index 00000000..80ebec89 --- /dev/null +++ b/Source/rewrite-hoc-clk/.gitlab-ci.yml @@ -0,0 +1,17 @@ +image: $CI_SERVER_HOST:4567/libretro/infrastructure/libretro-build-libnx-devkitpro:latest + +variables: + PACKAGE_FOLDER: "sys-clk" + +stages: + - package + +nightly: + stage: package + script: + - bash build.sh $PACKAGE_FOLDER + artifacts: + name: $PACKAGE_FOLDER + expire_in: 24 hours + paths: + - $PACKAGE_FOLDER diff --git a/Source/rewrite-hoc-clk/.gitmodules b/Source/rewrite-hoc-clk/.gitmodules new file mode 100644 index 00000000..3db71a4d --- /dev/null +++ b/Source/rewrite-hoc-clk/.gitmodules @@ -0,0 +1,3 @@ +[submodule "overlay/lib/libultrahand"] + path = overlay/lib/libultrahand + url = https://github.com/ppkantorski/libultrahand diff --git a/Source/rewrite-hoc-clk/LICENSE b/Source/rewrite-hoc-clk/LICENSE new file mode 100644 index 00000000..72648e0e --- /dev/null +++ b/Source/rewrite-hoc-clk/LICENSE @@ -0,0 +1,7 @@ +-------------------------------------------------------------------------- +"THE BEER-WARE LICENSE" (Revision 42): +, , +wrote this file. As long as you retain this notice you can do whatever you +want with this stuff. If you meet any of us some day, and you think this +stuff is worth it, you can buy us a beer in return. - The sys-clk authors +-------------------------------------------------------------------------- diff --git a/Source/rewrite-hoc-clk/README.md b/Source/rewrite-hoc-clk/README.md new file mode 100644 index 00000000..aeb88c8c --- /dev/null +++ b/Source/rewrite-hoc-clk/README.md @@ -0,0 +1,6 @@ +# hoc-clk + +Switch sysmodule allowing you to set cpu/gpu/mem clocks according to the running application and docked state. +Modified for Horizon OC + +Support is only provided for FW 16.0.0+. This MAY work on older firmwares but support is NOT guaranteed \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/bitmap.py b/Source/rewrite-hoc-clk/bitmap.py new file mode 100644 index 00000000..bdf81362 --- /dev/null +++ b/Source/rewrite-hoc-clk/bitmap.py @@ -0,0 +1,53 @@ +from PIL import Image +import argparse +import os + +def image_to_rgba8888_array(image_path, output_path): + # Open and convert to RGBA + img = Image.open(image_path).convert('RGBA') + width, height = img.size + + # Get pixel data + pixels = img.tobytes() + + # Write as C header file + with open(output_path, 'w') as f: + f.write('// This is a automatically generated file, do not edit manually.\n') + f.write(f'// {os.path.basename(image_path)} - {width}x{height}\n') + f.write(f'const unsigned int IMG_WIDTH = {width};\n') + f.write(f'const unsigned int IMG_HEIGHT = {height};\n') + f.write('const unsigned char IMG_DATA[] = {\n ') + + for i, byte in enumerate(pixels): + f.write(f'0x{byte:02X}') + if i < len(pixels) - 1: + f.write(', ') + if (i + 1) % 12 == 0: + f.write('\n ') + + f.write('\n};\n') + + print(f'Converted: {width}x{height} -> {len(pixels)} bytes') + print(f'Output: {output_path}') + +def main(): + parser = argparse.ArgumentParser( + description='PNG to RGB8888 script' + ) + parser.add_argument('input', help='Input image file (e.g. cat.png)') + parser.add_argument( + '-o', '--output', + help='Output header file (default: .h)' + ) + + args = parser.parse_args() + + output_path = args.output + if not output_path: + base, _ = os.path.splitext(args.input) + output_path = base + '.h' + + image_to_rgba8888_array(args.input, output_path) + +if __name__ == '__main__': + main() diff --git a/Source/rewrite-hoc-clk/build.sh b/Source/rewrite-hoc-clk/build.sh new file mode 100644 index 00000000..fbd37070 --- /dev/null +++ b/Source/rewrite-hoc-clk/build.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DIST_DIR="$ROOT_DIR/dist" +CORES="$(nproc --all)" + +if [[ -n "$1" ]]; then + DIST_DIR="$1" +fi + +echo "DIST_DIR: $DIST_DIR" +echo "CORES: $CORES" + +echo "*** sysmodule ***" +TITLE_ID="$(grep -oP '"title_id":\s*"0x\K(\w+)' "$ROOT_DIR/sysmodule/perms.json")" + +pushd "$ROOT_DIR/sysmodule" +make -j$CORES +popd > /dev/null + +mkdir -p "$DIST_DIR/atmosphere/contents/$TITLE_ID/flags" +cp -vf "$ROOT_DIR/sysmodule/out/horizon-oc.nsp" "$DIST_DIR/atmosphere/contents/$TITLE_ID/exefs.nsp" +>"$DIST_DIR/atmosphere/contents/$TITLE_ID/flags/boot2.flag" +cp -vf "$ROOT_DIR/sysmodule/toolbox.json" "$DIST_DIR/atmosphere/contents/$TITLE_ID/toolbox.json" + +echo "*** overlay ***" +pushd "$ROOT_DIR/overlay" +make -j$CORES +popd > /dev/null + +mkdir -p "$DIST_DIR/switch/.overlays" +cp -vf "$ROOT_DIR/overlay/out/horizon-oc-overlay.ovl" "$DIST_DIR/switch/.overlays/horizon-oc-overlay.ovl" + +echo "*** assets ***" +mkdir -p "$DIST_DIR/config/horizon-oc" +cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/horizon-oc/config.ini.template" +cp -vf "$ROOT_DIR/../../README.md" "$DIST_DIR/README.md" + +echo "*** lang ***" + +cp -r "$ROOT_DIR/overlay/lang/" "$DIST_DIR/config/horizon-oc/lang/" diff --git a/Source/rewrite-hoc-clk/common/include/SaltyNX.h b/Source/rewrite-hoc-clk/common/include/SaltyNX.h new file mode 100644 index 00000000..06ebc50b --- /dev/null +++ b/Source/rewrite-hoc-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/rewrite-hoc-clk/common/include/battery.h b/Source/rewrite-hoc-clk/common/include/battery.h new file mode 100644 index 00000000..dff0b08b --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/battery.h @@ -0,0 +1,252 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once +#include +#include +#include +typedef enum { + BatteryFlag_NoHub = BIT(0), // Hub is disconnected + BatteryFlag_Rail = BIT(8), // At least one Joy-con is charging from rail + BatteryFlag_SPDSRC = BIT(12), // OTG + BatteryFlag_ACC = BIT(16) // Accessory +} BatteryChargeFlags; + +typedef enum { + PDState_NewPDO = 1, // Received new Power Data Object + PDState_NoPD = 2, // No Power Delivery source is detected + PDState_AcceptedRDO = 3 // Received and accepted Request Data Object +} BatteryPDControllerState; + +// Charger type detection +typedef enum { + ChargerType_None = 0, + ChargerType_PD = 1, + ChargerType_TypeC_1500mA = 2, + ChargerType_TypeC_3000mA = 3, + ChargerType_DCP = 4, // Dedicated Charging Port + ChargerType_CDP = 5, // Charging Downstream Port + ChargerType_SDP = 6, // Standard Downstream Port + ChargerType_Apple_500mA = 7, + ChargerType_Apple_1000mA = 8, + ChargerType_Apple_2000mA = 9 +} BatteryChargerType; +typedef enum { + PowerRole_Sink = 1, // Device is receiving power + PowerRole_Source = 2 // Device is providing power +} BatteryPowerRole; + +typedef struct { + int32_t InputCurrentLimit; // Input (Sink) current limit in mA + int32_t VBUSCurrentLimit; // Output (Source/VBUS/OTG) current limit in mA + int32_t ChargeCurrentLimit; // Battery charging current limit in mA + int32_t ChargeVoltageLimit; // Battery charging voltage limit in mV + int32_t unk_x10; // Unknown field (possibly enum) + 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 percentmille + int32_t VoltageAvg; // Average voltage in mV + int32_t BatteryAge; // Battery health (capacity full/design) in pcm + BatteryPowerRole PowerRole; // Current power role + BatteryChargerType ChargerType; // Type of charger connected + int32_t ChargerVoltageLimit; // Charger voltage limit in mV + int32_t ChargerCurrentLimit; // Charger current limit in mA + BatteryChargeFlags Flags; // Various status flags +} BatteryChargeInfo; + +#define IS_BATTERY_CHARGING_ENABLED(info) (((info)->unk_x14 >> 8) & 1) + +Result batteryInfoInitialize(void); + +void batteryInfoExit(void); + +Result batteryInfoGetChargeInfo(BatteryChargeInfo *out); + +Result batteryInfoGetChargePercentage(u32 *out); + +Result batteryInfoIsEnoughPowerSupplied(bool *out); + +Result batteryInfoEnableCharging(void); +Result batteryInfoDisableCharging(void); +Result batteryInfoEnableFastCharging(void); +Result batteryInfoDisableFastCharging(void); + +const char* batteryInfoGetChargerTypeString(BatteryChargerType type); +const char* batteryInfoGetPowerRoleString(BatteryPowerRole role); +const char* batteryInfoGetPDStateString(BatteryPDControllerState state); + +static inline int batteryInfoGetTemperatureMiliCelsius(BatteryChargeInfo *info) { + return info->BatteryTemperature; +} + +static inline float batteryInfoGetChargePercent(BatteryChargeInfo *info) { + return (float)info->RawBatteryCharge / 1000.0f; +} + +static inline float batteryInfoGetBatteryHealthPercent(BatteryChargeInfo *info) { + return (float)info->BatteryAge / 1000.0f; +} + +static inline bool batteryInfoIsCharging(BatteryChargeInfo *info) { + return IS_BATTERY_CHARGING_ENABLED(info); +} + +static const char* s_chargerTypeStrings[] = { + "None", + "Power Delivery", + "USB-C @ 1.5A", + "USB-C @ 3.0A", + "USB-DCP", + "USB-CDP", + "USB-SDP", + "Apple @ 0.5A", + "Apple @ 1.0A", + "Apple @ 2.0A", +}; + +static const char* s_powerRoleStrings[] = { + "Unknown", + "Sink", + "Source", +}; + +static const char* s_pdStateStrings[] = { + "Unknown", + "New PDO Received", + "No PD Source", + "RDO Accepted" +}; + +// Internal PSM service handle +static Service g_psmService = {0}; +static bool g_batteryInfoInitialized = false; + +// Internal PSM command implementations +static Result psmGetBatteryChargeInfoFields(BatteryChargeInfo *out) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return serviceDispatchOut(&g_psmService, 17, *out); +} + +static Result psmEnableBatteryCharging_internal(void) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return serviceDispatch(&g_psmService, 2); +} + +static Result psmDisableBatteryCharging_internal(void) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return serviceDispatch(&g_psmService, 3); +} + +static Result psmEnableFastBatteryCharging_internal(void) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return serviceDispatch(&g_psmService, 10); +} + +static Result psmDisableFastBatteryCharging_internal(void) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return serviceDispatch(&g_psmService, 11); +} + +Result batteryInfoInitialize(void) { + if (g_batteryInfoInitialized) + return 0; + + Result rc = psmInitialize(); + if (R_SUCCEEDED(rc)) { + memcpy(&g_psmService, psmGetServiceSession(), sizeof(Service)); + g_batteryInfoInitialized = true; + } + + return rc; +} + +void batteryInfoExit(void) { + if (g_batteryInfoInitialized) { + psmExit(); + memset(&g_psmService, 0, sizeof(Service)); + g_batteryInfoInitialized = false; + } +} + +Result batteryInfoGetChargeInfo(BatteryChargeInfo *out) { + if (!out) + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + + return psmGetBatteryChargeInfoFields(out); +} + +Result batteryInfoGetChargePercentage(u32 *out) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return psmGetBatteryChargePercentage(out); +} + +Result batteryInfoIsEnoughPowerSupplied(bool *out) { + if (!g_batteryInfoInitialized) + return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); + + return psmIsEnoughPowerSupplied(out); +} + +Result batteryInfoEnableCharging(void) { + return psmEnableBatteryCharging_internal(); +} + +Result batteryInfoDisableCharging(void) { + return psmDisableBatteryCharging_internal(); +} + +Result batteryInfoEnableFastCharging(void) { + return psmEnableFastBatteryCharging_internal(); +} + +Result batteryInfoDisableFastCharging(void) { + return psmDisableFastBatteryCharging_internal(); +} + +const char* batteryInfoGetChargerTypeString(BatteryChargerType type) { + if (type < 0 || type > ChargerType_Apple_2000mA) + return "Unknown"; + + return s_chargerTypeStrings[type]; +} + +const char* batteryInfoGetPowerRoleString(BatteryPowerRole role) { + if (role < PowerRole_Sink || role > PowerRole_Source) + return s_powerRoleStrings[0]; + + return s_powerRoleStrings[role]; +} + +const char* batteryInfoGetPDStateString(BatteryPDControllerState state) { + if (state < PDState_NewPDO || state > PDState_AcceptedRDO) + return s_pdStateStrings[0]; + + return s_pdStateStrings[state]; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/cpp_util.hpp b/Source/rewrite-hoc-clk/common/include/cpp_util.hpp new file mode 100644 index 00000000..2e367ede --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/cpp_util.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) meha3945 (hanai3bi) + * + * 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 + +template +class ScopeGuard { +public: + ScopeGuard(F&& f) + : f(f), engaged(true) {}; + + ~ScopeGuard() { + if (engaged) + f(); + }; + + ScopeGuard(ScopeGuard&& rhs) + : f(std::move(rhs.f)) {}; + + void dismiss() { engaged = false; } + +private: + F f; + bool engaged; +}; + +struct MakeScopeExit { + template + ScopeGuard operator+=(F&& f) { + return ScopeGuard(std::move(f)); + }; +}; + +#define STRING_CAT2(x, y) x##y +#define STRING_CAT(x, y) STRING_CAT2(x, y) +#define SCOPE_GUARD MakeScopeExit() += [&]() __attribute__((always_inline)) +#define SCOPE_EXIT auto STRING_CAT(scope_exit_, __LINE__) = SCOPE_GUARD \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/crc32.h b/Source/rewrite-hoc-clk/common/include/crc32.h new file mode 100644 index 00000000..67539c7d --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/crc32.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once +#include +#include + +uint32_t crc32(const uint8_t *data, size_t length) { + uint32_t crc = 0xFFFFFFFF; + + for (size_t i = 0; i < length; i++) { + crc ^= data[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); + } + } + return ~crc; +} + +uint32_t checksum_file(const char *filename) { + FILE *file = fopen(filename, "rb"); + if (!file) { + perror("[crc32] Error opening file"); + return 0; + } + + uint8_t buffer[1024]; + uint32_t crc = 0xFFFFFFFF; + size_t bytes_read; + + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + for (size_t i = 0; i < bytes_read; i++) { + crc ^= buffer[i]; + for (int j = 0; j < 8; j++) { + crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1)); + } + } + } + + fclose(file); + return ~crc; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/display_refresh_rate.h b/Source/rewrite-hoc-clk/common/include/display_refresh_rate.h new file mode 100644 index 00000000..add49b3a --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/display_refresh_rate.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) Souldbminer, based on reasearch by MasaGratoR and Cooler3D + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include +#include +#include + +typedef struct { + uint16_t hFrontPorch; + uint8_t hSyncWidth; + uint8_t hBackPorch; + uint8_t vFrontPorch; + uint8_t vSyncWidth; + uint8_t vBackPorch; + uint8_t VIC; + uint32_t pixelClock_kHz; +} DockedTimings; + +typedef struct { + uint8_t hSyncWidth; + uint16_t hFrontPorch; + uint8_t hBackPorch; + uint8_t vSyncWidth; + uint16_t vFrontPorch; + uint8_t vBackPorch; + uint32_t pixelClock_kHz; +} HandheldTimings; + +typedef struct { + uint8_t min; + uint8_t max; +} MinMaxRefreshRate; + +typedef struct { + uint32_t unk0; + uint32_t hActive; + uint32_t vActive; + uint32_t hSyncWidth; + uint32_t vSyncWidth; + uint32_t hFrontPorch; + uint32_t vFrontPorch; + uint32_t hBackPorch; + uint32_t vBackPorch; + uint32_t pclkKHz; + uint32_t bitsPerPixel; + uint32_t vmode; + uint32_t sync; + uint32_t unk1; + uint32_t reserved; +} NvdcMode2; + +typedef struct { + NvdcMode2 modes[201]; + uint32_t num_modes; +} NvdcModeDB2; + +typedef struct { + unsigned int PLLD_DIVM: 8; + unsigned int reserved_1: 3; + unsigned int PLLD_DIVN: 8; + unsigned int reserved_2: 1; + unsigned int PLLD_DIVP: 3; + unsigned int CSI_CLK_SRC: 1; + unsigned int reserved_3: 1; + unsigned int PLL_D: 1; + unsigned int reserved_4: 1; + unsigned int PLLD_LOCK: 1; + unsigned int reserved_5: 1; + unsigned int PLLD_REF_DIS: 1; + unsigned int PLLD_ENABLE: 1; + unsigned int PLLD_BYPASS: 1; +} PLLD_BASE; + +typedef struct { + signed int PLLD_SDM_DIN: 16; + unsigned int PLLD_EN_SDM: 1; + unsigned int PLLD_LOCK_OVERRIDE: 1; + unsigned int PLLD_EN_LCKDET: 1; + unsigned int PLLD_FREQLOCK: 1; + unsigned int PLLD_IDDQ: 1; + unsigned int PLLD_ENABLE_CLK: 1; + unsigned int PLLD_KVCO: 1; + unsigned int PLLD_KCP: 2; + unsigned int PLLD_PTS: 2; + unsigned int PLLD_LDPULSE_ADJ: 3; + unsigned int reserved: 2; +} PLLD_MISC; + +typedef struct { + uint64_t clkVirtAddr; + uint64_t dsiVirtAddr; + bool isDocked; + bool isLite; + bool isRetroSUPER; + bool isPossiblySpoofedRetro; + bool dontForce60InDocked; + bool matchLowestDocked; + bool displaySync; + bool displaySyncOutOfFocus60; + bool displaySyncDocked; + bool displaySyncDockedOutOfFocus60; +} DisplayRefreshConfig; +bool DisplayRefresh_Initialize(const DisplayRefreshConfig* config); +void DisplayRefresh_SetDockedState(bool isDocked); +bool DisplayRefresh_SetRate(uint32_t new_refreshRate); +bool DisplayRefresh_GetRate(uint32_t* out_refreshRate, bool internal); +uint8_t DisplayRefresh_GetDockedHighestAllowed(void); +void DisplayRefresh_CorrectOledGamma(uint32_t refresh_rate); +void DisplayRefresh_SetAllowedDockedRatesIPC(uint32_t refreshRates, bool is720p); +void DisplayRefresh_Shutdown(void); diff --git a/Source/rewrite-hoc-clk/common/include/i2c.h b/Source/rewrite-hoc-clk/common/include/i2c.h new file mode 100644 index 00000000..feb7c9fb --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/i2c.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 KazushiMe + * Licensed under the GPLv2 + */ + +#pragma once + +#include + +// To use i2c service, sm and i2c should be intialized via smInitialize() and i2cInitialize(). + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val); + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out); +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out); + +// Max17050 fuel gauge +float I2c_Max17050_GetBatteryCurrent(); + +const u8 MAX17050_CURRENT_REG = 0x0A; + +// Buck Converter +typedef enum I2c_BuckConverter_Reg { + I2c_Max77620_SD1VOLT_REG = 0x17, // Used for Erista DDR VDDQ+VDD2 / Mariko VDD2 + I2c_Max77621_VOLT_REG = 0x00, + I2c_Max77812_CPUVOLT_REG = 0x26, + I2c_Max77812_GPUVOLT_REG = 0x23, + I2c_Max77812_MEMVOLT_REG = 0x25, // Master 3 (GPU 1 + 2, DRAM 3, CPU 4), used for Mariko VDDQ +} I2c_BuckConverter_Reg; + +typedef struct I2c_BuckConverter_Domain { + I2cDevice device; + I2c_BuckConverter_Reg reg; + u8 volt_mask; + u32 uv_step; + u32 uv_min; + u32 uv_max; + u8 por_val; +} I2c_BuckConverter_Domain; + +const I2c_BuckConverter_Domain I2c_Erista_CPU = { I2cDevice_Max77621Cpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_GPU = { I2cDevice_Max77621Gpu, I2c_Max77621_VOLT_REG, 0x7F, 6250, 606250, 1400000, }; +const I2c_BuckConverter_Domain I2c_Erista_DRAM = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; +const I2c_BuckConverter_Domain I2c_Mariko_CPU = { I2cDevice_Max77812_2, I2c_Max77812_CPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_GPU = { I2cDevice_Max77812_2, I2c_Max77812_GPUVOLT_REG, 0xFF, 5000, 250000, 1525000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDDQ = { I2cDevice_Max77812_2, I2c_Max77812_MEMVOLT_REG, 0xFF, 5000, 250000, 700000, 0x78 }; +const I2c_BuckConverter_Domain I2c_Mariko_DRAM_VDD2 = { I2cDevice_Max77620Pmic, I2c_Max77620_SD1VOLT_REG, 0x7F, 12500, 600000, 1250000, }; + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain); +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt); + +// Bq24193 Battery management +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw); +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma); + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma); +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma); + +const u32 MA_RANGE_MIN = 512; +const u32 MA_RANGE_MAX = 4544; + +const u8 BQ24193_CHARGE_CURRENT_CONTROL_REG = 0x2; \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/ipc.h b/Source/rewrite-hoc-clk/common/include/ipc.h new file mode 100644 index 00000000..3015af32 --- /dev/null +++ b/Source/rewrite-hoc-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/rewrite-hoc-clk/common/include/memmem.h b/Source/rewrite-hoc-clk/common/include/memmem.h new file mode 100644 index 00000000..0f791c1b --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/memmem.h @@ -0,0 +1,41 @@ +/* + MIT License + + Copyright (c) 2024 Roy Merkel + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + 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 OR COPYRIGHT HOLDERS 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. +*/ + +#ifndef MEMMEM_IMPL_H +#define MEMMEM_IMPL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void *memmem_impl(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Source/rewrite-hoc-clk/common/include/notification.h b/Source/rewrite-hoc-clk/common/include/notification.h new file mode 100644 index 00000000..a6389b6f --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/notification.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) ppkantorski (bord2death) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include +#include +#include + +static void writeNotification(const std::string& message) { + static const char* flagPath = "sdmc:/config/ultrahand/flags/NOTIFICATIONS.flag"; + + FILE* flagFile = fopen(flagPath, "r"); + if (!flagFile) { + return; + } + fclose(flagFile); + + std::string filename = "Horizon OC -" + std::to_string(std::time(nullptr)) + ".notify"; + std::string fullPath = "sdmc:/config/ultrahand/notifications/" + filename; + + FILE* file = fopen(fullPath.c_str(), "w"); + if (file) { + fprintf(file, "{\n"); + fprintf(file, " \"text\": \"%s\",\n", message.c_str()); + fprintf(file, " \"fontSize\": 28\n"); + fprintf(file, "}\n"); + fclose(file); + } + } diff --git a/Source/rewrite-hoc-clk/common/include/pcv_types.h b/Source/rewrite-hoc-clk/common/include/pcv_types.h new file mode 100644 index 00000000..03899f35 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/pcv_types.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) ppkantorski (bord2death) + * + * 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 + +/* + * SDx actual min is 625 mV. Multipliers 0/1 reserved. + * SD0 max is 1400 mV + * SD1 max is 1550 mV + * SD2 max is 3787.5 mV + * SD3 max is 3787.5 mV + */ + +/* +* Switch Power domains (max77620): +* Name | Usage | uV step | uV min | uV default | uV max | Init +*-------+---------------+---------+--------+------------+---------+------------------ +* sd0 | SoC | 12500 | 600000 | 625000 | 1400000 | 1.125V (pkg1.1) +* sd1 | SDRAM | 12500 | 600000 | 1125000 | 1125000 | 1.1V (pkg1.1) +* sd2 | ldo{0-1, 7-8} | 12500 | 600000 | 1325000 | 1350000 | 1.325V (pcv) +* sd3 | 1.8V general | 12500 | 600000 | 1800000 | 1800000 | +* ldo0 | Display Panel | 25000 | 800000 | 1200000 | 1200000 | 1.2V (pkg1.1) +* ldo1 | XUSB, PCIE | 25000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) +* ldo2 | SDMMC1 | 50000 | 800000 | 1800000 | 3300000 | +* ldo3 | GC ASIC | 50000 | 800000 | 3100000 | 3100000 | 3.1V (pcv) +* ldo4 | RTC | 12500 | 800000 | 850000 | 850000 | 0.85V (AO, pcv) +* ldo5 | GC Card | 50000 | 800000 | 1800000 | 1800000 | 1.8V (pcv) +* ldo6 | Touch, ALS | 50000 | 800000 | 2900000 | 2900000 | 2.9V (pcv) +* ldo7 | XUSB | 50000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) +* ldo8 | XUSB, DP, MCU | 50000 | 800000 | 1050000 | 2800000 | 1.05V/2.8V (pcv) +*/ + + +// GPIOs T210: 3: 3.3V, 5: CPU PMIC, 6: GPU PMIC, 7: DSI/VI 1.2V powered by ldo0. + +/* + * OTP: T210 - T210B01: + * SD0: 1.0V 1.05V - SoC. EN Based on FPSSRC. + * SD1: 1.15V 1.1V - DRAM for T210. EN Based on FPSSRC. + * SD2: 1.35V 1.35V + * SD3: 1.8V 1.8V + * All powered off? + * LDO0: -- -- - Display + * LDO1: 1.05V 1.05V + * LDO2: -- -- - SD + * LDO3: 3.1V 3.1V - GC ASIC + * LDO4: 1.0V 0.8V - Needed for RTC domain on T210. + * LDO5: 3.1V 3.1V + * LDO6: 2.8V 2.9V - Touch. + * LDO7: 1.05V 1.0V + * LDO8: 1.05V 1.0V + */ + +/* +* MAX77620_AME_GPIO: control GPIO modes (bits 0 - 7 correspond to GPIO0 - GPIO7); 0 -> GPIO, 1 -> alt-mode +* MAX77620_REG_GPIOx: 0x9 sets output and enable +*/ + +typedef enum { + PcvPowerDomain_Max77620_Sd0 = 0, + PcvPowerDomain_Max77620_Sd1 = 1, + PcvPowerDomain_Max77620_Sd2 = 2, + PcvPowerDomain_Max77620_Sd3 = 3, + PcvPowerDomain_Max77620_Ldo0 = 4, + PcvPowerDomain_Max77620_Ldo1 = 5, + PcvPowerDomain_Max77620_Ldo2 = 6, + PcvPowerDomain_Max77620_Ldo3 = 7, + PcvPowerDomain_Max77620_Ldo4 = 8, + PcvPowerDomain_Max77620_Ldo5 = 9, + PcvPowerDomain_Max77620_Ldo6 = 10, + PcvPowerDomain_Max77620_Ldo7 = 11, + PcvPowerDomain_Max77620_Ldo8 = 12, + PcvPowerDomain_Max77621_Cpu = 13, + PcvPowerDomain_Max77621_Gpu = 14, + PcvPowerDomain_Max77812_Cpu = 15, + PcvPowerDomain_Max77812_Gpu = 16, + PcvPowerDomain_Max77812_Dram = 17, +} PowerDomain; + +typedef enum { + PcvPowerDomainId_Max77620_Sd0 = 0x3A000080, + PcvPowerDomainId_Max77620_Sd1 = 0x3A000081, // vdd2 + PcvPowerDomainId_Max77620_Sd2 = 0x3A000082, + PcvPowerDomainId_Max77620_Sd3 = 0x3A000083, + PcvPowerDomainId_Max77620_Ldo0 = 0x3A0000A0, + PcvPowerDomainId_Max77620_Ldo1 = 0x3A0000A1, + PcvPowerDomainId_Max77620_Ldo2 = 0x3A0000A2, + PcvPowerDomainId_Max77620_Ldo3 = 0x3A0000A3, + PcvPowerDomainId_Max77620_Ldo4 = 0x3A0000A4, + PcvPowerDomainId_Max77620_Ldo5 = 0x3A0000A5, + PcvPowerDomainId_Max77620_Ldo6 = 0x3A0000A6, + PcvPowerDomainId_Max77620_Ldo7 = 0x3A0000A7, + PcvPowerDomainId_Max77620_Ldo8 = 0x3A0000A8, + PcvPowerDomainId_Max77621_Cpu = 0x3A000003, + PcvPowerDomainId_Max77621_Gpu = 0x3A000004, + PcvPowerDomainId_Max77812_Cpu = 0x3A000003, + PcvPowerDomainId_Max77812_Gpu = 0x3A000004, + PcvPowerDomainId_Max77812_Dram = 0x3A000005, // vddq +} PowerDomainId; \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/pwm.h b/Source/rewrite-hoc-clk/common/include/pwm.h new file mode 100644 index 00000000..ccc24e02 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/pwm.h @@ -0,0 +1,39 @@ +/* + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + Service s; +} PwmChannelSession; + +Result pwmInitialize(void); +void pwmExit(void); +Service* pwmGetServiceSession(void); +Result pwmOpenSession2(PwmChannelSession *out, u32 device_code); +Result pwmChannelSessionGetDutyCycle(PwmChannelSession *c, double* out); +void pwmChannelSessionClose(PwmChannelSession *c); + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/registers.h b/Source/rewrite-hoc-clk/common/include/registers.h new file mode 100644 index 00000000..82977371 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/registers.h @@ -0,0 +1,527 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * Copyright (c) Linux 4 Tegra & Linux 4 Switch contributors + * + * 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 + +#define EMC_INTSTATUS_0 0x0 +#define EMC_INTMASK_0 0x4 +#define EMC_DBG_0 0x8 +#define EMC_CFG_0 0xC +#define EMC_ADR_CFG_0 0x10 +#define EMC_REFCTRL_0 0x20 +#define EMC_PIN_0 0x24 +#define EMC_TIMING_CONTROL_0 0x28 +#define EMC_RC_0 0x2C +#define EMC_RFC_0 0x30 +#define EMC_RAS_0 0x34 +#define EMC_RP_0 0x38 +#define EMC_R2W_0 0x3C +#define EMC_W2R_0 0x40 +#define EMC_R2P_0 0x44 +#define EMC_W2P_0 0x48 +#define EMC_RD_RCD_0 0x4C +#define EMC_WR_RCD_0 0x50 +#define EMC_RRD_0 0x54 +#define EMC_REXT_0 0x58 +#define EMC_WDV_0 0x5C +#define EMC_QUSE_0 0x60 +#define EMC_QRST_0 0x64 +#define EMC_QSAFE_0 0x68 +#define EMC_RDV_0 0x6C +#define EMC_REFRESH_0 0x70 +#define EMC_BURST_REFRESH_NUM_0 0x74 +#define EMC_PDEX2WR_0 0x78 +#define EMC_PDEX2RD_0 0x7C +#define EMC_PCHG2PDEN_0 0x80 +#define EMC_ACT2PDEN_0 0x84 +#define EMC_AR2PDEN_0 0x88 +#define EMC_RW2PDEN_0 0x8C +#define EMC_TXSR_0 0x90 +#define EMC_TCKE_0 0x94 +#define EMC_TFAW_0 0x98 +#define EMC_TRPAB_0 0x9C +#define EMC_TCLKSTABLE_0 0xA0 +#define EMC_TCLKSTOP_0 0xA4 +#define EMC_TREFBW_0 0xA8 +#define EMC_TPPD_0 0xAC +#define EMC_ODT_WRITE_0 0xB0 +#define EMC_PDEX2MRR_0 0xB4 +#define EMC_WEXT_0 0xB8 +#define EMC_RFC_SLR_0 0xC0 +#define EMC_MRS_WAIT_CNT2_0 0xC4 +#define EMC_MRS_WAIT_CNT_0 0xC8 +#define EMC_MRS_0 0xCC +#define EMC_EMRS_0 0xD0 +#define EMC_REF_0 0xD4 +#define EMC_PRE_0 0xD8 +#define EMC_NOP_0 0xDC +#define EMC_SELF_REF_0 0xE0 +#define EMC_DPD_0 0xE4 +#define EMC_MRW_0 0xE8 +#define EMC_MRR_0 0xEC +#define EMC_CMDQ_0 0xF0 +#define EMC_MC2EMCQ_0 0xF4 +#define EMC_FBIO_SPARE_0 0x100 +#define EMC_FBIO_CFG5_0 0x104 +#define EMC_FBIO_CFG6_0 0x114 +#define EMC_PDEX2CKE_0 0x118 +#define EMC_CKE2PDEN_0 0x11C +#define EMC_CFG_RSV_0 0x120 +#define EMC_ACPD_CONTROL_0 0x124 +#define EMC_MPC_0 0x128 +#define EMC_EMRS2_0 0x12C +#define EMC_EMRS3_0 0x130 +#define EMC_MRW2_0 0x134 +#define EMC_MRW3_0 0x138 +#define EMC_MRW4_0 0x13C +#define EMC_CLKEN_OVERRIDE_0 0x140 +#define EMC_R2R_0 0x144 +#define EMC_W2W_0 0x148 +#define EMC_EINPUT_0 0x14C +#define EMC_EINPUT_DURATION_0 0x150 +#define EMC_PUTERM_EXTRA_0 0x154 +#define EMC_TCKESR_0 0x158 +#define EMC_TPD_0 0x15C +#define EMC_AUTO_CAL_CONFIG_0 0x2A4 +#define EMC_AUTO_CAL_INTERVAL_0 0x2A8 +#define EMC_AUTO_CAL_STATUS_0 0x2AC +#define EMC_REQ_CTRL_0 0x2B0 +#define EMC_EMC_STATUS_0 0x2B4 +#define EMC_CFG_2_0 0x2B8 +#define EMC_CFG_DIG_DLL_0 0x2BC +#define EMC_CFG_DIG_DLL_PERIOD_0 0x2C0 +#define EMC_DIG_DLL_STATUS_0 0x2C4 +#define EMC_CFG_DIG_DLL_1_0 0x2C8 +#define EMC_RDV_MASK_0 0x2CC +#define EMC_WDV_MASK_0 0x2D0 +#define EMC_RDV_EARLY_MASK_0 0x2D4 +#define EMC_RDV_EARLY_0 0x2D8 +#define EMC_AUTO_CAL_CONFIG8_0 0x2DC +#define EMC_ZCAL_INTERVAL_0 0x2E0 +#define EMC_ZCAL_WAIT_CNT_0 0x2E4 +#define EMC_ZCAL_MRW_CMD_0 0x2E8 +#define EMC_ZQ_CAL_0 0x2EC +#define EMC_XM2COMPPADCTRL3_0 0x2F4 +#define EMC_AUTO_CAL_VREF_SEL_0_0 0x2F8 +#define EMC_AUTO_CAL_VREF_SEL_1_0 0x300 +#define EMC_XM2COMPPADCTRL_0 0x30C +#define EMC_FDPD_CTRL_DQ_0 0x310 +#define EMC_FDPD_CTRL_CMD_0 0x314 +#define EMC_PMACRO_CMD_BRICK_CTRL_FDPD_0 0x318 +#define EMC_PMACRO_DATA_BRICK_CTRL_FDPD_0 0x31C +#define EMC_SCRATCH0_0 0x324 +#define EMC_PMACRO_BRICK_CTRL_RFU1_0 0x330 +#define EMC_PMACRO_BRICK_CTRL_RFU2_0 0x334 +#define EMC_CMD_MAPPING_CMD0_0_0 0x380 +#define EMC_CMD_MAPPING_CMD0_1_0 0x384 +#define EMC_CMD_MAPPING_CMD0_2_0 0x388 +#define EMC_CMD_MAPPING_CMD1_0_0 0x38C +#define EMC_CMD_MAPPING_CMD1_1_0 0x390 +#define EMC_CMD_MAPPING_CMD1_2_0 0x394 +#define EMC_CMD_MAPPING_CMD2_0_0 0x398 +#define EMC_CMD_MAPPING_CMD2_1_0 0x39C +#define EMC_CMD_MAPPING_CMD2_2_0 0x3A0 +#define EMC_CMD_MAPPING_CMD3_0_0 0x3A4 +#define EMC_CMD_MAPPING_CMD3_1_0 0x3A8 +#define EMC_CMD_MAPPING_CMD3_2_0 0x3AC +#define EMC_CMD_MAPPING_BYTE_0 0x3B0 +#define EMC_TR_TIMING_0_0 0x3B4 +#define EMC_TR_CTRL_0_0 0x3B8 +#define EMC_TR_CTRL_1_0 0x3BC +#define EMC_SWITCH_BACK_CTRL_0 0x3C0 +#define EMC_TR_RDV_0 0x3C4 +#define EMC_STALL_THEN_EXE_BEFORE_CLKCHANGE_0 0x3C8 +#define EMC_STALL_THEN_EXE_AFTER_CLKCHANGE_0 0x3CC +#define EMC_UNSTALL_RW_AFTER_CLKCHANGE_0 0x3D0 +#define EMC_AUTO_CAL_ 0x3D4 +#define EMC_SEL_DPD_CTRL_0 0x3D8 +#define EMC_PRE_REFRESH_REQ_CNT_0 0x3DC +#define EMC_DYN_SELF_REF_CONTROL_0 0x3E0 +#define EMC_TXSRDLL_0 0x3E4 +#define EMC_CCFIFO_ADDR_0 0x3E8 +#define EMC_CCFIFO_DATA_0 0x3EC +#define EMC_CCFIFO_STATUS_0 0x3F0 +#define EMC_TR_QPOP_0 0x3F4 +#define EMC_TR_RDV_MASK_0 0x3F8 +#define EMC_TR_QSAFE_0 0x3FC +#define EMC_TR_QRST_0 0x400 +#define EMC_SWIZZLE_RANK0_BYTE0_0 0x404 +#define EMC_SWIZZLE_RANK0_BYTE1_0 0x408 +#define EMC_SWIZZLE_RANK0_BYTE2_0 0x40C +#define EMC_SWIZZLE_RANK0_BYTE3_0 0x410 +#define EMC_SWIZZLE_RANK1_BYTE0_0 0x418 +#define EMC_SWIZZLE_RANK1_BYTE1_0 0x41C +#define EMC_SWIZZLE_RANK1_BYTE2_0 0x420 +#define EMC_SWIZZLE_RANK1_BYTE3_0 0x424 +#define EMC_ISSUE_QRST_0 0x428 +#define EMC_PMC_SCRATCH1_0 0x440 +#define EMC_PMC_SCRATCH2_0 0x444 +#define EMC_PMC_SCRATCH3_0 0x448 +#define EMC_AUTO_CAL_CONFIG2_0 0x458 +#define EMC_AUTO_CAL_CONFIG3_0 0x45C +#define EMC_TR_DVFS_0 0x460 +#define EMC_AUTO_CAL_CHANNEL_0 0x464 +#define EMC_IBDLY_0 0x468 +#define EMC_OBDLY_0 0x46C +#define EMC_TXDSRVTTGEN_0 0x480 +#define EMC_WE_DURATION_0 0x48C +#define EMC_WS_DURATION_0 0x490 +#define EMC_WEV_0 0x494 +#define EMC_WSV_0 0x498 +#define EMC_CFG_3_0 0x49C +#define EMC_MRW5_0 0x4A0 +#define EMC_MRW6_0 0x4A4 +#define EMC_MRW7_0 0x4A8 +#define EMC_MRW8_0 0x4AC +#define EMC_MRW9_0 0x4B0 +#define EMC_MRW10_0 0x4B4 +#define EMC_MRW11_0 0x4B8 +#define EMC_MRW12_0 0x4BC +#define EMC_MRW13_0 0x4C0 +#define EMC_MRW14_0 0x4C4 +#define EMC_MRW15_0 0x4D0 +#define EMC_CFG_SYNC_0 0x4D4 +#define EMC_FDPD_CTRL_CMD_NO_RAMP_0 0x4D8 +#define EMC_WDV_CHK_0 0x4E0 +#define EMC_CFG_PIPE_2_0 0x554 +#define EMC_CFG_PIPE_CLK_0 0x558 +#define EMC_CFG_PIPE_1_0 0x55C +#define EMC_CFG_PIPE_0 0x560 +#define EMC_QPOP_0 0x564 +#define EMC_QUSE_WIDTH_0 0x568 +#define EMC_PUTERM_WIDTH_0 0x56C +#define EMC_BGBIAS_CTL0_0 0x570 +#define EMC_AUTO_CAL_CONFIG7_0 0x574 +#define EMC_XM2COMPPADCTRL2_0 0x578 +#define EMC_COMP_PAD_SW_CTRL_0 0x57C +#define EMC_REFCTRL2_0 0x580 +#define EMC_FBIO_CFG7_0 0x584 +#define EMC_DATA_BRLSHFT_0_0 0x588 +#define EMC_DATA_BRLSHFT_1_0 0x58C +#define EMC_RFCPB_0 0x590 +#define EMC_DQS_BRLSHFT_0_0 0x594 +#define EMC_DQS_BRLSHFT_1_0 0x598 +#define EMC_CMD_BRLSHFT_0_0 0x59C +#define EMC_CMD_BRLSHFT_1_0 0x5A0 +#define EMC_CMD_BRLSHFT_2_0 0x5A4 +#define EMC_CMD_BRLSHFT_3_0 0x5A8 +#define EMC_QUSE_BRLSHFT_0_0 0x5AC +#define EMC_AUTO_CAL_CONFIG4_0 0x5B0 +#define EMC_AUTO_CAL_CONFIG5_0 0x5B4 +#define EMC_QUSE_BRLSHFT_1_0 0x5B8 +#define EMC_QUSE_BRLSHFT_2_0 0x5BC +#define EMC_CCDMW_0 0x5C0 +#define EMC_QUSE_BRLSHFT_3_0 0x5C4 +#define EMC_FBIO_CFG8_0 0x5C8 +#define EMC_AUTO_CAL_CONFIG6_0 0x5CC +#define EMC_PROTOBIST_CONFIG_ADR_1_0 0x5D0 +#define EMC_PROTOBIST_CONFIG_ADR_2_0 0x5D4 +#define EMC_PROTOBIST_MISC_0 0x5D8 +#define EMC_PROTOBIST_WDATA_LOWER_0 0x5DC +#define EMC_PROTOBIST_WDATA_UPPER_0 0x5E0 +#define EMC_PROTOBIST_RDATA_0 0x5EC +#define EMC_DLL_CFG_0_0 0x5E4 +#define EMC_DLL_CFG_1_0 0x5E8 +#define EMC_CONFIG_SAMPLE_DELAY_0 0x5F0 +#define EMC_CFG_UPDATE_0 0x5F4 +#define EMC_PMACRO_QUSE_DDLL_RANK0_0_0 0x600 +#define EMC_PMACRO_QUSE_DDLL_RANK0_1_0 0x604 +#define EMC_PMACRO_QUSE_DDLL_RANK0_2_0 0x608 +#define EMC_PMACRO_QUSE_DDLL_RANK0_3_0 0x60C +#define EMC_PMACRO_QUSE_DDLL_RANK0_4_0 0x610 +#define EMC_PMACRO_QUSE_DDLL_RANK0_5_0 0x614 +#define EMC_PMACRO_QUSE_DDLL_RANK1_0_0 0x620 +#define EMC_PMACRO_QUSE_DDLL_RANK1_1_0 0x624 +#define EMC_PMACRO_QUSE_DDLL_RANK1_2_0 0x628 +#define EMC_PMACRO_QUSE_DDLL_RANK1_3_0 0x62C +#define EMC_PMACRO_QUSE_DDLL_RANK1_4_0 0x630 +#define EMC_PMACRO_QUSE_DDLL_RANK1_5_0 0x634 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_0_0 0x640 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_1_0 0x644 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_2_0 0x648 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_3_0 0x64C +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_4_0 0x650 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK0_5_0 0x654 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_0_0 0x660 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_1_0 0x664 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_2_0 0x668 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_3_0 0x66C +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_4_0 0x670 +#define EMC_PMACRO_OB_DDLL_LONG_DQ_RANK1_5_0 0x674 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_0_0 0x680 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_1_0 0x684 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_2_0 0x688 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_3_0 0x68C +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_4_0 0x690 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK0_5_0 0x694 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_0_0 0x6A0 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_1_0 0x6A4 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_2_0 0x6A8 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_3_0 0x6AC +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_4_0 0x6B0 +#define EMC_PMACRO_OB_DDLL_LONG_DQS_RANK1_5_0 0x6B4 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_0_0 0x6C0 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_1_0 0x6C4 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_2_0 0x6C8 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_3_0 0x6CC +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_4_0 0x6D0 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK0_5_0 0x6D4 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_0_0 0x6E0 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_1_0 0x6E4 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_2_0 0x6E8 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_3_0 0x6EC +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_4_0 0x6F0 +#define EMC_PMACRO_IB_DDLL_LONG_DQS_RANK1_5_0 0x6F4 +#define EMC_PMACRO_AUTOCAL_CFG_0_0 0x700 +#define EMC_PMACRO_AUTOCAL_CFG_1_0 0x704 +#define EMC_PMACRO_AUTOCAL_CFG_2_0 0x708 +#define EMC_PMACRO_TX_PWRD_0_0 0x720 +#define EMC_PMACRO_TX_PWRD_1_0 0x724 +#define EMC_PMACRO_TX_PWRD_2_0 0x728 +#define EMC_PMACRO_TX_PWRD_3_0 0x72C +#define EMC_PMACRO_TX_PWRD_4_0 0x730 +#define EMC_PMACRO_TX_PWRD_5_0 0x734 +#define EMC_PMACRO_TX_SEL_CLK_SRC_0_0 0x740 +#define EMC_PMACRO_TX_SEL_CLK_SRC_1_0 0x744 +#define EMC_PMACRO_TX_SEL_CLK_SRC_2_0 0x748 +#define EMC_PMACRO_TX_SEL_CLK_SRC_3_0 0x74C +#define EMC_PMACRO_TX_SEL_CLK_SRC_4_0 0x750 +#define EMC_PMACRO_TX_SEL_CLK_SRC_5_0 0x754 +#define EMC_PMACRO_DDLL_BYPASS_0 0x760 +#define EMC_PMACRO_DDLL_PWRD_0_0 0x770 +#define EMC_PMACRO_DDLL_PWRD_1_0 0x774 +#define EMC_PMACRO_DDLL_PWRD_2_0 0x778 +#define EMC_PMACRO_CMD_CTRL_0_0 0x780 +#define EMC_PMACRO_CMD_CTRL_1_0 0x784 +#define EMC_PMACRO_CMD_CTRL_2_0 0x788 + +#define MC_REGISTER_BASE 0x70019000 +#define MC_REGISTER_REGION_SIZE 0x1000 + +#define MC_INTSTATUS_0 0x000 +#define MC_INTMASK_0 0x004 +#define MC_ERR_STATUS_0 0x008 +#define MC_ERR_ADR_0 0x00C +#define MC_SMMU_CONFIG_0 0x010 +#define MC_SMMU_PTB_ASID_0 0x01C +#define MC_SMMU_PTB_DATA_0 0x020 +#define MC_SMMU_TLB_FLUSH_0 0x030 +#define MC_SMMU_PTC_FLUSH_0_0 0x034 +#define MC_EMEM_CFG_0 0x050 +#define MC_EMEM_ADR_CFG_0 0x054 +#define MC_EMEM_ARB_CFG_0 0x090 +#define MC_EMEM_ARB_OUTSTANDING_REQ_0 0x094 +#define MC_EMEM_ARB_TIMING_RCD_0 0x098 +#define MC_EMEM_ARB_TIMING_RP_0 0x09C +#define MC_EMEM_ARB_TIMING_RC_0 0x0A0 +#define MC_EMEM_ARB_TIMING_RAS_0 0x0A4 +#define MC_EMEM_ARB_TIMING_FAW_0 0x0A8 +#define MC_EMEM_ARB_TIMING_RRD_0 0x0AC +#define MC_EMEM_ARB_TIMING_RAP2PRE_0 0x0B0 +#define MC_EMEM_ARB_TIMING_WAP2PRE_0 0x0B4 +#define MC_EMEM_ARB_TIMING_R2R_0 0x0B8 +#define MC_EMEM_ARB_TIMING_W2W_0 0x0BC +#define MC_EMEM_ARB_TIMING_R2W_0 0x0C0 +#define MC_EMEM_ARB_TIMING_W2R_0 0x0C4 +#define MC_EMEM_ARB_MISC2_0 0x0C8 +#define MC_EMEM_ARB_DA_TURNS_0 0x0D0 +#define MC_EMEM_ARB_DA_COVERS_0 0x0D4 +#define MC_EMEM_ARB_MISC0_0 0x0D8 +#define MC_EMEM_ARB_MISC1_0 0x0DC +#define MC_TIMING_CONTROL_0 0xFC +#define MC_EMEM_ARB_RING1_THROTTLE_0 0x0E0 +#define MC_CLIENT_HOTRESET_CTRL_0 0x200 +#define MC_CLIENT_HOTRESET_STATUS_0 0x204 +#define MC_SMMU_AFI_ASID_0 0x238 +#define MC_SMMU_DC_ASID_0 0x240 +#define MC_SMMU_DCB_ASID_0 0x244 +#define MC_SMMU_HC_ASID_0 0x250 +#define MC_SMMU_HDA_ASID_0 0x254 +#define MC_SMMU_ISP2_ASID_0 0x258 +#define MC_SMMU_MSENC_NVENC_ASID_0 0x264 +#define MC_SMMU_NV_ASID_0 0x268 +#define MC_SMMU_NV2_ASID_0 0x26C +#define MC_SMMU_PPCS_ASID_0 0x270 +#define MC_SMMU_SATA_ASID_0 0x274 +#define MC_SMMU_VI_ASID_0 0x280 +#define MC_SMMU_VIC_ASID_0 0x284 +#define MC_SMMU_XUSB_HOST_ASID_0 0x288 +#define MC_SMMU_XUSB_DEV_ASID_0 0x28C +#define MC_SMMU_TSEC_ASID_0 0x294 +#define MC_LATENCY_ALLOWANCE_AVPC_0 0x2E4 +#define MC_LATENCY_ALLOWANCE_DC_0 0x2E8 +#define MC_LATENCY_ALLOWANCE_DC_1 0x2EC +#define MC_LATENCY_ALLOWANCE_DCB_0 0x2F4 +#define MC_LATENCY_ALLOWANCE_DCB_1 0x2F8 +#define MC_LATENCY_ALLOWANCE_HC_0 0x310 +#define MC_LATENCY_ALLOWANCE_HC_1 0x314 +#define MC_LATENCY_ALLOWANCE_MPCORE_0 0x320 +#define MC_LATENCY_ALLOWANCE_NVENC_0 0x328 +#define MC_LATENCY_ALLOWANCE_PPCS_0 0x344 +#define MC_LATENCY_ALLOWANCE_PPCS_1 0x348 +#define MC_LATENCY_ALLOWANCE_ISP2_0 0x370 +#define MC_LATENCY_ALLOWANCE_ISP2_1 0x374 +#define MC_LATENCY_ALLOWANCE_XUSB_0 0x37C +#define MC_LATENCY_ALLOWANCE_XUSB_1 0x380 +#define MC_LATENCY_ALLOWANCE_TSEC_0 0x390 +#define MC_LATENCY_ALLOWANCE_VIC_0 0x394 +#define MC_LATENCY_ALLOWANCE_VI2_0 0x398 +#define MC_LATENCY_ALLOWANCE_GPU_0 0x3AC +#define MC_LATENCY_ALLOWANCE_SDMMCA_0 0x3B8 +#define MC_LATENCY_ALLOWANCE_SDMMCAA_0 0x3BC +#define MC_LATENCY_ALLOWANCE_SDMMC_0 0x3C0 +#define MC_LATENCY_ALLOWANCE_SDMMCAB_0 0x3C4 +#define MC_LATENCY_ALLOWANCE_NVDEC_0 0x3D8 +#define MC_LATENCY_ALLOWANCE_GPU2_0 0x3E8 +#define MC_DIS_PTSA_RATE_0 0x41C +#define MC_DIS_PTSA_MIN_0 0x420 +#define MC_DIS_PTSA_MAX_0 0x424 +#define MC_DISB_PTSA_RATE_0 0x428 +#define MC_DISB_PTSA_MIN_0 0x42C +#define MC_DISB_PTSA_MAX_0 0x430 +#define MC_VE_PTSA_RATE_0 0x434 +#define MC_VE_PTSA_MIN_0 0x438 +#define MC_VE_PTSA_MAX_0 0x43C +#define MC_MLL_MPCORER_PTSA_RATE_0 0x44C +#define MC_RING1_PTSA_RATE_0 0x47C +#define MC_RING1_PTSA_MIN_0 0x480 +#define MC_RING1_PTSA_MAX_0 0x484 +#define MC_PCX_PTSA_RATE_0 0x4AC +#define MC_PCX_PTSA_MIN_0 0x4B0 +#define MC_PCX_PTSA_MAX_0 0x4B4 +#define MC_MSE_PTSA_RATE_0 0x4C4 +#define MC_MSE_PTSA_MIN_0 0x4C8 +#define MC_MSE_PTSA_MAX_0 0x4CC +#define MC_AHB_PTSA_RATE_0 0x4DC +#define MC_AHB_PTSA_MIN_0 0x4E0 +#define MC_AHB_PTSA_MAX_0 0x4E4 +#define MC_APB_PTSA_RATE_0 0x4E8 +#define MC_APB_PTSA_MIN_0 0x4EC +#define MC_APB_PTSA_MAX_0 0x4F0 +#define MC_FTOP_PTSA_RATE_0 0x50C +#define MC_HOST_PTSA_RATE_0 0x518 +#define MC_HOST_PTSA_MIN_0 0x51C +#define MC_HOST_PTSA_MAX_0 0x520 +#define MC_USBX_PTSA_RATE_0 0x524 +#define MC_USBX_PTSA_MIN_0 0x528 +#define MC_USBX_PTSA_MAX_0 0x52C +#define MC_USBD_PTSA_RATE_0 0x530 +#define MC_USBD_PTSA_MIN_0 0x534 +#define MC_USBD_PTSA_MAX_0 0x538 +#define MC_GK_PTSA_RATE_0 0x53C +#define MC_GK_PTSA_MIN_0 0x540 +#define MC_GK_PTSA_MAX_0 0x544 +#define MC_AUD_PTSA_RATE_0 0x548 +#define MC_AUD_PTSA_MIN_0 0x54C +#define MC_AUD_PTSA_MAX_0 0x550 +#define MC_VICPC_PTSA_RATE_0 0x554 +#define MC_VICPC_PTSA_MIN_0 0x558 +#define MC_VICPC_PTSA_MAX_0 0x55C +#define MC_JPG_PTSA_RATE_0 0x584 +#define MC_JPG_PTSA_MIN_0 0x588 +#define MC_JPG_PTSA_MAX_0 0x58C +#define MC_GK2_PTSA_RATE_0 0x610 +#define MC_GK2_PTSA_MIN_0 0x614 +#define MC_GK2_PTSA_MAX_0 0x618 +#define MC_SDM_PTSA_RATE_0 0x61C +#define MC_SDM_PTSA_MIN_0 0x620 +#define MC_SDM_PTSA_MAX_0 0x624 +#define MC_HDAPC_PTSA_RATE_0 0x628 +#define MC_HDAPC_PTSA_MIN_0 0x62C +#define MC_HDAPC_PTSA_MAX_0 0x630 +#define MC_SEC_CARVEOUT_BOM_0 0x670 +#define MC_SEC_CARVEOUT_SIZE_MB_0 0x674 +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0A_0 0x690 +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0AB_0 0x694 +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0B_0 0x698 +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0BB_0 0x69C +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0C_0 0x6A0 +#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0CB_0 0x6A4 +#define MC_EMEM_ARB_TIMING_RFCPB_0 0x6C0 +#define MC_EMEM_ARB_TIMING_CCDMW_0 0x6C4 +#define MC_EMEM_ARB_REFPB_HP_CTRL_0 0x6F0 +#define MC_EMEM_ARB_REFPB_BANK_CTRL_0 0x6F4 +#define MC_PTSA_GRANT_DECREMENT_0 0x960 +#define MC_CLIENT_HOTRESET_CTRL_1 0x970 +#define MC_CLIENT_HOTRESET_STATUS_1 0x974 +#define MC_SMMU_PTC_FLUSH_1 0x9B8 +#define MC_SMMU_DC1_ASID_0 0xA88 +#define MC_SMMU_SDMMC1A_ASID_0 0xA94 +#define MC_SMMU_SDMMC2A_ASID_0 0xA98 +#define MC_SMMU_SDMMC3A_ASID_0 0xA9C +#define MC_SMMU_SDMMC4A_ASID_0 0xAA0 +#define MC_SMMU_ISP2B_ASID_0 0xAA4 +#define MC_SMMU_GPU_ASID_0 0xAA8 +#define MC_SMMU_GPUB_ASID_0 0xAAC +#define MC_SMMU_PPCS2_ASID_0 0xAB0 +#define MC_SMMU_NVDEC_ASID_0 0xAB4 +#define MC_SMMU_APE_ASID_0 0xAB8 +#define MC_SMMU_SE_ASID_0 0xABC +#define MC_SMMU_NVJPG_ASID_0 0xAC0 +#define MC_SMMU_HC1_ASID_0 0xAC4 +#define MC_SMMU_SE1_ASID_0 0xAC8 +#define MC_SMMU_AXIAP_ASID_0 0xACC +#define MC_SMMU_ETR_ASID_0 0xAD0 +#define MC_SMMU_TSECB_ASID_0 0xAD4 +#define MC_SMMU_TSEC1_ASID_0 0xAD8 +#define MC_SMMU_TSECB1_ASID_0 0xADC +#define MC_SMMU_NVDEC1_ASID_0 0xAE0 +#define MC_EMEM_ARB_DHYST_CTRL_0 0xBCC +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0 0xBD0 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1 0xBD4 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2 0xBD8 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3 0xBDC +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4 0xBE0 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5 0xBE4 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6 0xBE8 +#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7 0xBEC +#define MC_ERR_GENERALIZED_CARVEOUT_STATUS_0 0xC00 +#define MC_SECURITY_CARVEOUT2_BOM_0 0xC5C +#define MC_SECURITY_CARVEOUT3_BOM_0 0xCAC + +#define CLDVFS_REGION_BASE 0x70110000 +#define CLDVFS_REGION_SIZE 0x1000 +#define CL_DVFS_CTRL_0 0x0 +#define CL_DVFS_CONFIG_0 0x4 +#define CL_DVFS_PARAMS_0 0x8 +#define CL_DVFS_TUNE0_0 0xC +#define CL_DVFS_TUNE1_0 0x10 +#define CL_DVFS_FREQ_REQ_0 0x14 +#define CL_DVFS_SCALE_RAMP_0 0x18 +#define CL_DVFS_DROOP_CTRL_0 0x1C +#define CL_DVFS_OUTPUT_CFG_0 0x20 +#define CL_DVFS_OUTPUT_FORCE_0 0x24 +#define CL_DVFS_MONITOR_CTRL_0 0x28 +#define CL_DVFS_MONITOR_DATA_0 0x2C +#define CL_DVFS_I2C_CFG_0 0x40 +#define CL_DVFS_I2C_VDD_REG_ADDR_0 0x44 +#define CL_DVFS_I2C_STS_0 0x48 +#define CL_DVFS_INTR_STS_0 0x5C +#define CL_DVFS_INTR_EN_0 0x60 +#define DVFS_DFLL_THROTTLE_CTRL_0 0x64 +#define DVFS_DFLL_THROTTLE_LIGHT_0 0x68 +#define DVFS_DFLL_THROTTLE_MEDIUM_0 0x6C +#define DVFS_DFLL_THROTTLE_HEAVY_0 0x70 +#define DVFS_CC4_HVC_0 0x74 +#define CL_DVFS_MONITOR_DATA_0 0x2C +#define CL_DVFS_I2C_CFG_0 0x40 +#define CL_DVFS_I2C_VDD_REG_ADDR_0 0x44 +#define CL_DVFS_I2C_STS_0 0x48 +#define CL_DVFS_INTR_STS_0 0x5C +#define CL_DVFS_I2C_CLK_DIVISOR_REGISTER_0 0x16C diff --git a/Source/rewrite-hoc-clk/common/include/rgltr.h b/Source/rewrite-hoc-clk/common/include/rgltr.h new file mode 100644 index 00000000..33268b8a --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/rgltr.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) ppkantorski (bord2death) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once +#include +#include "pcv_types.h" + +typedef struct { + Service s; +} RgltrSession; + +Result rgltrInitialize(void); + +void rgltrExit(void); + +Service* rgltrGetServiceSession(void); + +Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id); +void rgltrCloseSession(RgltrSession* session); +Result rgltrGetVoltage(RgltrSession* session, u32 *out_volt); +Result rgltrGetPowerModuleNumLimit(u32 *out); +Result rgltrGetVoltageEnabled(RgltrSession* session, u32 *out); +Result rgltrRequestVoltage(RgltrSession* session, u32 microvolt); +Result rgltrCancelVoltageRequest(RgltrSession* session); \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/rgltr_services.h b/Source/rewrite-hoc-clk/common/include/rgltr_services.h new file mode 100644 index 00000000..2010cc33 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/rgltr_services.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) ppkantorski (bord2death) + * + * 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 // for Service, Result, hosversionBefore(), smGetService(), serviceClose(), etc. +#include "rgltr.h" // for RgltrSession, PowerDomainId, etc. + +extern Service g_rgltrSrv; + +Result rgltrInitialize(void); +void rgltrExit(void); + +Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id); + +Result rgltrGetVoltage(RgltrSession* session, u32* out_volt); + +void rgltrCloseSession(RgltrSession* session); diff --git a/Source/rewrite-hoc-clk/common/include/service_guard.h b/Source/rewrite-hoc-clk/common/include/service_guard.h new file mode 100644 index 00000000..96a348a5 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/service_guard.h @@ -0,0 +1,82 @@ +/* + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + +typedef struct ServiceGuard { + Mutex mutex; + u32 refCount; +} ServiceGuard; + +NX_INLINE bool serviceGuardBeginInit(ServiceGuard* g) +{ + mutexLock(&g->mutex); + return (g->refCount++) == 0; +} + +NX_INLINE Result serviceGuardEndInit(ServiceGuard* g, Result rc, void (*cleanupFunc)(void)) +{ + if (R_FAILED(rc)) { + cleanupFunc(); + --g->refCount; + } + mutexUnlock(&g->mutex); + return rc; +} + +NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) +{ + mutexLock(&g->mutex); + if (g->refCount && (--g->refCount) == 0) + cleanupFunc(); + mutexUnlock(&g->mutex); +} + +#define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ +\ +static ServiceGuard g_##name##Guard; \ +NX_INLINE Result _##name##Initialize _paramdecl; \ +static void _##name##Cleanup(void); \ +\ +Result name##Initialize _paramdecl \ +{ \ + Result rc = 0; \ + if (serviceGuardBeginInit(&g_##name##Guard)) \ + rc = _##name##Initialize _parampass; \ + return serviceGuardEndInit(&g_##name##Guard, rc, _##name##Cleanup); \ +} \ +\ +void name##Exit(void) \ +{ \ + serviceGuardExit(&g_##name##Guard, _##name##Cleanup); \ +} + +#define NX_GENERATE_SERVICE_GUARD(name) NX_GENERATE_SERVICE_GUARD_PARAMS(name, (void), ()) + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/common/include/sysclk.h b/Source/rewrite-hoc-clk/common/include/sysclk.h new file mode 100644 index 00000000..c44f506b --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once +#include +#include // include libnx +#ifdef __cplusplus +#include "cpp_util.hpp" +extern "C" { +#endif + +// typedef std::uint32_t Result; +// typedef std::uint32_t u32; +// typedef std::int32_t s32; +// typedef std::uint64_t u64; +// typedef std::int64_t s64; +// typedef std::uint8_t u8; +// typedef std::int16_t s16; +// typedef std::uint16_t u16; + +#include "sysclk/ipc.h" +#include "sysclk/board.h" +#include "sysclk/clock_manager.h" +#include "sysclk/apm.h" +#include "sysclk/config.h" +#include "sysclk/errors.h" +#include "sysclk/psm_ext.h" + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/apm.h b/Source/rewrite-hoc-clk/common/include/sysclk/apm.h new file mode 100644 index 00000000..9815a9b0 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/apm.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include "board.h" + +typedef struct { + uint32_t id; + uint32_t cpu_hz; + uint32_t gpu_hz; + uint32_t mem_hz; +} SysClkApmConfiguration; + +extern SysClkApmConfiguration sysclk_g_apm_configurations[]; diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/board.h b/Source/rewrite-hoc-clk/common/include/sysclk/board.h new file mode 100644 index 00000000..3f8d974d --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/board.h @@ -0,0 +1,270 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include +#include +#include +typedef enum +{ + SysClkSocType_Erista = 0, + SysClkSocType_Mariko, + SysClkSocType_EnumMax +} SysClkSocType; + +typedef enum +{ + HorizonOCConsoleType_Icosa = 0, + HorizonOCConsoleType_Copper, + HorizonOCConsoleType_Hoag, + HorizonOCConsoleType_Iowa, + HorizonOCConsoleType_Calcio, + HorizonOCConsoleType_Aula, + HorizonOCConsoleType_EnumMax, +} HorizonOCConsoleType; + +typedef enum { + HocClkVoltage_SOC = 0, + HocClkVoltage_EMCVDD2, + HocClkVoltage_CPU, + HocClkVoltage_GPU, + HocClkVoltage_EMCVDDQ_MarikoOnly, + HocClkVoltage_Display, + HocClkVoltage_Battery, + HocClkVoltage_EnumMax, +} HocClkVoltage; + +typedef enum +{ + SysClkProfile_Handheld = 0, + SysClkProfile_HandheldCharging, + SysClkProfile_HandheldChargingUSB, + SysClkProfile_HandheldChargingOfficial, + SysClkProfile_Docked, + SysClkProfile_EnumMax +} SysClkProfile; + +typedef enum +{ + SysClkModule_CPU = 0, + SysClkModule_GPU, + SysClkModule_MEM, + HorizonOCModule_Governor, + HorizonOCModule_Display, + SysClkModule_EnumMax, +} SysClkModule; + +typedef enum +{ + SysClkThermalSensor_SOC = 0, + SysClkThermalSensor_PCB, + SysClkThermalSensor_Skin, + HorizonOCThermalSensor_Battery, + HorizonOCThermalSensor_PMIC, + SysClkThermalSensor_EnumMax +} SysClkThermalSensor; + +typedef enum +{ + SysClkPowerSensor_Now = 0, + SysClkPowerSensor_Avg, + SysClkPowerSensor_EnumMax +} SysClkPowerSensor; + +typedef enum +{ + SysClkPartLoad_EMC = 0, + SysClkPartLoad_EMCCpu, + HocClkPartLoad_GPU, + HocClkPartLoad_CPUMax, + HocClkPartLoad_BAT, + HocClkPartLoad_FAN, + SysClkPartLoad_EnumMax +} SysClkPartLoad; + +typedef enum { + HorizonOCSpeedo_CPU = 0, + HorizonOCSpeedo_GPU, + HorizonOCSpeedo_SOC, + HorizonOCSpeedo_EnumMax, +} HorizonOCSpeedo; + +typedef enum { + GPUUVLevel_NoUV = 0, + GPUUVLevel_SLT, + GPUUVLevel_HiOPT, + GPUUVLevel_EnumMax, +} GPUUndervoltLevel; + +enum { + DVFSMode_Disabled = 0, + DVFSMode_Hijack, + // DVFSMode_OfficialService, + // DVFSMode_Hack, + DVFSMode_EnumMax, +}; + +typedef enum { + GpuSchedulingMode_DoNotOverride = 0, + GpuSchedulingMode_Enabled, + GpuSchedulingMode_Disabled, + GpuSchedulingMode_EnumMax, +} GpuSchedulingMode; + +typedef enum { + GpuSchedulingOverrideMethod_Ini = 0, + GpuSchedulingOverrideMethod_NvService, + GpuSchedulingOverrideMethod_EnumMax, +} GpuSchedulingOverrideMethod; +typedef enum { + ComponentGovernor_DoNotOverride = 0, + ComponentGovernor_Enabled = 1, + ComponentGovernor_Disabled = 2, + ComponentGovernor_EnumMax, +} ComponentGovernorState; +typedef enum { + RamDisplayMode_VDD2VDDQ = 0, + RamDisplayMode_VDD2Usage, + RamDisplayMode_VDDQUsage, + RamDisplayMode_EnumMax, +} RamDisplayMode; + +#define SYSCLK_ENUM_VALID(n, v) ((v) < n##_EnumMax) + +// Packed u32 +// Bits 0-7 - CPU +// Bits 8-15 - GPU +// Bits 16-23 - VRR +// Bits 24-32 - unused + +inline u32 GovernorStatePack(u8 cpu, u8 gpu, u8 vrr) { + return (u32)cpu | ((u32)gpu << 8) | ((u32)vrr << 16); +} +inline u8 GovernorStateCpu(u32 p) { + return (u8)(p & 0xFF); +} +inline u8 GovernorStateGpu(u32 p) { + return (u8)((p >> 8) & 0xFF); +} +inline u8 GovernorStateVrr(u32 p) { + return (u8)((p >> 16) & 0xFF); +} + +static inline const char* sysclkFormatModule(SysClkModule module, bool pretty) +{ + switch(module) + { + case SysClkModule_CPU: + return pretty ? "CPU" : "cpu"; + case SysClkModule_GPU: + return pretty ? "GPU" : "gpu"; + case SysClkModule_MEM: + return pretty ? "Memory" : "mem"; + case HorizonOCModule_Display: + return pretty ? "Display" : "display"; + case HorizonOCModule_Governor: + return pretty ? "Governor" : "governor"; + default: + return "null"; + } +} + +static inline const char* sysclkFormatThermalSensor(SysClkThermalSensor thermSensor, bool pretty) +{ + switch(thermSensor) + { + case SysClkThermalSensor_SOC: + return pretty ? "SOC" : "soc"; + case SysClkThermalSensor_PCB: + return pretty ? "PCB" : "pcb"; + case SysClkThermalSensor_Skin: + return pretty ? "Skin" : "skin"; + case HorizonOCThermalSensor_Battery: + return pretty ? "BAT" : "battery"; + case HorizonOCThermalSensor_PMIC: + return pretty ? "PMIC" : "pmic"; + + default: + return NULL; + } +} + +static inline const char* sysclkFormatPowerSensor(SysClkPowerSensor powSensor, bool pretty) +{ + switch(powSensor) + { + case SysClkPowerSensor_Now: + return pretty ? "Now" : "now"; + case SysClkPowerSensor_Avg: + return pretty ? "Avg" : "avg"; + default: + return NULL; + } +} + +static inline const char* sysclkFormatProfile(SysClkProfile profile, bool pretty) +{ + switch(profile) + { + case SysClkProfile_Docked: + return pretty ? "Docked" : "docked"; + case SysClkProfile_Handheld: + return pretty ? "Handheld" : "handheld"; + case SysClkProfile_HandheldCharging: + return pretty ? "Charging" : "handheld_charging"; + case SysClkProfile_HandheldChargingUSB: + return pretty ? "USB Charger" : "handheld_charging_usb"; + case SysClkProfile_HandheldChargingOfficial: + return pretty ? "PD Charger" : "handheld_charging_official"; + default: + return NULL; + } +} + + +static inline const char* hocClkFormatVoltage(HocClkVoltage voltage, bool pretty) +{ + switch(voltage) + { + case HocClkVoltage_CPU: + return pretty ? "CPU" : "cpu"; + case HocClkVoltage_GPU: + return pretty ? "GPU" : "gpu"; + case HocClkVoltage_EMCVDD2: + return pretty ? "VDD2" : "emcvdd2"; + case HocClkVoltage_EMCVDDQ_MarikoOnly: + return pretty ? "VDDQ" : "vddq"; + case HocClkVoltage_SOC: + return pretty ? "SOC" : "soc"; + case HocClkVoltage_Display: + return pretty ? "Display" : "display"; + default: + return NULL; + } +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/client/ipc.h b/Source/rewrite-hoc-clk/common/include/sysclk/client/ipc.h new file mode 100644 index 00000000..242e3d1a --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/client/ipc.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include "types.h" +#include "../config.h" +#include "../board.h" +#include "../ipc.h" + +bool sysclkIpcRunning(); +Result sysclkIpcInitialize(void); +void sysclkIpcExit(void); + +Result sysclkIpcGetAPIVersion(u32* out_ver); +Result sysclkIpcGetVersionString(char* out, size_t len); +Result sysclkIpcGetCurrentContext(SysClkContext* out_context); +Result sysclkIpcGetProfileCount(u64 tid, u8* out_count); +Result sysclkIpcSetEnabled(bool enabled); +Result sysclkIpcExitCmd(); +Result sysclkIpcSetOverride(SysClkModule module, u32 hz); +Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles); +Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles); +Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues); +Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues); +Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount); +Result hocClkIpcSetKipData(); +Result hocClkIpcGetKipData(); + +static inline Result sysclkIpcRemoveOverride(SysClkModule module) +{ + return sysclkIpcSetOverride(module, 0); +} diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/client/types.h b/Source/rewrite-hoc-clk/common/include/sysclk/client/types.h new file mode 100644 index 00000000..b9feb9ba --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/client/types.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __SWITCH__ + +#include +#include + +#else + +#define R_FAILED(res) ((res) != 0) +#define R_SUCCEEDED(res) ((res) == 0) + +typedef std::uint32_t Result; +typedef std::uint32_t u32; +typedef std::int32_t s32; +typedef std::uint64_t u64; +typedef std::uint8_t u8; + +#endif diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/clock_manager.h b/Source/rewrite-hoc-clk/common/include/sysclk/clock_manager.h new file mode 100644 index 00000000..917b3701 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/clock_manager.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include "board.h" + +typedef struct +{ + uint64_t applicationId; + SysClkProfile profile; + uint32_t freqs[SysClkModule_EnumMax]; + uint32_t realFreqs[SysClkModule_EnumMax]; + uint32_t overrideFreqs[SysClkModule_EnumMax]; + uint32_t temps[SysClkThermalSensor_EnumMax]; + int32_t power[SysClkPowerSensor_EnumMax]; + uint32_t partLoad[SysClkPartLoad_EnumMax]; + uint32_t voltages[HocClkVoltage_EnumMax]; + u16 speedos[HorizonOCSpeedo_EnumMax]; + u16 iddq[HorizonOCSpeedo_EnumMax]; + GpuSchedulingMode gpuSchedulingMode; + bool isSysDockInstalled; + bool isSaltyNXInstalled; + u8 maxDisplayFreq; + u8 dramID; + bool isDram8GB; + u8 fps; + u16 resolutionHeight; +} SysClkContext; + +typedef struct +{ + union { + uint32_t mhz[+SysClkProfile_EnumMax * +SysClkModule_EnumMax]; + uint32_t mhzMap[+SysClkProfile_EnumMax][+SysClkModule_EnumMax]; + }; +} SysClkTitleProfileList; + +#define SYSCLK_FREQ_LIST_MAX 32 + +#define GLOBAL_PROFILE_ID 0xA111111111111111 \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/config.h b/Source/rewrite-hoc-clk/common/include/sysclk/config.h new file mode 100644 index 00000000..ccde8e5e --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/config.h @@ -0,0 +1,593 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include + +typedef enum { + SysClkConfigValue_PollingIntervalMs = 0, + SysClkConfigValue_TempLogIntervalMs, + SysClkConfigValue_FreqLogIntervalMs, + SysClkConfigValue_PowerLogIntervalMs, + SysClkConfigValue_CsvWriteIntervalMs, + + HocClkConfigValue_UncappedClocks, + HocClkConfigValue_OverwriteBoostMode, + + HocClkConfigValue_EristaMaxCpuClock, + HocClkConfigValue_MarikoMaxCpuClock, + + HocClkConfigValue_ThermalThrottle, + HocClkConfigValue_ThermalThrottleThreshold, + + HocClkConfigValue_HandheldTDP, + HocClkConfigValue_HandheldTDPLimit, + + HocClkConfigValue_LiteTDPLimit, + + HorizonOCConfigValue_BatteryChargeCurrent, + + HorizonOCConfigValue_OverwriteRefreshRate, + HorizonOCConfigValue_EnableUnsafeDisplayFreqs, + + HorizonOCConfigValue_DVFSMode, + HorizonOCConfigValue_DVFSOffset, + HorizonOCConfigValue_LiveCpuUv, + HorizonOCConfigValue_EnableExperimentalSettings, + + HorizonOCConfigValue_GPUScheduling, + HorizonOCConfigValue_GPUSchedulingMethod, + + HorizonOCConfigValue_RAMVoltUsageDisplayMode, + HorizonOCConfigValue_CpuGovernorMinimumFreq, + + KipConfigValue_custRev, + // KipConfigValue_mtcConf, + KipConfigValue_hpMode, + + KipConfigValue_commonEmcMemVolt, + KipConfigValue_eristaEmcMaxClock, + KipConfigValue_eristaEmcMaxClock1, + KipConfigValue_eristaEmcMaxClock2, + KipConfigValue_marikoEmcMaxClock, + KipConfigValue_marikoEmcVddqVolt, + KipConfigValue_emcDvbShift, + + KipConfigValue_t1_tRCD, + KipConfigValue_t2_tRP, + KipConfigValue_t3_tRAS, + KipConfigValue_t4_tRRD, + KipConfigValue_t5_tRFC, + KipConfigValue_t6_tRTW, + KipConfigValue_t7_tWTR, + KipConfigValue_t8_tREFI, + KipConfigValue_mem_burst_read_latency, + KipConfigValue_mem_burst_write_latency, + + KipConfigValue_eristaCpuUV, + KipConfigValue_eristaCpuVmin, + KipConfigValue_eristaCpuMaxVolt, + KipConfigValue_eristaCpuUnlock, + + KipConfigValue_marikoCpuUVLow, + KipConfigValue_marikoCpuUVHigh, + KipConfigValue_tableConf, + KipConfigValue_marikoCpuLowVmin, + KipConfigValue_marikoCpuHighVmin, + KipConfigValue_marikoCpuMaxVolt, + KipConfigValue_marikoCpuMaxClock, + KipConfigValue_eristaCpuBoostClock, + KipConfigValue_marikoCpuBoostClock, + + KipConfigValue_eristaGpuUV, + KipConfigValue_eristaGpuVmin, + + KipConfigValue_marikoGpuUV, + KipConfigValue_marikoGpuVmin, + KipConfigValue_marikoGpuVmax, + + KipConfigValue_commonGpuVoltOffset, + KipConfigValue_gpuSpeedo, + + KipConfigValue_g_volt_76800, + KipConfigValue_g_volt_153600, + KipConfigValue_g_volt_230400, + KipConfigValue_g_volt_307200, + KipConfigValue_g_volt_384000, + KipConfigValue_g_volt_460800, + KipConfigValue_g_volt_537600, + KipConfigValue_g_volt_614400, + KipConfigValue_g_volt_691200, + KipConfigValue_g_volt_768000, + KipConfigValue_g_volt_844800, + KipConfigValue_g_volt_921600, + KipConfigValue_g_volt_998400, + KipConfigValue_g_volt_1075200, + KipConfigValue_g_volt_1152000, + KipConfigValue_g_volt_1228800, + KipConfigValue_g_volt_1267200, + KipConfigValue_g_volt_1305600, + KipConfigValue_g_volt_1344000, + KipConfigValue_g_volt_1382400, + KipConfigValue_g_volt_1420800, + KipConfigValue_g_volt_1459200, + KipConfigValue_g_volt_1497600, + KipConfigValue_g_volt_1536000, + + KipConfigValue_g_volt_e_76800, + KipConfigValue_g_volt_e_115200, + KipConfigValue_g_volt_e_153600, + KipConfigValue_g_volt_e_192000, + KipConfigValue_g_volt_e_230400, + KipConfigValue_g_volt_e_268800, + KipConfigValue_g_volt_e_307200, + KipConfigValue_g_volt_e_345600, + KipConfigValue_g_volt_e_384000, + KipConfigValue_g_volt_e_422400, + KipConfigValue_g_volt_e_460800, + KipConfigValue_g_volt_e_499200, + KipConfigValue_g_volt_e_537600, + KipConfigValue_g_volt_e_576000, + KipConfigValue_g_volt_e_614400, + KipConfigValue_g_volt_e_652800, + KipConfigValue_g_volt_e_691200, + KipConfigValue_g_volt_e_729600, + KipConfigValue_g_volt_e_768000, + KipConfigValue_g_volt_e_806400, + KipConfigValue_g_volt_e_844800, + KipConfigValue_g_volt_e_883200, + KipConfigValue_g_volt_e_921600, + KipConfigValue_g_volt_e_960000, + KipConfigValue_g_volt_e_998400, + KipConfigValue_g_volt_e_1036800, + KipConfigValue_g_volt_e_1075200, + + KipConfigValue_t6_tRTW_fine_tune, + KipConfigValue_t7_tWTR_fine_tune, + + KipCrc32, + HocClkConfigValue_IsFirstLoad, + SysClkConfigValue_EnumMax, +} SysClkConfigValue; + +typedef struct { + uint64_t values[SysClkConfigValue_EnumMax]; +} SysClkConfigValueList; + +static inline const char* sysclkFormatConfigValue(SysClkConfigValue val, bool pretty) +{ + switch(val) + { + case SysClkConfigValue_PollingIntervalMs: + return pretty ? "Polling Interval (ms)" : "poll_interval_ms"; + case SysClkConfigValue_TempLogIntervalMs: + return pretty ? "Temperature logging interval (ms)" : "temp_log_interval_ms"; + case SysClkConfigValue_FreqLogIntervalMs: + return pretty ? "Frequency logging interval (ms)" : "freq_log_interval_ms"; + case SysClkConfigValue_PowerLogIntervalMs: + return pretty ? "Power logging interval (ms)" : "power_log_interval_ms"; + case SysClkConfigValue_CsvWriteIntervalMs: + return pretty ? "CSV write interval (ms)" : "csv_write_interval_ms"; + + case HocClkConfigValue_UncappedClocks: + return pretty ? "Uncapped Clocks" : "uncapped_clocks"; + case HocClkConfigValue_OverwriteBoostMode: + return pretty ? "Overwrite Boost Mode" : "ow_boost"; + + case HocClkConfigValue_EristaMaxCpuClock: + return pretty ? "CPU Max Clock" : "cpu_max_e"; + + case HocClkConfigValue_MarikoMaxCpuClock: + return pretty ? "CPU Max Display Clock" : "cpu_max_m"; + + case HocClkConfigValue_ThermalThrottle: + return pretty ? "Thermal Throttle" : "thermal_throttle"; + + case HocClkConfigValue_ThermalThrottleThreshold: + return pretty ? "Thermal Throttle Threshold" : "thermal_throttle_threshold"; + + case HocClkConfigValue_HandheldTDP: + return pretty ? "Handheld TDP" : "handheld_tdp"; + + case HocClkConfigValue_HandheldTDPLimit: + return pretty ? "Handheld TDP Limit" : "tdp_limit"; + + case HocClkConfigValue_LiteTDPLimit: + return pretty ? "Handheld TDP Limit" : "tdp_limit_l"; + + case HorizonOCConfigValue_BatteryChargeCurrent: + return pretty ? "Battery Charge Current" : "bat_charge_current"; + + case HorizonOCConfigValue_OverwriteRefreshRate: + return pretty ? "Display Refresh Rate Changing" : "drr_changing"; + + case HorizonOCConfigValue_EnableUnsafeDisplayFreqs: + return pretty ? "Enable Unsafe Display Frequencies" : "drr_unsafe"; + + case HorizonOCConfigValue_DVFSMode: + return pretty ? "DVFS Mode" : "dvfs_mode"; + + case HorizonOCConfigValue_DVFSOffset: + return pretty ? "DVFS Offset" : "dvfs_offset"; + + case HorizonOCConfigValue_GPUScheduling: + return pretty ? "GPU Scheduling" : "gpu_scheduling"; + + case HorizonOCConfigValue_GPUSchedulingMethod: + return pretty ? "GPU Scheduling Method" : "gpu_sched_method"; + + case HorizonOCConfigValue_LiveCpuUv: + return pretty ? "Live CPU Undervolt" : "live_cpu_uv"; + + case HorizonOCConfigValue_EnableExperimentalSettings: + return pretty ? "Enable Experimental Settings" : "enable_experimental_settings"; + + case HorizonOCConfigValue_RAMVoltUsageDisplayMode: + return pretty ? "RAM Voltage / Usage Display Mode" : "ram_volt_usage_display_mode"; + case HorizonOCConfigValue_CpuGovernorMinimumFreq: + return pretty ? "CPU Governor Minimum Frequency" : "cpu_gov_min_freq"; + // KIP config values + case KipConfigValue_custRev: + return pretty ? "Custom Revision" : "kip_cust_rev"; + // case KipConfigValue_mtcConf: + // return pretty ? "MTC Config" : "kip_mtc_conf"; + case KipConfigValue_hpMode: + return pretty ? "HP Mode" : "kip_hp_mode"; + + // EMC + case KipConfigValue_commonEmcMemVolt: + return pretty ? "Common EMC/MEM Voltage" : "common_emc_mem_volt"; + case KipConfigValue_eristaEmcMaxClock: + return pretty ? "Erista EMC Max Clock 1" : "erista_emc_max_clock"; + case KipConfigValue_eristaEmcMaxClock1: + return pretty ? "Erista EMC Max Clock 2" : "erista_emc_max_clock1"; + case KipConfigValue_eristaEmcMaxClock2: + return pretty ? "Erista EMC Max Clock 3" : "erista_emc_max_clock2"; + case KipConfigValue_marikoEmcMaxClock: + return pretty ? "Mariko EMC Max Clock" : "mariko_emc_max_clock"; + case KipConfigValue_marikoEmcVddqVolt: + return pretty ? "Mariko EMC VDDQ Voltage" : "mariko_emc_vddq_volt"; + case KipConfigValue_emcDvbShift: + return pretty ? "EMC DVB Shift" : "emc_dvb_shift"; + + // Memory timings + case KipConfigValue_t1_tRCD: + return pretty ? "t1 - tRCD" : "t1_trcd"; + case KipConfigValue_t2_tRP: + return pretty ? "t2 - tRP" : "t2_trp"; + case KipConfigValue_t3_tRAS: + return pretty ? "t3 - tRAS" : "t3_tras"; + case KipConfigValue_t4_tRRD: + return pretty ? "t4 - tRRD" : "t4_trrd"; + case KipConfigValue_t5_tRFC: + return pretty ? "t5 - tRFC" : "t5_trfc"; + case KipConfigValue_t6_tRTW: + return pretty ? "t6 - tRTW" : "t6_trtw"; + case KipConfigValue_t7_tWTR: + return pretty ? "t7 - tWTR" : "t7_twtr"; + case KipConfigValue_t8_tREFI: + return pretty ? "t8 - tREFI" : "t8_trefi"; + case KipConfigValue_mem_burst_read_latency: + return pretty ? "Memory Burst Read Latency" : "mem_burst_read_latency"; + case KipConfigValue_mem_burst_write_latency: + return pretty ? "Memory Burst Write Latency" : "mem_burst_write_latency"; + + // CPU – Erista + case KipConfigValue_eristaCpuUV: + return pretty ? "Erista CPU Undervolt" : "erista_cpu_uv"; + case KipConfigValue_eristaCpuVmin: + return pretty ? "Erista CPU vMin" : "erista_cpu_vmin"; + case KipConfigValue_eristaCpuMaxVolt: + return pretty ? "Erista CPU Max Voltage" : "erista_cpu_max_volt"; + case KipConfigValue_eristaCpuUnlock: + return pretty ? "Erista CPU Unlock" : "erista_cpu_unlock"; + + // CPU – Mariko + case KipConfigValue_marikoCpuUVLow: + return pretty ? "Mariko CPU Undervolt (Low)" : "mariko_cpu_uv_low"; + case KipConfigValue_marikoCpuUVHigh: + return pretty ? "Mariko CPU Undervolt (High)" : "mariko_cpu_uv_high"; + case KipConfigValue_tableConf: + return pretty ? "Table Config" : "kip_table_conf"; + case KipConfigValue_marikoCpuLowVmin: + return pretty ? "Mariko CPU Low Vmin" : "mariko_cpu_low_vmin"; + case KipConfigValue_marikoCpuHighVmin: + return pretty ? "Mariko CPU High Vmin" : "mariko_cpu_high_vmin"; + case KipConfigValue_marikoCpuMaxVolt: + return pretty ? "Mariko CPU Max Voltage" : "mariko_cpu_max_volt"; + + case KipConfigValue_eristaCpuBoostClock: + return pretty ? "Erista CPU Boost Clock" : "erista_cpu_boost_clock"; + case KipConfigValue_marikoCpuBoostClock: + return pretty ? "Mariko CPU Boost Clock" : "mariko_cpu_boost_clock"; + + case KipConfigValue_marikoCpuMaxClock: + return pretty ? "Mariko CPU Max Clock" : "mariko_cpu_max_clock"; + + // GPU – Erista + case KipConfigValue_eristaGpuUV: + return pretty ? "Erista GPU Undervolt" : "erista_gpu_uv"; + case KipConfigValue_eristaGpuVmin: + return pretty ? "Erista GPU Vmin" : "erista_gpu_vmin"; + + // GPU – Mariko + case KipConfigValue_marikoGpuUV: + return pretty ? "Mariko GPU Undervolt" : "mariko_gpu_uv"; + case KipConfigValue_marikoGpuVmin: + return pretty ? "Mariko GPU Vmin" : "mariko_gpu_vmin"; + case KipConfigValue_marikoGpuVmax: + return pretty ? "Mariko GPU Vmax" : "mariko_gpu_vmax"; + + case KipConfigValue_commonGpuVoltOffset: + return pretty ? "Common GPU Voltage Offset" : "common_gpu_volt_offset"; + case KipConfigValue_gpuSpeedo: + return pretty ? "GPU Speedo" : "gpu_speedo"; + + // Mariko GPU voltages (24) + case KipConfigValue_g_volt_76800: return pretty ? "Mariko GPU Volt 76 MHz" : "g_volt_76800"; + case KipConfigValue_g_volt_153600: return pretty ? "Mariko GPU Volt 153 MHz" : "g_volt_153600"; + case KipConfigValue_g_volt_230400: return pretty ? "Mariko GPU Volt 230 MHz" : "g_volt_230400"; + case KipConfigValue_g_volt_307200: return pretty ? "Mariko GPU Volt 307 MHz" : "g_volt_307200"; + case KipConfigValue_g_volt_384000: return pretty ? "Mariko GPU Volt 384 MHz" : "g_volt_384000"; + case KipConfigValue_g_volt_460800: return pretty ? "Mariko GPU Volt 460 MHz" : "g_volt_460800"; + case KipConfigValue_g_volt_537600: return pretty ? "Mariko GPU Volt 537 MHz" : "g_volt_537600"; + case KipConfigValue_g_volt_614400: return pretty ? "Mariko GPU Volt 614 MHz" : "g_volt_614400"; + case KipConfigValue_g_volt_691200: return pretty ? "Mariko GPU Volt 691 MHz" : "g_volt_691200"; + case KipConfigValue_g_volt_768000: return pretty ? "Mariko GPU Volt 768 MHz" : "g_volt_768000"; + case KipConfigValue_g_volt_844800: return pretty ? "Mariko GPU Volt 844 MHz" : "g_volt_844800"; + case KipConfigValue_g_volt_921600: return pretty ? "Mariko GPU Volt 921 MHz" : "g_volt_921600"; + case KipConfigValue_g_volt_998400: return pretty ? "Mariko GPU Volt 998 MHz" : "g_volt_998400"; + case KipConfigValue_g_volt_1075200: return pretty ? "Mariko GPU Volt 1075 MHz" : "g_volt_1075200"; + case KipConfigValue_g_volt_1152000: return pretty ? "Mariko GPU Volt 1152 MHz" : "g_volt_1152000"; + case KipConfigValue_g_volt_1228800: return pretty ? "Mariko GPU Volt 1228 MHz" : "g_volt_1228800"; + case KipConfigValue_g_volt_1267200: return pretty ? "Mariko GPU Volt 1267 MHz" : "g_volt_1267200"; + case KipConfigValue_g_volt_1305600: return pretty ? "Mariko GPU Volt 1305 MHz" : "g_volt_1305600"; + case KipConfigValue_g_volt_1344000: return pretty ? "Mariko GPU Volt 1344 MHz" : "g_volt_1344000"; + case KipConfigValue_g_volt_1382400: return pretty ? "Mariko GPU Volt 1382 MHz" : "g_volt_1382400"; + case KipConfigValue_g_volt_1420800: return pretty ? "Mariko GPU Volt 1420 MHz" : "g_volt_1420800"; + case KipConfigValue_g_volt_1459200: return pretty ? "Mariko GPU Volt 1459 MHz" : "g_volt_1459200"; + case KipConfigValue_g_volt_1497600: return pretty ? "Mariko GPU Volt 1497 MHz" : "g_volt_1497600"; + case KipConfigValue_g_volt_1536000: return pretty ? "Mariko GPU Volt 1536 MHz" : "g_volt_1536000"; + + // Erista GPU voltages (27) + case KipConfigValue_g_volt_e_76800: return pretty ? "Erista GPU Volt 76 MHz" : "g_volt_e_76800"; + case KipConfigValue_g_volt_e_115200: return pretty ? "Erista GPU Volt 115 MHz" : "g_volt_e_115200"; + case KipConfigValue_g_volt_e_153600: return pretty ? "Erista GPU Volt 153 MHz" : "g_volt_e_153600"; + case KipConfigValue_g_volt_e_192000: return pretty ? "Erista GPU Volt 192 MHz" : "g_volt_e_192000"; + case KipConfigValue_g_volt_e_230400: return pretty ? "Erista GPU Volt 230 MHz" : "g_volt_e_230400"; + case KipConfigValue_g_volt_e_268800: return pretty ? "Erista GPU Volt 268 MHz" : "g_volt_e_268800"; + case KipConfigValue_g_volt_e_307200: return pretty ? "Erista GPU Volt 307 MHz" : "g_volt_e_307200"; + case KipConfigValue_g_volt_e_345600: return pretty ? "Erista GPU Volt 345 MHz" : "g_volt_e_345600"; + case KipConfigValue_g_volt_e_384000: return pretty ? "Erista GPU Volt 384 MHz" : "g_volt_e_384000"; + case KipConfigValue_g_volt_e_422400: return pretty ? "Erista GPU Volt 422 MHz" : "g_volt_e_422400"; + case KipConfigValue_g_volt_e_460800: return pretty ? "Erista GPU Volt 460 MHz" : "g_volt_e_460800"; + case KipConfigValue_g_volt_e_499200: return pretty ? "Erista GPU Volt 499 MHz" : "g_volt_e_499200"; + case KipConfigValue_g_volt_e_537600: return pretty ? "Erista GPU Volt 537 MHz" : "g_volt_e_537600"; + case KipConfigValue_g_volt_e_576000: return pretty ? "Erista GPU Volt 576 MHz" : "g_volt_e_576000"; + case KipConfigValue_g_volt_e_614400: return pretty ? "Erista GPU Volt 614 MHz" : "g_volt_e_614400"; + case KipConfigValue_g_volt_e_652800: return pretty ? "Erista GPU Volt 652 MHz" : "g_volt_e_652800"; + case KipConfigValue_g_volt_e_691200: return pretty ? "Erista GPU Volt 691 MHz" : "g_volt_e_691200"; + case KipConfigValue_g_volt_e_729600: return pretty ? "Erista GPU Volt 729 MHz" : "g_volt_e_729600"; + case KipConfigValue_g_volt_e_768000: return pretty ? "Erista GPU Volt 768 MHz" : "g_volt_e_768000"; + case KipConfigValue_g_volt_e_806400: return pretty ? "Erista GPU Volt 806 MHz" : "g_volt_e_806400"; + case KipConfigValue_g_volt_e_844800: return pretty ? "Erista GPU Volt 844 MHz" : "g_volt_e_844800"; + case KipConfigValue_g_volt_e_883200: return pretty ? "Erista GPU Volt 883 MHz" : "g_volt_e_883200"; + case KipConfigValue_g_volt_e_921600: return pretty ? "Erista GPU Volt 921 MHz" : "g_volt_e_921600"; + case KipConfigValue_g_volt_e_960000: return pretty ? "Erista GPU Volt 960 MHz" : "g_volt_e_960000"; + case KipConfigValue_g_volt_e_998400: return pretty ? "Erista GPU Volt 998 MHz" : "g_volt_e_998400"; + case KipConfigValue_g_volt_e_1036800: return pretty ? "Erista GPU Volt 1036 MHz" : "g_volt_e_1036800"; + case KipConfigValue_g_volt_e_1075200: return pretty ? "Erista GPU Volt 1075 MHz" : "g_volt_e_1075200"; + case KipConfigValue_t6_tRTW_fine_tune: return pretty ? "t6 - tRTW Fine Tune" : "t6_tRTW_fine_fune"; + case KipConfigValue_t7_tWTR_fine_tune: return pretty ? "t7 - tWTR Fine Tune" : "t7_tWTR_fine_tune"; + case KipCrc32: + return pretty ? "CRC32" : "crc32"; + case HocClkConfigValue_IsFirstLoad: + return pretty ? "Is First Load" : "is_first_load"; + default: + return pretty ? "[cfg] no enum format string" : "err_no_format_string"; + } +} + +static inline uint64_t sysclkDefaultConfigValue(SysClkConfigValue val) +{ + switch(val) + { + case SysClkConfigValue_PollingIntervalMs: + return 300ULL; + case SysClkConfigValue_TempLogIntervalMs: + case SysClkConfigValue_FreqLogIntervalMs: + case SysClkConfigValue_PowerLogIntervalMs: + case SysClkConfigValue_CsvWriteIntervalMs: + case HocClkConfigValue_UncappedClocks: + case HocClkConfigValue_OverwriteBoostMode: + case HorizonOCConfigValue_BatteryChargeCurrent: + case HorizonOCConfigValue_OverwriteRefreshRate: + case HorizonOCConfigValue_EnableUnsafeDisplayFreqs: + case HorizonOCConfigValue_GPUScheduling: + case HorizonOCConfigValue_LiveCpuUv: + case HorizonOCConfigValue_GPUSchedulingMethod: + return 0ULL; + case HocClkConfigValue_EristaMaxCpuClock: + return 1785ULL; + + case HocClkConfigValue_MarikoMaxCpuClock: + return 1963ULL; + + case HocClkConfigValue_ThermalThrottle: + case HocClkConfigValue_HandheldTDP: + case HocClkConfigValue_IsFirstLoad: + case HorizonOCConfigValue_DVFSMode: + return 1ULL; + case HocClkConfigValue_ThermalThrottleThreshold: + return 70ULL; + case HocClkConfigValue_HandheldTDPLimit: + return 9600ULL; // 8600mW will trigger on erista stock, so raise it a bit + case HocClkConfigValue_LiteTDPLimit: + return 6400ULL; // 0.5C + case HorizonOCConfigValue_CpuGovernorMinimumFreq: + return 612ULL; // 612MHz + default: + return 0ULL; + } +} + +static inline uint64_t sysclkValidConfigValue(SysClkConfigValue val, uint64_t input) +{ + switch(val) + { + case HocClkConfigValue_EristaMaxCpuClock: + case HocClkConfigValue_MarikoMaxCpuClock: + case HocClkConfigValue_ThermalThrottleThreshold: + case HocClkConfigValue_HandheldTDPLimit: + case HocClkConfigValue_LiteTDPLimit: + case SysClkConfigValue_PollingIntervalMs: + return input > 0; + + case SysClkConfigValue_TempLogIntervalMs: + case SysClkConfigValue_FreqLogIntervalMs: + case SysClkConfigValue_PowerLogIntervalMs: + case SysClkConfigValue_CsvWriteIntervalMs: + case HocClkConfigValue_UncappedClocks: + case HocClkConfigValue_OverwriteBoostMode: + case HocClkConfigValue_ThermalThrottle: + case HocClkConfigValue_HandheldTDP: + case HorizonOCConfigValue_OverwriteRefreshRate: + case HorizonOCConfigValue_EnableUnsafeDisplayFreqs: + case HocClkConfigValue_IsFirstLoad: + case HorizonOCConfigValue_EnableExperimentalSettings: + case HorizonOCConfigValue_LiveCpuUv: + case HorizonOCConfigValue_GPUSchedulingMethod: + return (input & 0x1) == input; + + case KipConfigValue_custRev: + // case KipConfigValue_mtcConf: + case KipConfigValue_hpMode: + case KipConfigValue_commonEmcMemVolt: + case KipConfigValue_eristaEmcMaxClock: + case KipConfigValue_eristaEmcMaxClock1: + case KipConfigValue_eristaEmcMaxClock2: + case KipConfigValue_marikoEmcMaxClock: + case KipConfigValue_marikoEmcVddqVolt: + case KipConfigValue_emcDvbShift: + case KipConfigValue_t1_tRCD: + case KipConfigValue_t2_tRP: + case KipConfigValue_t3_tRAS: + case KipConfigValue_t4_tRRD: + case KipConfigValue_t5_tRFC: + case KipConfigValue_t6_tRTW: + case KipConfigValue_t7_tWTR: + case KipConfigValue_t8_tREFI: + case KipConfigValue_mem_burst_read_latency: + case KipConfigValue_mem_burst_write_latency: + case KipConfigValue_eristaCpuUV: + case KipConfigValue_eristaCpuMaxVolt: + case KipConfigValue_marikoCpuUVLow: + case KipConfigValue_marikoCpuUVHigh: + case KipConfigValue_tableConf: + case KipConfigValue_marikoCpuLowVmin: + case KipConfigValue_marikoCpuHighVmin: + case KipConfigValue_marikoCpuMaxVolt: + case KipConfigValue_eristaCpuBoostClock: + case KipConfigValue_marikoCpuBoostClock: + case KipConfigValue_marikoCpuMaxClock: + case KipConfigValue_eristaGpuUV: + case KipConfigValue_eristaGpuVmin: + case KipConfigValue_marikoGpuUV: + case KipConfigValue_marikoGpuVmin: + case KipConfigValue_marikoGpuVmax: + case KipConfigValue_commonGpuVoltOffset: + case KipConfigValue_gpuSpeedo: + case KipConfigValue_g_volt_76800: + case KipConfigValue_g_volt_153600: + case KipConfigValue_g_volt_230400: + case KipConfigValue_g_volt_307200: + case KipConfigValue_g_volt_384000: + case KipConfigValue_g_volt_460800: + case KipConfigValue_g_volt_537600: + case KipConfigValue_g_volt_614400: + case KipConfigValue_g_volt_691200: + case KipConfigValue_g_volt_768000: + case KipConfigValue_g_volt_844800: + case KipConfigValue_g_volt_921600: + case KipConfigValue_g_volt_998400: + case KipConfigValue_g_volt_1075200: + case KipConfigValue_g_volt_1152000: + case KipConfigValue_g_volt_1228800: + case KipConfigValue_g_volt_1267200: + case KipConfigValue_g_volt_1305600: + case KipConfigValue_g_volt_1344000: + case KipConfigValue_g_volt_1382400: + case KipConfigValue_g_volt_1420800: + case KipConfigValue_g_volt_1459200: + case KipConfigValue_g_volt_1497600: + case KipConfigValue_g_volt_1536000: + case KipConfigValue_g_volt_e_76800: + case KipConfigValue_g_volt_e_115200: + case KipConfigValue_g_volt_e_153600: + case KipConfigValue_g_volt_e_192000: + case KipConfigValue_g_volt_e_230400: + case KipConfigValue_g_volt_e_268800: + case KipConfigValue_g_volt_e_307200: + case KipConfigValue_g_volt_e_345600: + case KipConfigValue_g_volt_e_384000: + case KipConfigValue_g_volt_e_422400: + case KipConfigValue_g_volt_e_460800: + case KipConfigValue_g_volt_e_499200: + case KipConfigValue_g_volt_e_537600: + case KipConfigValue_g_volt_e_576000: + case KipConfigValue_g_volt_e_614400: + case KipConfigValue_g_volt_e_652800: + case KipConfigValue_g_volt_e_691200: + case KipConfigValue_g_volt_e_729600: + case KipConfigValue_g_volt_e_768000: + case KipConfigValue_g_volt_e_806400: + case KipConfigValue_g_volt_e_844800: + case KipConfigValue_g_volt_e_883200: + case KipConfigValue_g_volt_e_921600: + case KipConfigValue_g_volt_e_960000: + case KipConfigValue_g_volt_e_998400: + case KipConfigValue_g_volt_e_1036800: + case KipConfigValue_g_volt_e_1075200: + case KipConfigValue_eristaCpuVmin: + case KipConfigValue_eristaCpuUnlock: + case KipConfigValue_t6_tRTW_fine_tune: + case KipConfigValue_t7_tWTR_fine_tune: + case KipCrc32: + case HorizonOCConfigValue_DVFSMode: + case HorizonOCConfigValue_DVFSOffset: + case HorizonOCConfigValue_GPUScheduling: + case HorizonOCConfigValue_RAMVoltUsageDisplayMode: + case HorizonOCConfigValue_CpuGovernorMinimumFreq: + return true; + case HorizonOCConfigValue_BatteryChargeCurrent: + return ((input >= 1024) && (input <= 3072)) || !input; + default: + return false; + } +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/errors.h b/Source/rewrite-hoc-clk/common/include/sysclk/errors.h new file mode 100644 index 00000000..a50f1920 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/errors.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#define SYSCLK_ERROR_MODULE 388 +#define SYSCLK_ERROR(desc) ((SYSCLK_ERROR_MODULE & 0x1FF) | (SysClkError_##desc & 0x1FFF)<<9) + +typedef enum +{ + SysClkError_Generic = 0, + SysClkError_ConfigNotLoaded = 1, + SysClkError_ConfigSaveFailed = 2, + // HocClkError_SocThermFail = 3, +} SysClkError; diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/ipc.h b/Source/rewrite-hoc-clk/common/include/sysclk/ipc.h new file mode 100644 index 00000000..6f0f6146 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/ipc.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include "board.h" +#include "clock_manager.h" + +#define SYSCLK_IPC_API_VERSION 1 +#define SYSCLK_IPC_SERVICE_NAME "hoc:clk" + +enum SysClkIpcCmd +{ + SysClkIpcCmd_GetApiVersion = 0, + SysClkIpcCmd_GetVersionString = 1, + SysClkIpcCmd_GetCurrentContext = 2, + SysClkIpcCmd_Exit = 3, + SysClkIpcCmd_GetProfileCount = 4, + SysClkIpcCmd_GetProfiles = 5, + SysClkIpcCmd_SetProfiles = 6, + SysClkIpcCmd_SetEnabled = 7, + SysClkIpcCmd_SetOverride = 8, + SysClkIpcCmd_GetConfigValues = 9, + SysClkIpcCmd_SetConfigValues = 10, + SysClkIpcCmd_GetFreqList = 11, + HocClkIpcCmd_SetKipData = 12, + HocClkIpcCmd_GetKipData = 13, +}; + + +typedef struct +{ + uint64_t tid; + SysClkTitleProfileList profiles; +} SysClkIpc_SetProfiles_Args; + +typedef struct +{ + SysClkModule module; + uint32_t hz; +} SysClkIpc_SetOverride_Args; + +typedef struct +{ + SysClkModule module; + uint32_t maxCount; +} SysClkIpc_GetFreqList_Args; \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/include/sysclk/psm_ext.h b/Source/rewrite-hoc-clk/common/include/sysclk/psm_ext.h new file mode 100644 index 00000000..8ba88c8a --- /dev/null +++ b/Source/rewrite-hoc-clk/common/include/sysclk/psm_ext.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) KazushiMe + * + * 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 + +typedef enum { + PsmPDC_NewPDO = 1, //Received new Power Data Object + PsmPDC_NoPD = 2, //No Power Delivery source is detected + PsmPDC_AcceptedRDO = 3 //Received and accepted Request Data Object +} PsmChargeInfoPDC; //BM92T series + +typedef enum { + PsmPowerRole_Sink = 1, + PsmPowerRole_Source = 2 +} PsmPowerRole; + +const char* PsmPowerRoleToStr(PsmPowerRole role); + +typedef enum { + PsmInfoChargerType_None = 0, + PsmInfoChargerType_PD = 1, + PsmInfoChargerType_TypeC_1500mA = 2, + PsmInfoChargerType_TypeC_3000mA = 3, + PsmInfoChargerType_DCP = 4, + PsmInfoChargerType_CDP = 5, + PsmInfoChargerType_SDP = 6, + PsmInfoChargerType_Apple_500mA = 7, + PsmInfoChargerType_Apple_1000mA = 8, + PsmInfoChargerType_Apple_2000mA = 9 +} PsmInfoChargerType; + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type); + +typedef enum { + PsmFlags_NoHub = BIT(0), //If hub is disconnected + PsmFlags_Rail = BIT(8), //At least one Joy-con is charging from rail + PsmFlags_SPDSRC = BIT(12), //OTG + PsmFlags_ACC = BIT(16) //Accessory +} PsmChargeInfoFlags; + +typedef struct { + int32_t InputCurrentLimit; //Input (Sink) current limit in mA + int32_t VBUSCurrentLimit; //Output (Source/VBUS/OTG) current limit in mA + int32_t ChargeCurrentLimit; //Battery charging current limit in mA (512mA when Docked, 768mA when BatteryTemperature < 17.0 C) + int32_t ChargeVoltageLimit; //Battery charging voltage limit in mV (3952mV when BatteryTemperature >= 51.0 C) + int32_t unk_x10; //Possibly an emum, getting the same value as PowerRole in all tested cases + int32_t unk_x14; //Possibly flags + PsmChargeInfoPDC PDCState; //Power Delivery Controller State + int32_t BatteryTemperature; //Battery temperature in milli C + int32_t RawBatteryCharge; //Raw battery charged capacity per cent-mille (i.e. 100% = 100000 pcm) + int32_t VoltageAvg; //Voltage avg in mV (more in Notes) + int32_t BatteryAge; //Battery age (capacity full / capacity design) per cent-mille (i.e. 100% = 100000 pcm) + PsmPowerRole PowerRole; + PsmInfoChargerType ChargerType; + int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV + int32_t ChargerCurrentLimit; //Charger and external device current limit in mA + PsmChargeInfoFlags Flags; //Unknown flags +} PsmChargeInfo; + +typedef enum { + Psm_EnableBatteryCharging = 2, + Psm_DisableBatteryCharging = 3, + Psm_EnableFastBatteryCharging = 10, + Psm_DisableFastBatteryCharging = 11, + Psm_GetBatteryChargeInfoFields = 17, +} IPsmServerCmd; + +bool PsmIsChargerConnected(const PsmChargeInfo* info); +bool PsmIsCharging(const PsmChargeInfo* info); + +typedef enum { + PsmBatteryState_Discharging, + PsmBatteryState_ChargingPaused, + PsmBatteryState_FastCharging +} PsmBatteryState; + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info); +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info); \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/src/apm_profile_table.c b/Source/rewrite-hoc-clk/common/src/apm_profile_table.c new file mode 100644 index 00000000..41e4e520 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/apm_profile_table.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include + +SysClkApmConfiguration sysclk_g_apm_configurations[] = { + {0x00010000, 1020000000, 384000000, 1600000000}, + {0x00010001, 1020000000, 768000000, 1600000000}, + {0x00010002, 1224000000, 691200000, 1600000000}, + {0x00020000, 1020000000, 230400000, 1600000000}, + {0x00020001, 1020000000, 307200000, 1600000000}, + {0x00020002, 1224000000, 230400000, 1600000000}, + {0x00020003, 1020000000, 307200000, 1331200000}, + {0x00020004, 1020000000, 384000000, 1331200000}, + {0x00020005, 1020000000, 307200000, 1065600000}, + {0x00020006, 1020000000, 384000000, 1065600000}, + {0x92220007, 1020000000, 460800000, 1600000000}, + {0x92220008, 1020000000, 460800000, 1331200000}, + {0x92220009, 1785000000, 76800000, 1600000000}, + {0x9222000A, 1785000000, 76800000, 1331200000}, + {0x9222000B, 1020000000, 76800000, 1600000000}, + {0x9222000C, 1020000000, 76800000, 1331200000}, + {0, 0, 0, 0}, +}; + diff --git a/Source/rewrite-hoc-clk/common/src/client/ipc.c b/Source/rewrite-hoc-clk/common/src/client/ipc.c new file mode 100644 index 00000000..f81004f0 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/client/ipc.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include +#include +#include + +static Service g_sysclkSrv; +static atomic_size_t g_refCnt; + +bool sysclkIpcRunning() +{ + Handle handle; + bool running = R_FAILED(smRegisterService(&handle, smEncodeName(SYSCLK_IPC_SERVICE_NAME), false, 1)); + + if (!running) + { + smUnregisterService(smEncodeName(SYSCLK_IPC_SERVICE_NAME)); + } + + return running; +} + +Result sysclkIpcInitialize(void) +{ + Result rc = 0; + + g_refCnt++; + + if (serviceIsActive(&g_sysclkSrv)) + return 0; + + rc = smGetService(&g_sysclkSrv, SYSCLK_IPC_SERVICE_NAME); + + if (R_FAILED(rc)) sysclkIpcExit(); + + return rc; +} + +void sysclkIpcExit(void) +{ + if (--g_refCnt == 0) + { + serviceClose(&g_sysclkSrv); + } +} + +Result sysclkIpcGetAPIVersion(u32* out_ver) +{ + return serviceDispatchOut(&g_sysclkSrv, SysClkIpcCmd_GetApiVersion, *out_ver); +} + +Result sysclkIpcGetVersionString(char* out, size_t len) +{ + return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetVersionString, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = {{out, len}}, + ); +} + +Result sysclkIpcGetCurrentContext(SysClkContext* out_context) +{ + return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetCurrentContext, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = {{out_context, sizeof(SysClkContext)}}, + ); +} + +Result sysclkIpcGetProfileCount(u64 tid, u8* out_count) +{ + return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetProfileCount, tid, *out_count); +} + +Result sysclkIpcSetEnabled(bool enabled) +{ + u8 enabledRaw = (u8)enabled; + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetEnabled, enabledRaw); +} + +Result sysclkIpcSetOverride(SysClkModule module, u32 hz) +{ + SysClkIpc_SetOverride_Args args = { + .module = module, + .hz = hz + }; + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetOverride, args); +} + +Result sysclkIpcGetProfiles(u64 tid, SysClkTitleProfileList* out_profiles) +{ + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_GetProfiles, tid, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = {{out_profiles, sizeof(SysClkTitleProfileList)}}, + ); +} + +Result sysclkIpcSetProfiles(u64 tid, SysClkTitleProfileList* profiles) +{ + SysClkIpc_SetProfiles_Args args; + args.tid = tid; + memcpy(&args.profiles, profiles, sizeof(SysClkTitleProfileList)); + return serviceDispatchIn(&g_sysclkSrv, SysClkIpcCmd_SetProfiles, args); +} + +Result sysclkIpcGetConfigValues(SysClkConfigValueList* out_configValues) +{ + return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_GetConfigValues, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = {{out_configValues, sizeof(SysClkConfigValueList)}}, + ); +} + +Result sysclkIpcSetConfigValues(SysClkConfigValueList* configValues) +{ + return serviceDispatch(&g_sysclkSrv, SysClkIpcCmd_SetConfigValues, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_In }, + .buffers = {{configValues, sizeof(SysClkConfigValueList)}}, + ); +} + +Result sysclkIpcGetFreqList(SysClkModule module, u32* list, u32 maxCount, u32* outCount) +{ + SysClkIpc_GetFreqList_Args args = { + .module = module, + .maxCount = maxCount + }; + return serviceDispatchInOut(&g_sysclkSrv, SysClkIpcCmd_GetFreqList, args, *outCount, + .buffer_attrs = { SfBufferAttr_HipcAutoSelect | SfBufferAttr_Out }, + .buffers = {{list, maxCount * sizeof(u32)}}, + ); +} + +Result hocClkIpcSetKipData() +{ + u32 temp = 0; + return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_SetKipData, temp); +} + +Result hocClkIpcGetKipData() +{ + u32 temp = 0; + return serviceDispatchIn(&g_sysclkSrv, HocClkIpcCmd_GetKipData, temp); +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/src/display_refresh_rate.cpp b/Source/rewrite-hoc-clk/common/src/display_refresh_rate.cpp new file mode 100644 index 00000000..7a169883 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/display_refresh_rate.cpp @@ -0,0 +1,720 @@ +/* + * Copyright (c) Souldbminer, based on reasearch by MasaGratoR and Cooler3D + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "display_refresh_rate.h" +#include +#include +#include +#include +#define DSI_CLOCK_HZ 234000000llu +#define NVDISP_GET_MODE2 0x803C021B +#define NVDISP_SET_MODE2 0x403C021C +#define NVDISP_VALIDATE_MODE2 0xC03C021D +#define NVDISP_GET_MODE_DB2 0xEF20021E +#define NVDISP_GET_PANEL_DATA 0xC01C0226 + +#define MAX_REFRESH_RATE 72 + +static DisplayRefreshConfig g_config = {0}; +static bool g_initialized = false; + +static uint8_t g_dockedHighestRefreshRate = 60; +static uint8_t g_dockedLinkRate = 10; +static bool g_wasRetroSuperTurnedOff = false; +static uint32_t g_lastVActive = 1080; +static bool g_canChangeRefreshRateDocked = false; +static uint8_t g_lastVActiveSet = 0; + +static const uint8_t g_dockedRefreshRates[] = {40, 45, 50, 55, 60, 70, 72, 75, 80, 90, 95, 100, 110, 120, 130, 140, 144, 150, 160, 165, 170, 180, 190, 200, 210, 220, 230, 240}; +// Calculate with this tool: + +// https://tomverbeure.github.io/video_timings_calculator?horiz_pixels=1920&vert_pixels=1080&refresh_rate=240&margins=false&interlaced=false&bpc=8&color_fmt=rgb444&video_opt=false&custom_hblank=80&custom_vblank=6 + +/* +typedef struct { + uint16_t hFrontPorch; + uint8_t hSyncWidth; + uint8_t hBackPorch; + uint8_t vFrontPorch; + uint8_t vSyncWidth; + uint8_t vBackPorch; + uint8_t VIC; + uint32_t pixelClock_kHz; +} DockedTimings; +*/ +static const DockedTimings g_dockedTimings1080p[] = { + {8, 32, 40, 7, 8, 6, 0, 88080}, // 40Hz + {8, 32, 40, 9, 8, 6, 0, 99270}, // 45Hz + {528, 44, 148, 4, 5, 36, 31, 148500}, // 50Hz + {8, 32, 40, 15, 8, 6, 0, 121990}, // 55Hz + {88, 44, 148, 4, 5, 36, 16, 148500}, // 60Hz + {8, 32, 40, 22, 8, 6, 0, 156240}, // 70Hz + {8, 32, 40, 23, 8, 6, 0, 160848}, // 72Hz + {8, 32, 40, 25, 8, 6, 0, 167850}, // 75Hz + {8, 32, 40, 28, 8, 6, 0, 179520}, // 80Hz + {8, 32, 40, 33, 8, 6, 0, 202860}, // 90Hz + {8, 32, 40, 36, 8, 6, 0, 214700}, // 95Hz + {528, 44, 148, 4, 5, 36, 64, 297000}, // 100Hz + {8, 32, 40, 44, 8, 6, 0, 250360}, // 110Hz + {88, 44, 148, 4, 5, 36, 63, 297000}, // 120Hz + {8, 32, 40, 55, 8, 6, 0, 298750}, //130Hz CVT-RBv2 + {8, 32, 40, 61, 8, 6, 0, 323400}, //140Hz CVT-RBv2 + {8, 32, 40, 63, 8, 6, 0, 333216}, //144Hz CVT-RBv2 + {8, 32, 40, 67, 8, 6, 0, 348300}, //150Hz CVT-RBv2 + {8, 32, 40, 72, 8, 6, 0, 373120}, //160Hz CVT-RBv2 + {8, 32, 40, 75, 8, 6, 0, 385770}, //165Hz CVT-RBv2 + {8, 32, 40, 78, 8, 6, 0, 398480}, //170Hz CVT-RBv2 + {8, 32, 40, 84, 8, 6, 0, 424080}, //180Hz CVT-RBv2 + {8, 32, 40, 90, 8, 6, 0, 449920}, //190Hz CVT-RBv2 + {8, 32, 40, 96, 8, 6, 0, 476000}, //200Hz CVT-RBv2 + {8, 32, 40, 102, 8, 6, 0, 502320}, //210Hz CVT-RBv2 + {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[] = { + {72, 136, 72, 1, 660, 9, 78000}, + {72, 136, 72, 1, 443, 9, 77985}, + {72, 136, 72, 1, 270, 9, 78000}, + {72, 136, 72, 1, 128, 9, 77990}, + {72, 136, 72, 1, 10, 9, 78000} +}; + +static const MinMaxRefreshRate g_handheldModeRefreshRate = {40, 80}; + +static uint8_t _getDockedRefreshRateIterator(uint32_t refreshRate) { + for (size_t i = 0; i < sizeof(g_dockedRefreshRates) / sizeof(g_dockedRefreshRates[0]); i++) { + if (g_dockedRefreshRates[i] == refreshRate) return i; + } + return 0xFF; +} + +static void _changeOledElvssSettings(const uint32_t* offsets, const uint32_t* value, uint32_t size, uint32_t start) { + if (!g_config.dsiVirtAddr || !value || !size) return; + + volatile uint32_t* dsi = (uint32_t*)g_config.dsiVirtAddr; + + #define DSI_VIDEO_MODE_CONTROL 0x4E + #define DSI_WR_DATA 0xA + #define DSI_TRIGGER 0x13 + #define MIPI_DSI_DCS_SHORT_WRITE_PARAM 0x15 + #define MIPI_DSI_DCS_LONG_WRITE 0x39 + #define MIPI_DCS_PRIV_SM_SET_REG_OFFSET 0xB0 + #define MIPI_DCS_PRIV_SM_SET_ELVSS 0xB1 + + dsi[DSI_VIDEO_MODE_CONTROL] = true; + svcSleepThread(20000000); + + dsi[DSI_WR_DATA] = MIPI_DSI_DCS_LONG_WRITE | (5 << 8); + dsi[DSI_WR_DATA] = 0x5A5A5AE2; + dsi[DSI_WR_DATA] = 0x5A; + dsi[DSI_TRIGGER] = 0; + + for (size_t i = start; i < size; i++) { + dsi[DSI_WR_DATA] = ((MIPI_DCS_PRIV_SM_SET_REG_OFFSET | ((offsets[i] % 0x100) << 8)) << 8) | MIPI_DSI_DCS_SHORT_WRITE_PARAM; + dsi[DSI_TRIGGER] = 0; + dsi[DSI_WR_DATA] = ((MIPI_DCS_PRIV_SM_SET_ELVSS | (value[i] << 8)) << 8) | MIPI_DSI_DCS_SHORT_WRITE_PARAM; + dsi[DSI_TRIGGER] = 0; + } + + dsi[DSI_WR_DATA] = MIPI_DSI_DCS_LONG_WRITE | (5 << 8); + dsi[DSI_WR_DATA] = 0xA55A5AE2; + dsi[DSI_WR_DATA] = 0xA5; + dsi[DSI_TRIGGER] = 0; + + dsi[DSI_VIDEO_MODE_CONTROL] = false; + svcSleepThread(20000000); +} +void DisplayRefresh_SetDockedState(bool isDocked) { + g_config.isDocked = isDocked; +} + +bool DisplayRefresh_Initialize(const DisplayRefreshConfig* config) { + if (!config) return false; + + g_config = *config; + g_initialized = true; + return true; +} + +void DisplayRefresh_CorrectOledGamma(uint32_t refresh_rate) { + static uint32_t last_refresh_rate = 60; + static int counter = 0; + + if (g_config.isDocked || refresh_rate < 45 || refresh_rate > 60) { + last_refresh_rate = 60; + return; + } + + if (counter != 9) { + counter++; + return; + } + counter = 0; + + uint32_t offsets[] = {0x1A, 0x24, 0x25}; + uint32_t values[] = {2, 0, 0x83}; + + if (refresh_rate == 60) { + if (last_refresh_rate == 60) return; + } else if (refresh_rate == 45) { + if (last_refresh_rate == 45) return; + uint32_t vals[] = {4, 1, 0}; + memcpy(values, vals, sizeof(vals)); + } else if (refresh_rate == 50) { + if (last_refresh_rate == 50) return; + uint32_t vals[] = {3, 1, 0}; + memcpy(values, vals, sizeof(vals)); + } else if (refresh_rate == 55) { + if (last_refresh_rate == 55) return; + uint32_t vals[] = {3, 1, 0}; + memcpy(values, vals, sizeof(vals)); + } else { + return; + } + + for (int i = 0; i < 5; i++) { + _changeOledElvssSettings(offsets, values, 3, 0); + } + last_refresh_rate = refresh_rate; +} + +void DisplayRefresh_SetAllowedDockedRatesIPC(uint32_t refreshRates, bool is720p) { + // Function kept for API compatibility but does nothing + (void)refreshRates; + (void)is720p; +} + +uint8_t DisplayRefresh_GetDockedHighestAllowed(void) { + return (g_dockedHighestRefreshRate > 60) ? g_dockedHighestRefreshRate : 60; +} + +static void _getDockedHighestRefreshRate(uint32_t fd_in) { + uint8_t highestRefreshRate = 60; + uint32_t fd = fd_in; + + if(!fd) nvOpen(&fd, "/dev/nvdisp-disp1"); + NvdcModeDB2 db2 = {0}; + int rc = nvIoctl(fd, NVDISP_GET_MODE_DB2, &db2); + + if (rc == 0) { + for (size_t i = 0; i < db2.num_modes; i++) { + if (db2.modes[i].hActive < 1920 || db2.modes[i].vActive < 1080) + continue; + + uint32_t v_total = db2.modes[i].vActive + db2.modes[i].vSyncWidth + db2.modes[i].vFrontPorch + db2.modes[i].vBackPorch; + uint32_t h_total = db2.modes[i].hActive + db2.modes[i].hSyncWidth + db2.modes[i].hFrontPorch + db2.modes[i].hBackPorch; + double refreshRate = round((double)(db2.modes[i].pclkKHz * 1000) / (double)(v_total * h_total)); + + if (highestRefreshRate < (uint8_t)refreshRate) + highestRefreshRate = (uint8_t)refreshRate; + } + } else { + g_dockedHighestRefreshRate = 60; + } + + const size_t numRates = sizeof(g_dockedRefreshRates) / sizeof(g_dockedRefreshRates[0]); + if (highestRefreshRate > g_dockedRefreshRates[numRates - 1]) + highestRefreshRate = g_dockedRefreshRates[numRates - 1]; + + NvdcMode2 display_b = {0}; + rc = nvIoctl(fd, NVDISP_GET_MODE2, &display_b); + + struct dpaux_read_0x100 { + uint32_t cmd; + uint32_t addr; + uint32_t size; + struct { + unsigned char link_rate; + unsigned int lane_count: 5; + unsigned int unk1: 2; + unsigned int isFramingEnhanced: 1; + unsigned char downspread; + unsigned char training_pattern; + unsigned char lane_pattern[4]; + unsigned char unk2[8]; + } set; + } dpaux = {6, 0x100, 0x10}; + + rc = nvIoctl(fd, NVDISP_GET_PANEL_DATA, &dpaux); + if (rc == 0) { + g_dockedLinkRate = dpaux.set.link_rate; + // if (display_b.hActive == 1920 && display_b.vActive == 1080 && highestRefreshRate > 75 && dpaux.set.link_rate < 20 && ) + // highestRefreshRate = 75; + } + + if (!fd_in) nvClose(fd); + g_dockedHighestRefreshRate = highestRefreshRate; +} + +static bool _setPLLDHandheldRefreshRate(uint32_t new_refreshRate) { + if (!g_config.clkVirtAddr) return false; + + uint32_t fd = 0; + if (nvOpen(&fd, "/dev/nvdisp-disp0")) { + return false; + } + + struct dpaux_read { + uint32_t cmd; + uint32_t addr; + uint32_t size; + struct { + unsigned int rev_minor : 4; + unsigned int rev_major : 4; + unsigned char link_rate; + unsigned int lane_count: 5; + unsigned int unk1: 2; + unsigned int isFramingEnhanced: 1; + unsigned char unk2[13]; + } DPCD; + } dpaux = {6, 0, 0x10}; + + int rc = nvIoctl(fd, NVDISP_GET_PANEL_DATA, &dpaux); + nvClose(fd); + if (rc != 0x75c) return false; + + PLLD_BASE base = {0}; + PLLD_MISC misc = {0}; + memcpy(&base, (void*)(g_config.clkVirtAddr + 0xD0), 4); + memcpy(&misc, (void*)(g_config.clkVirtAddr + 0xDC), 4); + + uint32_t value = ((base.PLLD_DIVN / base.PLLD_DIVM) * 10) / 4; + if (value == 0 || value == 80) return false; + + if (new_refreshRate > g_handheldModeRefreshRate.max) { + new_refreshRate = g_handheldModeRefreshRate.max; + } else if (new_refreshRate < g_handheldModeRefreshRate.min) { + bool skip = false; + for (size_t i = 2; i <= 4; i++) { + if (new_refreshRate * i == 60) { + skip = true; + new_refreshRate = 60; + break; + } + } + if (!skip) { + for (size_t i = 2; i <= 4; i++) { + if (((new_refreshRate * i) >= g_handheldModeRefreshRate.min) && ((new_refreshRate * i) <= g_handheldModeRefreshRate.max)) { + skip = true; + new_refreshRate *= i; + break; + } + } + } + if (!skip) new_refreshRate = 60; + } + + uint32_t pixelClock = (9375 * ((4096 * ((2 * base.PLLD_DIVN) + 1)) + misc.PLLD_SDM_DIN)) / (8 * base.PLLD_DIVM); + uint16_t refreshRateNow = pixelClock / (DSI_CLOCK_HZ / 60); + + if (refreshRateNow == new_refreshRate) { + return true; + } + + uint8_t base_refreshRate = new_refreshRate - (new_refreshRate % 5); + base.PLLD_DIVN = (4 * base_refreshRate) / 10; + base.PLLD_DIVM = 1; + + uint64_t expected_pixel_clock = (DSI_CLOCK_HZ * new_refreshRate) / 60; + misc.PLLD_SDM_DIN = ((8 * base.PLLD_DIVM * expected_pixel_clock) / 9375) - (4096 * ((2 * base.PLLD_DIVN) + 1)); + + memcpy((void*)(g_config.clkVirtAddr + 0xD0), &base, 4); + memcpy((void*)(g_config.clkVirtAddr + 0xDC), &misc, 4); + return true; +} + +static bool _setNvDispDockedRefreshRate(uint32_t new_refreshRate) { + if (g_config.isLite || !g_canChangeRefreshRateDocked) + return false; + + uint32_t fd = 0; + if (nvOpen(&fd, "/dev/nvdisp-disp1")) { + return false; + } + + NvdcMode2 display_b = {0}; + int rc = nvIoctl(fd, NVDISP_GET_MODE2, &display_b); + if (rc != 0) { + nvClose(fd); + return false; + } + + if (!display_b.pclkKHz) { + nvClose(fd); + return false; + } + + if (!((display_b.vActive == 480 && display_b.hActive == 720) || + (display_b.vActive == 720 && display_b.hActive == 1280) || + (display_b.vActive == 1080 && display_b.hActive == 1920))) { + nvClose(fd); + return false; + } + + if (display_b.vActive != g_lastVActiveSet) { + g_lastVActiveSet = display_b.vActive; + } + + uint32_t h_total = display_b.hActive + display_b.hFrontPorch + display_b.hSyncWidth + display_b.hBackPorch; + uint32_t v_total = display_b.vActive + display_b.vFrontPorch + display_b.vSyncWidth + display_b.vBackPorch; + uint32_t refreshRateNow = ((display_b.pclkKHz) * 1000 + 999) / (h_total * v_total); + + int8_t itr = -1; + const size_t numRates = sizeof(g_dockedRefreshRates) / sizeof(g_dockedRefreshRates[0]); + + // Find closest matching refresh rate + if ((new_refreshRate <= 60) && ((60 % new_refreshRate) == 0)) { + itr = _getDockedRefreshRateIterator(60); + } + + if (itr == -1) { + for (size_t i = 0; i < numRates; i++) { + uint8_t val = g_dockedRefreshRates[i]; + if ((val % new_refreshRate) == 0) { + itr = i; + break; + } + } + } + + if (itr == -1) { + if (!g_config.matchLowestDocked) { + itr = _getDockedRefreshRateIterator(60); + } else { + for (size_t i = 0; i < numRates; i++) { + if (new_refreshRate < g_dockedRefreshRates[i]) { + itr = i; + break; + } + } + } + } + + if (itr == -1) itr = _getDockedRefreshRateIterator(60); + + // Clamp to highest allowed refresh rate + if (g_dockedRefreshRates[itr] > g_dockedHighestRefreshRate) { + for (int8_t i = itr; i >= 0; i--) { + if (g_dockedRefreshRates[i] <= g_dockedHighestRefreshRate) { + itr = i; + break; + } + } + } + + if (refreshRateNow == g_dockedRefreshRates[itr]) { + nvClose(fd); + return true; + } + + if (itr >= 0 && itr < (int8_t)numRates) { + if (display_b.vActive == 720) { + uint32_t clock = ((h_total * v_total) * g_dockedRefreshRates[itr]) / 1000; + display_b.pclkKHz = clock; + } else { + display_b.hFrontPorch = g_dockedTimings1080p[itr].hFrontPorch; + display_b.hSyncWidth = g_dockedTimings1080p[itr].hSyncWidth; + display_b.hBackPorch = g_dockedTimings1080p[itr].hBackPorch; + display_b.vFrontPorch = g_dockedTimings1080p[itr].vFrontPorch; + display_b.vSyncWidth = g_dockedTimings1080p[itr].vSyncWidth; + display_b.vBackPorch = g_dockedTimings1080p[itr].vBackPorch; + display_b.pclkKHz = g_dockedTimings1080p[itr].pixelClock_kHz; + display_b.vmode = (g_dockedRefreshRates[itr] >= 100 ? 0x400000 : 0x200000); + display_b.unk1 = (g_dockedRefreshRates[itr] >= 100 ? 0x80 : 0); + display_b.sync = 3; + display_b.bitsPerPixel = 24; + } + + rc = nvIoctl(fd, NVDISP_VALIDATE_MODE2, &display_b); + if (rc == 0) { + rc = nvIoctl(fd, NVDISP_SET_MODE2, &display_b); + } + } + + nvClose(fd); + return true; +} + +static bool _setNvDispHandheldRefreshRate(uint32_t new_refreshRate) { + if (!g_config.isRetroSUPER) return false; + + if (!g_config.displaySync) { + g_wasRetroSuperTurnedOff = false; + } else if (g_wasRetroSuperTurnedOff) { + svcSleepThread(2000000000); + g_wasRetroSuperTurnedOff = false; + } + + svcSleepThread(1000000000); + + uint32_t fd = 0; + if (nvOpen(&fd, "/dev/nvdisp-disp0")) { + return false; + } + + NvdcMode2 display_b = {0}; + int rc = nvIoctl(fd, NVDISP_GET_MODE2, &display_b); + if (rc != 0) { + nvClose(fd); + return false; + } + + if (!display_b.pclkKHz) { + nvClose(fd); + return false; + } + + if ((display_b.vActive == 1280 && display_b.hActive == 720) == false) { + nvClose(fd); + return false; + } + + uint32_t h_total = display_b.hActive + display_b.hFrontPorch + display_b.hSyncWidth + display_b.hBackPorch; + uint32_t v_total = display_b.vActive + display_b.vFrontPorch + display_b.vSyncWidth + display_b.vBackPorch; + uint32_t refreshRateNow = ((display_b.pclkKHz) * 1000 + 999) / (h_total * v_total); + + if (new_refreshRate > g_handheldModeRefreshRate.max) { + new_refreshRate = g_handheldModeRefreshRate.max; + } else if (new_refreshRate < g_handheldModeRefreshRate.min) { + bool skip = false; + for (size_t i = 2; i <= 4; i++) { + if (new_refreshRate * i == 60) { + skip = true; + new_refreshRate = 60; + break; + } + } + if (!skip) { + for (size_t i = 2; i <= (sizeof(g_handheldTimingsRETRO) / sizeof(g_handheldTimingsRETRO[0])); i++) { + if (((new_refreshRate * i) >= g_handheldModeRefreshRate.min) && ((new_refreshRate * i) <= g_handheldModeRefreshRate.max)) { + skip = true; + new_refreshRate *= i; + break; + } + } + } + if (!skip) new_refreshRate = 60; + } + + if (new_refreshRate == refreshRateNow) { + nvClose(fd); + return true; + } + + uint32_t itr = (new_refreshRate - 40) / 5; + display_b.hFrontPorch = g_handheldTimingsRETRO[itr].hFrontPorch; + display_b.hSyncWidth = g_handheldTimingsRETRO[itr].hSyncWidth; + display_b.hBackPorch = g_handheldTimingsRETRO[itr].hBackPorch; + display_b.vFrontPorch = g_handheldTimingsRETRO[itr].vFrontPorch; + display_b.vSyncWidth = g_handheldTimingsRETRO[itr].vSyncWidth; + display_b.vBackPorch = g_handheldTimingsRETRO[itr].vBackPorch; + display_b.pclkKHz = g_handheldTimingsRETRO[itr].pixelClock_kHz; + + rc = nvIoctl(fd, NVDISP_VALIDATE_MODE2, &display_b); + if (rc == 0) { + for (size_t i = 0; i < 5; i++) { + nvIoctl(fd, NVDISP_SET_MODE2, &display_b); + } + } + + nvClose(fd); + return true; +} + +bool DisplayRefresh_SetRate(uint32_t new_refreshRate) { + if (!new_refreshRate || !g_initialized) return false; + + uint32_t fd = 0; + + if (g_config.isLite && g_config.isPossiblySpoofedRetro) { + g_config.isRetroSUPER = false; // Would check flag file here in original, but i dont care lol + } + + if (g_config.isRetroSUPER && !g_config.isDocked) { + return _setNvDispHandheldRefreshRate(new_refreshRate); + } + + else if ((!g_config.isRetroSUPER && g_config.isLite) || R_FAILED(nvOpen(&fd, "/dev/nvdisp-disp1"))) { + if (_setPLLDHandheldRefreshRate(new_refreshRate) == false) + return false; + } + else { + struct dpaux_read { + uint32_t cmd; + uint32_t addr; + uint32_t size; + struct { + unsigned int rev_minor : 4; + unsigned int rev_major : 4; + unsigned char link_rate; + unsigned int lane_count: 5; + unsigned int unk1: 2; + unsigned int isFramingEnhanced: 1; + unsigned char unk2[13]; + } DPCD; + } dpaux = {6, 0, 0x10}; + + int rc = nvIoctl(fd, NVDISP_GET_PANEL_DATA, &dpaux); + nvClose(fd); + + if (rc != 0) { + if (!g_config.isRetroSUPER) { + return _setPLLDHandheldRefreshRate(new_refreshRate); + } else { + return _setNvDispHandheldRefreshRate(new_refreshRate); + } + } else { + if(g_config.isDocked) + return _setNvDispDockedRefreshRate(new_refreshRate); + else + return true; + } + } + return false; +} + +bool DisplayRefresh_GetRate(uint32_t* out_refreshRate, bool internal) { + if (!out_refreshRate || !g_initialized || !g_config.clkVirtAddr) return false; + static uint32_t value = 60; + + if (g_config.isRetroSUPER && !g_config.isDocked) { + uint32_t fd = 0; + PLLD_BASE temp = {0}; + PLLD_MISC misc = {0}; + memcpy(&temp, (void*)(g_config.clkVirtAddr + 0xD0), 4); + memcpy(&misc, (void*)(g_config.clkVirtAddr + 0xDC), 4); + + value = ((temp.PLLD_DIVN / temp.PLLD_DIVM) * 10) / 4; + + if (value != 0 && value != 80) { + if (!nvOpen(&fd, "/dev/nvdisp-disp0")) { + NvdcMode2 display_b = {0}; + if (nvIoctl(fd, NVDISP_GET_MODE2, &display_b) == 0) { + uint32_t h_total = display_b.hActive + display_b.hFrontPorch + display_b.hSyncWidth + display_b.hBackPorch; + uint32_t v_total = display_b.vActive + display_b.vFrontPorch + display_b.vSyncWidth + display_b.vBackPorch; + uint32_t pixelClock = display_b.pclkKHz * 1000 + 999; + value = pixelClock / (h_total * v_total); + } + nvClose(fd); + } else { + return false; + } + } else { + g_wasRetroSuperTurnedOff = true; + } + } + else if ((!g_config.isPossiblySpoofedRetro) || (g_config.isPossiblySpoofedRetro && !g_config.isRetroSUPER)) { + PLLD_BASE temp = {0}; + PLLD_MISC misc = {0}; + memcpy(&temp, (void*)(g_config.clkVirtAddr + 0xD0), 4); + memcpy(&misc, (void*)(g_config.clkVirtAddr + 0xDC), 4); + + value = ((temp.PLLD_DIVN / temp.PLLD_DIVM) * 10) / 4; + + if (value == 0 || value == 80) { + // Docked mode + if (g_config.isLite) return false; + + g_config.isDocked = true; + + if (!g_canChangeRefreshRateDocked) { + uint32_t fd = 0; + if (!nvOpen(&fd, "/dev/nvdisp-disp1")) { + struct dpaux_read_0x100 { + uint32_t cmd; + uint32_t addr; + uint32_t size; + struct { + unsigned char link_rate; + unsigned int lane_count: 5; + unsigned int unk1: 2; + unsigned int isFramingEnhanced: 1; + unsigned char downspread; + unsigned char training_pattern; + unsigned char lane_pattern[4]; + unsigned char unk2[8]; + } set; + } dpaux = {6, 0x100, 0x10}; + + int rc = nvIoctl(fd, NVDISP_GET_PANEL_DATA, &dpaux); + nvClose(fd); + + if (rc == 0) { + _getDockedHighestRefreshRate(0); + g_canChangeRefreshRateDocked = true; + } else { + svcSleepThread(1000000000); + return false; + } + } else { + return false; + } + } + if(internal) { + *out_refreshRate = value; + return true; + } + uint32_t fd = 0; + if (!nvOpen(&fd, "/dev/nvdisp-disp1")) { + NvdcMode2 display_b = {0}; + if (nvIoctl(fd, NVDISP_GET_MODE2, &display_b) == 0) { + if (!display_b.pclkKHz) { + nvClose(fd); + return false; + } + + if (g_lastVActive != display_b.vActive) { + g_lastVActive = display_b.vActive; + _getDockedHighestRefreshRate(fd); + } + + uint32_t h_total = display_b.hActive + display_b.hFrontPorch + display_b.hSyncWidth + display_b.hBackPorch; + uint32_t v_total = display_b.vActive + display_b.vFrontPorch + display_b.vSyncWidth + display_b.vBackPorch; + uint32_t pixelClock = display_b.pclkKHz * 1000 + 999; + value = pixelClock / (h_total * v_total); + } else { + value = 60; + } + nvClose(fd); + } else { + value = 60; + } + } + else if (!g_config.isRetroSUPER) { + // Handheld mode + g_config.isDocked = false; + g_canChangeRefreshRateDocked = false; + + uint32_t pixelClock = (9375 * ((4096 * ((2 * temp.PLLD_DIVN) + 1)) + misc.PLLD_SDM_DIN)) / (8 * temp.PLLD_DIVM); + value = pixelClock / (DSI_CLOCK_HZ / 60); + } + else { + return false; + } + } + + *out_refreshRate = value; + return true; +} + +void DisplayRefresh_Shutdown(void) { + g_initialized = false; + memset(&g_config, 0, sizeof(g_config)); +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/src/i2c.cpp b/Source/rewrite-hoc-clk/common/src/i2c.cpp new file mode 100644 index 00000000..3760efeb --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/i2c.cpp @@ -0,0 +1,202 @@ +/* + * Copyright (c) KazushiMe + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#include "i2c.h" + +Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) { + // ams::fatal::srv::StopSoundTask::StopSound() + // I2C Bus Communication Reference: https://www.ti.com/lit/an/slva704/slva704.pdf + struct { + u8 reg; + u8 val; + } __attribute__((packed)) cmd; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + cmd.val = val; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + i2csessionClose(&_session); + return res; +} + +Result I2cRead_OutU8(I2cDevice dev, u8 reg, u8 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u8 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +Result I2cRead_OutU16(I2cDevice dev, u8 reg, u16 *out) { + struct { u8 reg; } __attribute__((packed)) cmd; + struct { u16 val; } __attribute__((packed)) rec; + + I2cSession _session; + Result res = i2cOpenSession(&_session, dev); + if (res) + return res; + + cmd.reg = reg; + res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All); + if (res) { + i2csessionClose(&_session); + return res; + } + + res = i2csessionReceiveAuto(&_session, &rec, sizeof(rec), I2cTransactionOption_All); + i2csessionClose(&_session); + if (res) { + return res; + } + + *out = rec.val; + return 0; +} + +float I2c_Max17050_GetBatteryCurrent() { + u16 val; + Result res = I2cRead_OutU16(I2cDevice_Max17050, MAX17050_CURRENT_REG, &val); + if (res) + return 0.f; + + const float SenseResistor = 5.; // in uOhm + const float CGain = 1.99993; + return (s16)val * (1.5625 / (SenseResistor * CGain)); +} + +u32 I2c_BuckConverter_MultiplierToMvOut(const I2c_BuckConverter_Domain* domain, u8 multiplier) { + return (domain->uv_min + domain->uv_step * multiplier) / 1000; +} + +u8 I2c_BuckConverter_MvOutToMultiplier(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u32 uvolt = mvolt * 1000; + if (uvolt < domain->uv_min) + uvolt = domain->uv_min; + if (uvolt > domain->uv_max) + uvolt = domain->uv_max; + + return (uvolt - domain->uv_min) / domain->uv_step; +} + +u32 I2c_BuckConverter_GetMvOut(const I2c_BuckConverter_Domain* domain) { + u8 val; + // Retry 5 times if received POR value + for (int i = 0; i < 5; i++) { + if (R_FAILED(I2cRead_OutU8(domain->device, domain->reg, &val))) + return 0u; + + // Wait 1us + svcSleepThread(1E3); + + if (!domain->por_val || val != domain->por_val) + break; + } + return I2c_BuckConverter_MultiplierToMvOut(domain, val & domain->volt_mask); +} + +Result I2c_BuckConverter_SetMvOut(const I2c_BuckConverter_Domain* domain, u32 mvolt) { + u8 val; + Result res = I2cRead_OutU8(domain->device, domain->reg, &val); + if (R_FAILED(res)) + return res; + + u8 multiplier = I2c_BuckConverter_MvOutToMultiplier(domain, mvolt); + val &= ~domain->volt_mask; + val |= multiplier & domain->volt_mask; + + res = I2cSet_U8(domain->device, domain->reg, val); + if (R_FAILED(res)) + return res; + + // 5ms Ramp delay + svcSleepThread(5E6); + u8 new_val; + res = I2cRead_OutU8(domain->device, domain->reg, &new_val); + if (R_FAILED(res)) + return res; + if (new_val != val) + return -1; + + return 0; +} + +u8 I2c_Bq24193_Convert_mA_Raw(u32 ma) { + // Adjustment is required + u8 raw = 0; + + if (ma > MA_RANGE_MAX) // capping + ma = MA_RANGE_MAX; + + bool pct20 = ma <= (MA_RANGE_MIN - 64); + if (pct20) { + ma = ma * 5; + raw |= 0x1; + } + + ma -= ma % 100; // round to 100 + ma -= (MA_RANGE_MIN - 64); // ceiling + raw |= (ma >> 6) << 2; + + return raw; +}; + +u32 I2c_Bq24193_Convert_Raw_mA(u8 raw) { + // No adjustment is allowed + u32 ma = (((raw >> 2)) << 6) + MA_RANGE_MIN; + + bool pct20 = raw & 1; + if (pct20) + ma = ma * 20 / 100; + + return ma; +}; + +Result I2c_Bq24193_GetFastChargeCurrentLimit(u32 *ma) { + u8 raw; + Result res = I2cRead_OutU8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, &raw); + if (res) + return res; + + *ma = I2c_Bq24193_Convert_Raw_mA(raw); + return 0; +} + +Result I2c_Bq24193_SetFastChargeCurrentLimit(u32 ma) { + u8 raw = I2c_Bq24193_Convert_mA_Raw(ma); + return I2cSet_U8(I2cDevice_Bq24193, BQ24193_CHARGE_CURRENT_CONTROL_REG, raw); +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/src/memmem.c b/Source/rewrite-hoc-clk/common/src/memmem.c new file mode 100644 index 00000000..33ff7e06 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/memmem.c @@ -0,0 +1,83 @@ +/* + MIT License + + Copyright (c) 2024 Roy Merkel + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + 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 OR COPYRIGHT HOLDERS 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. +*/ + +#include "memmem.h" + +void *memmem_impl(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) +{ + const unsigned char *cmpp; + const unsigned char *p; + const unsigned char *endp; + const unsigned char *q; + const unsigned char *endq; + unsigned char found; + + if(haystack == NULL) + { + return NULL; + } + if(needle == NULL) + { + return (void*)haystack; + } + if(haystacklen == 0) + { + return NULL; + } + if(needlelen == 0) + { + return (void*)haystack; + } + + if(needlelen > haystacklen) + { + return NULL; + } + + endp = haystack + haystacklen - needlelen; + endq = needle + needlelen; + for(p = haystack; p <= endp; p++) + { + found = 1; + cmpp = p; + for(q = needle; q < endq; q++) + { + if(*cmpp != *q) + { + found = 0; + break; + } + else + { + cmpp++; + } + } + if(found) + { + return (void*)p; + } + } + + return NULL; +} diff --git a/Source/rewrite-hoc-clk/common/src/psm_ext.c b/Source/rewrite-hoc-clk/common/src/psm_ext.c new file mode 100644 index 00000000..9bb1ae0c --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/psm_ext.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) KazushiMe + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include + +const char* PsmPowerRoleToStr(PsmPowerRole role) { + switch (role) { + case PsmPowerRole_Sink: return "Sink"; + case PsmPowerRole_Source: return "Source"; + default: return "Unknown"; + } +} + +const char* PsmInfoChargerTypeToStr(PsmInfoChargerType type) { + switch (type) { + case PsmInfoChargerType_None: return "None"; + case PsmInfoChargerType_PD: return "USB-C PD"; + case PsmInfoChargerType_TypeC_1500mA: + case PsmInfoChargerType_TypeC_3000mA: return "USB-C"; + case PsmInfoChargerType_DCP: return "USB DCP"; + case PsmInfoChargerType_CDP: return "USB CDP"; + case PsmInfoChargerType_SDP: return "USB SDP"; + case PsmInfoChargerType_Apple_500mA: + case PsmInfoChargerType_Apple_1000mA: + case PsmInfoChargerType_Apple_2000mA: return "Apple"; + default: return "Unknown"; + } +} + +bool PsmIsChargerConnected(const PsmChargeInfo* info) { + return info->ChargerType != PsmInfoChargerType_None; +} + +bool PsmIsCharging(const PsmChargeInfo* info) { + return PsmIsChargerConnected(info) && ((info->unk_x14 >> 8) & 1); +} + +PsmBatteryState PsmGetBatteryState(const PsmChargeInfo* info) { + if (!PsmIsChargerConnected(info)) + return PsmBatteryState_Discharging; + if (!PsmIsCharging(info)) + return PsmBatteryState_ChargingPaused; + return PsmBatteryState_FastCharging; +} + +const char* PsmGetBatteryStateIcon(const PsmChargeInfo* info) { + switch (PsmGetBatteryState(info)) { + case PsmBatteryState_Discharging: return "\u25c0"; // â—€ + case PsmBatteryState_ChargingPaused:return "| |"; + case PsmBatteryState_FastCharging: return "\u25b6"; // â–¶ + default: return "?"; + } +} diff --git a/Source/rewrite-hoc-clk/common/src/pwm.c b/Source/rewrite-hoc-clk/common/src/pwm.c new file mode 100644 index 00000000..96475997 --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/pwm.c @@ -0,0 +1,52 @@ +/* + * 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 . + * + */ + +#define NX_SERVICE_ASSUME_NON_DOMAIN +#include +#include "service_guard.h" +#include "pwm.h" + +static Service g_pwmSrv; + +NX_GENERATE_SERVICE_GUARD(pwm); + +Result _pwmInitialize(void) { + return smGetService(&g_pwmSrv, "pwm"); +} + +void _pwmCleanup(void) { + serviceClose(&g_pwmSrv); +} + +Service* pwmGetServiceSession(void) { + return &g_pwmSrv; +} + +Result pwmOpenSession2(PwmChannelSession *out, u32 device_code) { + return serviceDispatchIn(&g_pwmSrv, 2, device_code, + .out_num_objects = 1, + .out_objects = &out->s, + ); +} + +Result pwmChannelSessionGetDutyCycle(PwmChannelSession *c, double* out) { + return serviceDispatchOut(&c->s, 7, *out); +} + +void pwmChannelSessionClose(PwmChannelSession *controller) { + serviceClose(&controller->s); +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/common/src/rgltr_services.cpp b/Source/rewrite-hoc-clk/common/src/rgltr_services.cpp new file mode 100644 index 00000000..6365e2ed --- /dev/null +++ b/Source/rewrite-hoc-clk/common/src/rgltr_services.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) ppkantorski (bord2death) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "rgltr.h" +#include "rgltr_services.h" // for extern Service g_rgltrSrv, etc. + +// Global service handle +Service g_rgltrSrv; + +Result rgltrInitialize(void) { + if (hosversionBefore(8, 0, 0)) { + return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer); + } + return smGetService(&g_rgltrSrv, "rgltr"); +} + +void rgltrExit(void) { + serviceClose(&g_rgltrSrv); +} + +Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id) { + const u32 in = (u32)module_id; + return serviceDispatchIn( + &g_rgltrSrv, + 0, + in, + .out_num_objects = 1, + .out_objects = &session_out->s + ); +} + +Result rgltrGetVoltage(RgltrSession* session, u32* out_volt) { + u32 temp = 0; + Result rc = serviceDispatchOut(&session->s, 4, temp); + if (R_SUCCEEDED(rc)) { + *out_volt = temp; + } + return rc; +} + +Result rgltrRequestVoltage(RgltrSession* session, u32 microvolt) { + return serviceDispatchIn(&session->s, 5, microvolt); +} + +Result rgltrCancelVoltageRequest(RgltrSession* session) { + return serviceDispatch(&session->s, 6); +} + +void rgltrCloseSession(RgltrSession* session) { + serviceClose(&session->s); +} diff --git a/Source/rewrite-hoc-clk/config.ini.template b/Source/rewrite-hoc-clk/config.ini.template new file mode 100644 index 00000000..4305c725 --- /dev/null +++ b/Source/rewrite-hoc-clk/config.ini.template @@ -0,0 +1,19 @@ +[values] +; Defines how often sys-clk log temperatures, in milliseconds (set 0 to disable) +temp_log_interval_ms=0 +; Defines how often sys-clk writes to the CSV, in milliseconds (set 0 to disable) +csv_write_interval_ms=0 + +; Example #1: BOTW +; Overclock CPU when docked +; Overclock MEM to docked clocks when handheld +;[01007EF00011E000] +;docked_cpu=1224 +;handheld_mem=1600 + +; Example #2: Picross +; Underclock to save battery +;[0100BA0003EEA000] +;handheld_cpu=816 +;handheld_gpu=153 +;handheld_mem=800 \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/.gitignore b/Source/rewrite-hoc-clk/sysmodule/.gitignore new file mode 100644 index 00000000..36a52c92 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/.gitignore @@ -0,0 +1,2 @@ +/out +/build diff --git a/Source/rewrite-hoc-clk/sysmodule/Makefile b/Source/rewrite-hoc-clk/sysmodule/Makefile new file mode 100644 index 00000000..ae8bfa66 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/Makefile @@ -0,0 +1,164 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +#--------------------------------------------------------------------------------- +TARGET := horizon-oc +BUILD := build +OUTDIR := out +RESOURCES := res +SOURCES := src src/nx/ipc ../common/src +DATA := data +INCLUDES := ../common/include +EXEFS_SRC := exefs_src +LIBNAMES := minIni nxExt + +#--------------------------------------------------------------------------------- +# version control constants +#--------------------------------------------------------------------------------- +TARGET_VERSION := $(shell git describe --dirty --always --tags) + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +DEFINES := -DDISABLE_IPC -DTARGET="\"$(TARGET)\"" -DTARGET_VERSION="\"$(TARGET_VERSION)\"" + +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -g -Wall -Os -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := $(foreach lib,$(LIBNAMES),-l$(lib)) -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(foreach lib,$(LIBNAMES),$(TOPDIR)/lib/$(lib)) + +#--------------------------------------------------------------------------------- +# 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)/$(OUTDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +export APP_JSON := $(TOPDIR)/perms.json + +.PHONY: $(BUILD) clean clean-libs clean-build all libs + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +libs: + @$(foreach lib,$(LIBNAMES),$(MAKE) --no-print-directory -C $(TOPDIR)/lib/$(lib) && ) true + +$(LIBNAMES): + @echo $@ + +$(BUILD): libs + @[ -d $@ ] || mkdir -p $@ + @[ -d $(OUTDIR) ] || mkdir -p $(OUTDIR) + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean-libs: + @echo clean libs $(LIBNAMES) ... + @$(foreach lib,$(LIBNAMES),$(MAKE) -C $(TOPDIR)/lib/$(lib) clean && ) true + +clean-build: + @echo clean build ... + @rm -fr $(BUILD) $(TARGET).kip $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf $(OUTDIR) + +clean: clean-libs clean-build + + +#--------------------------------------------------------------------------------- +else +.PHONY: all $(LIBFILES) + +LIBFILES := $(foreach lib,$(LIBNAMES),$(TOPDIR)/lib/$(lib)/lib/lib$(lib).a) +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +all: $(OUTPUT).nsp + +$(OUTPUT).nsp: $(OUTPUT).nso $(OUTPUT).npdm + +$(OUTPUT).elf: $(OFILES) $(LIBFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitignore b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitignore new file mode 100644 index 00000000..0806f7bf --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitignore @@ -0,0 +1,13 @@ +# Editor files +*.swp +*~ + +# Objects +*.o +*.a +*.so + +# Lib +lib +release +debug \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitrepo b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitrepo new file mode 100644 index 00000000..44cc0b36 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme +; +[subrepo] + remote = https://github.com/compuphase/minIni + branch = master + commit = 8ce144c3c287fa4e59f8ed4c405cd8b7e29f189b + parent = f32c0b1bca87a6d7e55fb341f8db9ef33b13819f + method = merge + cmdver = 0.4.0 diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/LICENSE b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/LICENSE new file mode 100644 index 00000000..cbf8eb4c --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/LICENSE @@ -0,0 +1,189 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + + EXCEPTION TO THE APACHE 2.0 LICENSE + + As a special exception to the Apache License 2.0 (and referring to the + definitions in Section 1 of this license), you may link, statically or + dynamically, the "Work" to other modules to produce an executable file + containing portions of the "Work", and distribute that executable file + in "Object" form under the terms of your choice, without any of the + additional requirements listed in Section 4 of the Apache License 2.0. + This exception applies only to redistributions in "Object" form (not + "Source" form) and only if no modifications have been made to the "Work". + + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/Makefile b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/Makefile new file mode 100644 index 00000000..b9565d07 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/Makefile @@ -0,0 +1,133 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# 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)) +SOURCES := dev +DATA := data +INCLUDES := dev +SRC_H_FILES := + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g -Wall -Werror \ + -ffunction-sections \ + -fdata-sections \ + $(ARCH) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# 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 VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := minIni.c +CPPFILES := +SFILES := +BINFILES := + +#--------------------------------------------------------------------------------- +# 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 := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: clean all lib/lib$(TARGET).a + +#--------------------------------------------------------------------------------- +all: lib/lib$(TARGET).a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +debug: + @[ -d $@ ] || mkdir -p $@ + +lib/lib$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release debug lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/NOTICE b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/NOTICE new file mode 100644 index 00000000..dbd0bba0 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/NOTICE @@ -0,0 +1,12 @@ +minIni is a programmer's library to read and write "INI" files in embedded +systems. The library takes little resources and can be configured for various +kinds of file I/O libraries. + +The method for portable INI file management in minIni is, in part based, on the +article "Multiplatform .INI Files" by Joseph J. Graf in the March 1994 issue of +Dr. Dobb's Journal. + +The C++ class in minIni.h was contributed by Steven Van Ingelgem. + +The option to compile minIni as a read-only library was contributed by Luca +Bassanello. diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/README.md b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/README.md new file mode 100644 index 00000000..9f30a019 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/README.md @@ -0,0 +1,170 @@ +# minIni +minIni is a portable and configurable library for reading and writing ".INI" files. At 830 lines of commented source +code +(version 1.2), minIni truly is a "mini" INI file parser, especially considering its features. + +The library does not require the file I/O functions from the standard C/C++ library, but instead lets you configure +the file I/O interface to use via macros. minIni uses limited stack space and does not use dynamic memory (malloc and +friends) at all. + +Some minor variations on standard INI files are supported too, notably minIni supports INI files that lack sections. + + +# Acknowledgement + +minIni is derived from an earlier INI file parser (which I wrote) for desktop systems. + +In turn, that earlier parser was a re-write of the code from the article "Multiplatform .INI Files" by Joseph J. Graf +in the March 1994 issue of Dr. Dobb's Journal. In other words, minIni has its roots in the work of Joseph Graf (even +though the code has been almost completely re-written). + + +# Features + +minIni is a programmer's library to read and write "INI" files in embedded systems. minIni takes little resources, +can be configured for various kinds of file I/O libraries and provides functionality for reading, writing and +deleting keys from an INI file. + +Although the main feature of minIni is that it is small and minimal, it has a few other features: + + * minIni supports reading keys that are outside a section, and it thereby supports configuration files that do not use sections (but that are otherwise compatible with INI files). + * You may use a colon to separate key and value; the colon is equivalent to the equal sign. That is, the strings "Name: Value" and "Name=Value" have the same meaning. + * The hash character ("#") is an alternative for the semicolon to start a comment. Trailing comments (i.e. behind a key/value pair on a line) are allowed. + * Leading and trailing white space around key names and values is ignored. + * When writing a value that contains a comment character (";" or "#"), that value will automatically be put between double quotes; when reading the value, these quotes are removed. When a double-quote itself appears in the setting, these characters are escaped. + * Section and key enumeration are supported. + * You can optionally set the line termination (for text files) that minIni will use. (This is a compile-time setting, not a run-time setting.) + * Since writing speed is much lower than reading speed in Flash memory (SD/MMC cards, USB memory sticks), minIni minimizes "file writes" at the expense of double "file reads". + * The memory footprint is deterministic. There is no dynamic memory allocation. + +## INI file reading paradigms + +There are two approaches to reading settings from an INI file. One way is to call a function, such as +GetProfileString() for every section and key that you need. This is especially convenient if there is a large +INI file, but you only need a few settings from that file at any time —especially if the INI file can also +change while your program runs. This is the approach that the Microsoft Windows API uses. + +The above procedure is quite inefficient, however, when you need to retrieve quite a few settings in a row from +the INI file —especially if the INI file is not cached in memory (which it isn't, in minIni). A different approach +to getting settings from an INI file is to call a "parsing" function and let that function call the application +back with the section and key names plus the associated data. XML parsing libraries often use this approach; see +for example the Expat library. + +minIni supports both approaches. For reading a single setting, use functions like ini_gets(). For the callback +approach, implement a callback and call ini_browse(). See the minIni manual for details. + + +# INI file syntax + +INI files are best known from Microsoft Windows, but they are also used with applications that run on other +platforms (although their file extension is sometimes ".cfg" instead of ".ini"). + +INI files have a simple syntax with name/value pairs in a plain text file. The name must be unique (per section) +and the value must fit on a single line. INI files are commonly separated into sections —in minIni, this is +optional. A section is a name between square brackets, like "[Network]" in the example below. + +``` +[Network] +hostname=My Computer +address=dhcp +dns = 192.168.1.1 +``` + +In the API and in this documentation, the "name" for a setting is denoted as the key for the setting. The key +and the value are separated by an equal sign ("="). minIni supports the colon (":") as an alternative to the +equal sign for the key/value delimiter. + +Leading a trailing spaces around values or key names are removed. If you need to include leading and/or trailing +spaces in a value, put the value between double quotes. The ini_gets() function (from the minIni library, see the +minIni manual) strips off the double quotes from the returned value. Function ini_puts() adds double quotes if +the value to write contains trailing white space (or special characters). + +minIni ignores spaces around the "=" or ":" delimiters, but it does not ignore spaces between the brackets in a +section name. In other words, it is best not to put spaces behind the opening bracket "[" or before the closing +bracket "]" of a section name. + +Comments in the INI must start with a semicolon (";") or a hash character ("#"), and run to the end of the line. +A comment can be a line of its own, or it may follow a key/value pair (the "#" character and trailing comments +are extensions of minIni). + +For more details on the format, please see http://en.wikipedia.org/wiki/INI_file. + + +# Adapting minIni to a file system + +The minIni library must be configured for a platform with the help of a so- called "glue file". This glue file +contains macros (and possibly functions) that map file reading and writing functions used by the minIni library +to those provided by the operating system. The glue file must be called "minGlue.h". + +To get you started, the minIni distribution comes with the following example glue files: + + * a glue file that maps to the standard C/C++ library (specifically the file I/O functions from the "stdio" package), + * a glue file for Microchip's "Memory Disk Drive File System Library" (see http://www.microchip.com/), + * a glue file for the FAT library provided with the CCS PIC compiler (see http://www.ccsinfo.com/) + * a glue file for the EFS Library (EFSL, http://www.efsl.be/), + * and a glue file for the FatFs and Petit-FatFs libraries (http://elm-chan.org/fsw/ff/00index_e.html). + +The minIni library does not rely on the availability of a standard C library, because embedded operating systems +may have limited support for file I/O. Even on full operating systems, separating the file I/O from the INI format +parsing carries advantages, because it allows you to cache the INI file and thereby enhance performance. + +The glue file must specify the type that identifies a file, whether it is a handle or a pointer. For the standard +C/C++ file I/O library, this would be: + +```C +#define INI_FILETYPE FILE* +``` + +If you are not using the standard C/C++ file I/O library, chances are that you need a different handle or +"structure" to identify the storage than the ubiquitous "FILE*" type. For example, the glue file for the FatFs +library uses the following declaration: + +```C +#define INI_FILETYPE FIL +``` + +The minIni functions declare variables of this INI_FILETYPE type and pass these variables to sub-functions +(including the glue interface functions) by reference. + +For "write support", another type that must be defined is for variables that hold the "current position" in a +file. For the standard C/C++ I/O library, this is "fpos_t". + +Another item that needs to be configured is the buffer size. The functions in the minIni library allocate this +buffer on the stack, so the buffer size is directly related to the stack usage. In addition, the buffer size +determines the maximum line length that is supported in the INI file and the maximum path name length for the +temporary file. For example, minGlue.h could contain the definition: + +```C +#define INI_BUFFERSIZE 512 +``` + +The above macro limits the line length of the INI files supported by minIni to 512 characters. + +The temporary file is only used when writing to INI files. The minIni routines copy/change the INI file to a +temporary file and then rename that temporary file to the original file. This approach uses the least amount of +memory. The path name of the temporary file is the same as the input file, but with the last character set to a +tilde ("~"). + +Below is an example of a glue file (this is the one that maps to the C/C++ "stdio" library). + +```C +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"r")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"w")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS fpos_t +#define ini_tell(file,pos) (fgetpos(*(file), (pos)) == 0) +#define ini_seek(file,pos) (fsetpos(*(file), (pos)) == 0) +``` + +As you can see, a glue file is mostly a set of macros that wraps one function definition around another. + +The glue file may contain more settings, for support of rational numbers, to explicitly set the line termination +character(s), or to disable write support (for example). See the manual that comes with the archive for the details. diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-FatFs.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-FatFs.h new file mode 100644 index 00000000..51593a26 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-FatFs.h @@ -0,0 +1,37 @@ +/* Glue functions for the minIni library, based on the FatFs and Petit-FatFs + * libraries, see http://elm-chan.org/fsw/ff/00index_e.html + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FatFs and Petit-FatFs libraries are copyright by ChaN and licensed at + * its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +/* You must set _USE_STRFUNC to 1 or 2 in the include file ff.h (or tff.h) + * to enable the "string functions" fgets() and fputs(). + */ +#include "ff.h" /* include tff.h for Tiny-FatFs */ + +#define INI_FILETYPE FIL +#define ini_openread(filename,file) (f_open((file), (filename), FA_READ+FA_OPEN_EXISTING) == FR_OK) +#define ini_openwrite(filename,file) (f_open((file), (filename), FA_WRITE+FA_CREATE_ALWAYS) == FR_OK) +#define ini_close(file) (f_close(file) == FR_OK) +#define ini_read(buffer,size,file) f_gets((buffer), (size),(file)) +#define ini_write(buffer,file) f_puts((buffer), (file)) +#define ini_remove(filename) (f_unlink(filename) == FR_OK) + +#define INI_FILEPOS DWORD +#define ini_tell(file,pos) (*(pos) = f_tell((file))) +#define ini_seek(file,pos) (f_lseek((file), *(pos)) == FR_OK) + +static int ini_rename(TCHAR *source, const TCHAR *dest) +{ + /* Function f_rename() does not allow drive letters in the destination file */ + char *drive = strchr(dest, ':'); + drive = (drive == NULL) ? dest : drive + 1; + return (f_rename(source, drive) == FR_OK); +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ccs.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ccs.h new file mode 100644 index 00000000..22a8c92b --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ccs.h @@ -0,0 +1,64 @@ +/* minIni glue functions for FAT library by CCS, Inc. (as provided with their + * PIC MCU compiler) + * + * By CompuPhase, 2011-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FAT library is copyright (c) 2007 Custom Computer Services, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#ifndef FAT_PIC_C + #error FAT library must be included before this module +#endif +#define const /* keyword not supported by CCS */ + +#define INI_FILETYPE FILE +#define ini_openread(filename,file) (fatopen((filename), "r", (file)) == GOODEC) +#define ini_openwrite(filename,file) (fatopen((filename), "w", (file)) == GOODEC) +#define ini_close(file) (fatclose((file)) == 0) +#define ini_read(buffer,size,file) (fatgets((buffer), (size), (file)) != NULL) +#define ini_write(buffer,file) (fatputs((buffer), (file)) == GOODEC) +#define ini_remove(filename) (rm_file((filename)) == 0) + +#define INI_FILEPOS fatpos_t +#define ini_tell(file,pos) (fatgetpos((file), (pos)) == 0) +#define ini_seek(file,pos) (fatsetpos((file), (pos)) == 0) + +#ifndef INI_READONLY +/* CCS FAT library lacks a rename function, so instead we copy the file to the + * new name and delete the old file + */ +static int ini_rename(char *source, char *dest) +{ + FILE fr, fw; + int n; + + if (fatopen(source, "r", &fr) != GOODEC) + return 0; + if (rm_file(dest) != 0) + return 0; + if (fatopen(dest, "w", &fw) != GOODEC) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in a local buffer with the size INI_BUFFERSIZE. We can reuse + * this buffer for copying the file. + */ + while (n=fatread(source, 1, INI_BUFFERSIZE, &fr)) + fatwrite(source, 1, n, &fw); + + fatclose(&fr); + fatclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rm_file(source) == 0; +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-efsl.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-efsl.h new file mode 100644 index 00000000..5fe0fcf8 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-efsl.h @@ -0,0 +1,63 @@ +/* Glue functions for the minIni library, based on the EFS Library, see + * http://www.efsl.be/ + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (EFSL is copyright 2005-2006 Lennart Ysboodt and Michael De Nil, and + * licensed under the GPL with an exception clause for static linking.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#define INI_LINETERM "\r\n" /* set line termination explicitly */ + +#include "efs.h" +extern EmbeddedFileSystem g_efs; + +#define INI_FILETYPE EmbeddedFile +#define ini_openread(filename,file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'r') == 0) +#define ini_openwrite(filename,file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'w') == 0) +#define ini_close(file) file_fclose(file) +#define ini_read(buffer,size,file) (file_read((file), (size), (buffer)) > 0) +#define ini_write(buffer,file) (file_write((file), strlen(buffer), (char*)(buffer)) > 0) +#define ini_remove(filename) rmfile(&g_efs.myFs, (char*)(filename)) + +#define INI_FILEPOS euint32 +#define ini_tell(file,pos) (*(pos) = (file)->FilePtr)) +#define ini_seek(file,pos) file_setpos((file), (*pos)) + +#if ! defined INI_READONLY +/* EFSL lacks a rename function, so instead we copy the file to the new name + * and delete the old file + */ +static int ini_rename(char *source, const char *dest) +{ + EmbeddedFile fr, fw; + int n; + + if (file_fopen(&fr, &g_efs.myFs, source, 'r') != 0) + return 0; + if (rmfile(&g_efs.myFs, (char*)dest) != 0) + return 0; + if (file_fopen(&fw, &g_efs.myFs, (char*)dest, 'w') != 0) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in buffer and this buffer has the size INI_BUFFERSIZE. We can + * reuse this buffer for copying the file. + */ + while (n=file_read(&fr, INI_BUFFERSIZE, source)) + file_write(&fw, n, source); + + file_fclose(&fr); + file_fclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rmfile(&g_efs.myFs, source) == 0; +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ffs.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ffs.h new file mode 100644 index 00000000..bf874e41 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-ffs.h @@ -0,0 +1,26 @@ +/* Glue functions for the minIni library, based on the "FAT Filing System" + * library by embedded-code.com + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "FAT Filing System" library itself is copyright embedded-code.com, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#include + +#define INI_FILETYPE FFS_FILE* +#define ini_openread(filename,file) ((*(file) = ffs_fopen((filename),"r")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = ffs_fopen((filename),"w")) != NULL) +#define ini_close(file) (ffs_fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (ffs_fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (ffs_fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (ffs_rename((source), (dest)) == 0) +#define ini_remove(filename) (ffs_remove(filename) == 0) + +#define INI_FILEPOS long +#define ini_tell(file,pos) (ffs_fgetpos(*(file), (pos)) == 0) +#define ini_seek(file,pos) (ffs_fsetpos(*(file), (pos)) == 0) diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-mdd.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-mdd.h new file mode 100644 index 00000000..ec5e0be1 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-mdd.h @@ -0,0 +1,58 @@ +/* minIni glue functions for Microchip's "Memory Disk Drive" file system + * library, as presented in Microchip application note AN1045. + * + * By CompuPhase, 2011-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "Microchip Memory Disk Drive File System" is copyright (c) Microchip + * Technology Incorporated, and licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#include "MDD File System\fsio.h" +#include + +#define INI_FILETYPE FSFILE* +#define ini_openread(filename,file) ((*(file) = FSfopen((filename),FS_READ)) != NULL) +#define ini_openwrite(filename,file) ((*(file) = FSfopen((filename),FS_WRITE)) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),FS_READPLUS)) != NULL) +#define ini_close(file) (FSfclose(*(file)) == 0) +#define ini_write(buffer,file) (FSfwrite((buffer), 1, strlen(buffer), (*file)) > 0) +#define ini_remove(filename) (FSremove((filename)) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = FSftell(*(file))) +#define ini_seek(file,pos) (FSfseek(*(file), *(pos), SEEK_SET) == 0) + +/* Since the Memory Disk Drive file system library reads only blocks of files, + * the function to read a text line does so by "over-reading" a block of the + * of the maximum size and truncating it behind the end-of-line. + */ +static int ini_read(char *buffer, int size, INI_FILETYPE *file) +{ + size_t numread = size; + char *eol; + + if ((numread = FSfread(buffer, 1, size, *file)) == 0) + return 0; /* at EOF */ + if ((eol = strchr(buffer, '\n')) == NULL) + eol = strchr(buffer, '\r'); + if (eol != NULL) { + /* terminate the buffer */ + *++eol = '\0'; + /* "unread" the data that was read too much */ + FSfseek(*file, - (int)(numread - (size_t)(eol - buffer)), SEEK_CUR); + } /* if */ + return 1; +} + +#ifndef INI_READONLY +static int ini_rename(const char *source, const char *dest) +{ + FSFILE* ftmp = FSfopen((source), FS_READ); + FSrename((dest), ftmp); + return FSfclose(ftmp) == 0; +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-stdio.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-stdio.h new file mode 100644 index 00000000..67d24334 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue-stdio.h @@ -0,0 +1,31 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"rb")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"wb")) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),"r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = ftell(*(file))) +#define ini_seek(file,pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +#define INI_REAL float +#define ini_ftoa(string,value) sprintf((string),"%f",(value)) +#define ini_atof(string) (INI_REAL)strtod((string),NULL) diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue.h new file mode 100644 index 00000000..c3149627 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minGlue.h @@ -0,0 +1,35 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename,file) ((*(file) = fopen((filename),"rb")) != NULL) +#define ini_openwrite(filename,file) ((*(file) = fopen((filename),"wb")) != NULL) +#define ini_openrewrite(filename,file) ((*(file) = fopen((filename),"r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer,size,file) (fgets((buffer),(size),*(file)) != NULL) +#define ini_write(buffer,file) (fputs((buffer),*(file)) >= 0) +#define ini_rename(source,dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file,pos) (*(pos) = ftell(*(file))) +#define ini_seek(file,pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +//#define INI_REAL float +//#define ini_ftoa(string,value) sprintf((string),"%f",(value)) +//#define ini_atof(string) (INI_REAL)strtod((string),NULL) + +#define INI_ANSIONLY +#define INI_LINETERM "\n" +#define PORTABLE_STRNICMP \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.c b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.c new file mode 100644 index 00000000..a0f75a27 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.c @@ -0,0 +1,1009 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * These routines are in part based on the article "Multiplatform .INI Files" + * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal. + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY +# if !defined UNICODE /* for Windows */ +# define UNICODE +# endif +# if !defined _UNICODE /* for C library */ +# define _UNICODE +# endif +#endif + +#define MININI_IMPLEMENTATION +#include "minIni.h" +#if defined NDEBUG + #define assert(e) +#else + #include +#endif + +#if !defined __T || defined INI_ANSIONLY + #include + #include + #include + #define TCHAR char + #define __T(s) s + #define _tcscat strcat + #define _tcschr strchr + #define _tcscmp strcmp + #define _tcscpy strcpy + #define _tcsicmp stricmp + #define _tcslen strlen + #define _tcsncmp strncmp + #define _tcsnicmp strnicmp + #define _tcsrchr strrchr + #define _tcstol strtol + #define _tcstod strtod + #define _totupper toupper + #define _stprintf sprintf + #define _tfgets fgets + #define _tfputs fputs + #define _tfopen fopen + #define _tremove remove + #define _trename rename +#endif + +#if defined __linux || defined __linux__ + #define __LINUX__ +#elif defined FREEBSD && !defined __FreeBSD__ + #define __FreeBSD__ +#elif defined(_MSC_VER) + #pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */ +#endif +#if !defined strnicmp && !defined PORTABLE_STRNICMP + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #define strnicmp strncasecmp + #endif +#endif +#if !defined _totupper + #define _totupper toupper +#endif + +#if !defined INI_LINETERM + #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ + #define INI_LINETERM __T("\n") + #else + #define INI_LINETERM __T("\r\n") + #endif +#endif +#if !defined INI_FILETYPE + #error Missing definition for INI_FILETYPE. +#endif + +#if !defined sizearray + #define sizearray(a) (sizeof(a) / sizeof((a)[0])) +#endif + +enum quote_option { + QUOTE_NONE, + QUOTE_ENQUOTE, + QUOTE_DEQUOTE, +}; + +#if defined PORTABLE_STRNICMP +int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n) +{ + while (n-- != 0 && (*s1 || *s2)) { + register int c1, c2; + c1 = *s1++; + if ('a' <= c1 && c1 <= 'z') + c1 += ('A' - 'a'); + c2 = *s2++; + if ('a' <= c2 && c2 <= 'z') + c2 += ('A' - 'a'); + if (c1 != c2) + return c1 - c2; + } /* while */ + return 0; +} +#endif /* PORTABLE_STRNICMP */ + +static TCHAR *skipleading(const TCHAR *str) +{ + assert(str != NULL); + while ('\0' < *str && *str <= ' ') + str++; + return (TCHAR *)str; +} + +static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base) +{ + assert(str != NULL); + assert(base != NULL); + while (str > base && '\0' < *(str-1) && *(str-1) <= ' ') + str--; + return (TCHAR *)str; +} + +static TCHAR *striptrailing(TCHAR *str) +{ + TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str); + assert(ptr != NULL); + *ptr = '\0'; + return str; +} + +static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option) +{ + size_t d, s; + + assert(maxlen>0); + assert(source != NULL && dest != NULL); + assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source)); + if (option == QUOTE_ENQUOTE && maxlen < 3) + option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */ + + switch (option) { + case QUOTE_NONE: + for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++) + dest[d] = source[d]; + assert(d < maxlen); + dest[d] = '\0'; + break; + case QUOTE_ENQUOTE: + d = 0; + dest[d++] = '"'; + for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) { + if (source[s] == '"') { + if (d >= maxlen - 3) + break; /* no space to store the escape character plus the one that follows it */ + dest[d++] = '\\'; + } /* if */ + dest[d] = source[s]; + } /* for */ + dest[d++] = '"'; + dest[d] = '\0'; + break; + case QUOTE_DEQUOTE: + for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) { + if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"') + s++; + dest[d] = source[s]; + } /* for */ + dest[d] = '\0'; + break; + default: + assert(0); + } /* switch */ + + return dest; +} + +static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes) +{ + int isstring; + TCHAR *ep; + + assert(string != NULL); + assert(quotes != NULL); + + /* Remove a trailing comment */ + isstring = 0; + for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) { + if (*ep == '"') { + if (*(ep + 1) == '"') + ep++; /* skip "" (both quotes) */ + else + isstring = !isstring; /* single quote, toggle isstring */ + } else if (*ep == '\\' && *(ep + 1) == '"') { + ep++; /* skip \" (both quotes */ + } /* if */ + } /* for */ + assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#')); + *ep = '\0'; /* terminate at a comment */ + striptrailing(string); + /* Remove double quotes surrounding a value */ + *quotes = QUOTE_NONE; + if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') { + string++; + *--ep = '\0'; + *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */ + } /* if */ + return string; +} + +static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key, + int idxSection, int idxKey, TCHAR *Buffer, int BufferSize, + INI_FILEPOS *mark) +{ + TCHAR *sp, *ep; + int len, idx; + enum quote_option quotes; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + + assert(fp != NULL); + /* Move through file 1 line at a time until a section is matched or EOF. If + * parameter Section is NULL, only look at keys above the first section. If + * idxSection is positive, copy the relevant section name. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0 || idxSection >= 0) { + assert(idxSection >= 0 || Section != NULL); + idx = -1; + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) + return 0; + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + } while (*sp != '[' || ep == NULL || + (((int)(ep-sp-1) != len || Section == NULL || _tcsnicmp(sp+1,Section,len) != 0) && ++idx != idxSection)); + if (idxSection >= 0) { + if (idx == idxSection) { + assert(ep != NULL); + assert(*ep == ']'); + *ep = '\0'; + ini_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more section found */ + } /* if */ + } /* if */ + + /* Now that the section has been found, find the entry. + * Stop searching upon leaving the section's area. + */ + assert(Key != NULL || idxKey >= 0); + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + idx = -1; + do { + if (mark != NULL) + ini_tell(fp, mark); /* optionally keep the mark to the start of the line */ + if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[') + return 0; + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + } while (*sp == ';' || *sp == '#' || ep == NULL + || ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey)); + if (idxKey >= 0) { + if (idx == idxKey) { + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + *ep = '\0'; + striptrailing(sp); + ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more key found (in this section) */ + } /* if */ + + /* Copy up to BufferSize chars to buffer */ + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + sp = skipleading(ep + 1); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(Buffer, sp, BufferSize, quotes); + return 1; +} + +/** ini_gets() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default string in the event of a failed read + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue, + TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || Key == NULL) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE); + return (int)_tcslen(Buffer); +} + +/** ini_getl() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue + : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16) + : _tcstol(LocalBuffer, NULL, 10)); +} + +#if defined INI_REAL +/** ini_getf() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue : ini_atof(LocalBuffer); +} +#endif + +/** ini_getbool() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default value in the event of a failed read; it should + * zero (0) or one (1). + * \param Filename the name and full path of the .ini file to read from + * + * A true boolean is found if one of the following is matched: + * - A string starting with 'y' or 'Y' + * - A string starting with 't' or 'T' + * - A string starting with '1' + * + * A false boolean is found if one of the following is matched: + * - A string starting with 'n' or 'N' + * - A string starting with 'f' or 'F' + * - A string starting with '0' + * + * \return the true/false flag as interpreted at Key + */ +int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename) +{ + TCHAR LocalBuffer[2] = __T(""); + int ret; + + ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]); + if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T') + ret = 1; + else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F') + ret = 0; + else + ret = DefValue; + + return(ret); +} + +/** ini_getsection() + * \param idx the zero-based sequence number of the section to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + +/** ini_getkey() + * \param Section the name of the section to browse through, or NULL to + * browse through the keys outside any section + * \param idx the zero-based sequence number of the key to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) { + ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + + +#if !defined INI_NOBROWSE +/** ini_browse() + * \param Callback a pointer to a function that will be called for every + * setting in the INI file. + * \param UserData arbitrary data, which the function passes on the + * \c Callback function + * \param Filename the name and full path of the .ini file to read from + * + * \return 1 on success, 0 on failure (INI file not found) + * + * \note The \c Callback function must return 1 to continue + * browsing through the INI file, or 0 to stop. Even when the + * callback stops the browsing, this function will return 1 + * (for success). + */ +int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename) +{ + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int lenSec, lenKey; + enum quote_option quotes; + INI_FILETYPE fp; + + if (Callback == NULL) + return 0; + if (!ini_openread(Filename, &fp)) + return 0; + + LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */ + lenSec = (int)_tcslen(LocalBuffer) + 1; + for ( ;; ) { + TCHAR *sp, *ep; + if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp)) + break; + sp = skipleading(LocalBuffer + lenSec); + /* ignore empty strings and comments */ + if (*sp == '\0' || *sp == ';' || *sp == '#') + continue; + /* see whether we reached a new section */ + ep = _tcsrchr(sp, ']'); + if (*sp == '[' && ep != NULL) { + *ep = '\0'; + ini_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE); + lenSec = (int)_tcslen(LocalBuffer) + 1; + continue; + } /* if */ + /* not a new section, test for a key/value pair */ + ep = _tcschr(sp, '='); /* test for the equal sign or colon */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + if (ep == NULL) + continue; /* invalid line, ignore */ + *ep++ = '\0'; /* split the key from the value */ + striptrailing(sp); + ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); + lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1; + /* clean up the value */ + sp = skipleading(ep); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); + /* call the callback */ + if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) + break; + } /* for */ + + (void)ini_close(&fp); + return 1; +} +#endif /* INI_NOBROWSE */ + +#if ! defined INI_READONLY +static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength) +{ + TCHAR *p; + + ini_strncpy(dest, source, maxlength, QUOTE_NONE); + p = _tcsrchr(dest, '\0'); + assert(p != NULL); + *(p - 1) = '~'; +} + +static enum quote_option check_enquote(const TCHAR *Value) +{ + const TCHAR *p; + + /* run through the value, if it has trailing spaces, or '"', ';' or '#' + * characters, enquote it + */ + assert(Value != NULL); + for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++) + /* nothing */; + return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE; +} + +static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp) +{ + if (Section != NULL && _tcslen(Section) > 0) { + TCHAR *p; + LocalBuffer[0] = '['; + ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = ']'; + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); + } /* if */ +} + +static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp) +{ + TCHAR *p; + enum quote_option option = check_enquote(Value); + ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = '='; + ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); +} + +static int cache_accum(const TCHAR *string, int *size, int max) +{ + int len = (int)_tcslen(string); + if (*size + len >= max) + return 0; + *size += len; + return 1; +} + +static int cache_flush(TCHAR *buffer, int *size, + INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark) +{ + int terminator_len = (int)_tcslen(INI_LINETERM); + int pos = 0; + + (void)ini_seek(rfp, mark); + assert(buffer != NULL); + buffer[0] = '\0'; + assert(size != NULL); + assert(*size <= INI_BUFFERSIZE); + while (pos < *size) { + (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); + while (pos < *size && buffer[pos] != '\0') + pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */ + } /* while */ + if (buffer[0] != '\0') { + assert(pos > 0 && pos <= INI_BUFFERSIZE); + if (pos == INI_BUFFERSIZE) + pos--; + buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */ + (void)ini_write(buffer, wfp); + } + ini_tell(rfp, mark); /* update mark */ + *size = 0; + /* return whether the buffer ended with a line termination */ + return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0); +} + +static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer) +{ + (void)ini_close(rfp); + (void)ini_close(wfp); + (void)ini_remove(filename); + (void)ini_tempname(buffer, filename, INI_BUFFERSIZE); + (void)ini_rename(buffer, filename); + return 1; +} + +/** ini_puts() + * \param Section the name of the section to write the string in + * \param Key the name of the entry to write, or NULL to erase all keys in the section + * \param Value a pointer to the buffer the string, or NULL to erase the key + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename) +{ + INI_FILETYPE rfp; + INI_FILETYPE wfp; + INI_FILEPOS mark; + INI_FILEPOS head, tail; + TCHAR *sp, *ep; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int len, match, flag, cachelen; + + assert(Filename != NULL); + if (!ini_openread(Filename, &rfp)) { + /* If the .ini file doesn't exist, make a new file */ + if (Key != NULL && Value != NULL) { + if (!ini_openwrite(Filename, &wfp)) + return 0; + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + (void)ini_close(&wfp); + } /* if */ + return 1; + } /* if */ + + /* If parameters Key and Value are valid (so this is not an "erase" request) + * and the setting already exists, there are two short-cuts to avoid rewriting + * the INI file. + */ + if (Key != NULL && Value != NULL) { + ini_tell(&rfp, &mark); + match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head); + if (match) { + /* if the current setting is identical to the one to write, there is + * nothing to do. + */ + if (_tcscmp(LocalBuffer,Value) == 0) { + (void)ini_close(&rfp); + return 1; + } /* if */ + /* if the new setting has the same length as the current setting, and the + * glue file permits file read/write access, we can modify in place. + */ + #if defined ini_openrewrite + /* we already have the start of the (raw) line, get the end too */ + ini_tell(&rfp, &tail); + /* create new buffer (without writing it to file) */ + writekey(LocalBuffer, Key, Value, NULL); + if (_tcslen(LocalBuffer) == (size_t)(tail - head)) { + /* length matches, close the file & re-open for read/write, then + * write at the correct position + */ + (void)ini_close(&rfp); + if (!ini_openrewrite(Filename, &wfp)) + return 0; + (void)ini_seek(&wfp, &head); + (void)ini_write(LocalBuffer, &wfp); + (void)ini_close(&wfp); + return 1; + } /* if */ + #endif + } /* if */ + /* key not found, or different value & length -> proceed (but rewind the + * input file first) + */ + (void)ini_seek(&rfp, &mark); + } /* if */ + + /* Get a temporary file name to copy to. Use the existing name, but with + * the last character set to a '~'. + */ + ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); + if (!ini_openwrite(LocalBuffer, &wfp)) { + (void)ini_close(&rfp); + return 0; + } /* if */ + (void)ini_tell(&rfp, &mark); + cachelen = 0; + + /* Move through the file one line at a time until a section is + * matched or until EOF. Copy to temp file as it is read. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0) { + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* Failed to find section, so add one to the end */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key!=NULL && Value!=NULL) { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + /* Copy the line from source to dest, but not if this is the section that + * we are looking for and this section must be removed + */ + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0); + if (!match || Key != NULL) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } while (!match); + } /* if */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + /* when deleting a section, the section head that was just found has not been + * copied to the output file, but because this line was not "accumulated" in + * the cache, the position in the input file was reset to the point just + * before the section; this must now be skipped (again) + */ + if (Key == NULL) { + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + (void)ini_tell(&rfp, &mark); + } /* if */ + + /* Now that the section has been found, find the entry. Stop searching + * upon leaving the section's area. Copy the file as it is read + * and create an entry if one is not found. + */ + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + for( ;; ) { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* EOF without an entry so make one */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key!=NULL && Value!=NULL) { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0); + if ((Key != NULL && match) || *sp == '[') + break; /* found the key, or found a new section */ + /* copy other keys in the section */ + if (Key == NULL) { + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + } else { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } /* for */ + /* the key was found, or we just dropped on the next section (meaning that it + * wasn't found); in both cases we need to write the key, but in the latter + * case, we also need to write the line starting the new section after writing + * the key + */ + flag = (*sp == '['); + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key != NULL && Value != NULL) + writekey(LocalBuffer, Key, Value, &wfp); + /* cache_flush() reset the "read pointer" to the start of the line with the + * previous key or the new section; read it again (because writekey() destroyed + * the buffer) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + if (flag) { + /* the new section heading needs to be copied to the output file */ + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } else { + /* forget the old key line */ + (void)ini_tell(&rfp, &mark); + } /* if */ + /* Copy the rest of the INI file */ + while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* while */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ +} + +/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */ +#define ABS(v) ((v) < 0 ? -(v) : (v)) + +static void strreverse(TCHAR *str) +{ + int i, j; + for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) { + TCHAR t = str[i]; + str[i] = str[j]; + str[j] = t; + } /* for */ +} + +static void long2str(long value, TCHAR *str) +{ + int i = 0; + long sign = value; + + /* generate digits in reverse order */ + do { + int n = (int)(value % 10); /* get next lowest digit */ + str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */ + } while (value /= 10); /* delete the lowest digit */ + if (sign < 0) + str[i++] = '-'; + str[i] = '\0'; + + strreverse(str); +} + +/** ini_putl() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename) +{ + TCHAR LocalBuffer[32]; + long2str(Value, LocalBuffer); + return ini_puts(Section, Key, LocalBuffer, Filename); +} + +#if defined INI_REAL +/** ini_putf() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename) +{ + TCHAR LocalBuffer[64]; + ini_ftoa(LocalBuffer, Value); + return ini_puts(Section, Key, LocalBuffer, Filename); +} +#endif /* INI_REAL */ + +static void putsection(TCHAR *LocalBuffer, const TCHAR *Section, const TCHAR **Keys, const TCHAR **Values, INI_FILETYPE *fp) { + if(Keys && Values && *Keys && *Values) { + writesection(LocalBuffer, Section, fp); + while(*Keys && *Values) { + writekey(LocalBuffer, *Keys, *Values, fp); + Keys++; + Values++; + } + (void)ini_write(INI_LINETERM, fp); /* force a new line behind the last line of the section */ + } +} + +int ini_putsection(const TCHAR *Section, const TCHAR **Keys, const TCHAR **Values, const TCHAR *Filename) +{ + INI_FILETYPE rfp; + INI_FILETYPE wfp; + INI_FILEPOS mark; + TCHAR *sp = NULL; + TCHAR *ep; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int len, match, flag, cachelen; + + assert(Filename != NULL); + if (!ini_openread(Filename, &rfp)) { + /* If the .ini file doesn't exist, make a new file */ + if (!ini_openwrite(Filename, &wfp)) + return 0; + putsection(LocalBuffer, Section, Keys, Values, &wfp); + (void)ini_close(&wfp); + return 1; + } /* if */ + + /* Get a temporary file name to copy to. Use the existing name, but with + * the last character set to a '~'. + */ + ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); + if (!ini_openwrite(LocalBuffer, &wfp)) { + (void)ini_close(&rfp); + return 0; + } /* if */ + (void)ini_tell(&rfp, &mark); + cachelen = 0; + + /* Move through the file one line at a time until a section is + * matched or until EOF. Copy to temp file as it is read. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0) { + do { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* Failed to find section, so add one to the end */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + + putsection(LocalBuffer, Section, Keys, Values, &wfp); + + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + /* Copy the line from source to dest, but not if this is the section that + * we are looking for + */ + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + match = (*sp == '[' && ep != NULL && (int)(ep-sp-1) == len && _tcsnicmp(sp + 1,Section,len) == 0); + if (!match) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } while (!match); + } /* if */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + /* when deleting a section, the section head that was just found has not been + * copied to the output file, but because this line was not "accumulated" in + * the cache, the position in the input file was reset to the point just + * before the section; this must now be skipped (again) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + (void)ini_tell(&rfp, &mark); + + /* Now that the section has been found, find the entry. Stop searching + * upon leaving the section's area. Copy the file as it is read + * and create an entry if one is not found. + */ + len = 0; + for( ;; ) { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + /* EOF without an entry */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + break; + } /* if */ + sp = skipleading(LocalBuffer); + if (*sp == '[') + break; /* found a new section */ + /* copy other keys in the section */ + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + } /* for */ + /* we just dropped on the next section + * we also need to write the line starting the new section after writing + */ + flag = (sp && *sp == '['); + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + putsection(LocalBuffer, Section, Keys, Values, &wfp); + + /* cache_flush() reset the "read pointer" to the start of the line with the + * previous key or the new section; read it again (because writekey() destroyed + * the buffer) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + if (flag) { + /* the new section heading needs to be copied to the output file */ + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } else { + /* forget the old key line */ + (void)ini_tell(&rfp, &mark); + } /* if */ + /* Copy the rest of the INI file */ + while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* while */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ +} + +#endif /* !INI_READONLY */ diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.h new file mode 100644 index 00000000..73e33c01 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/minIni.h @@ -0,0 +1,68 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ +#ifndef MININI_H +#define MININI_H + +#include "minGlue.h" + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY + #include + #define mTCHAR TCHAR +#else + /* force TCHAR to be "char", but only for minIni */ + #define mTCHAR char +#endif + +#if !defined INI_BUFFERSIZE + #define INI_BUFFERSIZE 512 +#endif + +#if defined __cplusplus + extern "C" { +#endif + +int ini_getbool(const mTCHAR *Section, const mTCHAR *Key, int DefValue, const mTCHAR *Filename); +long ini_getl(const mTCHAR *Section, const mTCHAR *Key, long DefValue, const mTCHAR *Filename); +int ini_gets(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *DefValue, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); +int ini_getsection(int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); +int ini_getkey(const mTCHAR *Section, int idx, mTCHAR *Buffer, int BufferSize, const mTCHAR *Filename); + +#if defined INI_REAL +INI_REAL ini_getf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL DefValue, const mTCHAR *Filename); +#endif + +#if !defined INI_READONLY +int ini_putl(const mTCHAR *Section, const mTCHAR *Key, long Value, const mTCHAR *Filename); +int ini_puts(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, const mTCHAR *Filename); +int ini_putsection(const mTCHAR *Section, const mTCHAR **Keys, const mTCHAR **Values, const mTCHAR *Filename); +#if defined INI_REAL +int ini_putf(const mTCHAR *Section, const mTCHAR *Key, INI_REAL Value, const mTCHAR *Filename); +#endif +#endif /* INI_READONLY */ + +#if !defined INI_NOBROWSE +typedef int (*INI_CALLBACK)(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData); +int ini_browse(INI_CALLBACK Callback, void *UserData, const mTCHAR *Filename); +#endif /* INI_NOBROWSE */ + +#if defined __cplusplus + } +#endif + +#endif /* MININI_H */ diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.c b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.c new file mode 100644 index 00000000..80508cd7 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.c @@ -0,0 +1,117 @@ +/* Simple test program + * + * gcc -o test test.c minIni.c + */ +#include +#include +#include +#include "minIni.h" + +#define sizearray(a) (sizeof(a) / sizeof((a)[0])) + +const char inifile[] = "test.ini"; +const char inifile2[] = "testplain.ini"; + +int Callback(const char *section, const char *key, const char *value, void *userdata) +{ + (void)userdata; /* this parameter is not used in this example */ + printf(" [%s]\t%s=%s\n", section, key, value); + return 1; +} + +int main(void) +{ + char str[100]; + long n; + int s, k; + char section[50]; + + /* string reading */ + n = ini_gets("first", "string", "dummy", str, sizearray(str), inifile); + assert(n==4 && strcmp(str,"noot")==0); + n = ini_gets("second", "string", "dummy", str, sizearray(str), inifile); + assert(n==4 && strcmp(str,"mies")==0); + n = ini_gets("first", "undefined", "dummy", str, sizearray(str), inifile); + assert(n==5 && strcmp(str,"dummy")==0); + /* ----- */ + n = ini_gets("", "string", "dummy", str, sizearray(str), inifile2); + assert(n==4 && strcmp(str,"noot")==0); + n = ini_gets(NULL, "string", "dummy", str, sizearray(str), inifile2); + assert(n==4 && strcmp(str,"noot")==0); + /* ----- */ + printf("1. String reading tests passed\n"); + + /* value reading */ + n = ini_getl("first", "val", -1, inifile); + assert(n==1); + n = ini_getl("second", "val", -1, inifile); + assert(n==2); + n = ini_getl("first", "undefined", -1, inifile); + assert(n==-1); + /* ----- */ + n = ini_getl(NULL, "val", -1, inifile2); + assert(n==1); + /* ----- */ + printf("2. Value reading tests passed\n"); + + /* string writing */ + n = ini_puts("first", "alt", "flagged as \"correct\"", inifile); + assert(n==1); + n = ini_gets("first", "alt", "dummy", str, sizearray(str), inifile); + assert(n==20 && strcmp(str,"flagged as \"correct\"")==0); + /* ----- */ + n = ini_puts("second", "alt", "correct", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + n = ini_puts("third", "test", "correct", inifile); + assert(n==1); + n = ini_gets("third", "test", "dummy", str, sizearray(str), inifile); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + n = ini_puts("second", "alt", "overwrite", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==9 && strcmp(str,"overwrite")==0); + /* ----- */ + n = ini_puts("second", "alt", "123456789", inifile); + assert(n==1); + n = ini_gets("second", "alt", "dummy", str, sizearray(str), inifile); + assert(n==9 && strcmp(str,"123456789")==0); + /* ----- */ + n = ini_puts(NULL, "alt", "correct", inifile2); + assert(n==1); + n = ini_gets(NULL, "alt", "dummy", str, sizearray(str), inifile2); + assert(n==7 && strcmp(str,"correct")==0); + /* ----- */ + printf("3. String writing tests passed\n"); + + /* section/key enumeration */ + printf("4. Section/key enumeration, file structure follows\n"); + for (s = 0; ini_getsection(s, section, sizearray(section), inifile) > 0; s++) { + printf(" [%s]\n", section); + for (k = 0; ini_getkey(section, k, str, sizearray(str), inifile) > 0; k++) { + printf("\t%s\n", str); + } /* for */ + } /* for */ + + /* browsing through the file */ + printf("5. browse through all settings, file field list follows\n"); + ini_browse(Callback, NULL, inifile); + + /* string deletion */ + n = ini_puts("first", "alt", NULL, inifile); + assert(n==1); + n = ini_puts("second", "alt", NULL, inifile); + assert(n==1); + n = ini_puts("third", NULL, NULL, inifile); + assert(n==1); + /* ----- */ + n = ini_puts(NULL, "alt", NULL, inifile2); + assert(n==1); + printf("6. String deletion tests passed\n"); + + return 0; +} + diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.ini b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.ini new file mode 100644 index 00000000..565aef78 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test.ini @@ -0,0 +1,8 @@ +[First] +String=noot # trailing commment +Val=1 + +[Second] +Val = 2 +#comment=3 +String = mies diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test2.cc b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test2.cc new file mode 100644 index 00000000..4e19b319 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/test2.cc @@ -0,0 +1,80 @@ +/* + gcc -o minIni.o -c minIni.c + g++ -o test2.o -c test2.cc + g++ -o test2 test2.o minIni.o + ./test2 +*/ + + +#include +#include +#include +using namespace std ; + +#include "minIni.h" + +int main(void) +{ + minIni ini("test.ini"); + string s; + + /* string reading */ + s = ini.gets( "first", "string" , "aap" ); + assert(s == "noot"); + s = ini.gets( "second", "string" , "aap" ); + assert(s == "mies"); + s = ini.gets( "first", "dummy" , "aap" ); + assert(s == "aap"); + cout << "1. String reading tests passed" << endl ; + + + /* value reading */ + long n; + n = ini.getl("first", "val", -1 ); + assert(n==1); + n = ini.getl("second", "val", -1); + assert(n==2); + n = ini.getl("first", "dummy", -1); + assert(n==-1); + cout << "2. Value reading tests passed" << endl ; + + + /* string writing */ + bool b; + b = ini.put("first", "alt", "flagged as \"correct\""); + assert(b); + s = ini.gets("first", "alt", "aap"); + assert(s=="flagged as \"correct\""); + + b = ini.put("second", "alt", "correct"); + assert(b); + s = ini.gets("second", "alt", "aap"); + assert(s=="correct"); + + b = ini.put("third", "alt", "correct"); + assert(b); + s = ini.gets("third", "alt", "aap" ); + assert(s=="correct"); + cout << "3. String writing tests passed" << endl; + + /* section/key enumeration */ + cout << "4. section/key enumeration; file contents follows" << endl; + string section; + for (int is = 0; section = ini.getsection(is), section.length() > 0; is++) { + cout << " [" << section.c_str() << "]" << endl; + for (int ik = 0; s = ini.getkey(section, ik), s.length() > 0; ik++) { + cout << "\t" << s.c_str() << endl; + } + } + + /* string deletion */ + b = ini.del("first", "alt"); + assert(b); + b = ini.del("second", "alt"); + assert(b); + b = ini.del("third"); + assert(b); + cout << "5. string deletion passed " << endl; + + return 0; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/testplain.ini b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/testplain.ini new file mode 100644 index 00000000..2a5ce4b6 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/testplain.ini @@ -0,0 +1,3 @@ +String=noot # trailing commment +#comment=3 +Val=1 diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/wxMinIni.h b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/wxMinIni.h new file mode 100644 index 00000000..f932bb63 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/dev/wxMinIni.h @@ -0,0 +1,101 @@ +/* minIni - Multi-Platform INI file parser, wxWidgets interface + * + * Copyright (c) CompuPhase, 2008-2012 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: wxMinIni.h 44 2012-01-04 15:52:56Z thiadmer.riemersma@gmail.com $ + */ +#ifndef WXMININI_H +#define WXMININI_H + +#include +#include "minIni.h" + +class minIni +{ +public: + minIni(const wxString& filename) : iniFilename(filename) + { } + + bool getbool(const wxString& Section, const wxString& Key, bool DefValue=false) const + { return ini_getbool(Section.utf8_str(), Key.utf8_str(), int(DefValue), iniFilename.utf8_str()) != 0; } + + long getl(const wxString& Section, const wxString& Key, long DefValue=0) const + { return ini_getl(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); } + + int geti(const wxString& Section, const wxString& Key, int DefValue=0) const + { return static_cast(ini_getl(Section.utf8_str(), Key.utf8_str(), (long)DefValue, iniFilename.utf8_str())); } + + wxString gets(const wxString& Section, const wxString& Key, const wxString& DefValue=wxT("")) const + { + char buffer[INI_BUFFERSIZE]; + ini_gets(Section.utf8_str(), Key.utf8_str(), DefValue.utf8_str(), buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getsection(int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getkey(const wxString& Section, int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getkey(Section.utf8_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + +#if defined INI_REAL + INI_REAL getf(const wxString& Section, wxString& Key, INI_REAL DefValue=0) const + { return ini_getf(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); } +#endif + +#if ! defined INI_READONLY + bool put(const wxString& Section, const wxString& Key, long Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, int Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, bool Value) const + { return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, const wxString& Value) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), Value.utf8_str(), iniFilename.utf8_str()) != 0; } + + bool put(const wxString& Section, const wxString& Key, const char* Value) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } + +#if defined INI_REAL + bool put(const wxString& Section, const wxString& Key, INI_REAL Value) const + { return ini_putf(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; } +#endif + + bool del(const wxString& Section, const wxString& Key) const + { return ini_puts(Section.utf8_str(), Key.utf8_str(), 0, iniFilename.utf8_str()) != 0; } + + bool del(const wxString& Section) const + { return ini_puts(Section.utf8_str(), 0, 0, iniFilename.utf8_str()) != 0; } +#endif + +private: + wxString iniFilename; +}; + +#endif /* WXMININI_H */ diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/minIni/doc/minIni.pdf b/Source/rewrite-hoc-clk/sysmodule/lib/minIni/doc/minIni.pdf new file mode 100644 index 0000000000000000000000000000000000000000..603d13eaa0294d7dc3f125060667e842d147349f GIT binary patch literal 153827 zcma&MQ+F=Vx~!WK+Z?fN+qRvI*tTukwr$(V7u&Y2Gf!*hV(qi`#k}i(;H`RkRgo%) zh|x0Au|ks`-Ce&yGqDgb5ZD=7Li6y@i&f;oSsOT;h?p4J8Jp0{nAn;*n-egy zv2pP6K|47+ni$wXyKh`;$=VUK!}h$YPdi}1Sdl8_iS7vp*aqyh&eWU@X52W$P0G2RTEH< z+&CB17BJ@(CZV0l$(0{B!V+g^R+4b@EWOmQfDQOP&zwgQWc2jTLhVft@*5&5-NJTa z!^BQ@iIV^Ia`tzG;gT3Q$eN3y?f<)P@nB}YLM1QwmI4mt;;E3Ozwu1@>S~g?M*QiW zV4GEJI&VwG{t%f`N;Ceb3qczV0|T#Vd!V+*m@klCVWTPhVCYH|jmkifT*?(Q%|=F; z>kz%@xk_VzsS9C{vPL3WkzqwkN6TEntR`&gbJ|od5if84L$;!{*U3w7&eVY*l;&ap zudI#ZU)iL})X6mj{$i498`H<#R%w$R6H%G`;K?ERTu|jYo)(Hd4EgvP2m1T0l*ohUs=~8Rm9i9%#W4a zy8x8v*GJ{DpQEu-{H7!X)!$hiN={J@({wa_z%o)MiT!B4yFw7QG>GS#jIBuN{CyVP zd}2kB1GaZTL zP6^%)5vxKVP1;Edh8wFFN1aI4%=b^eiwk;ChojPY^w|g6EuhCS`AA{|%b&9S^9(SV zNG|MsmOwUPK{2-`S$p2}e@s^krUr&1M_fIs^0-d%foguC+661})}HTAe1}}DSNtN% zr{7_z=|m3y>Unkh#49idXWo3)P#{)MvB2;KQ%Q9?WZ&)G#=5Lr7}M* zSGr$B_Fdm^hX`1lEiyMYs>|bn4YNMq<7cRd2vx=2d*E3JD9!KrT{t~C)tu?Yqwp8+{#2Uto#@9_yA72*TqKTn1V@6+wzS|S zbHm@i9jf8#V@KmvA1vnS=Q)3(S|sEaI&;qJjJ2fqm)AC_#U*T;Zi7XF1#;M+$iqmo z=MO^GhwZsd#2qUlXo(!VPyjslo=X*&RW`-beMh9;+}1E;0z%p!C`p?j%C>-h`IwXsrxr`ZRjV))$+Hglb}*r4%0wsss1qJU;yJ5? z5IuT<)-6QcOY{0TcU$ZcZc2MLw$9PWe32D`N1RQ7UL-@F;qA7)YfqzE*@ywDL(;!S z7p}t7(ojptzQLPKCb#ZSJjw_5Z`rq-lrA2Ly!=CsKN?RZpVH#XKKUpoY#wN<;$%;b z49JRj$=Ac#tUqrvyK@E&@z5r=#{Zuk{pa=n8WkiovZyu>t z$U@NICkQF~?UDW|7sKq)Ng7;^=th41k{#h3DZJcxb@+z}Y7~Y#c{@42I|Ym^fxxD{~_gP=24m$eKl*Ol>twv8hU#B%WIoMr3Y(AGAFQ!x&{Lx?+Aq{Ix zr8pwgCn){!*O(jWbkr^B<@PQ6-o5lA%9VB1Yuj?~I0h|XGF9u?%6Y6%^5lytsGX!- z`gO;!jc7k@)R+6yc&6>K+P1J$$Y&8k}vWbJlkexy?ksHcH-nz-?}z#5=(hrG}bo`3~XXOl^xHak}sJU|Q5W z50#KS3*PbI=P~^8?Nis?T4qKe75YJ#;=E~s(2Ln13Q)-Cz5IF>xj1!kzA>;`yG(C- zNo>2tfwRQA?~LGE7aK~mj%yX-svs^TbvVZv=jM14n<(0suA~!9IPWUg8SiE1q-=?L zEHFworGdLO+G)@4+`B{N^P?3qurv{ttS^C&NCqq|0e{RBcv-Vex!vE;QS3@?G|(%t z^Qp$tStzfZM3g!(uV1u@Jzdwq-*pj}ZL}#@Q2A)2BtcXsEA$D(FK4E(dnLS3YH zxp`^Dmt<+|!bl{yZY$-Or2cA=U0Y^Un7-E2POCH}O#9XUorl;A042bOUltjJ(x264 zu3>!Vb1lk^EoyA|Do@r_gSpYt?0+o$khlc4rv6es)=U0}ZI;OF`f*G)95KGx+vK4GJ4Vw3W>ep`nCQ_Ww zW68W|T{_hzXO*Z}{G3FVgC~VFO;e0pC=WDHY>me#zSSuLB$~CCsM{BFmhv#wfnY5Pbo*H7aqbo+))O1S`_VZ442x;)$*rnj&+#4Fc4j{X!$RLDr3$zZ*7f~4q4yN+ ztpDG-#LW8N=n@k<6XXBXr5-I#C(;hM*>7sD<{N3Qc zN7|BP4=Qbo-;V`Qg|JECW@_O?G&Y<`$Xi8!Zkc#;=NrwcGPVlZImX}TCWdjII?ZLj?L_ZeQ+bTB|yQKD=KJ-wBn$Vn`9GU=fk$0wh(=XCx75(McgsWw~HTp}*^( ztd`4xk)maZsluq^GvPEOzU-Fy=Q_~}vE>(r-;XmA+PO5}f!mF>6fmxpU%kYlszs$` zLMYfdaK|-Xfr`+(DxK>2qA=l4BW|De;3WBVE>4*tTl|x&tF0C!o9zxZDn(!BiO;I& zSmr-%bks8`{&65yHD6U^cB4=z52=(1fO<(5)a(f5r7)O@KK7*5Ho{)otc+%bi0hZ* z*b6|GD@ z@DkOU>}>N@wt9;{Jn8ml_eI=+`Ah7g!vZVVDl2v%*jgkAGj!brj%hu!I>#r!r{nRK zq&tMc86N}Gh_7>9Wl``J(<7g

JVMxmu^UgE_c155sX?qzx>(B1riTE9X#@gOM{dqg&Q)|7vOY{PerRvD0Bc#SRUFa^zwGxCOXk_bl%SY<< z?s+PMXLHIjOd{iBE&V?Mv4ct744y@fcyS)7=MC8w zFlP+S^Ab9=7cnuuh^>Mr8H|TiX*^SKf0>Y!j6&(&+a4NC7wEeOXhJz_dwC%QSZc2~ z4L9YW^L#Ozy~-EtU29mXPpVqNX{Y_8rx^ybfjV#piDnF(yW+yx$(!i_+vJ0abXvW3|)6*B>1 zz&ft>ZOh~}JWoV&fFl%Hi{sv+?wX?1ht~Vd76@h_V4%O~6#H;ND|aVCMn0`t3ibXz zlpykUF>-ZWB?_0PgZ+LD2cZPKp*QOvMKj}9jPAKZ{=6fk3e2>**`#S;E9!oF+a1K? z19|c;{3QG%QK>i5n29vS9$}($szXOKC3?Xs2R=~!#h9zJ?VkXXx}viqG1(cqrJaH{OX*K_q zn5^u48=53W%7L;LmvFJBJIrRVqgd{Dl>066rw+owF#$k!r<5h5v8|nvN$mE7i8$z$ zwkG_!WP5gvWvF;JT84eHc$=h!>_|bdEtlR99v1=MA(6_dn=Nim$*EhWgnsUm_-^JB zBc=`;UMmCjcHLD1TeRPXm_p|{{6wT5o>mR}Jju#tz22$~tO@a<#|>oER%`|tLxqPk zQhjU@C)Hv=%_(3Tpa3Et!JN;EC1$|a#)h3>9d5_O%)QhLi)lu^5{iaMAURAXQ$kIL zG;rs^I%z#w=zeokLYmz33v<@4k}YV_QW zLR;y&^x{?`OV0{%G{twjkFyloKJ$gzu?rSKa)c)zjyp`FTQhu^HJGKvLMqfx2-V^& zxAU&rctj&PVx0k925^WkxEw~-e?EaD`~Sk=fLulvC@{^u06^p{P?MGeNifiGqPwCk zxUE$hgtXuh5a*V;tm*9}-z~%{p5am!0d!6l(S3TOF=j;UA1%uU7S`7!g^F%!0ftqS z`%;zF{yCKNT?VW@Xq5Pq?+Lgd4OctZ=Gr}#V~tR)r!UJu4o^NN{GHH%2!pM*#md?1 zIt0YrYq){)@i1I+-6gha+zEu5lM^{wkZtV6z^m@iApx|!HwQIbtAN?su8z}35XNk? z$AXyVGxvlFnTK-r#s7RE=Uiy^LfBZl5DHaL8_j|)N|9z9;C96#II^LfWK^HqMjb@$ z>I_$iAE=9orH#vBPvVuk4R{GmlEN*bgTRgro4WojE!0)Jn05*>@q9ims068oRisMyvtiyD*W!@vnUwRmbSOP< zyNv>1PaofZe=WjLrQ84!w3--sALrX^dIfbF#+;z+&89rZPRX&KNk$b=FcG5$T#r;0 zM;EY#a{>f+JKJ!*4CkNL=CL+A+fZ;x*?D1Jc$dU;(#2Gv5&*Lpd@qt5E_T9jH+pOMp9B}Q(p5yy} zfI=8_Rq-NmFE!E+DC~bR_)Cse(3Q23!OuD71k7E-OsEd|oqSg}RL=0KsgufiDu1sj zwC^*S%HGOTt55AN34^~838TERhtswxSqcD1DX=Uw#?3b2Uel=;@@aqDYbzT#{%!NIs>MbXr zUDiD>H0*cg^$)UN8?(^Sk@FoRfADOBLR;V>$@+L(VPgNF=V$D>;PdJeWV+X~A2O-1 z35C*c!9s5yw*CD|JQ3v3*eL*I0kh*@uf}|938|V{a1>~`t)@4cv_A2CZ~cki?XuND zC{5g1uFrFc4)ob@Ps87EcD51Gc6GdWs*Mq0@9Y=v^B8xf6&I%r?7hec)-*zeIlWY> z?n}_1KjM4Be#_}-$=%(JzH~mh08Irs)c?8TXH`j<_}`ez!th^VE-MS`|B1O1u_SFs zJ)hJM5RnJHKp}jX@vv`X9d@Azx}3V58W87&yBKNV|KcaAE7|r2{sB^Cm@uP{M+Uci zcbCW>`CGtxIIn|uUv^vj`t10>irYvM**8CZx-k1RX#+QR`o2G@N*wJlNK3jW5ob32 zc78L;KKZuY3uMVQ$S!rIiY9m4q7?V3UggP4dQN{EqKA3L-oN{1@$ zZlzsSQ0E8XPl)T<(L6&2H%C0x%}*57T{i;A0B!gtp?$52W%m^~6?-h6Fhn7Z8HsvF z-{@rn8N+FlR;aUEx8U*_q%Qn`WigV%%rf9(uw@Eo<4=4F6QNdRuEUu~fV=_kIN4wa zh{T+mg+1l6TZXt<UaLw0XvkGV_hrl8RZGqc@*TFX`nH!Unc>!d_y<73)4f4c6vqo)SD zxjtH!^9WgtEjo1blm(If!97dAV1cnp8q}4oAhtx7mFHY-KuQaCG%i363A=g zdBp`8td0nsq;1J_bE|R{7fil7TDy!ZKu=T%Mnc0P8Z$zPt!THg1?pIWc=3FD7U}is zND6MeitQMB80bMy2b{zW_-1nJ38UY~zno|!+5%iMZ?6G^zR)37$Twir4kM?MydX64 zONyo{ebra>{A!!)T;R#ofxw%aSOGi7TiD`1;o=fC{1Yy0_qw26&KNGD@d;)}^CQkf zGxnYY7Vm+wYRb8<7UP;Z(zp56p&(O7y(1Eek{eQX5TfE*)77|(Etp+UX=IUk=`t-@ zek}p6sYIX)4(R5%)Y@sc`k&_{4p7@QyS=y$cq}RI-<+P9BMp?hTt|sWEp#7_>^$&6Pv^vSA>0wY59NM#^Z|> zGELF4!6julX5?h$)03vjMT4sF$XrxiA8Bxl7%UuXlrxTf4)P~njHl?E?4{gqF-MJH zBP9@8L~6se@K(AgSCDzf6`d2O0h_QI_(Xx;<4UuV9;#I_gD$$>Y(DmMlqeLI#<6!9{< z$dKNOPVo>o_$$otcC2qDg_Rr1G{vzE7Li0(TM&#lbN-Pcr%r z25E3Ha?6f6!`B#PgIuCvaP~L&Hf<76EX1)=v>jg<2q7`q$oS4EWB-ipKzu0V3vt;q zN|rOKPih`8RU7A-VD(d3hW8taN#dmu)P|bQH5C){N^(s<77yJmfnfuqooOg;O?7L= z*?=l@K`TS=W=~W(x1c^(rv5pnXt4I((9v8jZV&uaHX*LBRF-8(;CM`D&&9JfU}A7% z!OGsvtg7&U0JjvWeAQA>V$uz>&P!=I5ll3z&Zxq&BEPjI z4x_4`$qFQWKZkD}LAKWz`08FXhaR0LoOp#*{$A3N1<6Tj<}=fL=_-8|Yv+@GDC~Qb zl((hxk3pf5jcE)D2Bx3B9*DjSPw3g#%n@?SQAw)Vs1>r5GdNOntY10z9zM+G!t;o_;-; zgc_A3pvp?wwtL!$uai@->gu(X{5txfCw(sB0AEy`Lz8mWc3v}xRal6^ldUg6o9$}4 znoiA%2jv?(^ncgf!9$Z=SeMb)gd}?=w7ut5ZwEtJ0Jouilbovb^P3SY2yuE&GIv+e zf8Lo^cb2p-g%R$-gm5KCx{Haw6~sf0B3Fz3oCy|E1zLV@Vb2|%;^8dvf*l%c1$O=3=p>I{|NH`zM;Wbrv`*9*mYv! z2Dd)qPS0A3B`I(6vzORZtXz6qIIWjE*I+pOqd5GevsT(Y4tjM6zovcc`u3#Ym4+Zt zuR)0z?zT&q@PFcIA_sc(tU1Up>)d6XIaU{OIr5u_E%ugFOAS+#Gi)cET2ANDNu|75 z{d2~Bk`Z7^OVhDpR;nk=n>xFCHB3dQIw6t>pihs88Vuv=SqoQ3tDjno2zbI0$$iz- ztB*rYA^UMh7%i%vdxApNy+qm38dj`X(c(T3iqhDCZ1WG)vzyk38r~<9i;IcP1y`o< z15e}K+g0nr2|(Q)-}KHBcej z9&3j%DC^;shWkfr=ZEQ+Xh?~|DLtnRC4$GzeQdG);X0T_pS-16q1dNl-{{(1G|DTY zggC>;V<2ybO7}eRR#_y}-!$nXnXYpw!~1*~7Pg8si_fLt!aMFp0>+G4+uR=sC(kjMY3 z?jmW$S(!Op+d|XVA^MN`zQEUibOS}jkUe$5yH;l)E{!Le*o|XOVLQD(8G_}Pfakd;yFblPBG8qCSoO{jCVfqtTFfO)QVRT z1!CyQ;9r%W2~+(yi(F90JH_GqmZ$_rz)8+B()7vs&Eo2tzYtHWz2HD>9>i>d`Z2uKDa}>9|JwUn3CxX5n<+t~lfK;mT;-Y8lA7gq2 zRb(MW@5J^`Q-y4)pXFn{X8oYer~GE#P8fC1S=;Lv_xHju&1>+dOkJ0(l{ zl6MLI{ZIB9Z%?C}^HahSxF@!)qo3uKw)2_3o!?Y|koct_Xc`+mrVn7ZZv+#C>=^ng zZ_3h>bz~z7*WvT^acIwQm%Y)B1E?Rr>)1L&7$D@ zWwBKDM8M-iY<4h#BCRjVp?w)d} zWH)nXg_tG%xqVojzHn}n8Y_fxSUsbaYHZcA=MNfaFgVm_ptPAQyNsp(xrPAdaz3<4 z?R*yfKyhB(AzA}U*ksgb&SX$rXRT9N^^Lp-smhnyYMtsVm0Ai%X%X*%Z^+agx*MB2 z_GcikVwExHeHN3LM4m4-k|fNA?B2|=lU0t}OqJ}k{LP93+V@&oTYy@?ke#TIf*oR~ zLPmj)cAi_e8BcR>E()hyqOp2*is8G2zDyUyo_m(FI6 z979N3qhpFnXEwDW$n4ey=NgKny2k;Po^Mh!ZzU+^xvlC4v(FxMOS79fhwiEE`=8|I zP6!JfbIf)TRm_DgS8?4+nITY3x`HjoWx}40Vzbh84eXF8$z2>{y04$E+n@T247Bw| z#Azo*JvA| zDBb{1R@))&RuXT}_zK}mwynrmHU+EDo1~ZIsJZH3xYig}=t=9luRtrZtQ8a&ij<%| z{~ADBs31?(92CY^F69tEFl zGZVf3xwN5|pYyCJ(}-;&=xu+Rn!b#a{4-}4M~EpYx29%k^*GqSBb}03wXvY% z%P!BfvY1yG%IG??I3Gj0Ub9ip)?1g@xlw>Rsp8azr)fr6%_t)@x65Eo)R1&oyZ8o2NF=3$!I*(V&fMUuoa{31LaHwOLvg zkK=UWB@Xv5p2Kouz16_gV5~s#MgL?Ze$1cg==}uz??UKJZB79gtJU!1j7L5;>8;Xnqb*pSFR`MZZVTB{sLDjSX3?9Z?>thlj1~Mv`ceK!|^+%ic zKh#I@=R(p=PYuQkvEHJT1%aX^&#VGs?Jyp^GUFN(qYwVuDjv+}8W{->c19hGbqr@O zmQ6AZ{s~h>ESyd69is!2S9qjzBijRH6Tx3nOt&GCMrAUrsSGH=u<1+(gT;gVP4)xWs0s6XuW_9MGp>lDtpN_wH$23VMbn3D|A;Se1>=cHf}sc7^vmyfV$ zf@<(~NU?C3Sp|11^XIzK)YCDf7)w(>R*2w)ANqpqNveoUx!U0#C?IU03G1MG!Vt zQ^XLw>%OL5Fq$O!8w+1G{r@tEx>Bd{eF&)R5ToyuS@T~lzY;xy7bBt?)KO1rHD|;| zw~R-;OAc8d_Z1u`@2PUf*1`;5%qnSx&Xc2W?9q-IrT`ZLKh?s8b5^cYX2BOu+)85f z%E;~~hq*I?Q|O%aaUERylI*%6zeQ;s$GuPfeCdq+$lQlCU}>)_yF^sldHT#=#WXau z0rxvjx>t9};HH~qou`&nqf{^h)93mlE=uGQB$mIHIr*DcEpiGU8=kc#S<*LZ7qji! zeei3wodKrVRi4UptuH+52osN8d-P9QlBI%)>RZn^#i90DjM|%viNZ2oMFC|BXp4OR z3forK$NK0pz@_h0UGL5G%uG5h+;>a1h9F+`Knlb&4) zvIhe}lX4ASdO9mq9&2xzND}H`5MusLg<~)jJh73XjUmgsnXguUmM^$6m5RG8%I6!1 zlbB*C*@vvnlsNo24Ec0--5c$_mZ^V>9nDiNIn!P4iW5=`pX6q6(Lm!~%_iP7s`>tm4rF>N}ll`>PO zN-DfcQ=ilTJcZcMnpG9W7O6JKLKJs7f~%h(vJnW4a4OZ3FJY7Ff7Fv^(mK0$x|gyS zi!zTChap@xUVx>84o6qx7Cmq`Gs7S1Xj835%%)KM#-=+> zMs&Zml)(;}ZB{A1H96&t7=5NWi}VX$CS-?AaydVWat}IBQeG5BMl$7HAjT%aARk3m zb5Jgn&CaIm-=!zvz=pcr^!<~@G#m2r8KyBc~@4*|A2-V%M)KE z%2P7SpM?=K>k)k@Hfh!N%j%Wo>~l4NzhCaRN64k>bsjn-IV`<79}QEHp&H9vq>z}E z%}o4LrP~2_xlHo4!AV=_6%X~#W2B38pZ8bA5f0`ZiGkI-R{*j2Yf(Vo`U$IZo9^Rw z=?5anB>@XpTP3!^Fciw2znlgEf?gy@tqXhT(J<^jWaOrW;^m2dFoM}6#kcNTs|r~c z0`y$&G_@^L^jlR8F*emEPSfUFA`OAGTuxvFm2y@P8p{q5i?_=~R$p783=QP>w*C7j zFSTjwf3rR7e-{ZeF*5wmNcdQbH;%at=|79C)@Xxpvanr$dr=^60WR`YQo7R zi-%WXN7M2B8>zC_uZ?;GoZv;r3BuFQjDplW^ro{1vsB0^h`PbXpZ2d~m-iEHjmF6V zy1KSw=xJ62T6KzD?==Y zw1*ChYm~UsCOv)+99rU(gSNV`F$PIe@nl`iTV5akpRlwfkaD}#2jhz*T6y1W;5is+ z6;8EALiNP&_7Hjs>6&wt&p(qV@t-mpZpGD;fey?wSSs0#MruYKSEu1fr{M`L%u3?W zwCWlR{nboHbbx4y-Ma4?v+%OW(wUAsFG7Y~Ww(a6^Rm>gIuRoU-TNakc1qMNsL7Lu zT~tlPUmMb#C6nTnax$)(DzZa*5nALhI$kwPx|5H~i={F%XG?3eT1p}*@JzSv;ZXvH zDjP*2;1q=`I~}vFB7cCsd62;5AeJVkK>Q?SV|$9GWZoELZ4`-8Ut(fN;HF&LC~zfm z<3qV*EcCZR0Hl@R2^)))U_MF-5FmDarGXU`36Dz^B7>S-kDq);d5J2s z^J?yw3TTg-#5$F1(Y3WW8Y2KTDdjqMo`x!cV2I6xMSBDHWyzdvtig+;A!Z3^k_Y#3 zaW3w?^{?vc$oAPWd{G(((qb3V!>7icraYwF?2&cdXL*sG(B_w|T9O|`9H62r&K(|y z$S*2?-g|B=6YG^SuOMW~3`v?8YS6&2zm2zn1DPPByKDRzGT|c=rbauYAJ$NQx>xb< z;^aT#sWq#Q@ZH>2DBo*hw-7bg&t`V|nv`G1(u>?ca*(5TiP3bD)fi&)0mLBF7)5*X z5n^Nx!H3g7A7_&07~+{Mm>V@jQSSHMOfThCz=|#&fU~D{Fi;v(5B2Xu8c3N_T?RE~ zC}Ssdi$pkr7*Lrp_jCpF0Es?(ZDMPp73?<1(>1zH?` zxwF4>HgPO>-me>L7RHJeO9_;o1ZYUS-b5Og)?lfuzb=rLAGJET(YOl&0mFx%jY&N5 z-Q|M-Y^R)JiA*`kK^E%h(|_a=^~@G)Y=4zeE!V z1qp;NwL-qX1>K-}z~Qlh%u4xoN(@pPOV|So^q0#KB5FA(^6I(Q~Dzxl0vUd z*RCTJ{4#~8-qD3}=+jLUMmMnbNP$JjA&23Fc$5#(%W%BWbiwGy5M!$>*42bQ)&~ka zL{igDyrtiIh2~ABGNAP%B4LdgMKhpQuI0a<;i%V?HT-*!5s*PPe<=X(uTF~LcXuQB-K$8Tv z*RLY!{gl;tjOungTRSL&FBvMd+RN9ExRbzbxA$9i{Q_k3tDMCbMCYA*=U%Xm`-Vv( zm;Y$Wfq(5#gCR`tU!8m(2)rB6 z09RQe5abgL(BO$_y?5gSuBhxP%>Fr>)*gcgZtQ3qLvYsD*fnU~m8cCR|n#?!5#}12_8c z{i5^Iehv!2K4Ch)Xdxd?kVgUZf)Rb~ji$|{HuJ#q4q5jSy9g=6^XfB|PXtQowwjpy zIbx1HB39yX?6LvygH`+3{b5~Nn!Z;(~vs59@761U7zHG&!% zb9s~|MD9^Pm8%ySi%oWkIzf*WJS%UE9 zxZ@^gV$AcHOxo+5=OZ5rNV4O`*SPfq;PQkTi!k@(3vsNAhGwDqo&Nc8|IGF??BtrJ z;^`)7lKUMX3dLb2f{zI~{rT;6rK=T2c)PyCv!b@CUv56dzus?$NwI=GjVmzfz+uHC zD&iWa!RqWo=nL(<%?^AY?|RwrQJxX@NTxrQAj^Eqyw2T^v?y7$kd(G5%+Cdm5 zVq6PfwD3#lizFJ0F5fPE_iP(ueF-M?wsk9t=s+MOx;VTRu;VNO#>`L4#djOg`8zLr zNdU_gC6Udig^EF`(?{y}Z<9D;4|(KNU5LeMs%n#x zI9duRo33Z6+{G}}EJ^o#u6eBA~nOSNJNk*D*maYs=exjGSm=qaZ0wzmbD`~S@F=Z{tYOwMENc7M zOl%Cpb>0tKnW2Kw1i#c>7C{_fR%LN`e3B6<`_Mk6b*|oEH;JX3%X&;11S?cya}@}GkzwQ4Rar>vIeGb4Bz)PNiN9I3ed2}c4tjz{BS5wPjtE(jo z?Hc}(aXj9Ynx9-d43N8>Eo|USb8C!PY*NJ#t%@O!Z@W7pS@hxbd0TX8fnfoeY3Fm< z98TEWzC)SC-k!>OYu!|wDN}HZ*KMnBt4&V!oj^x-oBV=W-;RL&vkjG+S-YpGS;hTM zA>&p?v(vag5=>EzHRrsOdZnDSOQX$6O$1}QEuKc(g3gs4^(I1+6`c9va4piGiCP;+ zLlP*;@x*ny8O;XmwLM?xJVnBZDp>e=nRkBL_uFxVk>cn$@xd$?4Dbd%M0%sRUT{k0 zVVW>1DTzTsq4WTG?cgvvMTHjGmvGl`B3ZIJahvCZVji6ktXjGL8u$?JY(+H~Z{sDa zMK{>SIlYKh&J$xD=cpR^kS#qcVwf{jDfiZ8WZI%$MNArdm%Tx(%Aq0r6sfIWXx zwWsfVwM$`l?urvL_H_;5U{_F8D-KaZW#s5)Ik_SfiGcqAY^d7k40uLiYBpf#nlFlATCZ?RPYR(2Rx1~vZ|kd zmq-tXSFqYjNr0GECZO)ow#23!N=|8Dr~K~Z?Q<#-DFgx3XoH}N1#_L|o1Ny*m>JsR ziENj@ZlPyh!k>8ODL!5s7p-+Jakj?a-R-vHpkqQx3@UG$s2Xy9?hc481oA*czsDZ_WpW^yn{bI{P7ktS=qiGj`XY|0&wU>qto* z`o?tUM8UoQ<(I^TSGWyz1=)=?0HMbeCwFtPI0IpZtJaO{2W(>o6`H+FJvD96?NE9Y zY=bh3S@A{jR-a~9^v39#TP<(JMOGuIw0E>>ZXk!`y6fMljVXBEV&E!KX1#7-bjDG! zYrSZWX{E%rkEi+7OSHVw~YQ}<;R)v-Cx(5rH{&PMbU7bINFBnB+7J-~; z^7DB>WxW*i%|GiFml0JisW6ifa@M8fW=$KUiL4u`3L|EuPFdy3cXXRmJE-u)xo%7& z{kHPW_!k!VW&omL^Y2YTQqW5C))Ldh6^KJWue_|sOx9`b9%V7fb`0XVYH4%%(`R|~ z(Eg`pHvdRSNd2ge074=*3RewCnO8KPtBv#bWd(ne^%InS^a5FCt!+^7>z z!;i9>)FV_E(H+lGZsmq2+znSyIg8Lu-!dR~Kg~aTyV*uqN3_d%^gOSdvwuX#w9HED z$nkt++PEtv8B3n+9cZBA+O$d#ZHB)tgOTWT-(bB7%Au~?12HRx`;nq>P&;(TOG>&3+HS1Cn3=i`xFVFzC7`kWnw1B2ev->L+A}y0Rm$jMUW=^!jLE3HOh-NpmRri#Wu~3WYx|l`vw-xL(S0V{(DO6H9 z4}Xhg{b05$4%ekPPDZPUu!Y_C6G_oactGZ*$RC)UPSk1(y~fA+Fhiom=aPL*qqU`(k8$<0kxO#6R-u6*>AbsTuOZL;*F=pXhV)G}GdPP0>gfGx@H_i22d*MnZD5 z0p-+UKF^1SeJzQd+@ifVW07Ij??Do5w)0``ZL-{?0`W9DPr5}H1OC-JzUR-eiK>m9 zjps^xn;GDAGE;>Z!L?nW%&1k;{FgctRwFbp*PFo{Cdr8CND?KYxJEbZ=QGae$>*&8n#4D zbipE{bQd=IR{Op@9mtlEe${?$wZdeI-bMBKA#iGl;STcn3I7_14H>8S1buDyHB0Uu zuA4H8r66t;R?A?Tg{k9jL|l{T|5DMx-T(U!Drx9I)gdC4?S1HLt2RJaEK+C!pX$gc ztv?T4wfynvc|+VxxTa>!=Sk!u0!#SwUvrO+K2p`PPpZzyucoR7ryv0k&<^vAdIwZQHhOqhnhg+qP}nww)E* z$%^gVzW2K?`#a}h|A(rY19OZ5pEWuS(HJGpw((~&+1?WQ(taE@vdVoeUUXbch2^4s}l zf*G99z@2M9AzWMXX5kioocng{@Zk@M+gW?F(F?Aff47zi8l81Ek;01#u$!|l3S-ufC4tu+ z>uQ1<9gAyx`k2QxqY`grQTP)3r2>xO45=FE7f~n;!TzFq(AZFu zyRGSjH;O`XtmqQ#&oT6BT&je5n)R6`eMUxMd%NwX)7jxa`y-!D`GgaCA3B9PXD=tq zDF)`fM#1FX5OLUFx+7RlYgtCx=WK=bA+SZi8T$O=Nq6IVUMmTfk|Z{K&k+BmvvS1b%++3 zA6Q?1k2W`;9$E0Hbzm(SxvK&9$6`v-{4u;A79}#`%>r(sa!F_YT*WwHK!pfr{6gPq z2t|3nLV!|#f$Ji~+%k)w804fWCy?Ts(uks?AB>9vOsplwf%Gsum%xPjVMilA>yRv5 zus1?~i7*H-@l0Ap8e<8ev!Z^;@8Baha;~COD0P;3#f7iqnZe{1ieT;#(zBF)_~f^Uk-mS z-yhzs(Qvg!_)qF20taO7$3uSsM^%+>@}e7xsH+&ol4O~vAo86AXZ)6A(Mke4C^}4Z zQ1PLraaAlzz#b8QMc&n>|UCdwa^2M7;@wC-c>8n9E zmp(pgKkj?^6&qul+feCQ(~Og&f)Mc3Sk83DH|S~A0g$OsjoxdPcnM&>@>?Q&0#%se z;Dp=kh(19tYK+@H>&+ij_)Yo*{)6)g*`PC=VBkyE1moFx3Dl+g$ZOU$u?#G%2=vtT z)f10o4fQ$Lma=Wv*i_@%KRX9_bKahVfdkLB+U%Ibo_KzhDIuz%-A}Wj03De*ft-i6 zogzw$(9EMD4jd5?;VukSyHaeL)O$$RsMr2zf5y3hZSIKe1GY3i)_zASRS2M=bp2Bi zN#VfFR%jXR*>Rw`B9g*Wr7b(55{@o{<3!Z_J`Ev0I`*>>4D~c$l2@W7M|!^*lA#yN zfX>@WxAc-NQ(MA2lp;UFQqR{mf{D(1z4vT49b`?IQ{Ap!K3+FJW}18V5u}=GPKwD` znq&8?BK;NOh>sR|f>VEpb4Z8`CQ{7R-0oY;bjbzTvy7+lJA{}QCcv;s4bkG7UmZpoL!qK*MY1t{*mHb9UFkf!JlDizmlS*p8`9F zyd9##kYpSnT6_~`szoA?8QiZdf?gY-FVG~RNl@7k3G6q+&~?cUsaDpO5@EkC$2uVU zi_*P70pDyCJJ8oY%_Gij4c?}U?@q%O=fm@fBwr0EtM(T zID4XJ`2q8jg)nlFL9X@ zRcD3yORuR zx9HY1QE|FvZK_qq9^Q7)SG-M@LrdX0to=Zo4~Bkz9NfozxI4dLyZ3ykDKkk(|$RaIsXGf zYjM{awIlrVxaHD^2O_qA_O*%n6-l&?IZH+WnGdM6t($#)N#m0!a9>w*2m<^2R+vZ? zy?lzAK3mFVk9{rSJe<~HdM|rzeLovMkFI^BiQF5#+wImp3=T}2p>5yq9`)7#ukyQn z_x<9)NOg$xp8ma4>>Mw4pnA1~6LlQ_?e!|Ir}k_Th&F#R4U8bnh?SP2tS#cBzTD{n zA+{bFevuVLqIJw)3~hW}Jp0)3)~TYqub%eB)cm5-xpM3U4hZJ=%O`(_3KiE_FV*$( zmeN@5pTu-ht`~YWmU}xo>X5@!6iqkv$aaN*g2&iu2CBC6$`$7 zw)Nged(>_QvI(VBKh}(?*`gOWy!q$;Hq7_f*HF9@)zw@_6LI`3@E zbqXimF}dVxFs?5t6mSFTbg^LEI_KBT?M-NXlZssiimz}yi`&w6gO&3r9fv&%aNF#T zPx_4KST|Y?)jY(rH8_4pWjw^II2F@D6^~*1ZUo6$ymx!-iAov(o`9y-Fjy~-y&L?k z`h8Y|De}32Qjc1sMcWN!L5O2b57V}t<|tuH($Usf5V>s3CQQm)%D}0#9`%wWR#Es_ zQTPGjJ86_N7q)fi2E}UnJP1>@Lxe+>QJmI+{R!QLO<&{X;DrS~W;k9t-Ij_C+SH&+ zxwSRmvM5@ERKIU83QeMrN6V$>#6V!9a7!RR|7yQnB>tLh=}q$5S&}Z@Hq39Wgm>t@ zh}((!AW)6cN~nMq9bx3=1q5@n-K~ z8?Nh1%0L5va2N9kIy*T;?{076gTdFYjfxhxs7+i;k|gmxStW4p16qnbZmXT-jjFn7 z5334@?t5rWgb7*5Vq-{oK*86CUHO1~UBmi0Bej!a*%dk&ll=Xufq$;vHWv$~4a0rG ztaX#SB&TMX;*B1t;PzKRz)kqyxT2my9Hx(jc*@s0y{EgJ$376WN@J=J6rM>jMMbih z%S>|JLeh&*5mNC?4MUYd5CEe2RXLBr_HR4p@}oKEI;7)%y=d_}x_w}xyBc`Xk0jcN zGtLCo7%}M?Q<9|Ygr4C8a2DNxRJqx@iRdLTbN0?qH`8xzd&T~o1V)VN=~oQB2* zA}%Ppc{){XI?6KxxaMlQ@5{@D3@D+CdF}+V!lyPv%vm)GxXy0^oUve=%!73^2y%L2 zJaFx*IR+}Y3J;$S$(AXy)03Ph$kr;d(lIhNtoPKO0EExbd796TH~Gs($XL)XFY zkYb$R!n-TcE+fN-pbPT(pE_UD$VdY#meny&MM3_XuIkNo`bb?PMxN|gV?;f-+bVW!W(z3t@+N0B}nC^2Dq#0YBS1ar-+SgGGBqsvlQtS_T zIzy=8hJIi%dlwVgzL41&jWO+xfK3EelwWJTfpU4T4!Gx#sZPJ(sdGEcrTvJ#hCcd^ zadDilk$S(_D#)Io2K@=*i25pw%_t*-0pR*$9Y7-b>S7nBNVJjsY{a1A+mqE?G3@pF2TrWNS& z$K8BTg`=&#anraYYNW`rtHaeFrOcLKy1_E~na!iS-d+uailpaFcBhjH*hM7d$Kyt} zYGC#6kasA4fArSisf*E`f@-updk)4mKg*oKhp*bKxdzO)d(5VoM z0CS?iZ?8~dLX4|YcE!kh<@^AG#2>R-xq*3nRk$t)zzcSV*dA?D%=s@x-bd5{Y^o*- zK^isVtqwDo;Ud7y4)Vd~AL9@&I-t`e*TZW6^1*fg_LhvW5wwck2ks*t+d< zXQ;o%q~J7txgb)M2NbHwE+*)=GX4{3_;}g?`Z0ouAoUqsNNvx~#lb+Rsyi3#`Kol@ z6(8f2f^ei8qjbNYW;}*5?bA??D#2j84_Xl2ZjszQh1I$%x-XNX+ z!k(P7j?D4%(sE7$+6;fSvC0rY-NH-rLh}Zi;~2Cey5xSI>PSeUA4DcEWyZi;HL1lc z(+9Qu`Ow&0xq(Q)76glRrpT*kT9IW;bF-Xy7($+Ql5XO$k*Ut;7caQEdZZGBF&foV zP)6;m7!A4oKpi03AERj%_NbxoP>s1H(LYf(htK$IP z=ltV-cR+vWrdOAV(iEp%$r9Dsbd6@a^AE!PO}g+TTd0+yQPbRo9!hVn{w}svu*cpE zM`_-3bGngvw=<2p#vL`SlJ`~OBQMTQ!Ln7GsDG`Ar0%~0EWv~Uz=O0C30(e?W`8PY z+swh30E&is0R`eO?3wl(an2t37cFS~vN_RyR*haJh#B;FGP2t@UXoC;tp`mpMV}Dj zC;!2FJ&j)LD3tv&Ft8W-2EQ5O>-EJ*Ax)5=*AEb&8BACXC?H2hJaTFN#}xY9gBKK$ zgeU)Ycb{I;g!7>1hhD(~j;-M8S9RdU>XvG6GmyTkYwfNnv8AvlP4;8LLy%SuXFJ3(a8Xv>p=W#73SF*_trJyoKet!d#!rD-7|vcd-~?m_qEk`Tya z<tf{Ump!X%EL-d7X*JDE_P-Lc?+ZN~H;mj19D!QZEhgfV;tjq?Rot1$0pfE5bsZ;loZVG7atq|Ld-?)^`tQLfXJZt|2WM}#V7 z#r?Pyr>uZ@7{sLnhQ~@WvGD|+n3wvqMf#((#->+llfL$ti9hLMudjq(0oN^18@iHs za9ag`G}#uLyoqnp_Pm?@p8?LehxM9}wdJqLC9d=##97?zc)*Xw4Sl6`tuB?>wmHV8PEuSg5 z{w0@fza)+wSjlzgLuki^u{rhsc$#Zto@pYjAPf8Yve2?$kLfnqHKMpsqSH-i`h^Bc z5ckK~7Pv~G80YvBVG~?C;H;oi6{(rC%|-miDFY zaO15xjb2<&GZhw1y2r%NjZ3=7qvXqF30azh$BU{4{U1-xR!qmK6zkncKDjL3zi>P7yE#i<^zvB{O-;vDv-+r{>2KSnZ^A!WABSmGR z%p;H@&zdyZl_^Z$5!o7yqiIrU)6TE1S(P=oU~lq;!>Rb)VK2gl!QZDsfP^W zE7>e2m5;V_kPTp=t3|)IUqpYvUW}LD>TAZW?AfX1X>kt5RzMVDBTCwtIsep!RY%%} zg%1X37L-i<}Gyz*~Z4fTrAw}>rB6 z&%Xl7Ap29`KY3FybWb;pp?SIBnowNvzHSSa)>~RP ze3|EXr2~m5W8Qpl@(VF zo`zS^k4f%w+*1YG4os#{meo1>Vp_nah7w|0-drNug}+z7w8|=mLd;+7!;B9<1B_;W zxt$WP)PRQ8hL(9nE;jRxcgQb^uQff())GY=ta8&ut!7x+NNh1`;}asDW|s?csb0rD zNQt>R4+!>}9u=9rw@|{;TefM5#Xi|AcA$&th=@|SQ7VTBaUM>&)c8DnrEW$4%x-z~ z8H9~ey2#k6^h-3a5#G_C|6;27{t%EH0r>Y}qBow}vm!&{nrJyBVH#8dIyUlSHny~t@wiu4{D-Z&n& zyPjfVFk+ail!pJaq9fuJYXioFkAcmD{n$MZdjO+lT;b~qCxjB(wsQ3p(&*qSsz}1J7;cLTHe`t0f)~R2}X9nzOSw$`JQ_0*V z=ZA~svm0+b6V%F<$$aA*8coc$(;Y!lf|p=xW&>OsseEK=q`yflJV>K2gdG5w`Zzr;}z>=WGQS8b(_z z(5aRiabe8Xkg9ME#^-6OWZ#_PbKrfdS4*Hg&;h1h%r)t9o)NWvDHdRgn4xD?f-1|L zE6@QaX*gfG>hO(GU{mf*STmf2+PrsLKB4?I*+T_uD$6=q$ z7E4DLS}E?lK6VxqYoQL%9$v_zzTCt(60D$p@dvw>6wC6|@BH!i;*&24f%VbFcCX?% z>l7L!Hl?u1qM-lsrPxnuZ@4*o<0W`QW7CF4t`bM93xD~~o?8&M8NyTiorjqJ&QJD7 zQ*M!?Kh6~Qs|W(ZkhzOwu}*qr7x8>EycKiz7Aly_6tZ;EsC)`nKBW_17W>BS)A%CE zsO)ymJakmrW0o=&i7^9gv0GF_ZAt_#!5HX@3r^7z1wF7~lb4>JHb9H_5hEV``V5Gx z&~7!)O8dWp-g%sBm01WarZsxWGdqVBO-`Ks%WrC%KHeFsx*n;u%{4qN6-9w7Cr(hK z%cwgr`O1ZvhT_qtPeQf z>OEj!b}cq6FY*lKv6pSgf1a3}u74+}di1zrgQmgZZ-vBBM0#1;Mb}Z(K;^%Uz+7bU z0r$qS1((OMv%Uv`U+*$;1$W6Yhr!;OTIPpHAcShWmAms~~pr8T!}< zThi2#n;hRkUi7`3zmc@7M{!?%%foNR@SUDCl0i-4$Rg`ji8D$nM+76EQOqCe%qg5@ z&f8IGxum}-E7}Z2UB?rBJ_f%*WIFj+|0nk4VEnIkGuWBg{}cOi#?t)1+Sv1-O;v#% ze*NH(%aB>syy<&?O16Qru7NHMzB^nJ;anA+m%9cG|B4a9i74^#DRF!8&*RR-arN-k zwD)4}TMy{*M{~c=V<-xkM@K&uMxY^}%=a#DZ!h)#H6u_LPxo?t`jqIClV+XOO8N5d z*og(ROuRij42y+)u=Zw(3-qy{JhPx(J&cqx?%KBu+~$AVZN2yMf90$KqPBK}d zS~6U7zw~Xbwy`()nFd}~_p|Rl{n#D=>m4lv zbNp2X6L@ebXycWfl(QTrj`X2e6sbnR3K@b(oWE)+@@?}wB zJ@r(BDX!i5X<*3l#gR0{v(pDt8MA{}EE@Ij0=%6)I3ZnpB+N;1b}4TtsyMFIMR*#i zQVKwsiS-f1EBt{+-or_*K`hXnr8kiH06M!EiI#Hd@F(X8>m9&)c9vxyDw@xLWZb*J z_D8~;u#cY5*2RUmU1E5eR~v*`j!mRFhO9`!xe$PTb%jw%G()xP*dn;z>k9=}q#2eHpdmG+VPLy?}VyL0GETA(uvW zqdEcuS{XKI={IyHoB1{@TBojO#x6S6=Ds>+;=5HV7Kz7&p&Z-_s}4v28wTU#CR$u^ zU2)Xg=+1iY_iOQpo|t@Q()oZxBtaG77rfNtxFtckPN5e`!e~fJp!k78)U zT3lUEJ0%zq(|uFWfx@fQQ=bV^Jp)NwnYdgzup?qlO5@u|ckr320An(|ZKJ>oO?S{@ zpeLhU*nOi=09%|6W&RYX9Uz*$Z6-eUXWaT;U6=xeYaxfx{lw46+{+2^qif7f-gyY@ zpq-;!HcqfMIDyx*g;=DCKJa7XL5*4eo`!cxN0KzqSaR1(U97MWV(dHpo|mJ-qRu&_ ztW+U5l7H1iXUTwB$ZeyR=2gV!bo4L0oXFXDAuE@C z;bV-5Vg*QAg`i_9&Sn9c(Bm@F>kM#f;xt@=Pk4C(Q$F zfDIH{l}<4^=HKqcy5dPo5|>Pz4_lIP`z`^vtLDTLSLB-{#2sZSatn<^mrrOVTP+)Ppwf&p0#El6sQi)|rf z=V|iG=V@%9XG%73$Ni8R{B9uh?0qoktpJ1SX<_Ae0!+K`)$SmYRG(jm9Vo-HI8KD4 zq&agN1XeQR>|G>Q1K5HX%tQV2&yJ??Xf6H$FOF9Aw_%^?An z*$)(!79L9gN~8kLWqKZ?UCSo%K zBkZ)u1pwHGTPGTDXGzKqHGIZoc#uYkEuYdj@$8tLl-RG`#BU50aJ@-RP)k)-%AA35 z8tn!v)ip=eXdSMKhDYNRqXNq!+fq1w;Q9J|l{cGE|bj%c-TO&4HKSScW zF_t80Et06^b90nN$xfqt;o+G^+n@w8s>035|&+SbuIu`&UA&VX&_; ze0Kh@bFr2@xkGJ-8(T^^sp`l8TJ%;~YVZ*W=&Juc{;>*IR=DxCtcn8G!sBN@M}Oo6 zKYvW-u*CN`K zd4-qI0jq_}usII@k~Kh4`&$SM!SRy)xP5(S)M1n2wlK5*Jef|AURw4+xKmrMudUdF zyMS9a=6IcQ*o|iRhj#{r?hB(&_D5Rxr{RHRyJYEI#hN)KwL=Wvi|<^kB*A7^$cs|I zHAk9ocY10hcB<$Qn+*0!7Ch>xjMg0Y<=z-oe zPa4aBp+^quD)MJ;W4ujNCcDzl;ox(Q(-TRQdW=|EdK~q>R=n2g-YM{;Kr)WVHB;rK z1m3_Pa>NRKTGr{Fg(|o)!KjDjJ6mb@j#Eg2VDWOalWVG*J5ir#DzONL8>VcsXZo&H z5epW|*%|M0YPkq$0hG);*D}$vS|ZM(ALY3^YhgrPUGnSK)C-ABT{&`54(@f9Vk`-wIa6YKzVCfnUJy;iC_rsGhZPy-dbW{%Q( zo88ifr09RemXQ^=T@dS@j*3i!wFV)$6!&VsYxb(U`ssvVdBk1VQu&a4J6KXSN;@Yrzn+zBd2yHI~HVUd{X35XISsfG@ z$!O#&!rBLt;0Ok}=OIQZ$gNoh5v5=rg@haL7=C4HpM0$%9ieok`K~;8ql&fJjU&Ug zgDN{&(z#=H9;ilzCBp=>h*b#zm5m_9~@5poa?pJG2~>eM2#QRaiRaHP&! zZ*RJ`SmuklKXyYP#F>dfwSRkH3LnR+Qi73uf_yo;QhWD6_m+p>Z}DQB>;+FwkL_^o zGIO$XE@5v6E*wZ=+3D?V-u^i#2(d-{uOweaMyCJB(lx73){wBmZ{4H5`op^O7pc+5frM`f zT>pyf0q8awpbduXjLKb*Jf4n1&jD0bmHx%(IAgTEP_-yt7KyO-qEiK@^oM?;-+s!A z8K(!Lri#k2L7N#r>^r`H7A3jf*v%%YNoA|ZJbjEFp)0`XLJWC>4662T&XV;lk`{k> zlM3h zD|CzJbaJnJq?cX~mCu#Xmffm+`dlsJjWC^|6wBW%{X$yF)5=C?CJ03erjUXCY*TU$ zTPl)2oEER$F=fAvnLNzo8?v8v2*T;ym5vEawgp*F-u%I8Rq0t4VLC5E3*tq*IQ*F& z(|IMGh8waClGZ6&iwEUDL{S5CHCXy)kF0CFAAPzSI zkU$F4y03{o(OCEQ&tB(fQMvViYb$uoq5>~8hT^Ju+@T<7;wJSzJMI12)cbzZ$=uc0 ztcr7qLnzlqYtv%hc%$+&mHeQMl0u$fOIK>59}sxbIeg6BXTP;@6G_C3#SnNX%5|gw zHT;$9wh|u>seLrzxHmi%XF++8qo#fz$76^-I3N^c?qqBX+{?J=&_c}Q+4jH>9F|DH zuj5Y~dZYWp_WtD3#M5{Lxi;n){HG=n{yxMW{zy!;1J#{%Zcfol`t0|5#_xP-#>!va z&a05!&ZmBS>uXJu{i<8dTK2OWq$N5w9Bm^X?D_5i8&j5y?KG-?qfM!+q4r3W!D*n% znQLR>WsnPRe~j`n*4hKJmMm<{;j}FQ)ybJqb>8r@pU4@W9~&PG{sQq2 zHon1}-5Cy__N2O$1wKC;#yF!P^)pXX~T zGhLqC4Uh9j>WgOt!TPq&i182F)&#Rq&JVYCv2p9ak5eGLvx1Swv%fN%YQsm^;zQ^R zRsv0KnOCBAEi~woDbW5l!@XW}LVh-^bg3F&C0u>kMeq zZ>bm4LIiJTs)u`PE*4)o=*C)gz?QGrH{{G3!3XP6K&xzJG*yrW_>Zp>t2Mb}H~;^KQw(Zkp?14W``XF!`W9Y z5pe2X@9BhQW%)(kSx6Xnc;$qg*RLTNJ@T$hFSBwmB_IYdi+46}pYCrTq;e*7^Rlz4 z<6`{WX*^SuGP8ER{Fn#mMghe;_x!WIa=twBJEd#MJfCUSQcevbx&se34Qo^3;@@(< zvii>(aKjy*eOqTgy!39uIZ|iI&3^YZ3;3FRz)5nE&v6Pxv_9#qg>rm-K%hV&nuc1I zm6l%2lq$hyAD$t>Q17@lw84YYM!Lp8pwBWg9VpE7|29fb?*|h$BnyY377DV5lrA7D zroT)*_B%0VZPZ4!K2hMYd^y}lInGk-oJpKM&!cS3^bfQ5zZP-HqscoYUr&s7dLSBI z_!}(K<)IQN|3Tv!PM~f?k`d3EeB7K~-Azte3k;50qzqjQ988CEsz3crn4{aL?{wA_ zia381=9OW9`}}*Hv%|PH`zJm1ZP77{ZG@9#PSSByF0+iex3t##<6`N~Qw-{yahYC; z0iFn!qqql+)M52t>y>De+ELM`9OBdk?fU3`jU~F{#W}CuS;C>WAbw={t6TkqdKw)| zN5y1c)}b7PaWS73?dqWvMQUt5L{w&WYFK;8Q#O}QhSOE7TzJUo@QHRx z!>bZw2uB7bc&{!i+o(88R~0MPncTYYHpFy7Yjb3soBDYyZBQu5nRk|2UAG#f47l2gLftP;rXtUQCFa=*;Jq@j& z^D0BNnV^76ij;X_jZBKvmMj8diTvXhdygr~U=~N8W32rRhP=q8w~1j+iayzqpi8^%t7#W~;Ye{WrHy?*baC02*~eN* zvMdaZ4sA2M^1xDDBTsfs#ECz8&>+MEv~__I@|ZO@Wr=@L01CB@b|c#)#IfKt?-~QhKCrSjwMb zcR$1@!61;xtADatat2S32ft4nrBGVg$B~zSWUA^vm5Z?nsktcoN@;RjnTVJTu z9IHTHt176l-^O4Dz#eT?!$UL%=L+(dVKuUzdgC>#XwQ*9ZXV~XyqjaXU2FEbDT5(5 zxC!dRI5~a?u0p+}PJ=l8VGg@qDnk|&2~)qzS-|E^HsvQQMDj& zoOp@u(Sr7TrJ*RLS~e=5GDu$4p7CKdo+|tRpL9Hx{uGOs1csTY;^u|eaTv&~d_M*E zSvH78qCz>M5gYNHYS1JM^QQmO>ixwc4zMy3#@#l`?Xk#2xpHU*)RZ%pVP&Y|gUV4B z><}BvWz}j&poR`^ogS3T7FQEmh!>X=^ZX1nwI?q`x`}fx)(>;1_NLjt&?)z>jJv#O zv!GTjYeJm9dYarYY!SV`6sb48p7!{j-?nq%boMDM!tD-XTFac&G~n9Z(u)XuhN4O} zj%CBT>-%CklLb~dgJ+d7;5?eg-CG}Q;nAkni(}96C;RC&Vf6(l{rclynBfwT1Qj-H z1W}KCte>05$UEcV=7MmMd>YJSAdH{(>gF)11J^Ik4Mka}xkhwx zuT5R{$HM~PH;?y_OB1tkg`NHwd0`w-fh?tq{o`lVfeZ=!V)fvG&p}091KbbkZ7I#r z*0_d9i$Gihp1q)9zzLtjCA?@e-tT3&)9;g*Py1S4KXGp@mvqELt4>cMCRrR7(oPh>iE{J6vpj0q)AK-Y4ADv~Ow(Bh> zgD4k?cS*&sirRMDPw6NL6(>Dc^XshAfUB1j!xeQ^NO0kxoP?dz%r)WeMM|Tqu}lfn zNC9b3uO<2Q2^34KUrF~iq0Iu~<3p(0lm$0s)omlZe=5b=n*kMFoh`^mM_Ul1mk0a{ z-ggi?*+69s!aAMS=4SY3z$roct6cAuW(^5>NoRc4Ilve?!ZO)p@zYkDx{^;LN>WYW zRCM#&8VENNWOQqR^{*Yz-Lu^c3ws?S75(dpm)I~PK-MVf{S$9$I;{tdhrvS z9JY(iz}P^A<92?J6LR9uX7kBfCfvx&3d+lI@0D(BJWSp{+}C%2BzY%GA8bR9c|CJwb5HzvBKqBh)+-i0o#v?H3h2Z$`FH;??OHr$MV#>8PpE6 zW92CpTWqiRAW#yxMM89x<-xKTTP-G{f=;D6y0qbr3nzK4baF$k8@o-Z#kx7^DxL4lO&3eT0&!{MC^lDNx1>!&vD(&RRkySW_lB_}XxYP?FQL?P5 zSZt(=5;tuW*B}N_ZrpJnOuV6c+scee@0m&Ls(FO)K%8KHIxqM? zbfyY8Oc}*|d)A|Sw{c)F|90~f6 z)n9E)M4sBFEXe`2lP6m-KIZb#;rOju=i$!-lr6JRPt`VPpCA18FvVuk91n~;{5?5^ zno-eS-%+P4^Yg*FlL_bnPFZD}z=)f_13l)Wi(VMCvrv4y<6+OWf^6by8bDGqmR|qX za1>X=_onb3XBytTV+=^-BNaLQq+TfrF3&9;AOo`fA*mD)AnAc0d8@RUpye$Of+ZHS z#OvEUEiM7sQpTaX=zv_DkX2d9KseWi8D4$C6Tgp&B{==ir_$_A;cky7Vd2&wJfNXa zC$03(=3F)8D+ZoRnUGKf`^TCQ+~gwCOb)gVS^tCPSCm{)?Z2ByC26%IhJ!(~>$_i9 zA8zhQtb$GZrei=G7{!^t3M7MlS1E}`Wy8gu?5Z=hq9InWAQxTOC^O_eu;oYk`TSeEjH-~B(8n<7P8q5yOqIBYo1txL-!My<%K;}4i8BJwlJ^L-=D~FFJ&XD0V6ij-%yvG6{ zpL2g}TP+Vt!-l4l-0q#)H6KP_tt=Aju&ql#OgyaVk1_@PY2BG%fbzqGL7p~SCllnh z{iSD@Sl{McF)IVZs(9q+@&h6jy+VZ;eoxZ#-U=_++T+?%WAl#bE39O< ziYg%ZEQ$G{knxlp!fSYWR{bfIf)s93*|P3KYua`hbIM9J3zo{5V8JvqqHzVE5!9$n zRZN=}&pUX+ADyHF!pl%C^?Gdn{uA%06I zu>4o#nTg{+$aAxnWGoFUe9t}gIkXUJ1N}nTPYJ&vgfwjXw6o_7xN8vfFTVN%61n1v zj!eZb)yYi0fGxuHR%~)5BdxuhdSl;>uWMa4ZC#Xj)dOW zT@^j`%6xbnLR`>7qRG8Df2psUekbgYrSch(EuFoK50hrZJi&F-il;-cX+g=ya#@m< zBXYfk-#P$<7X%zCU3|(fpItyx*%R0r({+O^qB)p_(P?ji89>agFe}@|KYH0P6_rpu zdJz3rWPpj&hB4d@^^`)9U13L9$&x@R7Du}O*(dgVQAd#&5XLg+Soghw>VnK7FlJVw z5&vxbu^=ksck!HBL*Bf~M$Wm)S5rV{ zXPaplW4ZE~jz)t#uR7@L{wZ!Pj^r-MB;Z~wieyAZ2RQ@Bf!5%4+bNl^Dkl(-ndi|1 zbox^6V{eOZH2FV_y+e>F(3UKE%C>FWwr$(Ceabpz+qP}nwr%@Ycl^QYh&#BmJ>P@1 zGFJlEXz9H+P{mVHy=acviVnG&L7X;#T9O*~FR_ATeybzO9C2Q_oAn2V^^y68S1pTT zYWByKCYOXgLP3*U)(jI@?woV--9WI|J{n~(SD40neuvKmC>_7e!?wC{JBvrO6yLZz z)uIKg2TnRuP2Pe36#h*88cKH-Cp)0n!oUmpI)!C~_@fqFZVJBQAET1XP;b7U6+pl) zC;G(D_+auHwgn(|POWeU(rAxe^6qTEe=9eERKW_gC8n$C+Jt9ajgHNN)!Uu`(7LNs+3|Kfv}L&+jNA((pN+E@T&gm)lk6td z4_HFmqg|fS3;96lr{gYprvZ)Q$OjxW`@?;L!_3L%sbGtrJ)mXtrllg5X+<9ju`3_f zt;L8Z5PM3r>#ICyHqdT!!N;%SgTTY?r{chS^LugN0ykxBvsV?6mgT0_$NNGBrzV~svHVG1#d*M=hg?N9jf!i7bIAlF6JZ3t;pMrKI^QSu?*>ir+rt?gX zWz;j~_Ti;FQLvFls7^wrbr9)&Xkf~_R=IMh)PP(36aC;ZV0RC6J)ef6t%1#JnijgO z)vIb|IOpwF%;$1oD*ZU03zJ6kR*OLYCW)1#c|pbLM2T(BVR-5>Qj(TL)ZYP3xy^ z3oDNE4Tq)8jA^0A$o9Eg>FsQOjA%`9C1NZUT-L#LmDBor zTUwr4G2+~^j2y#ze-_X7OmpH)Es7!zFoYTmI{GTu!FHPPV# zk*?7s$}&vxja!%MNHi38dlX;rI5jtWEXz^>n@%f);q((6tzFn>3-2r1!B@ictz)*) zkW9R-xzOFR;$}|WA5+)PY4g})5{o%*l1H_x4ZaLN?k$yJLmtq~bjA8^(&bc(t`zi2LC&B`-&!$GO|^_PnC7p>B(kYrmcJ7`Jmg_NVAt=(hpOw*w-l z9SHRvt2+YQc-LH-E@w9(O6kDffXUK&A0I0^3)Xk!?@?bfr($c}4gUbQ8xJx)J2an^ zHw8W7R9WJ+P#!-|GW%yA#~*Iy9{13jd#~rZyRG)mWnxXzV3Blk(NpR=rYjX3-=<^- zQeN!YAbzJmCu>vNa!&n}5NOo;JPuN6tNS*qrx$o}at9JA9@lG z58rG|0abSb2Ccd1I*=`kv`br2M{zb^@ z(?0yW$OH193Ul0(&8+;&W1bQRa8j$uDRRycErUD z5_{-!%Bb1d?J(;-p4TEi^lqW_lLeE=RQ2gi2E}oML2BCg>!x%dxbF@hM=&1m2bM&! zU+I5Xn;8DPNOcCr|Fbq-{ZC>O3;hWdCvevK>sPEQ7Cr#tTAyi{zTI<35KMH?PKkBO z)qR1N=}k0ro*JTMYCMSD*GoHD;XOROvA>J%g5fY3*$Ec0=05IyLzMgvY5ol z)XVej%ik$)?*P}WkHc?wx?+Sc+t;6{83(}L2*80imo5_v;fHh-FQ-`!OR3N9#~Ls? z6&&`*Z;SYI>Gg5tdE=S)K@eUoY&^<0BnAu+_B&)m2LjC_@h&6OE`$x#*aR3~_uh2yZ6p3hksjCIfAXRkYNT;SPOKD4e1JFn zx<~o(dcR0j`qjov8(+la9s-6mS4W|FIVpdvhvZ*ezHm9hw`SgM)ZHkaAv5S1S8k0> zvbtuUHsa=}M8R!bi^**EnLN#0Drgov{b-cWrpYc`*ojb3gx%mMO}5110tNvyt3e13F8qS=H6ttF;0S zLCi`weh!_w-ymCxSRP>spzpk*2-NxPE6^3f#}b5$fVMLRxpqTOp;Tj^hO1?$!k+^8 zjIovkhg-N)5<)0~!Ij!;?_c6jhVyu>TIH^ClxF=m3HHo8%$M^c7-I6xa$&AP<^Gmp z*u1YXWaIGP$Za`GLN|9jxsW^?yhYfIRIC7?7-C_gcBWW!Z<=b9rMz^zQ#p)q^#JE~ zgbgPf>rUFy7!q|IjMDafO65uuS?`US=XX`A3Cn#Dg{1&%3qy;Y7`go4P1i7+nvB1h zq&A-F7o`C79`;Pb!rO&H;R05c_f^zQbz+KU*aeGkHu0SLj~%EbV&wm#xhEhvZyB^) zSbaAXZNg@k6dw-CiU7luPLkhAr5J}~wI>pebizL+Onb+LI9vwC^BX}OJ8p!|BcQIn z%hFb*ssf==!> zl~u`L!bu5hbMpynB(4$!U4w1_758}*8QYTq?#8(B(YT$`rLPoyIheaspDWK<*hr`W znyM|-7A~C?oE$;*+Nb+aO)pfCNs`Hr>M*A&lQqYGr}=Y%bCK|=q@P@#OR4L$oe!-_ z!<|dfd0Og7b9t54DrL8b)=3g( zq^d))^wN~#hP)v-2XV@R`9ra}K|CCyw}gGntF}Kq*cghT>gQ7%1vi!@&x^F8dC&~S ziu}1)lqAy=BAMc%1o|Ykkez>L4DeE$<{G6Gf%dTdx}5y zI3Le}@l73Q21ZX|u_+anMZ@!mz=s5RU`p&&3fqVOBx^DiD9-<39ILzMm}0w)!3qWu zt+|~XMs&g2PFWf&Ew0LCuBn7A!ydu=ODh?n`z*mKaD24L# z3@SYt%#Oj=d*uU=^#JOu=&H!s!5QkuwbZ)%{u+f2q|+df8Q;OO+KBa~+^?HIsqvXYW(i|%@9D;Kk!}(35%VVWt}4(0L+znps`3SN1jxy|W)38YleO2{n0(ZL4IQT%0$3*DDTu92o1F z>me0xs=CjdSWBly#v5V){DF6em{puoT=5TKea;#7Q1~aA8(|}a zj)=nQNc?l+46>UsUHJOgdfRAXDZp!8`B5Xmu!henjzz#Mu1ilhI1^Zd8`I>rQ$q+B z_m~^`2R$Y=9>7-!QlFe~m0Fj>to|AR)@cH*|vW~{dUUs zu#NLl#Tb%QMQ?J2O=g{O(2pljUy;k`#~$$OPN+TMx(bZ?;+n=HcLMv35Z2BydFw))eQUTCBImE@6(M z_y(0BOmgPPz!Uz25HXftX{GUin$WdF_Enu!Xv;PEB*EvTI@4@>yX#*1cDbp_%)_Dk z6?=46>*KZb{Pp}km}(byg*zwW%T$Y8(ytbuT6dc}5ZcV4xn%WONZV_14Y z8dK=puySKuCxka3dV08P%ia{T5GHbmj?$)kowCyL;#yrv102Lbh6Mj`5C+G=MEKtGU7Dh@J}k2nDB89pa5cnlITO@{S@A zJ$C#a=upYns{Yej@?WLkFmSN^pS5I2gToGo4gPUv^G0WiLGa_(r56SHRyBW1b*v}risQUMqQt$@W5IL!`W<*ps07bo zIuO|iKlPQa2nfOYBT`>Rj(Jacp`)-E29Jg!(pYMifFr-Xpqea22pmrWBhL%q(r|>a zVp@BB4a0L z5O0p5C1(er^kmUsX;*VQQRIoFhS|6rrP@Ki7o<^>e?++hNoN!+X`|wLab9&uN`#)8 z-fgep^6YfKzBUg@Y_!E>}=w zs3NPT+a-vsdft@6%-O}{A)D)r0tzYR%IoR7p?e!sM<_? z6^fZZd;DIyOkbpj3N*q9*J%iD{4{>(SP;UYhDUIPn%C%8-H(;GWf(*!@cPx^#%Dcu z6%;ig$?Ue(vFNaDX_XBkaP?A>@bDkXe7q{m}VLzOhv>cBf~C_vw_1GA~0sG zEEF>7T4UU9W%0jIyzV?ba2p)G;^afc1L@dwIIUgmEv2jIid^|x9^L^v^X5{ct4yzK zTbahTimiaUoGs~q2r7WxN|#C+g3q3e_=!E3qZqkrp!h4e1>&X4yvj(5>rsZ+V9CTq zLN~0rMaNvi$11HmT~Ya=`DT{Jb(tlMXVL$o%aJ{lx#}UyPb5Iu0YPHSgqr1&f(d?wAHk`q?Hd{)=Ls#(6M5npDkVq~HXyFfX1KH1!M| zC7kAF!$M!YWo*3!0?Yki9+=TgJfAKToyaHERhmEpHwzA4csI%7>A zAPR&qcZ)jWi@IcsY9|AC3)ONUDgm_jiY;?n{)gt8I2K=1=z#rzMVS=y;r^YPbbe0HF981DGK9b zz~ht#HY9NRAx(lK&)mRLNPXt*;47hw@(-5IY)dJX5BC-avG7iRWHW0~x2^4Y-@5yB zNnCVCp;hMvG`U$)=L);ZHY6xBuw$RZF-r6hASEECYL#%)AT24X{#vav%=gyas8BuJ zX^R)S5lE>!B_@hCceBU$mA0o&xifrM91LypKpcH2xN>{9lW1u z`2w#9K;Vryx~B`eF}FyJ^73%lKx~>4A3ib{Qi<}_&&4Wz4DYThqEgQ%X-a@C&MRt% ztKztJH3bOHS7D#P{$3=Qa?2T#N=8ZXvt(JqjbfTr^`eB3v4_|(<;#{qx|T67q4m?} z#gMjiER;e;$VQjd7@hgcUA=qktAimt|Ek#>LSUHOIN!w8rm@57WP!x0JI7|7pS-G^-y!jp)i@P9spIqZqg^GL^SIN#Zq0o!V=KB;^655z zxasMyc#Xy?X27MLyE>&mH;z>d#7cpUac!SRX=>ML*!OffZPXb$<>M>AC5+A)fLC+d z;xZcXc<@(KsptmC%$<@Au?}wx3Yhls2p)Mh!=r(1<)q5`8XBjA9P?&tcvw2(Q|O-j zq0o8*(lP($;dUKy_9^Os=RR1b5GSX;;8R5-OKaBw(6#%bMF%)L)x$Aie_&fi(npGo z!WVaAkX-6GAGramQ+j*d+CdS9 z)A$d=p7FnPCeqXYpUAr^jE-n*w#peb>9k&Sk>AwlRziM6{H%nF0OS@gUADI0VgHdw zESb-oo~84l|IbcL=uRf0Fqtk=o&6IjG9fZ9WbNp_`SZT>^D{#m5K5J1X2nFsbuONx zfxGA1XJ6agy?=p{{e8!A8%7-o(fxlz=t?CEGg)E*vm6_p^mczX-0$=h0ruUKD@^20+-#C-g^L zpYRHRj*U!yMe0N3?W9#9mCkkv&Nb;d#uCMbZ1CARt%I)6Ii$o1;e6oTL){Y3n4m*OZxM0?CP>UQ(%L00DEBAN@f zRO)}0Y5N(gAPmObz=KGAYa!`UXYc49>T}_Q`$nA^3H%mO8s|%7f5SQk@ z=kVfx)(W?!S5U1_VGw-=kJaflUyxOC0Ha{#RM0poe_>gHJsPu!CgKjP(L{;+5=isBaI_*Uc=Taxep@BbzxE)KiweVhgv_1<9n1&* z{S!fV@4l}=q|(F?_v}BOkdAfEzkm)j;up|KzzK@n3qHb?P)RSBn*pIpQ&#-E^hS=9RG5lfC;Y!JE(a<$CZY z@8{!|G7uP`zD4w>_LkhOh_*RTfqLr#2{>fKe_e}*?ns@auF{feXm~xnorXo!<_fhd zL$y1PYAO>O^IURt<0%U7C({WeHo0N9{AXTqz6dI9U^$Bwo#A*h9mwmL3kE%cCI3 z{oxa^*|RatTlpEUhJkgCG-bm(8Kb7(pPG)WiL6wDPcYkqin&h9lxD1ibPD#f7qdxX z1|5fAw)4wH(yxu)|4@b2y}KCdH62meVQ}6hs#<|$oF`&J%VMJ=IY>(8T_uw#;$%v# zbM~{=xI5yQbjphCE4eNw=S1v>^>B-(-KZBU3yo(mFtFDpRNgE!q6=!I2=9y6#5$|D zM@!?paQbB%o3c+xw2%|NKNPB?RH{BskF4)2Re1@Ptf+wEJed3SB36-7j7db-Z8(68 z1j7I+t$y^oBH*msq$x+2kxKqLH+b5|0Ck*Y6<=vSZiG#*{0n|SypcIxnhbPd0JJVw zA)^D3oPdJpo`*Bjm|0#QUfv&iX1H*e;NvDU4g~&!UYtYQ9+2}}k^1#WV=LuH;9}sjZ@*4(&ayFy4)`|u4voL1VCH-vIshBxnKqr*LV1y8MDg!m* zQ%3=vJv2YSktk<&xybz1`dWBQVil_XcF6kp{dLLOYSe2c6_-9N0x$oqbYYSRHNI(c z!Buxirex_`OuXFqrPK)dDN!q#Zb(Y`CV22K|v-Yg;^I1+vLjFMn5gY>AyD}QCCS(Lr8 zc=B;u>UN^ax8!t}1Q9Y-Y4eZ5Wc_(pu_gqLIr@2*U7vS@3(nhip8_Z_0Ny3mJMFNn z47S7Nm0FRkN7mhaZ6O<2#jT!e?NYc5n4%QjisP|tJeBT8tkhNv9({2AMekNVeIKLU zjZ3`Uf#H67LElcszW)RuMdf&gLL}j-vn5{=uj5YsoAGFL1-r4DCllV(jvbt=Dh@D4 zGPL?QG`4oe47Rdp z!()w^m((lKNL`ZErsOtV#aYB`E7+B))H5x)Zq?D9)Aqk?&5B;YS{g76Z(LC&GqN@` zHrY0(33-Id@V2H+$$d#pEVtPhN_Q86db%3cogZl6PfC6UUDLx=$?u`ExQahp*`DwR zj2X|j+qj-@yxpt`zHfTCmS1*oq}b;ZapwE8$~*UR6`RN2jwrP%CCl3PX<~vO&KC)a z(y`UTe)NrHY8B0c(-zgti zf3pmEA)TBZO$=-x-Pf=9;3Tmvk}3%Pu1QvT`%|V0)5qtH0nT+id&SHg!g;Q*8nf&ge~6p z8_65+_V(7{sKw!k<3Tn2XFx& zMJcN`s|XYXd?teOA@C#&y$?TQQVQJDoD#Q9Pooa&FjnU!bd8^|l zdD*57;!@dSTBjqeX5eo^oJkwQRNN9k#7>>hW`jvvo|ZRsnKZg|eo-7^-09HaWEW`H z>@m6ICcapGx=_gIGrkCSOD*UnQA=}By{FlFe52j~BQA9go^0&)7d1I~#RN`Y&GBbm z#zQ9}g|cgc0*`}yH%rUCjEB~PjDQ34&c9B}ZVED91Tt`^Ly`ezm*QMpQ84rfxIH#% z4Uy}~YLb``?w~ZN@$CCFaISWd2oOd~j=%yqGDFp&rShsKCZQ^s1&yYNxT~p~Rz)1X z5qg3BTf#d^`P%cR=zg!neDnrX(=(-BH8EkGz;j974{I<%T^E9r*lqaX1=;DE_LPc^qGHKjH+l`r@Cfl_?mP4zUmbE zatfCpa+}vb(Z`}F4z~qfjG)!>coLpNYw?-0K+cqMNh@P9^i9CyHya3 zwZx;aY?di+1Tomtc~3;YA2o%$iDc8Ws;DYh}f@RILf{`}dYI7oq|U|Jwt|S;W6;knd@ zY@#14LiK!AtqVZ=p4k~*e8&o~mhf0%E`J?iq7YMBuKM+5tolsMNkv!aw(oCr+%+{q zTv-$ZadRVbun(>;$1oZgT^CDZTet1fK32`-l=G;*d;CP7+^|r6EgIY+tBr_8`Nk4W zKgN(;RBCOvCZ&~Jp6ztY$n6xD{6wFX`DA2Q3(3$Qy)_{zzvN`+uKx@HFZnD%lylMx zsMsMbwm_Vxq~{tA)H4cH5rAkd=cyOVkW=`O_@qdFW_n22A}+)oC#r{@z24-Uylt$J zHpTJcO0m(hWNMualQ*pES9)}-aKCOKoiP79BJ8(OxBc2)Y!mmeI`3RczWj&%NtKbt zfO^==w@fK?u!_^3j!z?Zt_@_MS^j!BLrM0&Wq|b5~fO? zEoq7`M2bhsdHD4@wKH~Wv-2P364QT0QZq8JF#NxBsq5FTht2jsiu__}{mT3oAof=9 z*k48d|M>N;wyQFJdE1{ac|-~YqN(dF9N^)1S5u1ax48J`nT&PfPbIC{j0M@(t9y%& zGl!2mv@Vjk&NbYcjj`9MU(Mn3>*q<}uO$EE^SiJx5sp##;k#yJM?d^V>hGq9zBbxw zWTNa*Pw@T8$khX>bi_uAOdn#(*xrv^Mo158&iD5BD(`Pq;DP8|qXQ@}0xtk?;R{)T z;IhJr9m5Z82Vgq|Ao9%9!V1%ovt({2E8(CFnEgb_)aqsSeXlZ<7CdTZib^Ems|-ip z<+QE7&%L6F2{*V8MZlqJgg#1KKio4k-ffjrl=fbRHPETKu097h`7DNROeJ8QWG%lF z>wPB42^ox>=6uT}sQPY#$#h-qHm^$FTAjwtCr^b2wvPZtMC-r~mBV_)mLI{blrQ&ge!R~t_WJX*D{QffWLno$&yOBaPuiSv6Kb}$49m2#N8Hp zr6Pwi`N&ZA+Dzbi>#^69*2g3fV9qT@@8TG_CYn~tbzIXuN!t`#6{mWB*1NwS;^vtl zE_5Uo%8}6=3wWy#mr*V4`hcy1qaWE51rBy3IQ`VJbPPX**A^GI`FE}k4kO|7>P1E2 z{n^$qtK7TFBrVlfY*gkUoHmwa=Rs=A>*!v;>RV8PNK3!ySaun}kvszy#Yh!wBms!q zlL;tHkUOpBB&Z0eJVsHoa$&6tPly1rO2<+eSbd>}VGWi!hLkT*KoN=wS5seQG6(KA z2oz|dM2qB0D>2jw_v8Uk2NPU#|4{^=nboM$|NeS?|8M^S(WbF5hq#QbA`_W{2b_)}Di$WG9n412R30gczKa+fRRm{{}; zOMx_iTdbAKw<-FLNSK?iP(<(!eHJYXsrbv_y8u7dEL~Fm>S+%t#()DOB?eU`K$;kf zIEdGEew<90Ss{m-z#8>2tPqg!9{hgraNy-0t7Yx&RTM=75N6}~QwJp0(UE){H#y06 zKekdl;TUZye1kmamzOng?)u%19Bzf0hJ14>|K+&=* zR(f$;6*S9CrBjV)F_qqr>U4XO_(d4K*E(|u$3ZQHJGUFBNzgLJSyLE83+ItkK`$EE zR5bzy0TBqAV2uD8jOMv%Qd;?ShD~wZt)xp^PA_FXu}*Oez_`WzEQ^~%qp3kRjv=5E zmbwtpUo#?OP>!^8DBDmzste_%q=FqqsHY{(OT}Q)lVABcXVP^|(bz1s7Gpv1+|>{# z3ws<~XjxvgB2Zw73G#IG9M|)~W)PF-BPn+y6je!GSJx8zqI#2+?(0X?FZxl&w0u4b zfMQ7-58oq^+g`TDt{QP!=+?v{0mMq?2T9QJrlHNob`*REKsUE5)iK|~=R^4%K~*Uy zN~Hbg9NGpNx5OKjong;oBz6i0=!yLgEK3D(JBp@PPO`SGT$(`S?LOH~ovulH9J~J? zNy9#EtK>4X^6*KxGx?+i;ynV}vdOecQlw>1A~M;oM5)l5Noq8=xl7x=ZXzV^+4UnF z4&ggl$<|=TC+2uI+9qa#eVLek&|d~XTDfm=jm827ARDe1i&Cvf-yuQQ{xqHSt=ban zH9DKW=pzrBO9SE#RDRI{z3;Vu0K6md|H>_tAE{#o9um^4#0^nd;!uz%YPUuVvHYq= zsY{{ZTnn~%xak#Bb+x4I$kN2}5CTpx-f3pN%wTJtyqQDYU>isH+a&-a;q~S~x1P$m zc72(!#`s#y64*>i>nfmGLd?vIII17jqm}hkPW=S{sUFj}2u_ncfwn5kab)o|T)`NF zf{XgApqnn7TA$Pf&@t`pk}g&X>;WmXOMM8)bYY*|n#*lUYg~LU zb?{ammU}CmsUMI>nK;=h@^?_6h8SYn0F+0976|MjSYQNAA7T%hi8(IyH$fMQ$LG8K zUamt`uqG0y=eV73mXCvBD$S1#Ljj*%AC$dGqNOH$#nIY)6`_&IYO@JnC8@?wTLe>ET( zoJ&Qv33LdrVsK$f2bvXOF0*GxahX`a+Yo7M%|tVlxtON~F?X>@oa04qBRm{8xuu1t z7ziCYfXx$9n||r&t^j&s^5~s>56T{&mbC?w)7WN^aE-SAvZW$a%BxEU49Wm*TBbj0 znJKIl7OUX?wHVTYQ?9!6Erbuc8pn#(BwJkXjuUGQvGt^|=)Io{ zK7D2rEysb33WeAr2SLtY82%}1XS$-Lm#O|p8?sr6GEbc`L_9Y(KJY#AFZnH>tQvAt z5Ng{{-%%f_pl1(h0`i2_plhB)4P3hEcE;E{H_12+?QFz(`M^zJLzix zLZ*xLOqoV43sUUQ(Rk^faOT?2z8zP45^b??!7C+>KW|OU`gO83Jt?4$X{$rdKHWY{ zJYl;lO>Ps=H&CFkc3lae$PG0uOl72Xt7+08QChBy>7<6OORd*epSN2*l(TIG|6yBU z{;ybe1~%sZ!?Iufvh4q>^urF2y?Xc{sAA>cj~#_&6#{N(fQG=r9{c(q5Utj-#ly4- z>F=!BT9yYSNDR49m&l(l}#%>{D&`1VN>h*@%|zZ z{+K!_>FnpgPL8h*FME}2*aiBMNlDSc2Fbbc5+ym=D)6_#o$m7BZ%-ier|JOG?%i$c zh=>$i$1ijy=t9c^TXF{oOSyl1Nb7mQQzk>>af1z-PV}CQk^0AAz;-L|K?wepMzih@ ziWpcUol%%yPgpNbN)l#<^bCC6`m%!3L_Q`02+CMv9haRUuj*AwB|ad#WK65VVOKzE zLbBpd^Ly+n#=#Ws11gm%b^d38OkyaV2-FWCa)2o;eH*?52%!#X1J@Wu6;BD~{kPXY za_(qXA@CCDht&R{BCRjHxf5+fQGpvj`h*bS8hg|qz5rXxn$!x_4eG}!g0YK_Kw3Bg zXE_Py*R4PX+k+h;uZ zb41(vA9%+*Z$X|WRYivR#u$VUsr1}2#@GQdmbp@cPK()w3~8hV2^$CS4jws%2fJg(JJaEaF|j>Tzm=5 zoq0>mBf`)l{EzCHH~2kQ9sq0DB|inqi|tz76F{u&tbW z&T+*dMEyEr9phk&a`&A8Zc7y>b7oB;qc)eQ>}WR%Oi=2$GQpFZNtvt06>kd)778^Q zfs*RBPs3NRhY~k~Z7N$XIm&`wNf^YylW z!e4_^!0$_IOqSn8b&`mZPzY4Ic+=pS$5qp)uqoMy^%boj?seHdlUCih4!ZAA+->g4 z5)Zw){OBMD*aLwW5_ke~4R`m|j-ximD5bd1Qi23p|)2WW)Q=RM=YmC?a6e% zkhV}N$}brpqk=bS&Vm1PP+W9)@l0H?0+tCT-gi;g0mX)hJO36~x-g4PBy~(YTxKu} zmsCNzSUeLPR-8aH%-@sWPEfj%bf?(Tsd;zLP6j0`BFZN7&sHr{_qc>~%2m&2=#VV8 z3RT)B5pF`8w{YH-adn(ZGD+u3D)sqaWgVkwZb2nIoK8oPy8*yNrb!!48{>x!#wb_l zZwvf0H=A=zjQ@v>OC{ThM)o~drBN~#rl&IHnC0oF;$Zf(KescmYr?tBi(ce&P1s(1 zhj@t(4pA8cS9@!^n7nMi^?Q7#$ZWH6bNOyNa4|+P7^kfNVe9dsRUBT9zcp~vaavp8mT{7%jtAJe%|<9 zjBdYiC!@3UO#EY_+7$lQ4sqWYw)Cc_it9G*C&-9M5_IfCoXKIrY4aY4fTQtL+F2l2 zzg)^LVXc~}bobqXC+`X&eb!+3;_3v8%AT=xuz>b8(xR2EZ3@%4D8mE>0pcMF2ZS_H z-5YOhoD)ljUZctw()nX|HQe#b?cbQe46#=X-1Z^Xp&G1_z)zO6yMz&}>3f0D;*#48 z(6@!O;li^OiR!D_8-3NCVzvv7F6b4rCs|mgfM%re#E)=7*U~_7LTu>^3{wL zE)N@BJ^KX@pPjo8M%>8@lhwkLpW{y5BGLfp%ZK;zLEXFE;=Q{ctTnuu)SBB*mt4Oc z{+(|wps%r^>ne)vH;vs2hmaZ?Hc_#NNUTKIva16&KcFN)Y^47*8`=IF^B@BY$N!O= z6=_J>{W8qI=0R`Zs^M1!Ux@^S5$YUP^mTkf+b*k2Q3x+k+h0WppQ2 zijNY$Y(Ifyc8yqWVdPEpT|?>GCo+Za*UE0nD=F0<%1G+G_=6w4#DSb%IlqdX#S_y= zcy96#mc9eB1F}6b-mFfUB~dlMi6<{682bVELU*g2v7IQQEC?4t=)#4I#H1-spo>QY z6MVSA;*`S<$_ycbe6$Ezi=ckB^Of6sndzWUYIqz9{?AKB!i|y~mb4Wk@U!6dJtl0kiUK;|v!zjR~ zUa6Q>Zf4isC&njs8eS)CcQMYqcRQEfN{wT1ZryC_GqmMT`!ttSzyN?iUB-}*FL69U zI)@TClfLa^KvsXf}9&GHN##Tzu0M;ZNTf%IN zVa|ND?&1R5nXY$c=K&j0nE!mXpiP4V^^f80n z3^ALeZ)iyrGmQyJNg!TCs!O7)#&a>+3AM1f9Hy*DycZ7;auDLzm`vwl)K-SXImM5# zTY46tOI_3lqM-dzm8S_N=z!zn055~U4Bp_DXr($T9b4b{kZ}bb4FH)A<7@R)Aq*}YPzVsyDeHqSESM*m+)mBiJje>O^*4)3E-1QXwt|isA`iZjJGku7Tsjls-TMe((};5`b{w8&jA&FZrwAwf_Xnv_MXkmd^DS z0_>uE+}S@eEZaC~fm1Nkr_Cg~`&N%69)`w)*4xNpe9MqfmCOhZ#zxmoy5_yRT9>%8RYrie6o*Ioc7;j`IqLyq zLeX2)|H^y5t-zc(MWPVr1Tcu|k`@arpluHAtF7j$Mk-HfAG4a)ori5rrqMzDoSH>V zSFLP&!!BDAm{9}5fhF8y?bWdrfa2hIDn~?pkDc&3*?Y7#OiD%;*lZ#2xiJunWT*R) zR6XZU+wS+}@%cWZ*AaOQ&Y6#h*1&2)(d1f=(V7-&989>RtBRvgLG6H>=i=q|eLcln zvJ9FC{&nru*wN10hTY`wVt3GI`BxIx+4VdUuoPu6XDF&up255@0?nWGPPf!>o|Fc0 z^2sQg)Ovg5i#nO5J_m%WZ~M+^798c?4$fZZ>+^HUZ~ny2^gj$CmjB9%#lXSJ`v20& zQ<_{c2mgQkTQEj91t1g}Ks@0V!3GJ1wTH@4g!0?RUM$n%Xuk2Eo#BKV}$v2r>`u|bXm6Pz_WFwcX)ejuIsrUb4$H8m6?bFOAW=RQkwQ z$WXXrRu~(kLfB4S3DX}~`{biYl_u0`-tp5Oc()~hVPNyp#DoeRnDXQ7=d3p`ZD6QJ z2AT(ykxDl)_%AWz&-6nTnosj#jGqLGucqWGluA^ryg1RBkpXd5CMoby8V^V&%5=ca zbt!i)wt2HGQxfgZ5!OG;@H+Ws*zJOs1@V`|+Q)dIWVlCY9!Lx3T0Y#$IeU`0TrojR z5Rd~=cyv?DR1Z#6gb;~O?vKQ&n8$k@F*SYdU9-`M zRS!DkyNubOI0|S&%g6r8 zqGwpgd55>JqxH{B}_%B9-BdOCkOMfr<q*j4}pUVP0v{V>~>$xE( z9$%8*V#ksNAuFT`DX7$SDio0*>QXWW7BU?4bQX$uM`AJ*C)dzJlt8Y|PYgeNorYwi zci9zY_8KRbYUh;TA@f?7LM^ZsU@THQLd$18lNhami)<5(4?nH$FC0-8#bDp>VgSn@ zk51OEPDaQxD%^*!iuI+0J&H#OjTM|OzR!saL{ucpiPaUSw2Q4jI&!~NET}MzlIara z*Q72QaE5c#N;4sHTFJf^wEyE%K-KiO+Oim46cTCrpD7F!Yz*y7u^UChZ?vS)*|a6X z!RI6Kgk@ho#yw!VNu+qGTI^y3ntO6Ex0XLJZ7imi3)(+z;gtySf%0C5^;84!+etG} z_pyRA+Qof!-wT8lTKRQQ%?V>%N@BVuCDLwBqBhXu1a27RC$2$zD2^!NO>~$TJjlOpnTG?Yw$eO9kYIGU^D! zW!5CDnb180l#I6-gT63!&qmPXf6@o_XM9@rnX!iCi{*Be4kS73Y^@6oI`Z)|i4`b0 zZ7q|Pz3r&cShL$;L_sNDz{5VeCEejGH2Xi?SNS-Qwmc8a-5tL-9vg?%L&iiIaL>uM zp$DY?glD@(+MN_>yMPDurLA&4k0WzSrNTV5salg8pJ1}p`l8%h(dn1+WTvMBD z*Ve)=tbjNMFjJBW%=D8JQp6~=Vdzwry3cZIxyLWTpe=m1_@);hw}%E1+sBBxCARi# zX6#@lo|fs87~X)($V;G_SKv3Cj*C0w?3+qP}nwr$(CZQGu0ch9zM z+qP}v%o8{6jlJWphZRw874`W4Svhj#H)f`1)fNN!`jm%@WG@9Ad${y#GVs@-mgE2W zFv8jo)AodCf`09Q;cljS{Y*>Q@_VAOy0vt2u(~stwWMnMyf6IFnLm4D2CPG|zurOb z?xh^wtp6c}nCu~y2l4%;aQF_!{g86M1}46W@Oj_&Pm#%M{ZB=o`M>Hs**O0fo_hKJ zEBYFGP?SE(KxlM8I#Dj+ZPH3(4XVdLI;p+*d|^ilM6~0bkVrHk%@$EP?`9|2`(sDg zh9MjLVld=UReH(q)bICFes7V<_4v&MtD!DJ$m8BcW6wXA*d;RUmkW$i=h&>lZC`%h z>wJZe#BLo6Zu@rt_!O$_QYB5l*TPb3v(epQ(&TFf=3|sG<$$p>@TjNj_D#FTkE{{q z5$7eS8468@ejB@=N)}UkL%D~y*e?3WJpxGk&0=h`Xsl! zL#zGC*7_A@Xvc>xL9eg2gz(bbo|psyn1PS9tQlS#)tb#;DMCi`NdvZkYn-IV zdpYRu)aKr`&=sUew)50g^bA|!XYrB*>oiXQ=smW;7R4V>O=}hU;29d0zvx~`SxQKh zLN0w;-bJ5BLEnn^7}bfLNO@UQ@pj2lfnUh8aWl@skoFN3R=Tn^NZWtYR*phARQI3W zNe(U)yOY~0B}r&-l9%oY>`AEbE-RTG(5uVqIcGTqX#K~S{ExUZ3*t%C5z_etbh42X z1&1Tb^I~3%yLGL4WHkRO`01(Kq~6M^Ds^8TbkZDNii68qQwkHSGgnxI58kpbJUWv2 zzYBYg4$mpe_8<{%uk-Hw3i=W14{&Oxj=s&HZSgvD`+meSJ^FIAF%M|)QyrVZ@+uGa z|A|L8>l1xHh&v&}!?Ob@fOIo|?X!i!Aks;nXZCbCK*VqkWMl|E9cnKqQT(PzNZZG= zjO+vqdQRDt3=S1Cl95d#@Vw)O&XzVmPOB$9Sb_*Miqhsmj>)`JzNRZ6W)fb5sTYWC z-c}n53ND>uj_`0-|97VZFs|DhAh%0xd+2I)LwYW*({+e4~`p;}E_S#&pB-R+UL*X~WcsMN&Dc(A7 zVUeoN%Jp+js>yE}8td6{*w}6Y&v;cq)Md`2_|xfxsBQ)KWZ!E${(>wR>n(~j)F1L? zF}hQ$f^E`;p<)?il@PT|0LNg71C~V?tj1NFj=~eUHe2PylSgz}4XH?~#YK^4VsL+X zue*p+;Opc=dW05LnQ$DLiFi0#u&(_4xd%_evGb&jUDL=X1iade6N10BW#%r)9bid? za3LJgEGb3f#IrW*FL1^Kip3I+e-%(5C5+7?@dzDky4m$c_g?*0s+E&yPZfK-jH6;| zn{R&6bkBPFhQ`!m_VPFF4%@ssGbHc1r#Ju)TY4QUmdys@#9);AQ#Wv&Zg#4&MK#wbdXCMNK+D*7)a? zLOF+9r;{X{th%Uaqw?n`F%Ki6Rch(#1Q)Ioh3916?Hcc4?)P|uFe6=5f)AVCU9+#f zTIGj%3}z?x*3CfiNywt))R@+*_3w3{!#(}C%cHcP9c~WOn|{~!XoDCtS433hPU1mBNq0+am#4DjQ*ADD`x+yBx3t5A`;=BDKz%4VMuCkOhb(5C z^3ZVaA}5%8v@CjrC6d*&$a%M$AQa@bj)s=#>VUL|z$&MbDLtT=$V4Kmbx*3RE#@Jt zEg3>OMp=_NQ22+cyQBUua)i|!>IZee_}{ssl#6jX_CB2eQJ|sHBe~ylChIgfoIoA< z?ectzP^lnA)l(}PI*$V>X-Y(ZsW1znM#sJCdEag0J%nZ%J2e&HM$pFyDCcwHOt_*C zFt1RB4cHLpWbMxsX-ZvehA3!XTJ@rfqJY~BO_M!lt}&8x$llR5vxFBYYibW;da;(_ z=73};5UtvMLGh(BkxFSsisli5qNDs$zq)O&Hj65RN0efEsBRsyGS#OJNXZiV!l}*F zfW3$@(XyLY@Y#*aKt02oqsP*|HMt{@Yp;@Mt@2TjGnFvCXBlmDo*9;*x#jw=8a;Si zu5G*y_;p-yDqYlvRugREmPxKgJVk3M&)tYWg{-dbKU?W(h(w6Oe;jXLZEP3)KJ&oJeJbeCur$tS*#NC(a>x%w6wSLA> z=M5gMJ(}-7unXQV`WS_<+~bMxZYH~HnaM}lJF7)Ll>#tp8*;!O|=%Y$}PFv7F^sI`uUKzpjn&JG0F+v4y8?h zbpKEmJNGPs2y>@-`e5FBe&|TpX zPiI`E#hASBsUj-hi9ol?L4bik7;J(C#d&01sxkGp+gd#T`BY`x=uYGnu0iG!ck-&a zJ|wC*IRRB7iqh0Dc{Mshyf;H&jGe&(|7bswUd6aW`fNQdQYEn_+ z2R1jFqws}5&clYxa(j#)ViSKzx9aIk*^{{M@U-y?EbiX_BOuBFx8kgW7gpHy^A?as z>40Hs6QSjG5&|5-)zulB+8*7Ke45m>r`8+;-~%io5*J!l^xYO3lPEa z?3V2>Aw69BWu`=!9Ks&hvdJJnf&*Q&G*pZ z@I!qWe2k?jzqkAg3%l|^SUd1Z@P1)jVTimYMTerJob?2;qSU(OFddTTx5zV48j|3Qr8WqvII2X4oPCzKpoRiC{cC6jb{>18e+d*f_ z9K%+)Ophs%)Cnl?F(gHLOl7VP(s0xu!j2EF%AgnG6}_L?A`s8y&#ATK6GTeg!WLn@`k}cnB|SIun8Dx&j8{-MzSsA zu9Ah-T!Zd2BIv`$)T?^fug+?< z*`HbDWfxo6U*p&*O^3a;CufBJ0deoVyRsYe{+EPeJM%EJ*Wmr!d3ZAJ4KJBG-E3wbsY&qcA6G^;mIUxg~3Z z`j%oq_Lz58-nc6HLuYSjX=D6jrdguq@Jxgh^J5V?)$osWXo_)@Ri%wET_=;NGOKfa zw^Nfx%HOdqM$2<(xQ+q+lbx_+x>fA%&`moFBI_5Mw_Js?HDXZ}aMF||lo5bF_EDoy z4FMhG$2w;O%Rh_l8?)+J(kp7&3-lkK)y&kFrhw+> zMgi*UiCyQ?!?>HqAA@{I8b{3d(3!Y}jFj7gzynM}Ev&f<=ua=x%+fIjD?p;MKv+g;8*{Q!?2x6{c*Q59F$SrU+ znuP(MV*?JfDtjmx$`6qo&FWP`)eSu+k0qDdSg72m3S;L+@6ebjqA@WCMI8n%lrUlo zn5y%8^&D0&Wt89AB7yLS_L|@Mk-of0JoxFr!N7Kr*x-vzS5jsD9}bs zNl$0XM#s-8yT8uKpEKEUb5J7t6roqR zZ$n>l3xdS+yQf*!LtbX!`@NNWDn^}{L;N`Lo+3K;ynQ>lB9w05*ySXun@5I3m$7Dx zsPk1DQ#Gw)C&l2MOX4h)W<oUtFv$*KIZ&DM|)5+zO(P z4bzmW-vJ~ALuUp}Gi*EQ?T8agIe3&QP~%5TC^6asD^;nv?LiuY8LV{IZAHzwtd}Yt zm*ZbrUx(!bFHfC}re~(r1d1kk?4S$6VOlh+b=t*?4JF5e;^yi$^4)VFz0fZ7pW2}< z*!SQ}=jQmoT z)3F;IJ0!ubHbA}u*^=#eD{gK?FQd(ze$T^E&(W&Gu-2|gi ztQ2Edb?gRZ5_by-!I12U*NE8UK!Q&^XQcy?xnQ@Z}pj*lt$BdTC}N>xPmC{ z@(+K3XIv_AlL>c`Eog`vIM#k2yo4*lvV~Y&I-??<8%Y6f8dU8-`tnSTeY!)qCbKoYn~Q4Rp`i8 zVcPtaIP=s3hdtL;e+$E$!bi2rJNMo0P=g5K+H>1xxDXnb7KX`_M4UvhqPbUAq%TRE z+%S*AU5eA%EGViqS0nUiIo#GDF8vy56B?(r^V)!3zj1!|eRCZ*uEp-@1r z$>%!|Z6x8gc-_J^tOarCr?{&d%uX-9t*5$;w9WwxhxuEAPBt&Ivt(y3TC;2@STk^T zYV=cGJ$}=EdzajwWqm*Iz^k5oX8*&#DcgUgMKH6l{I6}|Zp=-)&9UcheL@Th0YJ7% z!ffTOeMU?KS z2cq|zJb3>=;&mU8zC{>>$3PE!(T*^EuwJ4lC63I63^_ZzH>qZ&00KG0KqXI%%Vk|0 zv~mA%P|2A=rm{uOty;BuIB8S)OA0BZpJ4~Cz(q$m{=SSW5MYJ;0VYx?7d$gc1cTYK zG&KaRj5M!jp&&rY5H3wz^+>V%@u#{D{corQLnSmgg>cP%z=agRTFEcE$icOTT!0cU z@va@xcCX=_GREqZyU^{XAjZ3lQga2!S_BdyRGMBE^oXuCT`76gzWhbJd;>~K@N)>z zv9^q79sNBip@fvd;u!-o8pU!W=v4IP61?U=W_Dya$nwn&Q7;PhHUZ>AIc0{$0(b2? zmCWr7&qvtdl{(vy3U#2#diqMKA?r4SCQp%HZUUxPe=T*KT?)H!;pEMa2-&$tkv~$asW>AK zm##h7WT`Epspy6@6*VXboZP|Fe3%jH?jb%F^e8%;DTFj3l=6&F#k@6S?`~A4#<%Ml z?;0r}{Hda%B|`QZ3T8CbGskXTyJn>*6x-LJ|6YamU2uzHOCSd)(7X^$P2nz0$rdKq z9k`xKk|H8{`#_tFteyGn2bEtXX&sWPNe3oN(XPE3UBUbXwK5wvLJcn9c8(tXgFIzI zF@mi=!$@)WvCjg`KYQvhp-i8HRs~b7*XfXDZa8t%^S;R?8N8aUkGIp~-wM`D-aqL5 zb+NZUi;d^=JEP|^e`Nqswdz(bT;Hr)5G5n>LeX6|a&cpls;LF4TAe39>g_{mIo4i-n!nSRJ5d|^O z&sN?M79PBwZD`Rc&etoD0TvtRP}Yfdi*m3LXLu{it6ccDczgZsM&ehrtSh(=(#Fle zxcN*wCTiNRUfh&tfRx(*kKLK%JmH6+NQ4mu-7CWe!_yF0#IjONxn;F+&(hR+ZgQ1* zF+?0sCUyWpDWMtj_HE~zUa_a2-;Axd`8t>=uDqI{5Ry`IAZNw_G%TrwH2n4*b@jX$ z&`4D7__7r27kEJ;9hCm^v?Ncy1G+x!0Yug=EI@OZZ-v!B$Ksd+fAY8lcVeLtM105r z@LIah_z;pX1K?Qb`w@l7>j|P{?lI^!q~yJaLkJ-etX^~-bV#+k$&z25n+H#u3H7j( zC<=sv30n;_QX#Aj9r*=lh3)b!8Mf(-U~S8jajkVjt}K9^)Cu^V+pFte zz+$~knCVyn5zcNNxb7ml(KjH%8P>nT6d=%Rw-yi^nl^hs$S(T_}iAuIn zt7G{St5LN;Q#`=!vF7vkt|#3~EfvQDOBNRx1Mwm5~vp>5rw0 zN07riC?fP<`-cTX#X)p_P{V;$li1)0DVJ36y`l@e-1+kJ*$(zyo{T zD?7-LX1n#7SL0}iA%?>mwdYuVBo*<6Ua-WKlrfyiVnhx5C^64K9Rw>bSnvae{s(bC zVK_J;2J^b`FdxFYIF6>(8zIWNFlyukh7~wzcy5mXM51Si)jc4H;864gaf+;~fZqUW ztp5$H0K8QJr_L21ke-yN;S?v=gp@K)0s}`lmbBm|UfhiCwRR{pCJ8Q{Mz?*IWIG9j zP>lj=0OD8^+TP=i2R5lxeDaWHP)+SCOCn?2j@QZ#1HDpA&L$Ww=xrf>ZNbHo8kpx- zNYS(J43ut7fT&s?M&t!%x8+b3u;2=K(v{Wl5BPO|OgbQ^zb8hVsyc562(`EOPaLwj zbPlR|F?nkb4`xNtUd$t&?$iXBbf*GqWarp=sh(DpzNBKCD+2OFq+xY-tUT>R*%(Qi zgKr~xl7--e$yk;TXRv)R%SVwgxjK$_L#5D@mPT)X=sCzRnM68HF#$XFlt-wTc=6G6 z986H<6Lgwn1s?Xs@R;UOTL*;l9-O-H;O@8$IZJ&xfKdcZrMszl8{q*Fx`?BCVaQUw z`z400ATtegG1B;0wRSPT8e1y zCU9-g?yQj^gFdUrI57vu*xCSl*K}4PnLLtUWy;c7=%k$f3QFb>CrkPgx}cB~P1??! zh>DVM;vH#=?&?|LXtR_HUTq-JDpIe)B%4nXp9g&`ZK@E)pW^*o;m)KN(1ZxG8Lqom zbEja?d1#`2xS+d6l>Envjo_zpfy!%+(7uu=QWlNmbS61)x8INM&v}?F)Suc3p`s z1p6Iz1+d65j{?70+F?#n1CYvpI7O#9>7l!3lG#NR+ssQ6BU$>wD-$t8LpIlCtQ1*? zU_34~3CZms9RswM2hs=_>U0KZ=>NKl;3PQSJuGzu5!0VK?}3q*&rwF9tnK?)ibKx5 z)u_7WE1@^svrYs!jss7wcXW^Se4;$uAk%UkmdG2)n~L2#pr~cm;|d{$jHp@Vck?Gi zrg5GwBh^~tYx_+KK%6>x#31ok&(MW8Q#@7kJ4sI~*r)=uREtVqGf2RNCZ19V?5S!4 z=gh3otu?8oQ^#d;Ao%x5UTI*o&!Kt)miOaOH*jIZpR{AGwco>N0ill2_=Rv}_3ssd z#@)Ndi)**`T2ubrA6tQP&{oHVO=affZ=6d%5;jrFWf|^I5Ie7gdxQ{jZ9bJJwK3flo3m4%z)8)S=6B; z3mP@I9yFz29ec^`1G7A$iaya~Io~aQ&;2i5u$WC0rcN*k+mImhi@DUZ$R0s04jkh8H$Wzw6e^NPBt-p>cxbTe^816KK%Dn1mdF$7Dh>MD zNN(-nZJaa=!%DZ!SUTsx$pe)5+!be>5X<7yWrZsvei{ebI)|H^AT{-5E>~!EqpMVx zA;G4i^{=d^v}Fg2UJa?T1Wdwf7@pLEwjTx(`?@N)C)be^%EM(qx+_nAI#`#{fYypy zxkA*et~1RGo-}Dd@OrsR4(AYUkI`WtDJtdN`pZ9RW#Lmd&q4S!lQ!5D&B)f>a_M#oR9OeoW*z*n58O%1-4$!X5uwe(t`$) z9i@pCq|FpNAG^znJh6#tQob!=1EEVhj~P}`F8;Q=@sXHoWAacJN>clAI0sotzk=(A zb5EHFn~H03&Z&l?@814Sy$L(Y<#bNwuJ)H5M=;UbLCB}m7Awl_MX-o}QKB!`+2CwS zDADHeO1Zpu)gWpb8#%q6^NeajE_V7JanMXO#`WN(mmbFB+o3Xf)h1}a-k37B8}xFK zPqUetA9(SP$^d+%*J6*JNoy_$1`*`ltS5$c*>{;sY@B|!D6_OvCx*|L|6;yk%in=CP_ZNDjo=C`YF_!J35Lp#3Ttc&7+T()dyWo`>9TDJqPRcvBM zp+s82@$>3!=UhNSF1qJ%GWET6!fozye=6#*KiIoR1WSLwphtp!xjjr@72Vq_P4^(0 zTOPf9LL^fds)VlCCUF?8`kv?i=GS)rZ2{{K`DNOJ4}K?pocG)LK@J0deMg~9LP;Y; zyeRX{Gy8~h0%7zT?mnn2qCG3vQ*H1cTHB91`&5OMnKcOC6k zN@SliZG7y#4c1%mTDm!_a@GCx`~nHR@YSQIeywoM`9~F4V`0{G;dAiNCKqJqw~`em zCTRZEBh^crhhK&XPG>ZhD&u;Q2EOa2Zz#yp z`wc{IdD5K+1@=m$B=9wc+09p(Z`G8vsm1zqdDYb!3ENhkOUGT$G-7SB=*O+e9O#!M7NE2fvXgB-szIZNt{W;o=%TzE0_J;HUxb zMK+MzFY|a}5t_qS=zvUS)4s!xJQ*f@)YDib=3jWlRU{G@qRfnt7@i5^B?{UPLFRK_ zJzBk%p&^F_YyZZ4KTxev2Of_@vz_P6=gnNLsh1DhTy-Hrt#M5*;jN!uays!6paeteH65{kGe6Npdf2sjnO}M`C1e! zA1X-PD`iN(Lmv@m7}(6=t(KyaE8&9+tr3aKRrSq@}k)5>3nSEB-Z2O*rOFmZsvEqF&1I}49Swzy$BmU!O+E4}o@-LdvZVt{XPK?s;D=_47;5kPrqPkkN z)CU!wSncSdj1sO(Yd39GojQ9A3MB}cFLJ$Q)r9)JfyeQ@P-Ryqj{ZEkk!!khSM#pe z1_GBHhV`0-o;{-y-L~W9%}7&CFps(##j=3(%*r>Pps3OA7UsN%p`YGs&53|Tp7a&r zxj#do)n`mMQaq+%mttd&BD!+NJWJ5=PCQmn9#Kr!?_3lKd)L!CfXENg9UUJzFbVTo ziIrN^yF$wqfEY3s^ud?Q?ZOV`phQ5DfZCkF`Ak)|PD5flXI<>WX6`>al#3q#%5AYM ztEEY5<`t_r3howf>DH1X<3T@&pzmYo*3zba!Jg4dW4?kVsAL;nRfp*I%|h3L+tFR- z+CkZve8<-(Tcgl#aj~|Im4C~XyI2-Qsx^SpW*yGg2B0iq@VB-CttZoeK)|Y*el2k~ zk$ST10*L&3YHYX`<+fhQz2KVnA&W_L_rydllM0Hv=7MXDW3>wuvgqPi1x6#ogs5%K zG)(q_i@>J$coZUffKhI!fPD;7nF#=bVp$Sr@+6MNDj;e>Yys=-^Yi5A;w`WC3-*Mn zD8yvy$kk(PhoaWd)dc$YNoj<-z%DrVBCg>ahN$#NQROl7SrO&mK-6esK12Y*+#G&@ ziH0F^AUv4~m?;M7l{$cl1_m9y8XyORg921?3}zkn zp$%e_+{rFVkixH<_NB(g8jNk%Tx5ljd6Sa`rYsTH;yb+Q5{uLWUSWjxYD2{sg6u*E z8JujbF=pP>!#NcSsS(?PY*8f!6DL<4Ao6 zzz|<6gAueKKY$Xsl5jIQ@CKsCgmvh(YT&}v_``dL1z#OBhsQA`m~9SnB351SCD!(Z zkPTKnWJuQvh1SGojG3AyJ?8U;1-IWsB)gkRwt#DClo}sN>MT>Op-!kxs2&xaGgQ3P z|NNWo;^1Oe4=v^uP}3Q-SGb@jDN1-XyAv#+5GRxG3e0qf#Eb<+W)a*o1yY;@wqZBX zMxq%%04fF+6@U#Wi^HfXKLl6Oz+G>3Hhxj#3`#+*_$5c(VT_94APwVb6tv6Ql4p}+ zZI`mtaUuJrE*tR z8>K|lsgMXaxgs>5qW}r)I3+?XtWIpnP{kRM>jN;A?}*8!y%+GW3 zOMtvNy3i9@?xHx1-~)=9q3w&&aD72QS4>4kk^U{$Xt_{$NC~;k4}^~)9U#&g(cpkK zF(?OQ;wQv|cnXW-1!Sg+uVxNv;3(2@lDwnEtf+>MC}5nSXetSa|I5x}1I3vTbbpX$ zZa@(tqWOTjnuy;H7m?GEizfBg>m(b-ybz4hQ4u*gbE^ zXi~;Z1jd|!!UPh-pf}zDf$cz^tNx6n1DC+eUtEKDv^JR5`DmH0^eM&pU}2f>G3`Hr z#wLv!7>#WdsMHe8`(8UoAr-H){PNhL9s{4+WEj$s5iSmtM z2%3(BiM0$(LSisA!)*!#@^Ub8mb+Q<3*%jZ8)F!-&Q8n#f{!;M`%xyqr)kt>_Anx) zEM+fRGDJV}F41T%iESXpTk)=ZQ)S*lC&1Uz)9fy6$Mt8RewJdOI|rMZpyPyN!O3}1 zRribM!N^aEA%d06Wr0~eDG}CUT(47PZ(*ZO69mp2QA^G;88WFC%gp3g&&XAFqfjm@ z{iRk$-e>A(*p5b!HM3%~drY_@uaLWK#_ko^xCPR4;B#8gqMs2wO zPT4w-k1A=qb4vmgnN41wP<6mo)%xjw+s5u87WC-Mcr?y=HXdfN`nO0HF7# zSv5A%{Wpr$zF$Abb?iFdcgcmaFLRw_SP_e?ncj;LwPrNoHs~I*T*r?@C4? zlzUt|v?SRxGi8bO_T0JAl*yW*(Mz>=)Ct^W#N!Nqp@f;VZ=_O>b2NEgXmGyAXB=AN z*pJjxJW+AIgNYvg))hW_-ALGW)h}=e%Eq#*oeG-LD*jsq21Se*N2Qd4;1nDiS{50x zrjT?X9Ixp#AZ|Ms8>u=(9;;dA;BCHF>j%HO(w-*ex9x|$BiUy5qBVZ1W=8&XLH4ARIG~j312e;G$|!U5WRl3iV386)c)Ob90+(H}6#6yVR4414B=IUHLlho1 zLo>`J7m6N1B`x1r)?#okl*J6k6y-qE(6^25VR_9)rZg>!#~!q1`{OfOs@~Wa&-=%g z!@S+GZLYS0dZl2kwcRwzI&+)L>ZA&XD|3r=QgL$f4FRf@`^I}NxxR3SuS~#X#7JFH z2DOZhnJEGYUiG&`Pg>rRKyPk@mfsa zvu?>CE9uY7uxnJdOYx1#*V6eGn5a;u;IDO7x}W&t&I&LuQDSg%v4sdH?!N_0^5gzj zzprc9Ii=eer;~T0nRxqD24_>GlNH)vPTD+JSmzv)10+&?Qwh&->X zC%lS;DmNeZ4DR(zMO&QqcN{J9kK@VeC%hJK35)iSBm>g%dT7ss8Ng~_r3!J7;&~ik ze!;;-&0n-yH*a?iuCpn}-|#XY|KQS|{^LXZX;r&+{54N{`#n!JHz(wJZ#Nfa&0RgG zeUC43#xv}#?DpwZ$Jeo?y~ZEQ)8u4d)}D_qNtUY9>u(WC+@HCmXI%wLApugjgqP^j zmE5i9Znjo!1El5-WD-fMtpK|whz^ZmgiQIXy#e@KhXmO=Wg6u9$VWR??Oy)A<4xI{ z+3yc0+uIK(tCzdA_G&^u-yd-3UXR!eV5C0JlMmlc&*x4ZeVxY_Cm%mQCh4~hG!2UT z3UFC`b1d#}{24o=uj&h5`-j`%o1d4Uqk3%Bfn;%gjC{ZW(t@sWXTE!T_;ISvPVtQl zTRLiASEF%+tB!nOuHJL4zWT-!Swq1hPB5GY2k)7$8c#Y@Dfy`WMy8DQcE-XA1=bK! zWqc@RC1Hbd9US)hfvc;)`<^$0aJIVAU}B*soHQ;)a9tifYTsG@bp;%;k-J6Gl4NFE z&rl&BXbC46%%5?W-u5V{%^V06NI=~l)!7?uT}RcE${6zxB9m#g5iN(cM^P|h^?-UO zJ&2b#umUk7JQoLxY!rziMziv%8l`!eRggRhI)01z@OwGSEfuY*oi%u7PJoYc@efii z^|j}?B>Nun!|Nd1#NPxxIGfe|uuupZow; z6G+}OF~;aQ&eW@cCFFlULQ-Jq(AUvl_xIK!dC$toP)^ayDN}z~{|$Y=!bJR%6Hai( zi=tq{&0J2I`9ma_Ps0%`?OMWGM<6SS%JS#)q(HMN0l}dBvx(6vLDT9WRLnlJ=H+b4 zQ&q2@aPVx{5rIHLCEM$YNbHA{Gs9&_8{nIT~6e8b!n*AUJ`;=?AYb5}?eVw*9swqP zj7T0d5<|!1lV8CZ)du$gMW=@=#MmVw8~i%Lg!@$OoE$a&9gy-ye;cwR zceUcDnTK%G`^X31n*1)yH{uY{Qi~pb_-Bvg9F$hm-R$Qo{V(v;42k~#|3EXb{#Wai zOdOmH|5G0Pi{1WjR>%0N?qUHd{KywD3<7i|keNSi1K#+!Bc`?|(OXNUr)$E#p3^!o z2*BLE?7Dlj`O_O2JIG(p*^X(Mv7Os%&);kR`KEK zrd^i8?3%K<^B<6s$N+#a1Jdi$xRbI|GhzQ#)-naO_t*Z@_7l}01Bx$L!pt-@k{?OI zWcUh*;7ZnM1o+|p^%r114^HVIorvV$%>u@eyKHpJPRi3jy)RkbtMbz+nDL~J!qDFd zV=oi93L19m?y5G{ST@MdL>A(fqE}3sj1)V|p<6hcmzpG(kbm+(LdBomdJlCMZnXp#P9`L zh+I8phZOiHa|v=3p=0FVv}D6iT^JebLUMl*o53I#5I`?Iflw*s>scy4^ucFjl;(Ut zXB#_pf(G(sdiuu*kRn%x;ufmLY{FfO1785B2=CBlirrx-Xs&W~;=3ZbBj{?Oy+~k1 zmn)3Zf*Q`kk`SO$OVgy_$H;_uph}!a-%QU7v^2}zbJlY=_QMn}X~W^Qr>7MtBxKr> zzzJ0lrQic%pKz_!AOA~16p#6bGGsC1*vPV=;l;3m`Ignlgod^n$~~hIVpNVMD6P1C zqs$s`L0*KuPc2YD7*o&>q*+=vf+(m^0M8Sg&oLZ8goC1OirMSLt;$Qgl~3{$t|3pf zu2v{}uJ#a34|u0>6TXtsVhgnluq96nuOUE)yeqQVuW7+bLm@lB0@I4U^Z>5$5wCR3 zSJn+*W4O*yg>@{{R+qN&c}L4YFP0sE6G_wX@~cz|uyQQt1UE(#ChE0`AFwJNB&#tQ z7NV_}e~6j9&>%KI7p?-t1Lr7K*fnNjMwOCK70atgP&4*DgTZn|Q!0^5hwAC{gB3{D zt_WJ6E)FDWG3*~P*(lpnHe@*>+=Uk2Kr-%-qkE1C3a{voUw{D-Vb^K>bLN7{@bA}zF-o2`%drumqOR+Bh`7X{<(9E2+fI@D+_q1 zLTpNa7!OEQF}4^SIMyQG^au=Z%!4-Yr#w-^HjC?ASsI%=Q@#)qYgNSUk%YENK8`lCwbU4&C9`MJ}Z7E z2GRLfYz7Ai91e#lmImdd_cigIYKm0u-_xDTsgxl`J_En%SbYq6s6tTOQzB$(!C*JE z#eCGiXpWaGbvO?GGPp2d`s-(LE?*zPaM%$8b>-sOFDMY25oH_VWV+VsPG;;W~sVJ(#tNpI=R5O6W94#-JW$5F?F)nn2IMbibFYb_?DI$o#^IGcc= zi(`FHNUJ#f$g0RwUNLZkHkRFbP#BC)oE@7sf-pm`D{-u|#u zw9e0=IHxS>NkP@99C`_=0I26mW#j2UY#|gN-Fg?(cX$XN6d!XV2d9evQ2rg>+IS@JtheX(1rHhu9ZxV5!U^eu46Q%)f zQBXN~{oi-Rh@MRlylh-vvZzv|stQThTvp{!RhbBvm34Efkd;wSdqSuw5;)Mm9amzC zc~h6>MBJgxMw%u7>48x>csda>kt{_1yd#tsqKSdjw=1Gx4x3B0|2{I23W@qcAL0l*aisl!uVrp;Uj4V|wZ%oD~m$oc+XY40ShudnV@U#_z zq%>V3hJAB|fk$AHsbA)=uK4EN3zdp}$~yv)(eH*4o;+dx|6%N$f&_`$G+joQZQHhO z+qUiMvTfV8ZQHhO*Y@1~6SF(9S9vloPekhZUI<}vo-x>--*wdv+SD2|&&y^<6+~%2 zMDJh|E<~!u{zKwrn2v{?*z!%_z0GR#{SR! zO?8RhxfMcz?k#WCj+5?Qe+B`P{=^haWYo|@-HEcj}g#Qv$m%g`ZIV0`P8{29F zh#~b*O|jDajYkJI2L8a6UHXratUd=!&T9;0G>#>~Vm4THl7KGLm8rRRG+c$ zm2{dI#XHd#BJvHqJ3IyIa#NqL4^8_DdD=7K?duC3!B#Qb1WrQ5=KfBK!cX z=IX65kaV}mDf%DguUu~BM1|KnN=ejK2OvAU>wd$xPHJJJSEO=-6w0|$SKBx+!+jF0 zCL^Ez{jJbrqpmtOIlX`!1(Owu-`x z={H4AI7`m^{R(_M&Jvos`s=QQ^{Oxy(!sr2lOU+;8uKGpZ^jcy+}CE|inIj*7<=8)!t(#(gMS$ZOvWi)r6ZO{j}$RGcY%ECM2p&Nb~{8JEx~+_gUm z-OJEv4|y51hVCV;BKI5?TgY*bI_<5>-NoNEy~D*MM_)ru!G1>y*FOxsU6zAu<3eu1 zb+!TuJgebPrx+aX*7WfIg>+Q{DC-l!y4sS7pM&1BTaAw`M z__*oZmpF1_@}0QR1E9bdK$Oa1nue6`7`rxb^kBDk8>q7^XWQ)bCHtr_jAr-b_V*@< z`9$F`T5Ga75H6Fhr{}|Qi_@ef*rB`*y#ZJ=Qj!T$U36eke|Ta90~y%@Fxd+_PN|?qYx98 zVVX0$%jtNMa0(Z*6$q(*^qocc)21tpNP+Ka_T3X?F&>$a`yvBZPXYcRg$Cb*=QIGK%M`?uq;0YIwI73qm+S#J~23*Cu))$$sodU*-O zawBn1ftFbkaES8a9Nq$?7|+R+hq&X$hS} zr^zp8 z2n-lc6bA+hI{jfe^+`G!sh8*480(5fOlG<*#7-XgBo8bKlAp;RON&>sggHc^v~!CR zCqKmpti?E9aL3zSm7vOXl+aX|6szg7lc(kA`Iou9U^$WJ=)Dwg5O|VB3(x|SOYRYU zSebMKixEdrS$L;0p-);(c^uXvOj#U-jC8=S<`BK*udc!gAxShUmhNBkz{{Bt;sC*n zxG?6%B6+sATSS1Iw(BZM#C#w7x9j6zlr6N9Z+ok&r}N|Mv-aj~b+^Xu$K`qdbTbvN zr|0{`-s_p;=kqgx?``j*aB#D8x7XwTI{$0`qH%E5O-~co0y^N;I9s>4SQ0@RKJl;b zUH@%?y`%({1J&I|2(U69svprQ`!-VacZ87gm=8cq&OFRSJY~>h*dj~22k{Im-YsN^ z*&v{0B>pCQXnltHVl4fR;-xPncC+xL(-4E44p9-!?NvRq=^dHHh!s_g*ax*(!z4oDbJbx#!;wi`0^2COdX(-h;^=I5bbEU|o)1~Y?rcJmSrHpl;Co53 z_s#u#dIFbjJ3hXkxvQY1jrBWb`RVXCr#O)g)+~g~Waaa~4}T|*j6{67cTY1((%rn= z@27cA2pWPP{rz6Ruy)XbnGlUF$B+$1z8!-^3Yb`cCH@tA0^5vHQSrxw$^G?T%M$OL zoF>BLD_#D&czN#KwUu1kt5>&nluGu9VwR0a7pu4)Cs+`wV*Dm=Nqa0EfGB!bFrk)3k33eP)_?MWTEaHwWr| z{VcsS3&CtR6MX0*FL1*&Kf1ARqc-@Xe9O2aPVgR$0Bk5sd`=x%GP40YAe@ShCf+z; z)?#5SfU3PPUHq7QGwNHpTh4K=^ zUMl)UWIt7%Z4h9IWg~+iS-L7o-d4TcZ+u0gT88rrQMVjFh(3)TLdMLZdGKs4W2S8g zprm#i>mEh>X0vlM^Rl+rt8tv% z$dwjbT#mY$$~YJWZW0m-V}sE(-8mHWo-zrsWPxq+1}Y>&0QF%| z)W(D&R^>0aT^l^vovR`p0W4^_#V%m5NFA+!AD<0esZ=89%wpRKGmoYoW>?H8>Asj@ zMiFbOVjM`_&4Q>`s<9bb%r3tNL;7?DRiKLcvjWnpR+XQEcDo-*QrX3DB@g`zNNlgO zzP*2k95|TcL}KF&6DonTl(w;HjDr5CM&>z`k#TY)givs3StdR62mp;}rEWGP4)y}% z7zsh)yNXDo800=v>6%$RD7uL@D`g!f)1pnIETR1nYI1INe8ZHpE}JH5#9`R@%(I#d>0qn80MbyEJ;+nTIoRz}aGx&P+V^JuBY>{OvX+ey>d3)58=J zmJ`={ke#Lh&OiBl;nTIc*m)#yr`e`Pz>KCVy?|*RbUvP6_iN65efk_P|M2#_RtY|s z7&2EWYZXx`Zb0{|;g3?tK!@LNht?mgyD40p-5OEDK}6zxR>Qm>v#NBnq@+db+6p2L zE;E3wD!lil&DP3`hvi$|7`zMZ`k*>pd-uQj$_6lO|D1F)qfdLUnp5!993ihOUZo=|rpl zz>~{3R%a7Q^LA;N3u*vup*GjbNDa9D?Li!!0wD(qV6JAEM{Dx%sm3Bi*5oYplD0mr zKkzT4YNNv<9f_tFKvN-z?8!OZZ}Z0YxY+O8Q?1RVUft6Emkh#f-yr_SnRw20o2@Yd zzmHa-NRomaf3^=IB4iWGuSyUL1faB}p_ldIkgwWel=Cc+pLb*504Qw`w)MC9q&~*F zSso1wh`AkopqzB~C#=d^ClL*z)$R19HYk{~iBUQ=gm<$Ofdg^$Kg}xH$58)Ia?D&t z4`M=}FREQRU;Ef^oG*+#JmfNKrc0Y4-Jhe3CUJ;o)&qe|a>U`%Kw;Pw@E_qfR!#Ef zG{RS;xeOk6;)I(9HN|O0Ex06_T@RhxAfa^DCQBzJ-V_+upW_bzgjaMrj zul!A|z*;dOsbIG}0LM^)n&ayP>AkNzLXY|h9F1fwAa{|t6$7Bea zp1PkPiadcPwzhzwa$i84@cb34KH;+^n*$V?_d;0O2jV4X8r&uwBMYgOYsb+ZEqM=%#s@-7~ ziY_m8x_BI@n4;X%Gqnw(+n&+?U>OBnX!H%(ds9-g6*S?dSq zv~&=rq7or?u#LU8KM%*5u%nq71h*{#pRszDq3lAG;bXU!-hzNMC8I+=HChlP=g|6O zqSZI9$X9RFn`5?tyAA@my1>Dtwg2_M(O$RXzL*kg|C_e9K)r-=^Z4LTKQ8xZxOou= zWzcU{mR;vvGZU;I?L<8ZF>r*7DrHP?a!!ny;tmkp$Dv{ym-gv1dXx1b&jZ!R-AdT zPr`-F);ty#v;oQP<~fD)&18@mqVeDiN~_n5$U$Y{vK?0^@EounEO>2E>&OzHoYG;N z1Pqu=hJ@}dA-#X$DQ1Xai%St7hemE>mQw>@{EUk^N$+f`H-I3K7Oqab(6;*O{#SN` z;2byH*)T~6-8}-B%`u9>t$Hefh-eTMQ%H(!PVU4O$HD`%LeQ&nB+)`}GR3RA0hqy4yn zb=hVMsOba&>)3XY(SXM|H~H}`7#HivR-XFkajO;vYR;C1b?mrcf_j_k06IgE)j^!&Fszj~X#zDA1&h z20%8#3NOqJYYcJ^Gtu|5J9-Nkfw?*C2E<@m+dAZDqz3PtdNMhf^Jt5^!YL6Ejx!G7 zA~+q+E$5hJl0m7zgO_@gTMkQBokPi^1z(EZ8k1%!Wb|O`1wOHV+3Q$3^m;uXK1tvW zC|)+%EN8fojCdtEBS`Q%*$k_4&qiI`hr%NjAAi8iGQM$Qy77~E5X2#407tlQk$lkfx7)t|P_5crQjZ1VAgssm)Jupm= zCkbL{b_`OvJqovJ-rxr7iZ$BgBEl$b3}fZKE(}=O<-Bp@t7TpG03nnbPGBg)>InqV z+WYJxZ!@r*r*#>)-8^L%NZKF~rp8`N3;RJT?jpY{C5Z#Y&Z^FSV+rjzm>k#5I16ic z4dW{!=jSy#whD-t389yCk81`py32rpMVk&-*~{{XzrmKa$Q}b6d&v5jQuYs2Q&)5K z|D-64?Ck$PSq}_Mtc?FT)+|=u1#fk9WYp=F)C z3s)XW!e=b!>cEgzUn`B95Gs~5>;@<}j|Wj3ysXN}@Hb+Ycc}KSIf98ZIj^pvAX!w( zPDE_&-`yl2F}yChyrd2ayt|*-(cGsntJpcm%Q1Uj{#i*4O&e|>pl{z2GT&eL-QXeg zq26(D;qs_AWPUktCEDKb-l0@zNDy@lnE>O0C-SbPjyHZSdGih|9y*{S7RHelbxCGBe~09B!;(kX<|r4=V&offeRza60oZ=2{~hLF*<0*OI)3Dmdw*H zO1IfXNti--$4|{iw{e?p^7Z)#rZ-_GQwx(>-^NT=pVJZ$lLJdO?&~&WVQW7}&CW^D zao$yhp3P^B6_&9!bv!R=s@V)eh0sR;0nQExZ9+a7@a(OT{K=Q`f}rO-J_e%>t+VMT zDs|NV3W;qd76Q-nc~PjVAii`2bM@4bl(aoOC+LZE1NMM;c0uO*VPoS-T@yH_F!xWY zG|a8B6#0&4D9#65(wYMd1|tf3#k4u|tiu-Gf8{j3TUMuJNt3Y_|saUcgGskt2Ox5bk&DuYy71mlB40T!t-=$rR zER%K~Lr8J>kjD0kVRZHrxOU)rPUN~%@t4~V>Q5b#N>E&-591uyxMVD?WEn)ss<-u} z&;1fu7vpD47}dWdqPiDJwI-hV~g#9vNH;U z-qX+URxCa+)Zt&2u zT}ijng6TD{vYui#4aadm3L33Smq*LxYg zQGyhKxFP6M?rg7Vk)mda9U!*Nh(5MbaiVBlsy=&jP;yD)12*Ttoq+0WDWu12w_sTu z@{rP;kx;(!n^2^Zru_El#+0+Tj|DX5FGox+mahnl0o`K0B{t0wL}_|SNcCVB1;3&g z%F17Z_D{op3796nN05XM=!t$;k4$mZEtH)bIlbTk#`RrZ`vzoAlR7 zhY0}|c4>aaD;><@eWnH$gfd6=Vq{((cRwV_aJnE)-2%YJM&J@dC5;R$l9rI|aoT|BDhT0Bsqi2Z%-Ra-^?>gFVw`$L@Ih4`5owg5`jc?haO2&+g|}{gxj(lLKWoyVguvbeC+3rA71?3JaXSr zQ|4;`YycetK9^6-+LyBdCNl7Ad!Kv;ih%U|Hr@C)80}uAcsS?A#a8omxi+2))1%;6fHi9VnIwGKi#;DZyDY&BJSRTjYvA_Y{@&(t0D3>l;S zd`1Y1ehsU%FvR|bpcdG8{!0=L_j1lCg0KreGWwh4wqZ!HV5PLMiF^b^AVg9hJ|(aN zx;ZO81%zn?z-b>plrei%pI&@7|2%$|2}!*{o`?oTiO`v{Y7{%L&t!yyNghpSj#cng@X z+(BK$+ezl~Z0>F5a%<=IPm|}by~n}Twdw2SMd$1GiQDI(i<5fyXzxF@-7E8 z)%5GH`|ECx`uCD{Hm$v0QpQ%wb+__(LcC!Yl|dRDo$lA_Z)zMT(n4|a6O@u_gqyk! zqiZ#-L##K9GBHKihBIAt>wRT5yR6bh@cV5pX;*EkA<)bN$c@0%fTMcRw$T-92ARTQx@*#kdNSh2jv zo4Q`l-7H$w+n&G8AMNkXn%(O)cA2U7_d4EP5^cM-Et!k=2kUUTE1RF4qFXyW%m=1U ztJsl6XiDDd-)%drkNj2S{4I~rCFj1HDAe$u9Iq~{YM_(DzBN#)(cfFh)VK_I3|MY9 z2l6UBq1&)hQ@F8I>J~3fdUaLjr!M`D4Y{EK$EO}Oe^hC|n<&*iXzPlN=S7abXHPoxmp5i*`VX3OI|GU@Jz(wA8El|Xm7t{C57svyD!OcBQ$N4A!x#>xs{oG2 zrXc2`r`kd+jYpnJ_JSAWIAXaIca9ByH|r_f2VJFbps2Y@re-|$e?n_pAh3gmZLy?5Wl9kD(yWr+wA#Q1Z3F zI0)h1f#}aI{S~ZR8FzQZGJs__WpIr62AnZhI`lei#rvM1%(AVx)Ip3kU{p2hoAIjd zwRt9Ow!k}IkKK-p8aq)+V?)0Ouf@0v0f$i%)Ea@6NhRFkZm~Ja8-b#)R$DW-GjYBq zj17elKzH3Xf~Qp)3t^>^IgC!;?Em)i!7s(Mro0=Qa3`aR;n~_|1hU)yv?BC!Z`SbH zw7_XOw63!p)U0pk@ct;uBiX6`MPY;MRE?89E6p}jK*jKRf9Q69zMfs{<*tIubvQUY zcm6$HzG&U`rK+vLADr&#+OXRcBEb3BU;b~Rcgc`*wSTPU7db@to(Kn0wc6s&eqM<#rZ^<-hO#s9(}Ia!m}mHy8%dy7|Qoo z&DchPHs=C{bs2Np87=Uvcx08RQ(GVf#W`3kDl#~|{yZ6Fn@5Vtbt`=)JMfV@%*p`P zw%_J7HJxE#*ZVHSO{rQ8WG}haEA6Gz)WDha6`XPQ^iu(H@>$fY# z>5OJP;|4$d@$v5K_R6t&ne_N$&{@oPYlrT@mbn>9-mq3v3+pAhRDOLLn;wd4oj3H{ zd(Ha9!>-Y&rMJUUc{v>~*O7X=#0nolSLswCK8{p4ZL7qHSl*`%uH*TfZ3Y{4)LFGK z)V<{Z!ryo?KHu0Yw{gBwRb^4g~#R(*>rr-scd?ZiF6 zsuphb;P~P=_h-q3A)`#zlfVuX3+EJLqN3xbbPGlOQA0sBSXwC(1i32_lgSSq7*e1lbXs2jj#K0p5R_T zpFZ@mDT6D8ht5ggX!>i;&2m)tn!_9?hix5^EbT*$TzmJ$#elTO9e~D;6o4mxy3Om0Gnkg=b5VcN;^~EYhktMP*;sNI z?d=#}#ANG1+HqW}wf5)_3CVsL^^Tzx`DZCswS^SVnUDrkBbOM?OzY-cBlY(R#|v-Lf;AdHr#Ajo5AJ<(;8>l2`v zrHVt#`SX-yMqv^uCLVeO-z4rcSBZ}`4~_1Ptder!%!(%`NkcmBH4GY#s=pEk`!TS^ z6{iWY722&xUjKTU1!=hQMMKI2ExxBhnpDc43W-w?3l!uGEGQyU7kpZJ0B+F^7))Z# zN06Hw61rGq9WiV7T8uE!IW-EhE2obVU}q1zYnlS1kn_PN>U}5f z05lqA_(OYgWE;A0{t?7T4|9fPYO~bi&5g`mw7*hr&u6H`?$sy?NDr6u_20)bpUI`) zV9gxcEVB>3@=(bbcP@-?q061m`Rmymbaz{y_3zG>y_2zV(R$TLg1baftv4Y1okEYd zFOk3K20LFF4XYJLz$2lDK(V2%zSNA*Gg13%O z&q1HTH+xBvPd6l#yrqaE%DCr%qJln-9ym_>h}m1|(k#){3TiG9JUyS(`Ll)7(gpv_ znsdT5Y%-aqXk{|*crWrw^v1rJP`kW8(x`t;;s3hGDIPP*9N4X~#t{49{D=- zr-B=CKb`rwd05S)=y#W(hGzygm`d9-;`tCSuQe}G;J{}_Fr6#N$uLdAaIwWm)EbFm zG9qRde=<#2HSOso_P-GE2YU;(r5`ucvj!2f)S!6g?5=%%lFk8{B8L2mkuzo*I>`J9 zQg2YrLA@fb!u_5}F^8!Me4%j+SG6VQ8b|m|y>-BWVm0%xy@R3_hU7veCHM}G^~7m5 z8O1x?U*b!eKoN=UK)v?oI56Jc}5_>kPR z!>_#`n27c5$ZlM>)-J0VU_xlOwA!|aCB~>;KF+3IW{%0kTySZ@B%fGzf2;&}w~ZnU zn%9y-)Yqbd9EVDT(!@QIyPU==%(Z%KxH?-ja^jx^y2D5p`C9Z;vSqSAs>O0)W-|w% zvj-8#Upm5U$nzc|%*A1-SO}4toMxn(Dvm(oZZ73qni!3FPf2X2mhn z5@~HBI3e-sK50Xy@nP_UKg@zdIq_k9uJGrFZqi``v+tF!az$5SKNpo`6wJ*{n$^N; z$Te_fZ{Axw0kS;KOl7-rkYA9(GHl_~%8FP@p>{|m1&GX1x-dL8+G z+dz8g?o%r7D`pvg+zu!~OnhSI=1j@WY#~GSqIh9a?6=Q&VQ1uk~p z0?x=OR)Mi}9G3xR97&`x#s_(zh2ZPZ60y$XO**X3bO*eah zJ0q#Ryh5yJ^rI_*AHkbSL$VM&UCcoT;bzFt)i|b74UEX7L>5uCzwVeAS+^VXHR)UG zTZ{{qN$CSiVWX-eK8SkE{PdD#HoT`yx1N5@x;f8e5q(Z~e!KmBl@!)cmnV!L^9)`b z5Q$`saZ_)$4SD};0?2y0q$^REGJqiQCPxf;foHF$Uo#{Z2}k^hCky^a%YFUG$i<5v zy<7aIfQk=;Tu)?oI7os-B9nEE0}lfA80Qwc8flAZ&SbV)iHf}@mCItEwDr&Ric76* z?A@k3g_m>VJB5E3UbXGsTAt(~p0bVVHWL@`yOQHmAxB2a`VJr)3p92b^|da&`*Yu< zb&7k1+qAhoZ5zJDH)Pw~nA@4HaSVA98n{J=&pgC4W}Cb<%`=v3Zn-VRob?fgNrbuU z2G}^@nC(u~oW3E-v8hN%H4&>Qk61xwBBG(xO@UPbx14$*_FM`k4@{AQDGXCmltl9k z$P)AshbbGKCz_Bcs!&+Yu%vE@{mk$T$>RT!lp!utP$I8XZn5NSNy?J>P=KlkyF_To z&>ZxUr71~Mn4&~gmb^r1$?^=^9HJ>zUAUrzMJ_fGz9DWy_+g#jqA2AhPdE|4Nwh)I zu|?E5id5gNPQy#oLxDdh{ ztbN(p4eW*2?9gIy(bzP5-($U-=KJ{Zz4q7X*9mw4+!^)>H%y)FWhxIeumVgA=0~7I zYQuPQwPoyO6D|qwi`)1z4hxU=v(EG^B7a;6&*{=VgU_UO$2K>wg<%?qXh5`xAxwT3 zOWf^ncy~xJzOS*Y7XeNXH9<_T1^0jvUn#LXM!EcDK$kSnTuwzrK|$rRCIco5+I3{i zyF1!Saoew-klUH5<;Wq{q5=GkvRevrk^`eU)M|V>8fHWEGG5fP9Z}Q68RdNEn-8>x zxU~s)sVU)?>I^haW%Q8ij3iR{X|b10hipO1;CCl&=#CsBXe{Z@uvipQAuH=^!gz-L|N z`f`&rs*k9}Amr1|c~`Qvh|2AO{gy8mk$gk6jku1)9I!ydya~|90KRZu5FCn^e$v-L zyGs-XQrcV7TJxnavut*K&q!N|r2*x0zlgnrCy?(g5VlhyW1n6q~ZWTUr8 z0q1M0rf%43M!sRvAYMa+?yvo<^xWw_9*0}5#@n6lH=c&QJBThn7_GPvQ=(6-a?oos z9MR~ty0v&ahVk_&Z1G^?5`0FT@p1xG!{q!2I@ANeJ%Gpw)NDvf$mu)qli!Z`P(vB1 zwf;xm>j|Sduuw*=2p3I@(u3Cg3iM`7Y|PibA2DtadsO|`V|T`qk&sZU=Al(c35Rsy z`538G`QE_4<)X-WIesA=L>xN-S7EIpYZV|qUdY?jsE~=V5uj>KnIdK+9LMsy2te`N zypb~!j(Obd5Q2mp-mtlag#iTYWzdcu!aWiN_-$Q46OJ5MPc*Kc*<@T230G5Q0;=n@A zLn>rX@tH{?6%ZgWOS{)lCQ|t&T_s_MbXC!(p$DD~87cp&Qy=Bosq^y`*xL1;c)6=Q zVk=rA_)+0t1E3W`AOn`YVd0AHMP9k8>lC-4d7$3(ySy=ZXRhixei}+ZWd$Ma1Y14g zxWLk*p!NlnB}atkhIRyKAfpHWITS!_Yw9$7Qd81vkt|GfqEeMz>Hr`roZLrj!b+QA z-oPplb-H1c$cKHFuv`oMsFaeR=vXWyFypi9IpcbIT1F~78=xxkq(pr(=eQ(M2&G&V zrck+e>V!8eJ~}0P4so(soz?yR!->A+wdTSN|C-J4yeLx*nSQ(sIx^7q5xvz@TbtAZ z`1mzs*65MeVHq9^J$k5K&WOs4Zx$4e*cfVAOTd6>%KtR^Vma?d7ypIEP$3Mru2qVC zZLbF&+uTJZJ?_Z6Su(o;MT3>giW*qoo6QGit{)2M;d+D^`#HgIko)PJq8tH&M%a3X);jVqr%aP(_+*>=XI4s(qHT9b$th zaJ&fkMJ#pcchILJmx0lg>}yR>HH!LRcFc$6{J2@!ofrdV=uMqH-L>c8dq*M}7Ofx* zb7cf@0CVSHe=Z~?M0Gg@bq(*Kw;?ocxR?>eOaJ=V3DXkrdY5MaSRr7cZy!%PN8KWY zGk75HklNbxo@)1}zHsyfdX`a6PP;=W7xd=eYFpnUtC;m1NM% zZG!~9HrJ3~^(BfH$e9|lg1g%M+!j1Is11b0KbNzQOyx<^G{Q_l;XF(TUM z)HilJ&1S6_@Wc0c_ditzhWcKc2<%CNEQ@|w8NGHq>o(02($u`6HNFz^$loJ7mfR~~ zYU!ngvQdLPq#WK;{Vj^Mf+G7jO+$@2mw}_~1NLB%2;F}q=KOMR2qOZeY)e;GM2fvo z#Y&jmMZvMs$Z7+?P6l$ZdfHuFCJV-iT+P`LHFUHY59@+Ngs!QGV&di3XL8$ZHcDU2 zZXN8qzn|DXJD1C^+pOThkdl3cgJ*{ZoSn0VIk!f?p=gtch5LF^j*2+P^eG3VzP|>L ze{yqOeIrEk2#yYA)&yfihfa6uhz+(q>Fm_l(uVc`>39Wr+PUUFOx6j&;IyVX{KaCv z?8Skx{ao3Br4v0xfKnm(fr`> z>=r!#9v#x2XC(JNC1$!Dx;bM2!SH&`Mz!GBmOz9GFF) z<1K`keyh}fR`i}>j!jNpWP6sB>uHR8Ss0o+4loJm27&QwAxEd-*j!dB)VwNKx0Cvq}^}&mch(r6X@Yy&3ojf_j z?TS868ZbYIF0kr_04rQWHpm!8e6HY#JR%NIhV>ZB0(hOFlzl2CEjhymM!UtUb9O?S(mK6)y9zG1D7&v)_Ka z%Rn9d@+k5*82N>gJH^Sw_LiV(Vkk!H&7m)dFh$5%@c)9qcocardImYTCwRQ0G+Z;5 zXf<-wouhmBDtT}zo04K=8L&fU`y(RRVG@;?mo9&;F0L+@Jp>o5;>$F?OK(pY+ z%m!6_X+UBpW}npl-nQ52*`nCUOqJu$^yoiNB@)dIsNWhGH?W{585z z)8@B5=#u6?2~NRlcF5POhk=*sf>Rqh2jsu%OKWR)bHC8crKN(4j|Jqal3H6&d$ zCubh*Z1@WJitQ{oia8!;T=Vauckcwl1EPc}L1Qkv$gd}wZJDrwVWJI_tTM(%jx1gN z6f6U2L>Hk@A}TgXn@!`L6_*CHWaFgAbK0=3p$dp1&|w!2(jG=@~72pXssN{vL&7lE9~%9se@EmhB_#N*i62S76IoORJ%PrX=Dt<_C}do*wA= zHsKG-6XC0imBh^s?woR>?vCU;zAJnkTj;MmN&znYO9piYC>CE`1TWhskm{)i5l|dN z+kx$B_spHC4{*}C(8*#EhchGQ$EsQ>t$vAfBsG?PVf}QN#&zNS`#t3cVUp6dQ192j z;z5lYq)4nkSFA!>e0h-vl7=@g--l-qS*|-6R$3)~izxtPkMR9ifPNlqag}u72W9Wq z?))=g1PkNOl=u*ON#qO})PlmVi4e=`N|f=3`JxSd-SfO*k}2_sSn5MAgE=Df8KpBy zciQ&z5FX`AT_H=Ih7OA2-XCY`pgD5H=@8)W9D3-8)gyrm9sNFh>0Uk3{&ao2Hs@KQ zA`@dnq}8Gmt#up5I~min@7)y&T&$`uTSt`g0YyRqou)|HV$@>z_2__`o@BAm_K}oA6$BwsQnD%`1}!>{lU=g9{GJE6VJdFagaCW1b!tmhmxAi z4i{*(d@Vb2T#I08+O$*jt6;0!|*s9v&Vdqq=KO*e}yNj1pCNohLlxppb>GZ z<`~Prr#hZyjYW$a75Wg&a*dD*?y9Ac$XpN#;Tl1%m)0<)`3dHxDJSnG6FosdGOEfM zl%qsEAaa+^Ne97#?t-u!xlcQfdIAJT?)bekxkcss6!-5(R*I&Qu$lx#Qu7frPf2CO zCIoA!{EnKIypO01LAtTSCG7(?Ezgya63eF5-^;d|e5;`2_a|DfXrkoqVG+@?^83f^ zViB!{De8|^an@QJ&?qOoks{$YSl7)(cF2oldCd81cog?Y6gzh<@=ttZcx7S*$P3&7 zQ!Wcbg)G~h6aj}pHU%XA7H9-X!J2JupPHH#J6+ro(UW13Im~FU8cIFy=0*xK$plp+ z$%+3QGqf#MY|nalqzg%4$i`(piU#n_yJB9pdbLD_WRwQ`Qs!Gbdy1_re_`nH=#r~n zC}3)?W-|e`R7m}_N zkA2b6zm?ARHoLJcNUWdl{93SQejvDfMf+l8v@T;D=X{SyH8VuB?24Hox&YgoT&|%u zaUsH_wg54$wy_eaTto-DNt zK(I;_Z$LqjF-YEtRci=OCZLTF?GCe}hy9keHN|7||l4>nvbvV5*^k z2TY9?2zz{(?W9;OeTBRDPJyA z?3(D46;=bI9V>X^R1#!w{c=ATU9X?MorX(U{yzyOUnt**UYvJ#9D>{f%c&dIvu9z; zj`y}E3HILn?;J7O9c`d}`{#sT%<0jLaQ-7t;HPm{#+C=qp!Ed)h7OAbMQMmW#Q4(X zL_>Z^xgs+7+pWSF&Mat7uh+34IN23R+S^jpgiaa0=x%`{>ht-Uo{Nf{zDFymeB0GcN39fAF{byF_Zx+`*ob1aTD=f;qFdU8)@}&`#Pe&fm2z zuxCPfL$SVy!7Q8-d%i8SXY|Epmy3Jo=i@rCS~H$LhRsiN{>Tpy>=4!M^^cW^3*h3> zeQNBu)DO(91lIq<**gY_7Ie#kZQHhO+wRkS+V0b~ZQHhO+qP}n_VgRX+#B)DiyQN! z_TL>*YptlgGb=L(R94HK?_D5mcd8la-(*x)$3llO0tuT)vaAbK<_(|s#w=UvnNJe~ z*-Ac)tae5do0afRT_rmDeaO;t zwXSj~qX0_YjF;x$&wr+K<)m9}8n5nNu*b}O6ZH{qBX7L)KakBI3@1jwH5DW}*UgHj zu62=00lgB;BiBZb_((w}M*Y&qRUG zK`FQ1VA8%iJAR}wWJDKIh zl4sz)1}i0haK-5YA#A^p&&c&VeWiKTZtcZ*lOvWfIgrE{Y&jB@SHgftS5Lyg|M%w<`cJ?`SvO%bs za1umzfIgpiy0{8N;eQyJNb`_7M?297#%mZ6b2YPBLR|mb+)dx!*IfJ$9Z&22ydYs? z5GeSZa2G|XLP_Z;PF#iS$tq-+p%iHv#Yt};XB{F*adp(;SH|RUBL^JD!u1y$o@q{UVJd2G~m(tjm9v= zz|=0FU%IIG+JM7phlm@i8b6qolwR@U6CqG`d` zfudAKn4~?_JrVmQaLXKSju#HvQ;mRs16QZjAb#*eHu%AM1|@`+kNgigNi8x-aT6T1 z^zTyHjSxqvc*cCd#d~I|7@37iQB8qr`VH%9rD_cpm_}V=hY!UEbl{-lO?g5lGtKx@mIxus zn5iMHZ%~GYWBX|O*k}?06>DQrh{&bs!;oG!yMSMaQMbhZ6;Pnz+{gMDi`9W}o6Lz- zY;?h#Eibn`RCze4pfQJ6c|ZY!f8+BJ6Q2Jk`s}}0PX3SSGiJvBMm-uB>gnt285$ls zKt4P?LM%H!KRvOq0EDd?{#<`F9h)Fuz?%i*GezFHIP=;i$trOv_ft&yA}r(=#%HOhi#TyGTh%xgb48yEsBG z+Auq(NHj?{qyQ;GD?v*tDyC>XI!U5t64V0j6m^x_^TLk?>qgk z5r```$1f}{PV`J1eCH);OBWO0+vpq5UcgcGZojj0|91@yQLE7CbP7e+IPSrpm@OE4 z&S;N5bK7Bc;GjpmdE!k=@@Vn$Y*w_=Inbw#4m^D2K>z^V7(D%_TKv!40$Ki>S>P}4 zYaYaa(0M}r{3=B`Jp256(%tR#H7oHxVk9h~mr91-?e46IxP{==Us@Xnr^$x5S2o?p zdU6tLJTg$x;?fyA0qOTjn#D84O1(OSD^G657n-(y?J^75cd@oq`_QF7lNZr@sTSQ3 z*zpi-86&Q8t{8RD;+<&3fYnC=knD8q?{9&nRImW^1#OiioF-=P&ZApxHdZwCa1=ZX zNM?(-J>HT;4i4G-MPEQlikZQwD<6S0eZGvq4KsvuyKvuIi8HBm9((8pXD1sSW`*V= z)br(sFSTt6^GL{`M^Z$^WT+beQ+M+lJ=!e&r(I+Hf5k}u;|^wHVEeE9FRi?+JhB3c zS54NxpuEyLMFZ0u3|JAx;D5g&Qwatn64s{OehCS(1<0(zt3>@=Ga@FGAh}}NqX89v zFqGzM%0#Hue{RUIqZ6Q!GsAXj$G4Zmdkt?SUn7 z;4uEbO}b0GAxmI1^o^cu&}&sH!yfmkZxie50KO}9D|Kx}(a+K-eYjA~=&Az1ft4dE zJKrdAudSJ(gKn%;65$BQpqyzcO3@cj4qAGRa)oQ9;aq`oiwiqDD?vawD=}jG`9zSO zK%i&&5-pTrd6Md}BCJtdVEN_gojY4uQbnJhp4;@)4Z>O46r^PeC}=05MhcO+;`TzK z@~^F-)3R?f6Qwxvbfx+J{`MkDNRLj#fa!wJr7af>&`JytIBWob!1KG*rG0$V7xnc; zz!skP$uvXJF_nQHZIvggQ7ggK7Fc_bH*+l7C7!(U1-U-Se1&%0ZAWYBTiW9$4q|p* z3!~T>Vs);Lr+oviueTH_8>HP{<#{o|VC>b(KhIAr?wiN+WqxYg^@NX!*GvaXgY zJ*m&@!dkWm+Drj%^#X;H=rZ6oz88Fyn#J~)-I1pldJ+5dmEEDHNe!f)F+t$pCx~=;GcNV@tJVI@w$xw zXIe&rY~fTtxc*Mh==d1qw7c6tA{ro8f;xFJKt(Q=dva8kadgQkPenyeMrh;XP}xJS z42Ot-RGH&vqe`^9ARbWKK$)%)FIBo*7><#blCp=6mUN7ehR9)DR*pj{3;zQWNE%FP z2??z{855yQ=^0?qkHRa~@x*g7I5cRZ5=2Vjxi2(;^slVS>k3nKILgk1jH#hjbQy5o zC|-|lolLMjDjj#=(deBK7(?6`ggG8_DAIVC!N(vot!|RzSh?MTXN%p>Eh=r$l_4RG za#G2djX5N3V$$H485)D$nDBwDA@ok)6O-SV@nD7`jdW6xELMH2Rh@$%dLZ{J;4{t*Ysn~N>B4o0$>K17GA59UIy`v8dW*&q=H_52ic}xVlMEy0ouB> zD7+nH&5}PF3y%706pYoJc8e~BAMtE#y@9GCl87x~$iH+AtaEv%$}UM)rqIb~0$|V; z%JG>oZF9LYl473%(4``b}QK99jS%em%Rem+^y217DyseN|9oR0b{DkE$j0g zryOWFqDZiE_K6G2*!@%Xl)cC6C20lBL1{_u@o=+Y&J1*ENtvP70sY&HecX5Hcoc2+ zkiZTgZUclvSIsV2ec%eySEqQ#W_^S{#$tx)&~jusb_a}VYuI{OW&HPH2-$^;?6lybvqlR$q6y~+HQS!2SjDYlu$Tnk^0KZ7&pD}feASa_7;9V7$daB+ zLZ=XEf}8zo96~9xzbO^wy2%qHUnL!Yu?k@pizQy(fzN3)mx8>g$$AzGqZO%W{5orU z{Mh2g@>uB(g)%N^La}%@W1vgkNpV30l>sTG9??5&;JDe*UOh5C*}I+UL)!E9A(;G4;Q^`}>Em1qZW zLog##P=~Z8SkAwUp?m&Du{Q`;x0n1@`{?T;i=C^7q8ny(fW1aJdNKlBme&@v{(W=P z-CPhRbx?D0jFLm@QQYM$XeTSukP(k}4sn1nvb7-7GASWd_saDSZCbw$Y}EV(QoyP_ znLG7n-c~t{p=8279@^bWa;l^*-Jd8sQj0yNQ&9VMxV)miOHh0@NNa!4Vo(Mb~%k1f&^UaUwN|hM8 zcOecy@<#Gp6%Qz>! zcLiSSj=!d#D{gqGiWHiIb1RLQJs|(9#>n3@Psfl0o)? z-`oV`e5wnKKQ=O;{5GIbJxs$D%^=KWOV7sQBStkQKUXbB_*uu4u$UAZyqM!I*f(Mn z`AE~P(OBhWFLU@>3KOd44kWzXSjSnHP;Q|rQPmYca&j8r{_WeO+Q#iWxg)Qq8dBSn z3G3y8SLTA}%f8V5D=^%Eb;)aZz-wHH8Vq(~Ncqw)akGL4aF&ZC`x?_aufzqNMu?d5 zTNi-{v9$tu?FCZ%x9d*JqS*j9P|2N*&v;P02CSPL&c6qctrQB&eXghn4MR5eXjA~x zY|(@zE4BR3N;{a7gs$dC4a;E4=)ujTSl&Mx@aHsTQ0?Hlkyx6}> zR2n!Ruu8}0IPaue0#QDj$MQ0%$zLH&a~;u=oi4%Df!Zu+&a;C2sS{%z4$EHjTDBG- zPrdXZ#!tE-!$2bTg8k8w<~>H55u0bQ63 z_7Tw;UdjB$)PjWz8r?ajSgDn&>hI@C8{S~?ob_!bzxTj_J(0M))TxspM4Frh69TFs zkjkDQPB18;W-UQbLGdR@;0vC z=02V{#B>uQ3|PTEW;Oe`n)9@p;WctO&s}jy`f;qeTKAnV|9kPxDbNK6K&4I=zh=;mcyl z-?`rfi!?SV>7k2W)NmVxkt`HTt3<}v!`rgh>N@F?RHGexO1mJm7L#ckI5;$Xg}QT8 zRJFvGRJh&&O6$^OXviY)Qsgl6tCOb(M)jngnn2CL&EiFWJV;F*F~50h-ZmmM*Dlu-;uiI}k|^KC$hj8v1cI=$*aC@se$sz|RmQw1Xp(YJaSg zY*drVouBTY)#Yw&DMArNnYZu!Ux0%cyC{SX~iJbA^5M7 z!0j;tAV)+vwZCun9krWlbJ~*2)<0r$P5^5yT!~YxX_N*9YYzrZx)4>L8a!+}U_GU! zb_D2r1P;;Ir<25aahG|9Ex~M8Z{=^nX_YFfyaw%E2HiMoK2DH@hO7cT4!D}bOV+0q zBsO7p`%YKWFL{_!{*hwXFvhgABc_UsH^ypHa!d>duTA=~+zT0;$f4+UyV{c_ms`Q( zq7-l%#&fDBQ>_5!O>q&`wQV4_u1A=uCQ-EyZNSzT;!XYmGSQ?qZ*u}i=0m0%drlJ0 z3UdF-tre5Q*>D^8s>=)0FISEQ&15>A#pLu17KY=SE*fVbnV?`2du*SluGK7s8?t=J z;JKS9yxLbJ3>@$1;TcE{kV`S4psKE*tbc%G*+Cd+7^_sUgnbJ2+jDqUWOmkzPYVD#f#u-bvtn_a7^DZlI4 z?6%74l(J2*qdvBw1#?TtjD1Oes!X6|;}S+2nuWFj-nxRMgb!4xOi#29=l;u{DaAT} z|DGZZN>(WeTU_2xta2}LBNbSc3V?La!l9wj{Pr*?t92Q_5NIc3p%c?ys3mG0qSiMr zCMF+Krj@6ffZ(O1AXj1~+dhLK9kyMUf<5qu9>Vk755Z6*xF^V^?_vF8zmjy-k+7Yh zf_T4ZX@1y`$4AP|=-%dV0cOzA8!M=?JM0s9ed}#-;&^&{ctHqKbPa-#-qdsIR0KSI zzLhU=wltZ?1mUjQ<2H)2mKY5#GFzhPl6Cl~Ry@E2M^DBBBN96A`KO4l)RV_wtS>k^ zkn!-f(c5X;qB_WG9(S0C<&fU|p0S$vs82u~5I==J>QM)XzV}cW+*Hg{Le&R zMpSVLG_DADJXx(eHMsN$loVe|aX8*n--z!aW`7)AV5+QK9C;7+717#quy**mVLS%8}q2^HrW z%Ad(!$m8-C^UBE^8&JR-9$wFzJ(`go$`%@O-b=1sdk%OotPT!a?6cfilwj9WPSh2} zRq(FU(z=i1IWRz{TtsJecj}0cPv|j;(g6-GS$K}z+O zeW2he60$-K%f0}ZFpc~FK{6F0CysYWZsb-R&o}f8fapY@j>DC(-f( z>puJ5%e>=cprR2{|C<+_Rhx9NeJ8HjHTVzj`OML%sERfPrcvf5y96i%#+}%<*psZ1 zJ4Z$E?eGu0y9c%(EnGvdH88v`EB`x{e1)uRFbG{EU2|Dkb@v_ZkpjLx90=&phpk3q zeB2s6*zK=ra|8vV!&H2OOYLp6QYN)rQ{-e=Ki!{!XPGEAR_c-a8Jy9%kVG&|chuMZ z@K@i{Nuyk#{@lOwQ&~5LojnPPeLyA>C|ZR3i^~t{s2X1V4sEQ>s#d6ec;!+L4igdg zu()+6dBNj2p1`x}aKnQGB4_U1rIa-X{h+Jr(bq@h9 zWN#K5a8SHm;P_v0W@HiFJx+M|&fzYnGVoST6}~(3jQ20AR{D@3^I0!}#J7N2twU2r zb2lw)fu{Ok_2X)xigxXiiZKC&`%ZJg^fU`+<@VB!PkI2;gTr%*(GieKLuyiP{1}ZM z^^o%SnW4TzY+?yAytF1u>2Bs!F4gs|-5ZqDti!rlyd2{;XU!0!wIo8S3W|;J~#1 z3=qw7MQweO1zz(bI(cHS-h~>0!wffXK5wav{T99+Ef+()o9Q3!|;RbSSa2rsopxo&G5Y zn+KfZDVrz7?DR)Xbr`Ds9Cjp{EC$y>tZmlARMc*Ed(sR8!}N6m3L{&*^QB9lAxzOa z_f}yN;1S>v{w|{5ClTJiC(Z89-XG+(=>THJ5*lVp^O7;y6S>LH#I!Z61*+)quk6sY zwzZ6j^~D6mhq1MzdT#yb8f4oweohJDwGZs5#jer+RJQ*Spw7(l-}1Dy<7I9B&$4}n z<8YfrBAhPEL6`*+KGniUIdd=kKV{pYAFQkh2U#}wT$j7_wYiV7goV^`qL*SQG&8Ew zKNY9-z1*9XqBktG4oVyI5>=(7-<3~tul{w(=y~C(KJYjZ0e7P^ z8O(?!R#pH(DW*YK6~{ypJs5ZY&=8nnvNx2tonBT#)R5w`J?ZGTfR>po5UH`j*s5#? zoNYWO#bK3jzEo&`Lq7-HO8=h<#Pa_Fq5qq?V)<`iy>|T8|C+0y|9h?=QJUo-%7TdO z>hS;9T=hB1kO~r-o^QkqR%V2Q%$ZysAd~B87zUZL-svPAlA6vp=BqG_D%cT#b2Rts z^Ob2 zcHxYWfQ5y+oaROFIVPf>2?(x6h$6AOMA+|={Q|3+Hms{Y0<@O76kSTF=ZGKTqqQo; z`S=FGg^(>xAW@0VGoZ7;cn=RKA4JS_y%#`6I$22IAULo1K~%xT`c|h*n5w7DnAgj~ zxEGd|#-|j5ip7f+DrBASDk5k?skWR^=l#5TGNElTn{0{5$wyiSg8@~g2(1c0J@fhHAf&Zw=sRh&Ue>nkE zt39nRoi6a}POSQ8cFu6zwXRVxXX5;{T1u=nv)y1V;Br4$4d)gl#|k@u{zw3OAeX%7 z%;%(80X8hSm>b07m#~iKIy_(H#RP8naN{AdSXad#nB7p&YwUoxVTp|teO^s)l(4=l z2i@?#wO6ATXvWir@H%>rO7)XHi6QGF8*Uy<%>vvXfFms8TAP;`JIR`^FB6^~3Cu#= zPu!3Ey9}Vl+}{Ar-T0E++EezH6PBMX@1S-UkAbgAw5-Q+gVXw-fNexd!~WCc{1=Vr z|CLO@NWjX;%E9u#Q9UxUu(PoISK8~!3tCG_`TLdgWz*wiIt!c`82BE6j({k<*Z-9c z90f&`L|{Q9A`TM-IT+(MuIW`xbU{V7kLfR!)71FnI#2_$R$aa*LjX<0f6}wLs}^Ra-VwiI?-GaiI4dmrxk0^7OAy zAIhCdpmTAbXVJR)QS_aRH$*iwV!K#*nbS(-{1+@SpMu6<+ovqf%M%z zh`z*`VH$IUr}TMSrqJ7vszSp%e}l)j8WA-jlNapInUKPb9`E%zm_EprUp6FpNY_d1z^cqqk?+5rDtibz*kOZg3~S!$_geGo1V`t4K+MPrXbSSt_ci$B8Wjdw09YH@9{K(V`aZE$?nHL- zAVvSfEZZb#1z#P|X;}5pWm4l9e^*}w;7-ZC`d5LG9}%KWG2*Nyx|(r@Rfj_lvTmj;lIQU7rr3$?HJoy840x$ZDO!8-R}1lv z5*(I#^l=1Qt{IAsoC;v^qnYkwvo%o;)CHjbHC!$hA^ez#gI*)VmEgy$-hAXT5Q$g~ zhs-N@XBL`eR1?{thQWl};vQ)nE`G%n*TahLa=0W! zLDzZ8Ced85jA<&dL?^fvQ-)*cst5{A2JHy>6)auacJN^YJzNf>nGFIq@yIPE`GUNr zc%&~Zmt2W{Ttmoe|Ne(Vny}?St0;9co#yuZW}{t#>4!+y^CG3w;z-WH3ZE-}OHS}t z^ME&Avu-4@g!s#XzG3ZMACmu((F20&};=yXh*(Z<>LP2UDy;e6Xnt`QELxR+}#kTXvf8_fXT&-UIxIn=}Kkfn8w zEy^*hb6_sSq&}TLLHv}Ju=KB0YyA2&hV8W@7DdoBk*A<8^S&)u^hx1XmhKH&^mjuz z7d0HK4o_bVXaq|xLoSz^WnCWHf%`@}-+O7UDUwmlB``*2fxGSfrrAUq3(kl-ZF8h! z#!^3NF>wXx(8!#N^;7c4O$h(Xjt}6)QVpHfpPqjAygy!=UeG!=1Iw7M5Ywg#UFT3u z3QzkTw?dxS-4XL&ghHRN0`R3l1KF|~uGOMy`NFjBlwy%Y6kymi;-2XNWxwg|E2;6` z(dA^Z5QV&`BL1${$F*t|v1ze_q6Pb5=UuB`WZA2AUhFqnvs`%IoHXJ=>Y=ME^NeOSU54}9v+>x!{Y2S@ zXiKP6&?lzjIcJW&H&6mT5k!BxO%cIr38Hr*kuxrjTzqDIV1s-t$`*Xln-cPXsJE!# zWSXspzZX>*p%JbUNFSw!)y86uzK&{^6=qBZfSD#dS8HK+9Q%D|mrwVgkr_9=AuN&= zO5oeM7zZCYX)E{r6rKT3-+DFrC~qe*9kqAfw$GjbuWVl|$zlgayBl~d&Nem~=~ z_^8Zp@@NB?=Hd4ZSUMc%f7y2rgQv>fV)DPK4y^6G>s#e3B8k}3j z>mM0@lAnwOQw#>u4zMj|I~66sZ1^U8!hKrEfSaat>pFi6u;PB^*=l^s^9awWdFp07 zK(_oul?*l0eMzB5M$&mxcYj#dWxLAMJ7i8plNwgutLI@xID6IqnFsFs}zgJg1g8|!NxQ-Kj;0f^=e#Zjj^cZh2*~ zs@~#ghTECiMF*IU+=~SI zb<@#Y`-FwuG-zdQAf$}?@IZMO8G7oZ*T;5OP{-MjkeWl?@1&I1phCvb@Cbph9xhU= zko`%8NcvHbji{YWCixV!3*-$oYTaEB%w_>k4A%W|jJU*4VGC^QiO_5BrQDH+*gEIm zpldNPHw3tyVEeDXpRD-9A6SimM=lyWqPd4Wj?9eU5{IDSwN`&k-xIr*1P$+QYrn-% zRr`pj7u%P3V?LNlofw0%60$myZ0_9bJ)}iZd)odxe2=-k&_${9w;d4M9(}VXXY-QlX(G&EY?M7i< zkGzM!*HP0wEY&&{jKR657?ig4QQJnUW67S9b(l&@)`C zA!*~%Uq=i9FPuoA?!?C9iLb7-@1-`-4g9yvko2Frwh27AiCY7*h+Av8t5Kt`RUBMg zvRAi2`SEry6ygVt17`TiAX5QTSa-41Q5<9Q$WLF{M`5#Awe(N6*(nzxJGes#b7;zZ zt|G)ZP)J1f$&l7a*6?bt>)6v)q%HC}QH*f=O&%fH)$;X2q7!hhw3(D+{=B3o_uiAo zy-3HwUr_3Pqdu8UtJHYU=kt{=A1u8VchpV)U8vLGE%5dWsJlwm7QcJX%7PvGd)L(p zrF!5&e)xTNtoYP&ezWAVv_tl4hAzgqO~x%$NOsMLM;j+fpoLLA;=Sz5K+pV60yF=N z$}}y6r9YL6iN~-jd+oFB*}x2Udb8QzHVvPz_1rg%twxu8596L$uJD+(FLQ{8aFTNgu}wXBRHR@R!1(<$3x*Odywj>?P)Q%3 zIzG002r}A~(ywZa>yYOfTI(}`bZ3kYya2gPZ09rdWstxr#U6)84GvFhXE8vtyXEv3 zc09!yRxeDfK`_2JhGrdJGHkk~wyUzPvSV)3?bLl?d5gMMzAwLyIfQSQA2azZ>Hi7W zjjIXr2_Iv&5oNXk728!if51p-`Pa=HFH-t*I+@$4GcA0$Pe zzM)iP!KlPwL@)ICSwhJI&O3{Co;9QTT*xWYk<_)bb)0oxXR2noCRNRWkeekz zA4mM)fnhua>@0j=!S7v|;BDHextRnsf8F*7mc0)Nh`?~YagNzPIPz^F;0N5cz<8hK z>$VbBrs&bm5{DNL0fDJ-g9Q)x&TB$o55Um4b6nKC+-igwlVJb+lmrL4=)&>KAKn7t zt|&yCEJWb1{%N2JYX=frQLt;V`(f8q9F99haAt)*<>QKnwsFY+CK#Y^wJK^+(2cl+ z%i7xS7}qjX_8DoU_0|PhSgX!BPXk)g58mQuq&R zt^3zm z>>$vk`d>j3eUV$gNfa(?F6f_DsON<5{?MVH*rN*Z>e5fb>rhrNZV_)6M5K)=e%J20 z#`{xtEvp3;BWYI>xERs4KVKQp%V>4&zGl2SMRFxORV&_Do403hBeY+mn}b@LW-ql# zalD7{Z#Cs)%)1rat<@hX#hkT03C_ldJVK9{Cd4sgx1Oe&O3s`a(vgvIX|joo+&V{I z!UtMjE*KtV%npM{Fo20n1gA>ihDX4#!!HMg48qpp*nsnLL1HoqFX2qQbG4t@=pqlDqD-yNx6~xNJYq;V<=+mK=XN z-Q_Pd#o(H5cAgfn+pX?U|Ik?aIIYeOJ&bQM?gd2idR+!IF1py;tA6LdKd15q=z1PD zC4Bi^N1Od$h;UynZ2H+?l$#p_(M7ktN3xGg*6x7!#- z$awtGrbsa$qvoHSJRH>(ug5}R=dUy(VDWdKu6tni;Ob(#T2%)m+3&RobpA02VENyz z{3(OvkdXOAu;Q2;0oYS9Xu)B~f8eI~?q7NIJ2`-5ktf=ux)obtrvete348vOqN7W{ zzF0y%~Ot960+t9Rp1FN3Myj7n(G3}fa8(Y--{30to-A07jS%cLINiF*97b*2jU zM>|J*1Y>Z2DTRm}DifwH)G`4a;)_60dqSHUCT`)-)4D#EmirKM6XzODzgdflejlrK zBM3~tZgY|q(#uI=MsV>^)D{OK9}>R3WPzBMenQMJn!tIRE(n{}I4A}cMPZIov9miT zQ}54YxEl+5yx~2jP&$hee?P-;s`0dFn=8@CJq#hXzf$vIEY7>#yA40UZ|f z)X}Ep{-FtE(Z8~4CHph&`UwX7!`e_yf#V0Kej&R&>ux1tS_U=@>`CfYvHEyjQks<= zGc}9tK^ta%>+U!)QYs>#qa76Ocjg50E)cv6eDv8PAoILu_vg8L7GoG$h zz-QO+q2Q=!z-~)e2cRzkoLRXb5~GD(DtoSNLf@kE$4N!2oyu&9;(QERTS6_el;I*k zReMhG69k-jr%k7Rr}&Hy(iXplz>b-+RG+Ru3b$ zDxN!a(+wd673-5pE}oDjfdLw@wurCueV;_JE50foD^vdkl3`mdYxFmoerM zHci!#%=y35P?!S4C;~Nh29J1DMVzat5j^XCuLTaWzoND@_!}r*z*S|V;iw^#9*>rR z#!fajw}N;TYDW>RCa$Hw(|_8ZDpbVK%uvlxM`q`?GkiLnDpthk?6%oHm^|1JHuqih zE&2^wG*`q}BxPnP>nJM|rHI_Y;chqi&&bX)Ck;XW>nP3Qmchc4j8^w?28W4-xY@*s zGSTDvOFYH(+RDNT*Fo>7AcTuv?|LOeDu>RF-)G>5d%`G@KK+RISxM3RB!25?It9=3 zT2yo3JTY-AEDSeM^}|nfV+dhXI)BzyoWqP6>}=zGbp@nJ%o<9ZZR3?sOd)*K=#K3R zXW~nK-hAG|WImfNN(W~PNftof2Nv|t5DNLI5=SpX$CD^bc{-du0kFW-eG=-R&1RYMp&i+P4 z>xO$P{i;VjE9N{%LQdOSzB7I`%|!YgL{}8;$p)&aUv=}%n$Nj4oc^It54qbkN(MT&-L!JfN-iRJR zhDOuRa8u#qST;co1M8s-f+PVX_HA?K!$%{Ni^oSg@1~c1blzY_JA0Zj=pkNn%fr6q zB>yjcnnujK2CsF?s$o`zIA^(fX;UN<=<%?B6wM79^hP_9DhE6zOY`x=k5y+I z^DkN5CLd4+vlu+V*arB>lq465MM_@vmx6egdpyqah2FZXf~R!4zEGza8&00W zBYX!NGn1Sg6_;7R+jTCOxp;@T++@$KVr|Xrz`k|+*+(e+3?nlFT@kY(IdocxL*W#Gfd}Tct4Gn%eE8jb6Gg75-AOH z08eXy*+Dcec7}6f`MKLk!HRZE(%ptB=1MkG;y8_4v-$?uB+_k8tn(Q)p`4s%djG7W~K#lr?S z_E79}mGz2l8Eh9w27REHnU^Diq1ViVd@gXNFDc%U^OCr{^DHp<)EU%MQS1uj<>kb9 z5q*=;fi!(%C}Gaq*(%Vj;q9}eY7HxnK*r|~806729b0Cnd0k&WRUZ38CQXoim{vT4 zMEbB`U~=cCr#@wi)N4|sp9LD5B)lD)hW;*$$TBQJ7*7>F%sgi#YT;sQI86wIS4yi& zOPb~U6K-bo1gvYO8KLxAyP6ek7nyz}W7zgx-?l6m9D#rb9Vw29ztRDn9_Bg+MsgSmZ!cC9oN!^IQlJSkL4?kslYSZS%k>o=j9Dx)#Z%@ z?=G+M#cytIm$KvapP=LZWhFaSe9f6lKA1~BHOq!PHUnzjfers^Vm6Oy(j8i^{gFU= zp@=~+?SRExn^~dc3?|n1Pr8N~LOZ2^Y(EvS|{vi#ojrg7m&b@KNB^J1G->-}2 zIsrMv;O|0~STp%bF`?F+Zu9v0IPSD!UI$2`r8}J%eYf*d*2v66%H{J zH`jBJ&|o!jOFQO7D@fLSy`?r9bU)$wcmgT=%)b9#?Tgg7mw zi;W!HwDk;3jAZm~6E%PdUlRd;^GE-(UF4l`zxefzPk{Jco%r$W6I`Z*%Dqi54Rsqh z*}c?Z-A5>UjzWEu^Re_um|pRzqKiryXQ3daF#GO16|S^ldA{zL_3K}FRX^ibC=fZ} zC@a_}5{{<=2{Nz6TH+Mplg2PbGGU<?m5kH*?` zJp#}V>w5d*Lv0dQKs#^MnlDvG8LYc_K9ykWq-lPqJDg#?5J!t&zwNdh$=?|~!(jYT z+#ii#h3|A(N-jA#$eEeUwA(7|56tzJm2Tm~sv2{^px8?3SE&^p4W9Hz5y2Bv@f0vm zgzYt&Wr>)Qv5EuTcidrzo%9j7+<0k46E}T?g~Sy#u=|n03aWDD(iLw`Q^6+P6;zO7>2jNHfM*$EMYf%!+ zDYQV%G5)dTH}Xy71m3rdNA9MZ9`*9a+3$v8{Ma% zXaANS?tS&H0@7Ba=wuOf^Pe?uOIE_En0vknYZU}i-;4MoK++(!BH8#L@t6djoTlyb zB(a-kHG`m|#KJ5KPr&3ZbdkMMG_@otzh)_dbdkGK2zs2TXP0e8D-P{Q#VA#%7WJU< zVkEm=#u)D>Z$Ft`%B_W6&13#!x1&P)vd{A1-AbpwO+R~A{`V)3?z(~yUe|2Dqaju; z0kW&%U=H_vV3`K5UU?u>K9CLm`ml-56VN}&nNM?7@$qe-~;QJYcyN^O6tkIRGX-3TQN@@5HG>|g9( zAh3^sHv#urHU;mw^J;p}!bY({)R|8A224--B`NR;MWo{15dyX(Dq`-mh{BGL134Jw zQV2-TKgQ~UT@N6b67i{`a-D#!fyRz-J)57u0$Jqv@?_%njQRh3CLqJ_6mwJqq*<7i$yXno%v-BA`)fGfN+T zf_crrHg6*{nK4V&8Mh(OeER~~cYee4?@3B;`d!S%1#0JjZ@cJD!@|Kc>H*8mQg8vI{Nzsy>B3gZ{UXfBgGHHFMC zn8Qf#7iHItm>M`&Lm6Dzub>2s&Did>Te)Ae!FF4!noZACoBs!C zXBAUf@U3|oce-iZ-Q8UqXx!c1-QA^ecWd0;-Ql2(yE_MWILKulCYhW3b2F1kR#K^Y z*z&U1u3GE&efmZ*1DJ-V$;akmWWNh?(?t%Ml7`Xj$Jq`6$_L={NUyjD+)jm-^_IZ^ z3Q{3(x+wz=$WTLG^Idw8bUY(iX7O~=AXG)ch%WeASjjms-=K||e>3S8I2Ht0MH=CO zt&<0H&uoTmd<#qqTpMfyd?Zv;nW1qzS{ap{C`Yu4{M557RLN?a4_^!mr70_OC^yEY^!T>EV%&R#N7HT+`k zjmTyDbv{EeIQ@V136qO^w=QFiTM(LOv+uF6fbk!(U;1tI{L#qq;ZT1JhKv9?a<}X*UR{N*wzs$&%H_{zVvcWJf`s8&?uirTh$NX2$w`aYAP&`$ z_m?$Jy;wv$Ruv+p+ntog)8HG6pa+cx_rrcaZf4{V^sqXfSDVP03p}Ze;%_MD*!8%m zk9y;}B|u^v1I&veeI=}f{=~Z%R(mxJm8A_k;J3k|#h%(@Z}Z%yit*oSzq0nv-XjD* zPwpU+)VX&ZTtAYFd9eCY=JAJ(mND{=Y%zvcQ)-L-G6W8Y$#~$!f<0P-`M$}ZtYEvL z5@z>pF`tFe)^eO6VLk1+ll+e;3zI00d zW^7=_C#X1hUr)JCb@XXtU0CWZ7(2r!moNfa(ImXzEm5o3|b=35vot- zEbg1xeOUUpWj?Q95cfL)`cX_E=)vI=ZSpi=#^>M=1(aJI>P=jhAn170bxp7)VR$A` zxIGh4B(HP+Oo8*FZ1JONb0)1&`|CUS!Pb!i;+HBm?hku(Cbhr^KjJToaSi|H=au)F zKD9mb%=V)Oh8X3K#Nf^GU7f>ndn_G_^)_DCg~{BAD!?m z?>dHiXy5;J1QOeznB&NhZ;lfqK7sZjMvTmP!p9g+v)#9+{@Vlj7$?W-C80yGy^Xl2 z_|$C-{TS1kY+ko*A8&C6WGo)*)Tl6il`A* zisYF{cr~K%I054ds6HiyY$w=EnNX}~$wuf@Z^J=d@4wH$_B}yMsP` zn$;B7tq!f$MjA~(C4;nL!_l;AMo{^{uIN_*^mhw5O^w-(q8mjrp)D zMy&IUqgI8F9q)0uVxEvF5i2= zB>cFLXeCxM&*RWco)gy8gXUZS174?lzFyRx{#OChQgE|Ai}GF$sSyYBTwz2FRPV?P z|MkvOL8Y$e#d!dT}^}nR;QPV0%!f4Q%jW9@V#c`G0#h+6uAnsB_?fK$aSHxPo zYG8?lz`n#{>sr4((&1#KI*7{UX=QeJ>y!X+p5$bNJV|AI-NfzpqR5{MytDCxOR4YH zMckJnEFsNsO?|F&2LFX}hq%ddavIga?a)qH7WU#QV150V32aTtqJIpA^MVU+f7gzF{ael64<*@B#weCslS7X=K<`;SZU@uef zsqCTp1gSpE@}B-$V4aegoN4uGu<2gZGrx!t6m^4=4#aRg{6Lo6q(j7^6m{-aBIg>?rx zw7SmPiyiI=6O68d14h=|`v9!TsLuvIn))2zz)2|J>~nV#S?^#@qceZwks4|RLhq|U zHRE-`9~K*Ig1mIUTEng6Ybhjb1URTdUQ<5jel=k7LC5XoP2Q67FkJ7JJJv)9m+}?F zQ%}|hC<=5IxR(x?6Hn5by-gaiS+8I);W+KKi%TO93MGYv&1yW;g>;`r-vp?`?SpTY z4{@?)021a{W2;L$<%}BHCQF`+bcW=<`B6~|=vjFve8>aW+S6zAP;?FN)B18yDYEDS zuEK%KIX8&l!`fyW>EkC8Mn35SZewP5RG8FX5@l>PW#K9#EPjgZ*8_52=7^zYz#F7MCDbve>CeJQ{U`lC8cO_`-( zU5>a%lQ`H?NdQZ9+O+~|1myL2W z8y8ZPHlL?l?O*3^IldM>+Pps>CcSj&1ecwJ?A6uw3_bHpX<0L7ayz`U)tc7+lrD3} zU7nlN3$|M``qSOsk2&ce0^55wyVQ4%7DI6Eal34?XSoAGT@P zVb+Y?vaShGHQAtqrHhLy}iw+bX_irO0 z^RM($xRU9qf|1*rn8x0&3Gyn2k9td5Oi7+NlXA+hStzU_duj;(^mZk0R}XnVJ(Ifi zx73yFG18TML0L{YurI@WGyht2uc4*>OzlYYN$HqdEVx>%lwNz$nIQXEuaLeaUnD=I ztYQFc_vseHzDx7KWrQbTmvwX#g`FS80#i@jh7+DI#o%;;(;@2b*NTJbfz+NCM!0n3#dtTyj_ z1i|mF`u%>Z*S^Qs9CI{mbc5L3tCeRJfUoSie9yR9bpD@tZ`5h$I;SZ)Qo7aihc!-1 zTLm8DjG-hGp_gkFQjDNkonrNJhvg2#+STT1Ddx>D_(vDu; zxeZd3=aij%rVpx@XFTTk!=+tL46v49p<@_S!@0K|FMS_vWMPukoh3{A6^zcTOf^zO zRZeQYIazx|Pg?PaMhscnR|V;lwN;hZ3(MbFR8x9lPLP{%XusKXV;(6xC8p4D7HgTo z?^XZ4te0TfWL;FQ^w=bVN!NuddmZaFv5v$GsQw#Wlf=GMA9JK0@MZfR5c#*Orv-<@ zr7@HtZtsDNcI)eRYQxm^9=W)7gB3G5236&#+)Y)9r;F6I9kP$x-1Dd%8!8in8L}fe ze@z+2=-SG?T*az`UC6t{gErgwc)W2#pynHn0r|9^i&up(qg4uxd^IT2gw_?odgD-T zg-T4c;%T1dSfma^5?(0`32QLhxfZdnbtF!LlTE1B>r$k+Av|*;Ia|xU zxlt-h8b)vYHw|X5XPfZxMzhVVk9_q+<&f*DA>QWtr%-I68SbbCbRytsb%Ksl12t3& zg(aUaWU@OSnNHksX(7Oq+kDD(9tP(EECcz#?ym|LFd4oSlc=N8`zFLnx7DpP z`PC{7W!Fx{G%Q6pl=tbq>=#8s=-H<$V}j-vC93Kpa}!EnH;`$CBi9} zq*5^zQdQ=ggt3sT18CC!%8b)urBxlsFw%`vGo+THQF|sK+}%0t=;`&Zyp`|Y!jI6d z*3}qD{YlK@G|maqD{CtYm2}Pz@5aTQkBwG zRT4Sfto+mxl(?G99HbnP`>dmCMjmg}G03VF-F4AcvwJxL2d*_#csQY-#9UOKnNYEJD zEcy+cC~bf`LrG;Wbo{|{m>VFarl`l9^4o3}O(<>LS&CskpuA6>rVE^~M@9U8>u~K` zlyFkNpRqNYl@h-OvqXjpYKLV zcZ&i@LG!O6`*c5$YM==B*RCP6h6V*Yk-tbxrW{=ANWSE@CvMO(J`=Pj`ZRw{AHSxg z>SvZc-E-s-$2(+xRW6c`#nR&_QfUWzCk@W~aXBo2MS??^QkmPhOcp>-vgFGLb&`+e zc#%(D@>lBm8q<;`8336ao_lDKZc8CoDfocRX;hLzvZ+Ly~zLz*`e6_*rHEFLrW&TJpr9AfY? z=qUhRzTeJ3-n#Or(|1F=(8ow9|Mk$ z0iP8IjR^~B5a{731pptucq-lI?Whhok=vuxRd{+5ccpY*(dQ_>;I$(~1%z11{o6J4#%;eom2K#V(U{}iIC9LXh~XMOVcPL&2T z*T_0?@=E2g9HFBeO_N2b;5wR+Dz

w%HRB+5lx*)G;U8mtv_@`eyhEm4*eW)H0KL zDIAr(w&y6`%kO@F6L~L*T06%IP&@0W0yjz$LuoV(DDGlRa%Ij`mE3bZW|G$K4wmkU z#K{R#R4PsE=-S1|ovT&{vW=9=6C-)YY}%;Q|7eplq$HP{q)KIzN}hT;B_&L%0Ags` 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%0DdPQm#a==I&_R`flB`rBWnql<$LC?g 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⪻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. + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "../dev/minIni.h" + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/.gitignore b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/.gitignore new file mode 100644 index 00000000..0806f7bf --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/.gitignore @@ -0,0 +1,13 @@ +# Editor files +*.swp +*~ + +# Objects +*.o +*.a +*.so + +# Lib +lib +release +debug \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/Makefile b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/Makefile new file mode 100644 index 00000000..34be0db8 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/Makefile @@ -0,0 +1,132 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# 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)) +SOURCES := src +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + +CFLAGS := -g -Wall -Werror \ + -ffunction-sections \ + -fdata-sections \ + $(ARCH) \ + $(BUILD_CFLAGS) + +CFLAGS += $(INCLUDE) -std=gnu11 + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# 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 VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +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 := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: clean all lib/lib$(TARGET).a lib/lib$(TARGET)d.a + +#--------------------------------------------------------------------------------- +all: lib/lib$(TARGET).a lib/lib$(TARGET)d.a + +lib: + @[ -d $@ ] || mkdir -p $@ + +release: + @[ -d $@ ] || mkdir -p $@ + +debug: + @[ -d $@ ] || mkdir -p $@ + +lib/lib$(TARGET).a : lib release $(SOURCES) $(INCLUDES) + @$(MAKE) BUILD=release OUTPUT=$(CURDIR)/$@ \ + BUILD_CFLAGS="-DNDEBUG=1 -O2" \ + DEPSDIR=$(CURDIR)/release \ + --no-print-directory -C release \ + -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr release debug lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +$(OFILES_SRC) : $(HFILES) + +#--------------------------------------------------------------------------------- +%_bin.h %.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt.h new file mode 100644 index 00000000..29369887 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include "nxExt/apm_ext.h" +#include "nxExt/i2c.h" +#include "nxExt/t210.h" +#include "nxExt/max17050.h" +#include "nxExt/tmp451.h" +#include "nxExt/ipc_server.h" +#include "nxExt/cpp/lockable_mutex.h" diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/apm_ext.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/apm_ext.h new file mode 100644 index 00000000..e032fb35 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/apm_ext.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +Result apmExtInitialize(void); +void apmExtExit(void); + +Result apmExtGetPerformanceMode(u32* out_mode); +Result apmExtSysRequestPerformanceMode(u32 mode); +Result apmExtGetCurrentPerformanceConfiguration(u32* out_conf); +Result apmExtSysRequestPerformanceMode(u32 mode); +Result apmExtSysSetCpuBoostMode(u32 mode); + +Result apmExtGetPerformanceMode(u32 *out_mode); +Result apmExtGetCurrentPerformanceConfiguration(u32 *out_conf); + +inline bool apmExtIsCPUBoosted(u32 conf_id) { // CPU boosted to 1785 MHz + return (conf_id == 0x92220009 || conf_id == 0x9222000A); +}; +inline bool apmExtIsBoostMode(u32 conf_id) { // GPU throttled to 76.8 MHz + return (conf_id >= 0x92220009 && conf_id <= 0x9222000C); +}; + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h new file mode 100644 index 00000000..f2c9f60a --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/cpp/lockable_mutex.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus + +#include +#include + +class LockableMutex +{ +public: + LockableMutex() + { + mutexInit(&this->m); + } + + virtual ~LockableMutex() {} + + void Lock() + { + mutexLock(&this->m); + } + + bool TryLock() + { + return mutexTryLock(&this->m); + } + + void Unlock() + { + mutexUnlock(&this->m); + } + + // snake_case aliases in order to implement Lockable + + void lock() + { + this->Lock(); + } + + bool try_lock() + { + return this->TryLock(); + } + + void unlock() + { + this->Unlock(); + } + +private: + Mutex m; +}; + +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/i2c.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/i2c.h new file mode 100644 index 00000000..c3dbe340 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/i2c.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +Result i2csessionExtRegReceive(I2cSession* s, u8 in, void* out, u8 out_size); + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/ipc_server.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/ipc_server.h new file mode 100644 index 00000000..0c8f294b --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/ipc_server.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#define IPC_SERVER_EXT_RESPONSE_MAX_DATA_SIZE (0x100 - 0x10 - sizeof(IpcServerRawHeader)) + +typedef struct +{ + u64 magic; + union + { + u64 cmdId; + u64 result; + }; +} IpcServerRawHeader; + +typedef struct +{ + SmServiceName srvName; + Handle handles[MAX_WAIT_OBJECTS]; + u32 max; + u32 count; +} IpcServer; + +typedef struct +{ + u64 cmdId; + void* ptr; + size_t size; +} IpcServerRequestData; + +typedef struct +{ + HipcParsedRequest hipc; + IpcServerRequestData data; +} IpcServerRequest; + +typedef Result (*IpcServerRequestHandler)(void* userdata, const IpcServerRequest* r, u8* out_data, size_t* out_dataSize); + +Result ipcServerInit(IpcServer* server, const char* name, u32 max_sessions); +Result ipcServerExit(IpcServer* server); +Result ipcServerProcess(IpcServer* server, IpcServerRequestHandler handler, void* userdata); +Result ipcServerParseCommand(const IpcServerRequest* r, size_t* out_datasize, void** out_data, u64* out_cmd); + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/max17050.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/max17050.h new file mode 100644 index 00000000..a22046db --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/max17050.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +Result max17050Initialize(void); +void max17050Exit(void); +s32 max17050PowerNow(void); +s32 max17050PowerAvg(void); + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/t210.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/t210.h new file mode 100644 index 00000000..9e5b41e0 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/t210.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +u32 t210ClkCpuFreq(void); +u32 t210ClkMemFreq(void); +u32 t210ClkGpuFreq(void); +u32 t210EmcLoadAll(void); +u32 t210EmcLoadCpu(void); + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/tmp451.h b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/tmp451.h new file mode 100644 index 00000000..9faf2480 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/include/nxExt/tmp451.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +Result tmp451Initialize(void); +void tmp451Exit(void); +s32 tmp451TempPcb(void); +s32 tmp451TempSoc(void); + +#ifdef __cplusplus +} +#endif diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/apm_ext.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/apm_ext.c new file mode 100644 index 00000000..fba71f4f --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/apm_ext.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "nxExt/apm_ext.h" + +#include + +static Service g_apmSrv; +static Service g_apmSysSrv; +static atomic_size_t g_refCnt; + +Result apmExtInitialize(void) +{ + g_refCnt++; + + if (serviceIsActive(&g_apmSrv)) + { + return 0; + } + + Result rc = 0; + + rc = smGetService(&g_apmSrv, "apm"); + if(R_SUCCEEDED(rc)) + { + rc = smGetService(&g_apmSysSrv, "apm:sys"); + } + + if (R_FAILED(rc)) + { + apmExtExit(); + } + + return rc; +} + +void apmExtExit(void) +{ + if (--g_refCnt == 0) + { + serviceClose(&g_apmSrv); + serviceClose(&g_apmSysSrv); + } +} + +Result apmExtGetPerformanceMode(u32* out_mode) +{ + return serviceDispatchOut(&g_apmSrv, 1, *out_mode); +} + +Result apmExtSysRequestPerformanceMode(u32 mode) +{ + return serviceDispatchIn(&g_apmSysSrv, 0, mode); +} + +Result apmExtGetCurrentPerformanceConfiguration(u32* out_conf) +{ + return serviceDispatchOut(&g_apmSysSrv, 7, *out_conf); +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/i2c.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/i2c.c new file mode 100644 index 00000000..d5db4ac1 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/i2c.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "nxExt/i2c.h" + +#define I2C_CMD_SND 0 +#define I2C_CMD_RCV 1 + +Result i2csessionExtRegReceive(I2cSession* s, u8 in, void* out, u8 out_size) +{ + u8 cmdlist[5] = { + I2C_CMD_SND | (I2cTransactionOption_Start << 6), + sizeof(in), + in, + + I2C_CMD_RCV | (I2cTransactionOption_All << 6), + out_size + }; + + return i2csessionExecuteCommandList(s, out, out_size, cmdlist, sizeof(cmdlist)); +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/ipc_server.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/ipc_server.c new file mode 100644 index 00000000..68e92ff8 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/ipc_server.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "nxExt/ipc_server.h" +#include + +Result ipcServerInit(IpcServer* server, const char* name, u32 max_sessions) +{ + if(max_sessions < 1 || max_sessions > (MAX_WAIT_OBJECTS - 1)) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + server->srvName = smEncodeName(name); + server->max = max_sessions + 1; + server->count = 0; + + Result rc = smRegisterService(&server->handles[0], server->srvName, false, max_sessions); + if(R_SUCCEEDED(rc)) + { + server->count = 1; + } + return rc; +} + +Result ipcServerExit(IpcServer* server) +{ + for(u32 i = 0; i < server->count; i++) + { + svcCloseHandle(server->handles[i]); + } + server->count = 0; + return smUnregisterService(server->srvName); +} + +static Result _ipcServerAddSession(IpcServer* server, Handle session) +{ + if(server->count >= server->max) + { + return MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + server->handles[server->count] = session; + server->count++; + return 0; +} + +static Result _ipcServerDeleteSession(IpcServer* server, u32 index) +{ + if(!index || index >= server->count) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + svcCloseHandle(server->handles[index]); + + for(u32 j = index; j < (server->count - 1); j++) + { + server->handles[j] = server->handles[j + 1]; + } + server->count--; + return 0; +} + +static Result _ipcServerParseRequest(IpcServerRequest* r) +{ + u8* base = armGetTls(); + + r->hipc = hipcParseRequest(base); + r->data.cmdId = 0; + r->data.size = 0; + r->data.ptr = NULL; + + if(r->hipc.meta.type == CmifCommandType_Request) + { + IpcServerRawHeader* header = cmifGetAlignedDataStart(r->hipc.data.data_words, base); + size_t dataSize = r->hipc.meta.num_data_words * 4; + + if(!header || dataSize < sizeof(IpcServerRawHeader) || header->magic != CMIF_IN_HEADER_MAGIC) + { + return MAKERESULT(Module_Libnx, LibnxError_BadInput); + } + + r->data.cmdId = header->cmdId; + if(dataSize > sizeof(IpcServerRawHeader)) + { + r->data.size = dataSize - sizeof(IpcServerRawHeader); + r->data.ptr = ((u8*)header) + sizeof(IpcServerRawHeader); + } + } + + return 0; +} + +static void _ipcServerPrepareResponse(Result rc, void* data, size_t dataSize) +{ + u8* base = armGetTls(); + HipcRequest hipc = hipcMakeRequestInline(base, + .type = CmifCommandType_Request, + .num_data_words = (sizeof(IpcServerRawHeader) + dataSize + 0x10) / 4, + ); + + IpcServerRawHeader* rawHeader = cmifGetAlignedDataStart(hipc.data_words, base); + rawHeader->magic = CMIF_OUT_HEADER_MAGIC; + rawHeader->result = rc; + + if(R_SUCCEEDED(rc)) + { + memcpy(((u8*)rawHeader) + sizeof(IpcServerRawHeader), data, dataSize); + } +} + +static Result _ipcServerProcessNewSession(IpcServer* server) +{ + Handle session; + Result rc = svcAcceptSession(&session, server->handles[0]); + if(R_SUCCEEDED(rc) && R_FAILED(rc = _ipcServerAddSession(server, session))) + { + svcCloseHandle(session); + } + return rc; +} + +static Result _ipcServerProcessSession(IpcServer* server, IpcServerRequestHandler handler, void* userdata, u32 handleIndex) +{ + s32 unusedIndex; + IpcServerRequest r; + size_t dataSize = 0; + u8 data[IPC_SERVER_EXT_RESPONSE_MAX_DATA_SIZE]; + bool close = false; + + Result rc = svcReplyAndReceive(&unusedIndex, &server->handles[handleIndex], 1, 0, UINT64_MAX); + if(R_SUCCEEDED(rc)) + { + rc = _ipcServerParseRequest(&r); + } + + if(R_SUCCEEDED(rc)) + { + switch(r.hipc.meta.type) + { + case CmifCommandType_Request: + _ipcServerPrepareResponse( + handler(userdata, &r, data, &dataSize), + data, + dataSize + ); + break; + case CmifCommandType_Close: + _ipcServerPrepareResponse(0, NULL, 0); + close = true; + break; + default: + _ipcServerPrepareResponse(MAKERESULT(11, 403), NULL, 0); + break; + } + + rc = svcReplyAndReceive(&unusedIndex, &server->handles[handleIndex], 0, server->handles[handleIndex], 0); + if(rc == KERNELRESULT(TimedOut)) + { + rc = 0; + } + } + + if(R_FAILED(rc) || close) + { + _ipcServerDeleteSession(server, handleIndex); + } + + return rc; +} + +Result ipcServerProcess(IpcServer* server, IpcServerRequestHandler handler, void* userdata) +{ + s32 handleIndex = -1; + Result rc = svcWaitSynchronization(&handleIndex, server->handles, server->count, UINT64_MAX); + + if(R_SUCCEEDED(rc) && (handleIndex < 0 || handleIndex >= server->count)) + { + rc = MAKERESULT(Module_Libnx, LibnxError_NotFound); + } + + if(R_SUCCEEDED(rc)) + { + if(handleIndex) + { + rc = _ipcServerProcessSession(server, handler, userdata, handleIndex); + } + else + { + rc = _ipcServerProcessNewSession(server); + } + } + + return rc; +} + diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/max17050.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/max17050.c new file mode 100644 index 00000000..fe6d00bc --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/max17050.c @@ -0,0 +1,124 @@ +/* + * Fuel gauge driver for Nintendo Switch's Maxim 17050 + * + * Copyright (c) 2011 Samsung Electronics + * MyungJoo Ham + * Copyright (c) 2018 CTCaer + * Copyright (c) 2022 p-sam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "nxExt/max17050.h" +#include "nxExt/i2c.h" + +#define MAX17050_WAIT_NS 1000000000UL + +#define MAX17050_VCELL 0x09 +#define MAX17050_Current 0x0A +#define MAX17050_AvgCurrent 0x0B +#define MAX17050_AvgVCELL 0x19 + +#define MAX17050_BOARD_CGAIN 2 +#define MAX17050_BOARD_SNS_RESISTOR_UOHM 5000 + +static I2cSession g_i2c_session; +static u64 g_update_ticks = 0; +static s32 g_power_now = 0; +static s32 g_power_avg = 0; + +static Result _max17050_get_power(s32 *out_mw_now, s32 *out_mw_avg) +{ + s64 ma, mv; + u16 values[3] = {0}; + + Result rc = i2csessionExtRegReceive(&g_i2c_session, MAX17050_VCELL, values, sizeof(values)); + + if (R_SUCCEEDED(rc)) + { + ma = (s16)values[1]; + ma = ma * 1562500 / (MAX17050_BOARD_SNS_RESISTOR_UOHM * MAX17050_BOARD_CGAIN); + + mv = (int)(values[0] >> 3) * 625 / 1000; + + *out_mw_now = ma * mv / 1000000; + } + + if (R_SUCCEEDED(rc)) + { + rc = i2csessionExtRegReceive(&g_i2c_session, MAX17050_AvgVCELL, values, sizeof(u16)); + } + + if (R_SUCCEEDED(rc)) + { + ma = (s16)values[2]; + ma = ma * 1562500 / (MAX17050_BOARD_SNS_RESISTOR_UOHM * MAX17050_BOARD_CGAIN); + + mv = (int)(values[0] >> 3) * 625 / 1000; + + *out_mw_avg = ma * mv / 1000000; + } + + return rc; +} + +static void _max17050_update() +{ + u64 ticks = armGetSystemTick(); + if(armTicksToNs(ticks - g_update_ticks) <= MAX17050_WAIT_NS) + { + return; + } + + g_update_ticks = ticks; + + if(!serviceIsActive(&g_i2c_session.s)) + { + return; + } + + _max17050_get_power(&g_power_now, &g_power_avg); +} + +Result max17050Initialize(void) +{ + Result rc = i2cInitialize(); + + if(R_SUCCEEDED(rc)) + { + rc = i2cOpenSession(&g_i2c_session, I2cDevice_Max17050); + } + + return rc; +} + +void max17050Exit(void) +{ + i2csessionClose(&g_i2c_session); + i2cExit(); +} + +s32 max17050PowerNow(void) +{ + _max17050_update(); + return g_power_now; +} + +s32 max17050PowerAvg(void) +{ + _max17050_update(); + return g_power_avg; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/t210.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/t210.c new file mode 100644 index 00000000..91ce4ec2 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/t210.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2020-2023 CTCaer + * Copyright (c) 2023 p-sam + * Copyright (c) 2026 Souldbminer + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nxExt/t210.h" + +#define WAIT_NS 1000000000UL + +#define usleep(x) svcSleepThread(1000UL * x) + +#define GPU_TRIM_SYS_GPCPLL_COEFF 0x4 +#define GPU_TRIM_SYS_GPCPLL(x) (*(volatile u32 *)(g_gpu_base + 0x137000ul + (x))) + +#define CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL 0x60 +#define CLK_RST_CONTROLLER_PTO_CLK_CNT_STATUS 0x64 +#define CLK_RST_CONTROLLER_CLK_OUT_ENB_X 0x280 +#define CLK_RST_CONTROLLER_RST_DEVICES_X 0x28C + +/*! PTO_CLK_CNT */ +#define PTO_REF_CLK_WIN_CFG_MASK 0xF +#define PTO_REF_CLK_WIN_CFG_16P 0xF +#define PTO_CNT_EN BIT(9) +#define PTO_CNT_RST BIT(10) +#define PTO_CLK_ENABLE BIT(13) +#define PTO_SRC_SEL_SHIFT 14 +#define PTO_SRC_SEL_MASK 0x1FF +#define PTO_DIV_SEL_MASK (3 << 23) +#define PTO_DIV_SEL_GATED (0 << 23) +#define PTO_DIV_SEL_DIV1 (1 << 23) +#define PTO_DIV_SEL_DIV2_RISING (2 << 23) +#define PTO_DIV_SEL_DIV2_FALLING (3 << 23) +#define PTO_DIV_SEL_CPU_EARLY (0 << 23) +#define PTO_DIV_SEL_CPU_LATE (1 << 23) + +#define PTO_CLK_CNT_BUSY BIT(31) +#define PTO_CLK_CNT 0xFFFFFF +#define CLK_PTO_CCLK_G_DIV2 0x13 +#define CLK_PTO_EMC 0x24 + +#define CLOCK(x) (*(volatile u32 *)(g_clk_base + (x))) + +/* Actmon Global registers */ +#define ACTMON_GLB_STATUS 0x0 +#define ACTMON_MCCPU_MON_ACT BIT(8) +#define ACTMON_MCALL_MON_ACT BIT(9) +#define ACTMON_CPU_FREQ_MON_ACT BIT(10) +#define ACTMON_BPMP_MON_ACT BIT(14) +#define ACTMON_CPU_MON_ACT BIT(15) + +#define ACTMON_GLB_PERIOD_CTRL 0x4 +#define ACTMON_GLB_PERIOD_USEC BIT(8) +#define ACTMON_GLB_PERIOD_SAMPLE(n) (((n) - 1) & 0xFF) + +/* Actmon Device Registers */ +#define ACTMON_DEV_SIZE 0x40 +/* Actmon CTRL */ +#define ACTMON_DEV_CTRL_K_VAL(k) (((k) & 7) << 10) +#define ACTMON_DEV_CTRL_ENB_PERIODIC BIT(18) +#define ACTMON_DEV_CTRL_ENB BIT(31) + +#define ACTMON_PERIOD_MS 20 +#define DEV_COUNT_WEIGHT 1024 + +#define ACTMON_BASE (g_act_base + 0x800) +#define ACTMON_DEV_BASE (ACTMON_BASE + 0x80) +#define ACTMON(x) (*(volatile u32 *)(ACTMON_BASE + (x))) + +typedef enum _actmon_dev_t +{ + ACTMON_DEV_CPU, + ACTMON_DEV_BPMP, + ACTMON_DEV_AHB, + ACTMON_DEV_APB, + ACTMON_DEV_CPU_FREQ, + ACTMON_DEV_MC_ALL, + ACTMON_DEV_MC_CPU, + + ACTMON_DEV_NUM, +} actmon_dev_t; + +typedef struct _actmon_dev_reg_t +{ + vu32 ctrl; + vu32 upper_wnark; + vu32 lower_wmark; + vu32 init_avg; + vu32 avg_upper_wmark; + vu32 avg_lower_wmark; + vu32 count_weight; + vu32 count; + vu32 avg_count; + vu32 intr_status; + vu32 ctrl2; + vu32 rsvd[5]; +} actmon_dev_reg_t; + +static uintptr_t g_clk_base = 0; +static uintptr_t g_gpu_base = 0; +static uintptr_t g_act_base = 0; +static u64 g_update_ticks = 0; +static u32 g_cpu_freq = 0; +static u32 g_gpu_freq = 0; +static u32 g_mem_freq = 0; +static u32 g_emc_lall = 0; +static u32 g_emc_lcpu = 0; + +static u32 _clock_get_dev_freq(u32 id, u32 multiplier) +{ + const u32 pto_win = 16; + const u32 pto_osc = 32768; + + u32 val = ((id & PTO_SRC_SEL_MASK) << PTO_SRC_SEL_SHIFT) | PTO_DIV_SEL_DIV1 | PTO_CLK_ENABLE | (pto_win - 1); + CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL) = val; + (void)CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL); + usleep(2); + + CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL) = val | PTO_CNT_RST; + (void)CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL); + usleep(2); + + CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL) = val; + (void)CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL); + usleep(2); + + CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL) = val | PTO_CNT_EN; + (void)CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL); + usleep((1000000ULL * pto_win / pto_osc) + 12 + 2); // 502 us. + + while (CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_STATUS) & PTO_CLK_CNT_BUSY) + ; + + u32 cnt = CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_STATUS) & PTO_CLK_CNT; + + CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL) = 0; + (void)CLOCK(CLK_RST_CONTROLLER_PTO_CLK_CNT_CNTL); + usleep(2); + + u32 freq_khz = (u64)cnt * multiplier * pto_osc / pto_win; + + return freq_khz; +} + +static void _actmon_dev_enable(actmon_dev_t dev, u32 freq, u32 weight) +{ + actmon_dev_reg_t *regs = (actmon_dev_reg_t *)(ACTMON_DEV_BASE + (dev * ACTMON_DEV_SIZE)); + + regs->init_avg = (u32)freq * ACTMON_PERIOD_MS / 2; + regs->count_weight = weight; + + regs->ctrl = ACTMON_DEV_CTRL_ENB | ACTMON_DEV_CTRL_ENB_PERIODIC | ACTMON_DEV_CTRL_K_VAL(3); // 8 samples average. +} + +static u32 _actmon_dev_get_count_avg(actmon_dev_t dev) +{ + actmon_dev_reg_t *regs = (actmon_dev_reg_t *)(ACTMON_DEV_BASE + (dev * ACTMON_DEV_SIZE)); + + return regs->avg_count; +} + +static inline Result _svcQueryMemoryMappingFallback(u64* virtaddr, u64 physaddr, u64 size) +{ + if(hosversionAtLeast(10,0,0)) + { + u64 out_size; + return svcQueryMemoryMapping(virtaddr, &out_size, physaddr, size); + } + else + { + return svcLegacyQueryIoMapping(virtaddr, physaddr, size); + } +} + +static void _clock_update_freqs(void) +{ + u64 ticks = armGetSystemTick(); + if(armTicksToNs(ticks - g_update_ticks) <= WAIT_NS) + { + return; + } + + g_update_ticks = ticks; + + if (!g_clk_base) + { + _svcQueryMemoryMappingFallback(&g_clk_base, 0x60006000ul, 0x1000); + } + + if(!g_clk_base) + { + return; + } + + g_mem_freq = _clock_get_dev_freq(CLK_PTO_EMC, 1); + g_cpu_freq = _clock_get_dev_freq(CLK_PTO_CCLK_G_DIV2, 2); + + if (!g_gpu_base) + { + _svcQueryMemoryMappingFallback(&g_gpu_base, 0x57000000ul, 0x1000000); + } + + if (!g_gpu_base) + { + return; + } + + bool gpu_enabled = (CLOCK(CLK_RST_CONTROLLER_CLK_OUT_ENB_X) & BIT(24)) && !(CLOCK(CLK_RST_CONTROLLER_RST_DEVICES_X) & BIT(24)); + if(!gpu_enabled) + { + return; + } + + if (!g_act_base) + { + _svcQueryMemoryMappingFallback(&g_act_base, 0x6000C000ul, 0x1000); + } + + if(!g_act_base) + { + return; + } + + const u32 osc = 38400000; + u32 coeff = GPU_TRIM_SYS_GPCPLL(GPU_TRIM_SYS_GPCPLL_COEFF); + u32 divm = coeff & 0xFF; + u32 divn = (coeff >> 8) & 0xFF; + u32 divp = (coeff >> 16) & 0x3F; + g_gpu_freq = osc * divn / (divm * divp) / 2; + + u32 emc_freq = g_mem_freq / 1000; + + // Check if actmon is disabled + if (!(ACTMON(ACTMON_GLB_STATUS) & ACTMON_MCALL_MON_ACT)) + { + ACTMON(ACTMON_GLB_PERIOD_CTRL) = ACTMON_GLB_PERIOD_SAMPLE(ACTMON_PERIOD_MS); + _actmon_dev_enable(ACTMON_DEV_MC_ALL, emc_freq, 256 * 4); + } + + // Check if actmon is disabled + if (!(ACTMON(ACTMON_GLB_STATUS) & ACTMON_MCCPU_MON_ACT)) + _actmon_dev_enable(ACTMON_DEV_MC_CPU, emc_freq, 256 * 4); + + // Get 1000 -> 100.0. + g_emc_lall = (u64)_actmon_dev_get_count_avg(ACTMON_DEV_MC_ALL) * 10 * 100 / (emc_freq * ACTMON_PERIOD_MS); + g_emc_lcpu = (u64)_actmon_dev_get_count_avg(ACTMON_DEV_MC_CPU) * 10 * 100 / (emc_freq * ACTMON_PERIOD_MS); +} + + +u32 t210ClkCpuFreq(void) +{ + _clock_update_freqs(); + return g_cpu_freq; +} + +u32 t210ClkMemFreq(void) +{ + _clock_update_freqs(); + return g_mem_freq; +} + +u32 t210ClkGpuFreq(void) +{ + _clock_update_freqs(); + return g_gpu_freq; +} + +u32 t210EmcLoadAll() +{ + _clock_update_freqs(); + return g_emc_lall; +} + +u32 t210EmcLoadCpu() +{ + _clock_update_freqs(); + return g_emc_lcpu; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/tmp451.c b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/tmp451.c new file mode 100644 index 00000000..90c8d1c2 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/lib/nxExt/src/tmp451.c @@ -0,0 +1,102 @@ +/* + * SOC/PCB Temperature driver for Nintendo Switch's TI TMP451 + * + * Copyright (c) 2018-2024 CTCaer + * Copyright (c) 2024 p-sam + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nxExt/tmp451.h" +#include "nxExt/i2c.h" + +#define TMP451_WAIT_NS 1000000000UL + +#define TMP451_PCB_TEMP_REG 0x00 +#define TMP451_SOC_TEMP_REG 0x01 + +#define TMP451_SOC_TMP_DEC_REG 0x10 +#define TMP451_PCB_TMP_DEC_REG 0x15 + +static I2cSession g_i2c_session; +static u64 g_update_ticks = 0; +static s32 g_temp_pcb = 0; +static s32 g_temp_soc = 0; + +static Result _tmp451_get_temp(u8 reg, u8 dec_reg, s32* out) +{ + u8 val = 0; + Result rc = i2csessionExtRegReceive(&g_i2c_session, reg, &val, sizeof(val)); + + if(R_SUCCEEDED(rc)) + { + *out = (s32)val * 1000; + rc = i2csessionExtRegReceive(&g_i2c_session, dec_reg, &val, sizeof(val)); + } + + if(R_SUCCEEDED(rc)) + { + *out += ((s32)(val >> 4) * 625) / 10; + } + + return rc; +} + +static void _tmp451_update() +{ + u64 ticks = armGetSystemTick(); + if(armTicksToNs(ticks - g_update_ticks) <= TMP451_WAIT_NS) + { + return; + } + + g_update_ticks = ticks; + + if(!serviceIsActive(&g_i2c_session.s)) + { + return; + } + + _tmp451_get_temp(TMP451_PCB_TEMP_REG, TMP451_PCB_TMP_DEC_REG, &g_temp_pcb); + _tmp451_get_temp(TMP451_SOC_TEMP_REG, TMP451_SOC_TMP_DEC_REG, &g_temp_soc); +} + +Result tmp451Initialize(void) +{ + Result rc = i2cInitialize(); + + if(R_SUCCEEDED(rc)) + { + rc = i2cOpenSession(&g_i2c_session, I2cDevice_Tmp451); + } + + return rc; +} + +void tmp451Exit(void) +{ + i2csessionClose(&g_i2c_session); + i2cExit(); +} + +s32 tmp451TempPcb(void) +{ + _tmp451_update(); + return g_temp_pcb; +} + +s32 tmp451TempSoc(void) +{ + _tmp451_update(); + return g_temp_soc; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/perms.json b/Source/rewrite-hoc-clk/sysmodule/perms.json new file mode 100644 index 00000000..b5c09894 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/perms.json @@ -0,0 +1,240 @@ +{ + "name": "hoc:clk", + "title_id": "0x00FF0000636C6BFF", + "title_id_range_min": "0x00FF0000636C6BFF", + "title_id_range_max": "0x00FF0000636C6BFF", + "main_thread_stack_size": "0x0000C000", + "main_thread_priority": 16, + "default_cpu_id": 3, + "process_category": 1, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_access": [ + "*" + ], + "service_host": [ + "hoc:clk" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 16, + "lowest_cpu_id": 0, + "highest_cpu_id": 3 + } + }, + { + "type": "map", + "value": { + "address": "0x60006000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x57000000", + "size": "0x1000000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x6000C000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "map", + "value": { + "address": "0x70110000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, + { + "type": "syscalls", + "value": { + "svcUnknown": "0x00", + "svcSetHeapSize": "0x01", + "svcSetMemoryPermission": "0x02", + "svcSetMemoryAttribute": "0x03", + "svcMapMemory": "0x04", + "svcUnmapMemory": "0x05", + "svcQueryMemory": "0x06", + "svcExitProcess": "0x07", + "svcCreateThread": "0x08", + "svcStartThread": "0x09", + "svcExitThread": "0x0a", + "svcSleepThread": "0x0b", + "svcGetThreadPriority": "0x0c", + "svcSetThreadPriority": "0x0d", + "svcGetThreadCoreMask": "0x0e", + "svcSetThreadCoreMask": "0x0f", + "svcGetCurrentProcessorNumber": "0x10", + "svcSignalEvent": "0x11", + "svcClearEvent": "0x12", + "svcMapSharedMemory": "0x13", + "svcUnmapSharedMemory": "0x14", + "svcCreateTransferMemory": "0x15", + "svcCloseHandle": "0x16", + "svcResetSignal": "0x17", + "svcWaitSynchronization": "0x18", + "svcCancelSynchronization": "0x19", + "svcArbitrateLock": "0x1a", + "svcArbitrateUnlock": "0x1b", + "svcWaitProcessWideKeyAtomic": "0x1c", + "svcSignalProcessWideKey": "0x1d", + "svcGetSystemTick": "0x1e", + "svcConnectToNamedPort": "0x1f", + "svcSendSyncRequestLight": "0x20", + "svcSendSyncRequest": "0x21", + "svcSendSyncRequestWithUserBuffer": "0x22", + "svcSendAsyncRequestWithUserBuffer": "0x23", + "svcGetProcessId": "0x24", + "svcGetThreadId": "0x25", + "svcBreak": "0x26", + "svcOutputDebugString": "0x27", + "svcReturnFromException": "0x28", + "svcGetInfo": "0x29", + "svcFlushEntireDataCache": "0x2a", + "svcFlushDataCache": "0x2b", + "svcMapPhysicalMemory": "0x2c", + "svcUnmapPhysicalMemory": "0x2d", + "svcGetFutureThreadInfo": "0x2e", + "svcGetLastThreadInfo": "0x2f", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcUnknown": "0x36", + "svcUnknown": "0x37", + "svcUnknown": "0x38", + "svcUnknown": "0x39", + "svcUnknown": "0x3a", + "svcUnknown": "0x3b", + "svcDumpInfo": "0x3c", + "svcDumpInfoNew": "0x3d", + "svcUnknown": "0x3e", + "svcUnknown": "0x3f", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown": "0x46", + "svcUnknown": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4a", + "svcCreateCodeMemory": "0x4b", + "svcControlCodeMemory": "0x4c", + "svcSleepSystem": "0x4d", + "svcReadWriteRegister": "0x4e", + "svcSetProcessActivity": "0x4f", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcCreateInterruptEvent": "0x53", + "svcQueryPhysicalAddress": "0x54", + "svcQueryMemoryMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceByForce": "0x59", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcMapDeviceAddressSpace": "0x5b", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcInvalidateProcessDataCache": "0x5d", + "svcStoreProcessDataCache": "0x5e", + "svcFlushProcessDataCache": "0x5f", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6a", + "svcWriteDebugProcessMemory": "0x6b", + "svcSetHardwareBreakPoint": "0x6c", + "svcGetDebugThreadParam": "0x6d", + "svcUnknown": "0x6e", + "svcGetSystemInfo": "0x6f", + "svcCreatePort": "0x70", + "svcManageNamedPort": "0x71", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78", + "svcCreateProcess": "0x79", + "svcStartProcess": "0x7a", + "svcTerminateProcess": "0x7b", + "svcGetProcessInfo": "0x7c", + "svcCreateResourceLimit": "0x7d", + "svcSetResourceLimitLimitValue": "0x7e", + "svcCallSecureMonitor": "0x7f" + } + }, { + "type": "min_kernel_version", + "value": "0x0060" + }, { + "type": "handle_table_size", + "value": 1023 + }, { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + }, { + "type": "map", + "value": { + "address": "0x60006000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + }, { + "type": "map", + "value": { + "address": "0x54300000", + "size": "0x40000", + "is_ro": false, + "is_io": true + } + }, { + "type": "map", + "value": { + "address": "0x7001b000", + "size": "0x1000", + "is_ro": false, + "is_io": true + } + } + ] +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board.hpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board.hpp new file mode 100644 index 00000000..1ea200d5 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +#include + +#define HOSSVC_HAS_CLKRST (hosversionAtLeast(8,0,0)) +#define HOSSVC_HAS_TC (hosversionAtLeast(5,0,0)) + +namespace board { + + void Initialize(); + void Exit(); + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.cpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.cpp new file mode 100644 index 00000000..516cbe79 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include "board_fuse.hpp" + +namespace board { + + namespace { + constexpr u32 FuseCpuSpeedoCalib = 0x114; + // constexpr u32 FuseCpuSpeedo1Calib = 0x12C; + constexpr u32 FuseGpuSpeedoCalib = 0x130; + + constexpr u32 FuseSocSpeedoCalib = 0x134; + // constexpr u32 FuseSocSpeedo1Calib = 0x138; + // constexpr u32 FuseSocSpeedo2Calib = 0x13C; + + constexpr u32 FuseCpuIddqCalib = 0x118; + constexpr u32 FuseSocIddqCalib = 0x140; + constexpr u32 FuseGpuIddqCalib = 0x228; + } + + void SetGpuBracket(u8 speedo, u8 &gpuBracket) { + if (speedo <= 1624) { + gpuBracket = 0; + return; + } + + if (speedo <= 1689) { + gpuBracket = 1; + return; + } + + if (speedo <= 1753) { + gpuBracket = 2; + return; + } + + /* >= 1754 */ + gpuBracket = 3; + } + + FuseReadSpeedo(FuseSpeedoData &speedo) { + u64 pid = 0; + constexpr u64 UsbID = 0x0100000000000006; + if (R_FAILED(pmdmntGetProcessId(&pid, UsbID))) { + return; + } + + Handle debug; + if (R_FAILED(svcDebugActiveProcess(&debug, pid))) { + return; + } + + MemoryInfo mem_info = {}; + u32 pageinfo = 0; + u64 addr = 0; + + u8 stack[0x10] = {}; + const u8 compare[0x10] = {}; + u8 dump[0x400] = {}; + constexpr u64 PageSize = 0x1000; + + while (true) { + if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &pageinfo, debug, addr)) || mem_info.addr < addr) { + break; + } + + if (mem_info.type == MemType_Io && mem_info.size == PageSize) { + if (R_FAILED(svcReadDebugProcessMemory(stack, debug, mem_info.addr, sizeof(stack)))) { + break; + } + + if (memcmp(stack, compare, sizeof(stack)) == 0) { + if (R_FAILED(svcReadDebugProcessMemory(dump, debug, mem_info.addr + 0x800, sizeof(dump)))) { + break; + } + + speedo.cpuSpeedo = *reinterpret_cast(dump + FuseCpuSpeedoCalib); + speedo.gpuSpeedo = *reinterpret_cast(dump + FuseGpuSpeedoCalib); + speedo.socSpeedo = *reinterpret_cast(dump + FuseSocSpeedoCalib); + speedo.cpuIDDQ = *reinterpret_cast(dump + FuseCpuIddqCalib); + speedo.gpuIDDQ = *reinterpret_cast(dump + FuseSocIddqCalib); + speedo.socIDDQ = *reinterpret_cast(dump + FUSE_GPU_IDDQ_CALIB); + + svcCloseHandle(debug); + return; + } + } + + addr = mem_info.addr + mem_info.size; + } + + svcCloseHandle(debug); + } + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.hpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.hpp new file mode 100644 index 00000000..c67e0a2b --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board_fuse.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include + +namespace board { + + struct FuseSpeedoData { + u16 cpuSpeedo; + u16 gpuSpeedo; + u16 socSpeedo; + + u16 cpuIDDQ; + u16 gpuIDDQ; + u16 socIDDQ; + }; + + FuseReadSpeedo(FuseSpeedoData speedo); + FuseSetGpuBracket(u8 gpuSpeedo, u8 &gpuBracket); + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board_init.cpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board_init.cpp new file mode 100644 index 00000000..a6720770 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board_init.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include +#include +#include + +#include "board.hpp" +#include "board_fuse.hpp" +#include "board_load.hpp" + +namespace board { + + SysClkSocType gSocType; + u8 gDramID; + HorizonOCConsoleType gConsoleType = HorizonOCConsoleType_Iowa; + FuseSpeedoData gSpeedos; + u8 speedoBracket; + Result nvCheck = 1; + u23 fd = 0, fd2 = 0; + + void FetchHardwareInfos() { + FuseReadSpeedos(gSpeedos); + FuseSetGpuBracket(gSpeedos.gpuSpeedo, speedoBracket); + + u64 sku = 0, dramID = 0; + Result rc = splInitialize(); + ASSERT_RESULT_OK(rc, "splInitialize"); + + rc = splGetConfig(SplConfigItem_HardwareType, &sku); + ASSERT_RESULT_OK(rc, "splGetConfig"); + + rc = splGetConfig(SplConfigItem_DramId, &dramID); + ASSERT_RESULT_OK(rc, "splGetConfig"); + gDramID = dramID; + splExit(); + + switch(sku) { + case 2 ... 5: + gSocType = SysClkSocType_Mariko; + break; + default: + gSocType = SysClkSocType_Erista; + } + + if (gSocType == SysClkSocType_Mariko) { + CacheDvfsTable(); + } + + gConsoleType = static_cast sku; + g_dramID = dramID; + } + + /* TODO: Check for config */ + void Initialize() { + Result rc = 0; + + if (HOSSVC_HAS_CLKRST) { + rc = clkrstInitialize(); + ASSERT_RESULT_OK(rc, "clkrstInitialize"); + } else { + rc = pcvInitialize(); + ASSERT_RESULT_OK(rc, "pcvInitialize"); + } + + if(HOSSVC_HAS_TC) { + rc = tcInitialize(); + ASSERT_RESULT_OK(rc, "tcInitialize"); + } + + rc = max17050Initialize(); + ASSERT_RESULT_OK(rc, "max17050Initialize"); + + rc = tmp451Initialize(); + ASSERT_RESULT_OK(rc, "tmp451Initialize"); + + nvInitialize_rc = nvInitialize(); + if (R_SUCCEEDED(nvInitialize_rc)) { + nvCheck = nvOpen(&fd, "/dev/nvhost-ctrl-gpu"); + nvCheck_sched = nvOpen(&fd2, "/dev/nvsched-ctrl"); + } + + rc = rgltrInitialize(); + ASSERT_RESULT_OK(rc, "rgltrInitialize"); + + rc = pmdmntInitialize(); + ASSERT_RESULT_OK(rc, "pmdmntInitialize"); + + StartGpuLoad(nvCheck, fd); + + leventClear(&threadexit); + threadCreate(&cpuCore0Thread, CheckCore, &idletick0, NULL, 0x1000, 0x10, 0); + threadCreate(&cpuCore1Thread, CheckCore, &idletick1, NULL, 0x1000, 0x10, 1); + threadCreate(&cpuCore2Thread, CheckCore, &idletick2, NULL, 0x1000, 0x10, 2); + // threadCreate(&cpuCore3Thread, CheckCore, &idletick3, NULL, 0x1000, 0x10, 3); + threadCreate(&miscThread, miscThreadFunc, NULL, NULL, 0x1000, 0x10, 3); + + threadStart(&cpuCore0Thread); + threadStart(&cpuCore1Thread); + threadStart(&cpuCore2Thread); + // threadStart(&cpuCore3Thread); + + threadStart(&miscThread); + batteryInfoInitialize(); + FetchHardwareInfos(); + + if (hosversionAtLeast(6,0,0) && R_SUCCEEDED(pwmInitialize())) { + pwmCheck = pwmOpenSession2(&g_ICon, 0x3D000001); + } + + if (gConsoleType != HorizonOCConsoleType_Hoag) { + u64 clkVirtAddr, dsiVirtAddr, outsize; + + rc = svcQueryMemoryMapping(&clkVirtAddr, &outsize, 0x60006000, 0x1000); + ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (clk)"); + + rc = svcQueryMemoryMapping(&dsiVirtAddr, &outsize, 0x54300000, 0x40000); + ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (dsi)"); + + DisplayRefreshConfig cfg = {.clkVirtAddr = clkVirtAddr, .dsiVirtAddr = dsiVirtAddr}; + DisplayRefresh_Initialize(&cfg); + } + + // rc = svcQueryMemoryMapping(&cldvfs, &cldvfs_temp, CLDVFS_REGION_BASE, CLDVFS_REGION_SIZE); + // ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (cldvfs)"); + + // if (socType == SysClkSocType_Erista) { + // cachedEristaUvLowTune0 = *(u32*) (cldvfs + CL_DVFS_TUNE0_0); + // cachedEristaUvLowTune1 = *(u32*) (cldvfs + CL_DVFS_TUNE1_0); + // } else { + // SetHz(SysClkModule_CPU, 1785000000); + // cachedMarikoUvHighTune0 = *(u32*) (cldvfs + CL_DVFS_TUNE0_0); + // ResetToStockCpu(); + // } + } + + void Exit() { + if (HOSSVC_HAS_CLKRST) { + clkrstExit(); + } else { + pcvExit(); + } + + apmExtExit(); + psmExit(); + + if (HOSSVC_HAS_TC) { + tcExit(); + } + + max17050Exit(); + tmp451Exit(); + + ExitLoad(); + + // threadClose(&cpuCore3Thread); + threadClose(&miscThread); + + pwmChannelSessionClose(&g_ICon); + pwmExit(); + rgltrExit(); + batteryInfoExit(); + pmdmntExit(); + nvExit(); + + if (gConsoleType != HorizonOCConsoleType_Hoag) { + DisplayRefresh_Shutdown(); + } + } + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.cpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.cpp new file mode 100644 index 00000000..ece59a2b --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include + +namespace board { + + Thread gpuThread; + u32 gpuLoad; + u32 _fd; + + void GpuLoadThread(Result *nvCheck) { + constexpr u32 GpuSamples = 8; + u32 gpu_load_array[GpuSamples] = {}; + size_t i = 0; + constexpr u32 NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD = 0x80044715; + + if (R_SUCCEEDED(nvCheck)) do { + u32 temp; + if (R_SUCCEEDED(nvIoctl(_fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &temp))) { + gpu_load_array[i++ % gpu_samples_average] = temp; + gpuLoad = std::accumulate(&gpu_load_array[0], &gpu_load_array[gpu_samples_average], 0) / gpu_samples_average; + } + svcSleepThread(16'666'000); // wait a bit (this is the perfect amount of time to keep the reading accurate) + } while(true); + } + + void StartGpuLoad(Result nvCheck, u32 fd) { + _fd = fd; + + threadCreate(&gpuThread, GpuLoadThread, &nvCheck, NULL, 0x1000, 0x3F, -2); + threadStart(&gpuThread); + } + + void ExitLoad() { + threadClose(gpuThread); + } + + void StartCpuLoad() { + + } + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.hpp b/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.hpp new file mode 100644 index 00000000..5c5b3ed4 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/board/board_load.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#pragma once + +namespace board { + + void StartGpuLoad(Result nvCheck, u32 fd); + void StartCpuLoad(); + void ExitLoad(); + +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.cpp b/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.cpp new file mode 100644 index 00000000..1e1c98bb --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.cpp @@ -0,0 +1,1299 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "clock_manager.h" +#include +#include "file_utils.h" +#include "board.h" +#include "process_management.h" +#include "errors.h" +#include "ipc_service.h" +#include "kip.h" +#include +#include "notification.h" +#include +#include +#include +#include + +#define HOSPPC_HAS_BOOST (hosversionAtLeast(7,0,0)) + +// governor constants +#define POLL_NS 5'000'000 // 5 ms – governor poll rate +#define DOWN_HOLD_TICKS 10 // 50 ms – how long to in POLL_NS to hold while ramping down +#define STEP_UTIL 900 // multiplier for step calculations + +bool isGpuGovernorEnabled = false; +bool isCpuGovernorEnabled = false; +bool lastGpuGovernorState = false; +bool lastCpuGovernorState = false; +bool lastVrrGovernorState = false; +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; +bool isVRREnabled = false; +ClockManager *ClockManager::GetInstance() +{ + return instance; +} + +void ClockManager::Exit() +{ + if (instance) + { + delete instance; + } +} + +void ClockManager::Initialize() +{ + if (!instance) + { + instance = new ClockManager(); + } +} + +ClockManager::ClockManager() +{ + this->config = Config::CreateDefault(); + this->context = new SysClkContext; + this->context->applicationId = 0; + this->context->profile = SysClkProfile_Handheld; + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + this->context->freqs[module] = 0; + this->context->realFreqs[module] = 0; + this->context->overrideFreqs[module] = 0; + this->RefreshFreqTableRow((SysClkModule)module); + } + + this->running = false; + this->lastTempLogNs = 0; + this->lastCsvWriteNs = 0; + + this->sysDockIntegration = new SysDockIntegration; + this->saltyNXIntegration = new SaltyNXIntegration; + + memset(&initialConfigValues, 0, sizeof(initialConfigValues)); + this->GetKipData(); + + threadCreate( + &cpuGovernorTHREAD, + ClockManager::CpuGovernorThread, + this, + NULL, + 0x2000, + 0x3F, + -2 + ); + + threadCreate( + &gpuGovernorTHREAD, + ClockManager::GovernorThread, + this, + NULL, + 0x2000, + 0x3F, + -2 + ); + + 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); + this->context->iddq[i] = Board::getIDDQ((HorizonOCSpeedo)i); + } + + this->context->dramID = Board::GetDramID(); + 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; +} + +SysClkContext ClockManager::GetCurrentContext() +{ + std::scoped_lock lock{this->contextMutex}; + return *this->context; +} + +Config *ClockManager::GetConfig() +{ + return this->config; +} + +void ClockManager::SetRunning(bool running) +{ + this->running = running; +} + +bool ClockManager::Running() +{ + return this->running; +} + +void ClockManager::GetFreqList(SysClkModule module, std::uint32_t *list, std::uint32_t maxCount, std::uint32_t *outCount) +{ + ASSERT_ENUM_VALID(SysClkModule, module); + + *outCount = std::min(maxCount, this->freqTable[module].count); + memcpy(list, &this->freqTable[module].list[0], *outCount * sizeof(this->freqTable[0].list[0])); +} + +bool ClockManager::IsAssignableHz(SysClkModule module, std::uint32_t hz) +{ + switch (module) + { + case SysClkModule_CPU: + return hz >= 500000000; + case SysClkModule_MEM: + return hz >= 665600000; + default: + return true; + } +} + +std::uint32_t ClockManager::GetMaxAllowedHz(SysClkModule module, SysClkProfile profile) +{ + if (this->config->GetConfigValue(HocClkConfigValue_UncappedClocks)) + { + return ~0; // Integer limit, uncapped clocks ON + } + else + { + if (module == SysClkModule_GPU) + { + if (profile < SysClkProfile_HandheldCharging) + { + switch(Board::GetSocType()) { + case SysClkSocType_Erista: + return 460800000; + case SysClkSocType_Mariko: + switch(this->config->GetConfigValue(KipConfigValue_marikoGpuUV)) { + case 0: + return 614400000; + case 1: + return 691200000; + case 2: + return 768000000; + default: + return 614400000; + } + default: + return 460800000; + } + } + else if (profile <= SysClkProfile_HandheldChargingUSB) + { + switch(Board::GetSocType()) { + case SysClkSocType_Erista: + return 768000000; + case SysClkSocType_Mariko: + switch(this->config->GetConfigValue(KipConfigValue_marikoGpuUV)) { + case 0: + return 844800000; + case 1: + return 921600000; + case 2: + return 998400000; + default: + return 844800000; + } + default: + return 768000000; + } + } + } else if(module == SysClkModule_CPU) { + if(profile < SysClkProfile_HandheldCharging && Board::GetSocType() == SysClkSocType_Erista) { + return 1581000000; + } else { + return ~0; + } + } + } + return 0; +} + +std::uint32_t ClockManager::GetNearestHz(SysClkModule module, std::uint32_t inHz, std::uint32_t maxHz) +{ + std::uint32_t *freqs = &this->freqTable[module].list[0]; + size_t count = this->freqTable[module].count - 1; + + size_t i = 0; + while (i < count) + { + if (maxHz > 0 && freqs[i] >= maxHz) + { + break; + } + + if (inHz <= ((std::uint64_t)freqs[i] + freqs[i + 1]) / 2) + { + break; + } + + i++; + } + + return freqs[i]; +} + +bool ClockManager::ConfigIntervalTimeout(SysClkConfigValue intervalMsConfigValue, std::uint64_t ns, std::uint64_t *lastLogNs) +{ + std::uint64_t logInterval = this->GetConfig()->GetConfigValue(intervalMsConfigValue) * 1000000ULL; + bool shouldLog = logInterval && ((ns - *lastLogNs) > logInterval); + + if (shouldLog) + { + *lastLogNs = ns; + } + + return shouldLog; +} + +void ClockManager::RefreshFreqTableRow(SysClkModule module) +{ + std::scoped_lock lock{this->contextMutex}; + + std::uint32_t freqs[SYSCLK_FREQ_LIST_MAX]; + std::uint32_t count; + + FileUtils::LogLine("[mgr] %s freq list refresh", Board::GetModuleName(module, true)); + Board::GetFreqList(module, &freqs[0], SYSCLK_FREQ_LIST_MAX, &count); + + std::uint32_t *hz = &this->freqTable[module].list[0]; + this->freqTable[module].count = 0; + for (std::uint32_t i = 0; i < count; i++) + { + if (!this->IsAssignableHz(module, freqs[i])) + { + continue; + } + + *hz = freqs[i]; + FileUtils::LogLine("[mgr] %02u - %u - %u.%u MHz", this->freqTable[module].count, *hz, *hz / 1000000, *hz / 100000 - *hz / 1000000 * 10); + + this->freqTable[module].count++; + hz++; + } + + FileUtils::LogLine("[mgr] count = %u", this->freqTable[module].count); +} + +u32 ClockManager::SchedutilTargetHz(u32 util, u32 tableMaxHz) { + u64 hz = (u64)tableMaxHz * util / STEP_UTIL; + return (u32)(std::min(hz, static_cast(tableMaxHz))); +} + +u32 ClockManager::TableIndexForHz(const FreqTable& table, u32 targetHz) { // must pass in a freqTable as tables are different for cpu/gpu + for (u32 i = 0; i < table.count; i++) + if (table.list[i] >= targetHz) + return i; + return table.count - 1; +} + +u32 ClockManager::ResolveTargetHz(ClockManager* mgr, SysClkModule module) { + u32 hz = mgr->context->overrideFreqs[module]; + if (!hz) + hz = mgr->config->GetAutoClockHz( + mgr->context->applicationId, module, + mgr->context->profile, false); + if (!hz) + hz = mgr->config->GetAutoClockHz( + GLOBAL_PROFILE_ID, module, + mgr->context->profile, false); + return hz; +} + +void ClockManager::CpuGovernorThread(void* arg) { + ClockManager* mgr = static_cast(arg); + + u32 downHoldRemaining = 0; + u32 lastHz = 0; + + for (;;) { + if (!mgr->running || !isCpuGovernorEnabled) { + downHoldRemaining = 0; + lastHz = 0; + svcSleepThread(POLL_NS); + continue; + } + + u32 mode = 0; + Result rc = apmExtGetCurrentPerformanceConfiguration(&mode); + + if (R_SUCCEEDED(rc) && apmExtIsBoostMode(mode)) { + isCpuGovernorInBoostMode = true; + downHoldRemaining = 0; + lastHz = 0; + continue; // TODO: figure out a way to get boost clock easily and set it instead of just skipping the governor + } else if(!apmExtIsBoostMode(mode)) { + isCpuGovernorInBoostMode = false; + } + + auto& table = mgr->freqTable[SysClkModule_CPU]; + + if (table.count == 0) + continue; + + std::scoped_lock lock{mgr->contextMutex}; + + u32 cpuLoad = Board::GetPartLoad(HocClkPartLoad_CPUMax); + + u32 tableMaxHz = table.list[table.count - 1]; + u32 desiredHz = ClockManager::SchedutilTargetHz(cpuLoad, tableMaxHz); + u32 targetHz = ClockManager::ResolveTargetHz(mgr, SysClkModule_CPU); + u32 maxHz = mgr->GetMaxAllowedHz(SysClkModule_CPU, mgr->context->profile); + + if (targetHz && desiredHz > targetHz) + desiredHz = targetHz; + + if (maxHz && desiredHz > maxHz) + desiredHz = maxHz; + + u32 newHz = table.list[ClockManager::TableIndexForHz(table, desiredHz)]; + + // ramp up fast, go down slow + bool goingDown = (lastHz != 0) && (newHz < lastHz); + + if (!goingDown) + downHoldRemaining = 0; + else if (downHoldRemaining == 0) + downHoldRemaining = DOWN_HOLD_TICKS; + + if (downHoldRemaining > 0) + downHoldRemaining--; + + if ((!goingDown || (downHoldRemaining == 0)) && mgr->IsAssignableHz(SysClkModule_CPU, newHz)) { + Board::SetHz(SysClkModule_CPU, newHz); + mgr->context->freqs[SysClkModule_CPU] = newHz; + lastHz = newHz; + } + + svcSleepThread(POLL_NS); + } +} + +void ClockManager::GovernorThread(void* arg) { + ClockManager* mgr = static_cast(arg); + + u32 downHoldRemaining = 0; + u32 lastHz = 0; + + for (;;) { + if (!mgr->running || !isGpuGovernorEnabled) { + downHoldRemaining = 0; + lastHz = 0; + svcSleepThread(POLL_NS); + continue; + } + + auto& table = mgr->freqTable[SysClkModule_GPU]; + if (table.count == 0) + continue; + + std::scoped_lock lock{mgr->contextMutex}; + + u32 gpuLoad = Board::GetPartLoad(HocClkPartLoad_GPU); + u32 tableMaxHz = table.list[table.count - 1]; + u32 desiredHz = ClockManager::SchedutilTargetHz(gpuLoad, tableMaxHz); + u32 targetHz = ClockManager::ResolveTargetHz(mgr, SysClkModule_GPU); + u32 maxHz = mgr->GetMaxAllowedHz(SysClkModule_GPU, mgr->context->profile); + + if (targetHz && desiredHz > targetHz) + desiredHz = targetHz; + + if (maxHz && desiredHz > maxHz) + desiredHz = maxHz; + + u32 newHz = table.list[ClockManager::TableIndexForHz(table, desiredHz)]; + bool goingDown = (lastHz != 0) && (newHz < lastHz); + + if (!goingDown) + downHoldRemaining = 0; + else if (downHoldRemaining == 0) + downHoldRemaining = DOWN_HOLD_TICKS; + + if (downHoldRemaining > 0) + downHoldRemaining--; + + if ((!goingDown || (downHoldRemaining == 0)) && mgr->IsAssignableHz(SysClkModule_GPU, newHz)) { + Board::SetHz(SysClkModule_GPU, newHz); + mgr->context->freqs[SysClkModule_GPU] = newHz; + lastHz = newHz; + } + + svcSleepThread(POLL_NS); + } +} + +void ClockManager::VRRThread(void* arg) { + ClockManager* mgr = static_cast(arg); + u8 tick = 0; + for (;;) { + if (!mgr->running || mgr->context->profile == SysClkProfile_Docked || !isVRREnabled) { + svcSleepThread(POLL_NS); + continue; + } + + if(Board::IsHoag()) { // don't do anything on lite + 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) { + svcSleepThread(POLL_NS); + continue; + } + // if(appletGetFocusState() != AppletFocusState_InFocus) { + // Board::ResetToStockDisplay(); + // continue; + // } + + u32 targetHz = mgr->context->overrideFreqs[HorizonOCModule_Display]; + if (!targetHz) + { + targetHz = mgr->config->GetAutoClockHz(mgr->context->applicationId, HorizonOCModule_Display, mgr->context->profile, false); + if(!targetHz) + targetHz = mgr->config->GetAutoClockHz(GLOBAL_PROFILE_ID, HorizonOCModule_Display, mgr->context->profile, false); + } + + u8 maxDisplay; + if(targetHz) { + maxDisplay = targetHz; + } else { + 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 ? 45 : 40; + if(maxDisplay == minDisplay) + continue; + + 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 > 50) { + Board::ResetToStockDisplay(); + tick = 0; + svcSleepThread(25'000'000); + } + + svcSleepThread(POLL_NS); + } +} + + + +void ClockManager::HandleSafetyFeatures() { + AppletOperationMode opMode = appletGetOperationMode(); + if(this->config->GetConfigValue(HocClkConfigValue_HandheldTDP) && opMode == AppletOperationMode_Handheld) { + if(Board::GetConsoleType() == HorizonOCConsoleType_Hoag) { + if(Board::GetPowerMw(SysClkPowerSensor_Avg) < -(int)this->config->GetConfigValue(HocClkConfigValue_LiteTDPLimit)) { + ResetToStockClocks(); + return; + } + } else { + if(Board::GetPowerMw(SysClkPowerSensor_Avg) < -(int)this->config->GetConfigValue(HocClkConfigValue_HandheldTDPLimit)) { + ResetToStockClocks(); + return; + } + } + } + + if(((tmp451TempSoc() / 1000) > (int)this->config->GetConfigValue(HocClkConfigValue_ThermalThrottleThreshold)) && this->config->GetConfigValue(HocClkConfigValue_ThermalThrottle)) { + ResetToStockClocks(); + return; + } +} + +void ClockManager::HandleMiscFeatures() { + if(this->config->GetConfigValue(HorizonOCConfigValue_BatteryChargeCurrent)) { + I2c_Bq24193_SetFastChargeCurrentLimit(this->config->GetConfigValue(HorizonOCConfigValue_BatteryChargeCurrent)); + } +} + +void ClockManager::HandleGovernor(uint32_t targetHz) { + u32 tempTargetHz = this->context->overrideFreqs[HorizonOCModule_Governor]; + if (!tempTargetHz) + { + tempTargetHz = this->config->GetAutoClockHz(this->context->applicationId, HorizonOCModule_Governor, this->context->profile, true); + if (!tempTargetHz) + tempTargetHz = this->config->GetAutoClockHz(GLOBAL_PROFILE_ID, HorizonOCModule_Governor, this->context->profile, true); + } + + auto resolve = [](u8 app, u8 temp) -> u8 { + if (temp == ComponentGovernor_Disabled) return ComponentGovernor_Disabled; + if (temp != ComponentGovernor_DoNotOverride) return temp; + return app; + }; + + u8 effectiveCpu = resolve(GovernorStateCpu(targetHz), GovernorStateCpu(tempTargetHz)); + u8 effectiveGpu = resolve(GovernorStateGpu(targetHz), GovernorStateGpu(tempTargetHz)); + u8 effectiveVrr = resolve(GovernorStateVrr(targetHz), GovernorStateVrr(tempTargetHz)); + + bool newCpuGovernorState = (effectiveCpu == ComponentGovernor_Enabled); + bool newGpuGovernorState = (effectiveGpu == ComponentGovernor_Enabled); + bool newVrrGovernorState = (effectiveVrr == ComponentGovernor_Enabled); + + isCpuGovernorEnabled = newCpuGovernorState; + isGpuGovernorEnabled = newGpuGovernorState; + isVRREnabled = newVrrGovernorState; + + if(newCpuGovernorState == false && lastCpuGovernorState == true) { + svcSleepThread(100'000'000); // thread syncing. probably a cleaner way to do this but hey, it works! + Board::ResetToStockCpu(); + } + if(newGpuGovernorState == false && lastGpuGovernorState == true) { + svcSleepThread(100'000'000); + Board::ResetToStockGpu(); + } + if (newVrrGovernorState == false && lastVrrGovernorState == true) { + svcSleepThread(100'000'000); + Board::ResetToStockDisplay(); + } + if(newCpuGovernorState != lastCpuGovernorState || newGpuGovernorState != lastGpuGovernorState || newVrrGovernorState != lastVrrGovernorState) { + FileUtils::LogLine("[mgr] Governor state changed: CPU %s, GPU %s, VRR %s", newCpuGovernorState ? "enabled" : "disabled", newGpuGovernorState ? "enabled" : "disabled", newVrrGovernorState ? "enabled" : "disabled"); + lastCpuGovernorState = newCpuGovernorState; + lastGpuGovernorState = newGpuGovernorState; + lastVrrGovernorState = newVrrGovernorState; + } +} + +void ClockManager::DVFSBeforeSet(u32 targetHz) { + s32 dvfsOffset = this->config->GetConfigValue(HorizonOCConfigValue_DVFSOffset); + u32 vmin = Board::GetMinimumGpuVoltage(targetHz / 1000000) + dvfsOffset; + + Board::PcvHijackDvfs(vmin); + + /* Update the voltage. */ + if (I2c_BuckConverter_GetMvOut(&I2c_Mariko_GPU) < vmin) { + I2c_BuckConverter_SetMvOut(&I2c_Mariko_GPU, vmin); + } + + this->context->voltages[HocClkVoltage_GPU] = vmin * 1000; +} + +void ClockManager::DVFSAfterSet(u32 targetHz) { + s32 dvfsOffset = this->config->GetConfigValue(HorizonOCConfigValue_DVFSOffset); + dvfsOffset = std::max(dvfsOffset, -80); + u32 vmin = Board::GetMinimumGpuVoltage(targetHz / 1000000); + + if (vmin) { + vmin += dvfsOffset; + } + + u32 maxHz = this->GetMaxAllowedHz(SysClkModule_GPU, this->context->profile); + u32 nearestHz = this->GetNearestHz(SysClkModule_GPU, targetHz, maxHz); + Board::PcvHijackDvfs(vmin); + + if (targetHz) { + Board::SetHz(SysClkModule_GPU, ~0); + Board::SetHz(SysClkModule_GPU, nearestHz); + } else { + Board::SetHz(SysClkModule_GPU, ~0); + Board::ResetToStockGpu(); + } +} + +void ClockManager::HandleCpuUv() { + if(Board::GetSocType() == SysClkSocType_Erista) + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_eristaCpuUV), 0, 1581000000); + else + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_marikoCpuUVLow), this->config->GetConfigValue(KipConfigValue_marikoCpuUVHigh), Board::CalculateTbreak(this->config->GetConfigValue(KipConfigValue_tableConf))); +} + +void ClockManager::DVFSReset() { + if (Board::GetSocType() == SysClkSocType_Mariko && this->config->GetConfigValue(HorizonOCConfigValue_DVFSMode) == DVFSMode_Hijack) { + Board::PcvHijackDvfs(0); + + u32 targetHz = this->context->overrideFreqs[SysClkModule_GPU]; + if (!targetHz) { + targetHz = this->config->GetAutoClockHz(this->context->applicationId, SysClkModule_GPU, this->context->profile, false); + if(!targetHz) { + targetHz = this->config->GetAutoClockHz(GLOBAL_PROFILE_ID, SysClkModule_GPU, this->context->profile, false); + } + } + u32 maxHz = this->GetMaxAllowedHz(SysClkModule_GPU, this->context->profile); + u32 nearestHz = this->GetNearestHz(SysClkModule_GPU, targetHz, maxHz); + + Board::SetHz(SysClkModule_GPU, ~0); + if(targetHz) { + Board::SetHz(SysClkModule_GPU, nearestHz); + } else { + Board::ResetToStockGpu(); + } + } +} + +void ClockManager::HandleFreqReset(SysClkModule module, bool isBoost) { + switch (module) + { + case SysClkModule_CPU: + if(!(isBoost || (this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode) && isBoost))) + Board::ResetToStockCpu(); + if(this->config->GetConfigValue(HorizonOCConfigValue_LiveCpuUv)) { + if(Board::GetSocType() == SysClkSocType_Erista) + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_eristaCpuUV), 0, 1581000000); + else + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_marikoCpuUVLow), this->config->GetConfigValue(KipConfigValue_marikoCpuUVHigh), Board::CalculateTbreak(this->config->GetConfigValue(KipConfigValue_tableConf))); + } + + break; + case SysClkModule_GPU: + Board::ResetToStockGpu(); + break; + case SysClkModule_MEM: + Board::ResetToStockMem(); + DVFSReset(); + break; + case HorizonOCModule_Display: + if(this->config->GetConfigValue(HorizonOCConfigValue_OverwriteRefreshRate) && !Board::IsHoag()) { + Board::ResetToStockDisplay(); + } + break; + default: + break; + } + +} + +void ClockManager::SetClocks(bool isBoost) { + std::uint32_t targetHz = 0; + std::uint32_t maxHz = 0; + std::uint32_t nearestHz = 0; + + if(isBoost && !this->config->GetConfigValue(HocClkConfigValue_OverwriteBoostMode)) { + u32 boostFreq = Board::GetHz(SysClkModule_CPU); + if (boostFreq / 1000000 > 1785) { + Board::SetHz(SysClkModule_CPU, boostFreq); + } + return; // Return if we are't overwriting boost mode + } + + bool returnRaw = false; // Return a value scaled to MHz instead of raw value + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + u32 oldHz = Board::GetHz((SysClkModule)module); // Get Old hz (used primarily for DVFS Logic) + + if(module > SysClkModule_MEM) + returnRaw = true; + else + returnRaw = false; + targetHz = this->context->overrideFreqs[module]; + if (!targetHz) + { + targetHz = this->config->GetAutoClockHz(this->context->applicationId, (SysClkModule)module, this->context->profile, returnRaw); + if(!targetHz) + targetHz = this->config->GetAutoClockHz(GLOBAL_PROFILE_ID, (SysClkModule)module, this->context->profile, returnRaw); + } + + if(module == HorizonOCModule_Governor) { + HandleGovernor(targetHz); + } + + bool noCPU = isCpuGovernorEnabled; + bool noGPU = isGpuGovernorEnabled; + if(!Board::IsHoag()) { + bool noDisp = isVRREnabled; + if(noDisp && module == HorizonOCModule_Display) + continue; + + if(module == HorizonOCModule_Display && this->config->GetConfigValue(HorizonOCConfigValue_OverwriteRefreshRate)) { + if(targetHz) + Board::SetHz(HorizonOCModule_Display, targetHz); + else + Board::ResetToStockDisplay(); + } + } + + // Skip GPU and CPU if governors handle them + if(module > SysClkModule_MEM) { + continue; + } + + + if(noCPU && module == SysClkModule_CPU) + continue; + if(noGPU && module == SysClkModule_GPU) + continue; + + if (targetHz) + { + maxHz = this->GetMaxAllowedHz((SysClkModule)module, this->context->profile); + nearestHz = this->GetNearestHz((SysClkModule)module, targetHz, maxHz); + + if (nearestHz != this->context->freqs[module]) { + FileUtils::LogLine( + "[mgr] %s clock set : %u.%u MHz (target = %u.%u MHz)", + Board::GetModuleName((SysClkModule)module, true), + nearestHz / 1000000, nearestHz / 100000 - nearestHz / 1000000 * 10, + targetHz / 1000000, targetHz / 100000 - targetHz / 1000000 * 10 + ); + + if(module == SysClkModule_MEM && Board::GetSocType() == SysClkSocType_Mariko && targetHz > oldHz && this->config->GetConfigValue(HorizonOCConfigValue_DVFSMode) == DVFSMode_Hijack) { + DVFSBeforeSet(targetHz); + } + + Board::SetHz((SysClkModule)module, nearestHz); + this->context->freqs[module] = nearestHz; + + if(module == SysClkModule_CPU && (this->config->GetConfigValue(HorizonOCConfigValue_LiveCpuUv))) { + HandleCpuUv(); + } + + if(module == SysClkModule_MEM && Board::GetSocType() == SysClkSocType_Mariko && targetHz < oldHz && this->config->GetConfigValue(HorizonOCConfigValue_DVFSMode) == DVFSMode_Hijack) { + DVFSAfterSet(targetHz); + } + } + } else { + HandleFreqReset((SysClkModule)module, isBoost); + } + } + +} + +void ClockManager::Tick() +{ + std::scoped_lock lock{this->contextMutex}; + std::uint32_t mode = 0; + Result rc = apmExtGetCurrentPerformanceConfiguration(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + bool isBoost = apmExtIsBoostMode(mode); + + HandleSafetyFeatures(); + + if (this->RefreshContext() || this->config->Refresh()) + { + HandleMiscFeatures(); + SetClocks(isBoost); + } +} + +void ClockManager::ResetToStockClocks() { + Board::ResetToStockCpu(); + if(this->config->GetConfigValue(HorizonOCConfigValue_LiveCpuUv)) + { + if(Board::GetSocType() == SysClkSocType_Erista) + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_eristaCpuUV), 0, 1581000000); + else + Board::SetCpuUvLevel(this->config->GetConfigValue(KipConfigValue_marikoCpuUVLow), this->config->GetConfigValue(KipConfigValue_marikoCpuUVHigh), Board::CalculateTbreak(this->config->GetConfigValue(KipConfigValue_tableConf))); + } + + Board::ResetToStockGpu(); +} + +void ClockManager::WaitForNextTick() +{ + if(!(Board::GetHz(SysClkModule_MEM) < 665000000)) + svcSleepThread(this->GetConfig()->GetConfigValue(SysClkConfigValue_PollingIntervalMs) * 1000000ULL); + else + svcSleepThread(5000 * 1000000ULL); // 5 seconds in sleep mode +} + +bool ClockManager::RefreshContext() +{ + bool hasChanged = false; + + std::uint32_t mode = 0; + Result rc = apmExtGetCurrentPerformanceConfiguration(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + std::uint64_t applicationId = ProcessManagement::GetCurrentApplicationId(); + if (applicationId != this->context->applicationId) + { + FileUtils::LogLine("[mgr] TitleID change: %016lX", applicationId); + this->context->applicationId = applicationId; + hasChanged = true; + } + + SysClkProfile profile = Board::GetProfile(); + if (profile != this->context->profile) + { + FileUtils::LogLine("[mgr] Profile change: %s", Board::GetProfileName(profile, true)); + this->context->profile = profile; + hasChanged = true; + } + + // restore clocks to stock values on app or profile change + if (hasChanged) + { + Board::ResetToStock(); + if (Board::GetSocType() == SysClkSocType_Mariko && this->config->GetConfigValue(HorizonOCConfigValue_DVFSMode) == DVFSMode_Hijack) { + Board::PcvHijackDvfs(0); + Board::SetHz(SysClkModule_GPU, ~0); + Board::ResetToStockGpu(); + } + this->WaitForNextTick(); + } + + std::uint32_t hz = 0; + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + hz = Board::GetHz((SysClkModule)module); + if (hz != 0 && hz != this->context->freqs[module]) + { + FileUtils::LogLine("[mgr] %s clock change: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), hz / 1000000, hz / 100000 - hz / 1000000 * 10); + this->context->freqs[module] = hz; + hasChanged = true; + } + + hz = this->GetConfig()->GetOverrideHz((SysClkModule)module); + if (hz != this->context->overrideFreqs[module]) + { + if (hz) + { + FileUtils::LogLine("[mgr] %s override change: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), hz / 1000000, hz / 100000 - hz / 1000000 * 10); + } + this->context->overrideFreqs[module] = hz; + hasChanged = true; + } + } + + std::uint64_t ns = armTicksToNs(armGetSystemTick()); + + // temperatures do not and should not force a refresh, hasChanged untouched + std::uint32_t millis = 0; + bool shouldLogTemp = this->ConfigIntervalTimeout(SysClkConfigValue_TempLogIntervalMs, ns, &this->lastTempLogNs); + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + millis = Board::GetTemperatureMilli((SysClkThermalSensor)sensor); + if (shouldLogTemp) + { + FileUtils::LogLine("[mgr] %s temp: %u.%u °C", Board::GetThermalSensorName((SysClkThermalSensor)sensor, true), millis / 1000, (millis - millis / 1000 * 1000) / 100); + } + this->context->temps[sensor] = millis; + } + + // power stats do not and should not force a refresh, hasChanged untouched + std::int32_t mw = 0; + bool shouldLogPower = this->ConfigIntervalTimeout(SysClkConfigValue_PowerLogIntervalMs, ns, &this->lastPowerLogNs); + for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++) + { + mw = Board::GetPowerMw((SysClkPowerSensor)sensor); + if (shouldLogPower) + { + FileUtils::LogLine("[mgr] Power %s: %d mW", Board::GetPowerSensorName((SysClkPowerSensor)sensor, false), mw); + } + this->context->power[sensor] = mw; + } + + // real freqs do not and should not force a refresh, hasChanged untouched + std::uint32_t realHz = 0; + bool shouldLogFreq = this->ConfigIntervalTimeout(SysClkConfigValue_FreqLogIntervalMs, ns, &this->lastFreqLogNs); + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + realHz = Board::GetRealHz((SysClkModule)module); + if (shouldLogFreq) + { + FileUtils::LogLine("[mgr] %s real freq: %u.%u MHz", Board::GetModuleName((SysClkModule)module, true), realHz / 1000000, realHz / 100000 - realHz / 1000000 * 10); + } + this->context->realFreqs[module] = realHz; + } + + // ram load do not and should not force a refresh, hasChanged untouched + for (unsigned int loadSource = 0; loadSource < SysClkPartLoad_EnumMax; loadSource++) + { + this->context->partLoad[loadSource] = Board::GetPartLoad((SysClkPartLoad)loadSource); + } + + for (unsigned int voltageSource = 0; voltageSource < HocClkVoltage_EnumMax; voltageSource++) + { + this->context->voltages[voltageSource] = Board::GetVoltage((HocClkVoltage)voltageSource); + } + + if (this->ConfigIntervalTimeout(SysClkConfigValue_CsvWriteIntervalMs, ns, &this->lastCsvWriteNs)) + { + FileUtils::WriteContextToCsv(this->context); + } + + // this->context->maxDisplayFreq = Board::GetHighestDockedDisplayRate(); + if(!Board::IsHoag()) { + u32 targetHz = this->context->overrideFreqs[HorizonOCModule_Display]; + if (!targetHz) + { + targetHz = this->config->GetAutoClockHz(this->context->applicationId, HorizonOCModule_Display, this->context->profile, true); + if(!targetHz) + targetHz = this->config->GetAutoClockHz(GLOBAL_PROFILE_ID, HorizonOCModule_Display, this->context->profile, true); + } + + if(targetHz && this->context->realFreqs[HorizonOCModule_Display] > targetHz && this->context->profile != SysClkProfile_Docked) + this->context->realFreqs[HorizonOCModule_Display] = targetHz; // clean up display real freqs, should probably be moved to the real freqs loop? + + Board::SetDisplayRefreshDockedState(this->context->profile == SysClkProfile_Docked); + } + if(this->context->isSaltyNXInstalled) + this->context->fps = saltyNXIntegration->GetFPS(); + else + this->context->fps = 254; // N/A + + if(this->context->isSaltyNXInstalled) + this->context->resolutionHeight = saltyNXIntegration->GetResolutionHeight(); + else + this->context->resolutionHeight = 0; // N/A + + return hasChanged; +} + +void ClockManager::SetKipData() { + // TODO: figure out if this REALLY causes issues (i doubt it) + // if(Board::GetSocType() == SysClkSocType_Mariko) { + // if(R_FAILED(I2c_BuckConverter_SetMvOut(&I2c_Mariko_DRAM_VDDQ, this->config->GetConfigValue(KipConfigValue_marikoEmcVddqVolt) / 1000))) { + // FileUtils::LogLine("[clock_manager] Failed set i2c vddq"); + // writeNotification("Horizon OC\nFailed to write I2C\nwhile setting vddq"); + // } + // } + CustomizeTable table; + FILE* fp; + fp = fopen("sdmc:/atmosphere/kips/hoc.kip", "r"); + + if (fp == NULL) { + writeNotification("Horizon OC\nKip opening failed"); + kipAvailable = false; + return; + } else { + kipAvailable = true; + fclose(fp); + } + + if (!cust_read_and_cache("sdmc:/atmosphere/kips/hoc.kip", &table)) { + FileUtils::LogLine("[clock_manager] Failed to read KIP file"); + writeNotification("Horizon OC\nKip read failed"); + return; + } + + CUST_WRITE_FIELD_BATCH(&table, custRev, this->config->GetConfigValue(KipConfigValue_custRev)); + // CUST_WRITE_FIELD_BATCH(&table, mtcConf, this->config->GetConfigValue(KipConfigValue_mtcConf)); + CUST_WRITE_FIELD_BATCH(&table, hpMode, this->config->GetConfigValue(KipConfigValue_hpMode)); + + CUST_WRITE_FIELD_BATCH(&table, commonEmcMemVolt, this->config->GetConfigValue(KipConfigValue_commonEmcMemVolt)); + CUST_WRITE_FIELD_BATCH(&table, eristaEmcMaxClock, this->config->GetConfigValue(KipConfigValue_eristaEmcMaxClock)); + CUST_WRITE_FIELD_BATCH(&table, eristaEmcMaxClock1, this->config->GetConfigValue(KipConfigValue_eristaEmcMaxClock1)); + CUST_WRITE_FIELD_BATCH(&table, eristaEmcMaxClock2, this->config->GetConfigValue(KipConfigValue_eristaEmcMaxClock2)); + CUST_WRITE_FIELD_BATCH(&table, marikoEmcMaxClock, this->config->GetConfigValue(KipConfigValue_marikoEmcMaxClock)); + CUST_WRITE_FIELD_BATCH(&table, marikoEmcVddqVolt, this->config->GetConfigValue(KipConfigValue_marikoEmcVddqVolt)); + CUST_WRITE_FIELD_BATCH(&table, emcDvbShift, this->config->GetConfigValue(KipConfigValue_emcDvbShift)); + + CUST_WRITE_FIELD_BATCH(&table, t1_tRCD, this->config->GetConfigValue(KipConfigValue_t1_tRCD)); + CUST_WRITE_FIELD_BATCH(&table, t2_tRP, this->config->GetConfigValue(KipConfigValue_t2_tRP)); + CUST_WRITE_FIELD_BATCH(&table, t3_tRAS, this->config->GetConfigValue(KipConfigValue_t3_tRAS)); + CUST_WRITE_FIELD_BATCH(&table, t4_tRRD, this->config->GetConfigValue(KipConfigValue_t4_tRRD)); + CUST_WRITE_FIELD_BATCH(&table, t5_tRFC, this->config->GetConfigValue(KipConfigValue_t5_tRFC)); + CUST_WRITE_FIELD_BATCH(&table, t6_tRTW, this->config->GetConfigValue(KipConfigValue_t6_tRTW)); + CUST_WRITE_FIELD_BATCH(&table, t7_tWTR, this->config->GetConfigValue(KipConfigValue_t7_tWTR)); + CUST_WRITE_FIELD_BATCH(&table, t8_tREFI, this->config->GetConfigValue(KipConfigValue_t8_tREFI)); + CUST_WRITE_FIELD_BATCH(&table, mem_burst_read_latency, this->config->GetConfigValue(KipConfigValue_mem_burst_read_latency)); + CUST_WRITE_FIELD_BATCH(&table, mem_burst_write_latency, this->config->GetConfigValue(KipConfigValue_mem_burst_write_latency)); + CUST_WRITE_FIELD_BATCH(&table, eristaCpuUV, this->config->GetConfigValue(KipConfigValue_eristaCpuUV)); + CUST_WRITE_FIELD_BATCH(&table, eristaCpuVmin, this->config->GetConfigValue(KipConfigValue_eristaCpuVmin)); + CUST_WRITE_FIELD_BATCH(&table, eristaCpuMaxVolt, this->config->GetConfigValue(KipConfigValue_eristaCpuMaxVolt)); + CUST_WRITE_FIELD_BATCH(&table, eristaCpuUnlock, this->config->GetConfigValue(KipConfigValue_eristaCpuUnlock)); + + CUST_WRITE_FIELD_BATCH(&table, marikoCpuUVLow, this->config->GetConfigValue(KipConfigValue_marikoCpuUVLow)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuUVHigh, this->config->GetConfigValue(KipConfigValue_marikoCpuUVHigh)); + CUST_WRITE_FIELD_BATCH(&table, tableConf, this->config->GetConfigValue(KipConfigValue_tableConf)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuLowVmin, this->config->GetConfigValue(KipConfigValue_marikoCpuLowVmin)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuHighVmin, this->config->GetConfigValue(KipConfigValue_marikoCpuHighVmin)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuMaxVolt, this->config->GetConfigValue(KipConfigValue_marikoCpuMaxVolt)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuMaxClock, this->config->GetConfigValue(KipConfigValue_marikoCpuMaxClock)); + + CUST_WRITE_FIELD_BATCH(&table, eristaCpuBoostClock, this->config->GetConfigValue(KipConfigValue_eristaCpuBoostClock)); + CUST_WRITE_FIELD_BATCH(&table, marikoCpuBoostClock, this->config->GetConfigValue(KipConfigValue_marikoCpuBoostClock)); + + CUST_WRITE_FIELD_BATCH(&table, eristaGpuUV, this->config->GetConfigValue(KipConfigValue_eristaGpuUV)); + CUST_WRITE_FIELD_BATCH(&table, eristaGpuVmin, this->config->GetConfigValue(KipConfigValue_eristaGpuVmin)); + + CUST_WRITE_FIELD_BATCH(&table, marikoGpuUV, this->config->GetConfigValue(KipConfigValue_marikoGpuUV)); + CUST_WRITE_FIELD_BATCH(&table, marikoGpuVmin, this->config->GetConfigValue(KipConfigValue_marikoGpuVmin)); + CUST_WRITE_FIELD_BATCH(&table, marikoGpuVmax, this->config->GetConfigValue(KipConfigValue_marikoGpuVmax)); + + CUST_WRITE_FIELD_BATCH(&table, commonGpuVoltOffset, this->config->GetConfigValue(KipConfigValue_commonGpuVoltOffset)); + CUST_WRITE_FIELD_BATCH(&table, gpuSpeedo, this->config->GetConfigValue(KipConfigValue_gpuSpeedo)); + + for (int i = 0; i < 24; i++) { + table.marikoGpuVoltArray[i] = this->config->GetConfigValue((SysClkConfigValue)(KipConfigValue_g_volt_76800 + i)); + } + + for (int i = 0; i < 27; i++) { + table.eristaGpuVoltArray[i] = this->config->GetConfigValue((SysClkConfigValue)(KipConfigValue_g_volt_e_76800 + i)); + } + + CUST_WRITE_FIELD_BATCH(&table, t6_tRTW_fine_tune, this->config->GetConfigValue(KipConfigValue_t6_tRTW_fine_tune)); + CUST_WRITE_FIELD_BATCH(&table, t7_tWTR_fine_tune, this->config->GetConfigValue(KipConfigValue_t7_tWTR_fine_tune)); + + if (!cust_write_table("sdmc:/atmosphere/kips/hoc.kip", &table)) { + FileUtils::LogLine("[clock_manager] Failed to write KIP file"); + writeNotification("Horizon OC\nKip write failed"); + } + + SysClkConfigValueList configValues; + this->config->GetConfigValues(&configValues); + + configValues.values[KipCrc32] = (u64)checksum_file("sdmc:/atmosphere/kips/hoc.kip"); // write checksum + + if (this->config->SetConfigValues(&configValues, false)) { + FileUtils::LogLine("[clock_manager] Successfully loaded KIP data into config"); + } else { + FileUtils::LogLine("[clock_manager] Warning: Failed to set config values from KIP"); + writeNotification("Horizon OC\nKip config set failed"); + } +} + +// I know this is very hacky, but the config system in the sysmodule doesn't really support writing + +void ClockManager::GetKipData() { + FILE* fp; + if(this->config->Refresh()) { + + fp = fopen("sdmc:/atmosphere/kips/hoc.kip", "r"); + + if (fp == NULL) { + writeNotification("Horizon OC\nKip opening failed"); + kipAvailable = false; + return; + } else { + kipAvailable = true; + fclose(fp); + } + + + + SysClkConfigValueList configValues; + this->config->GetConfigValues(&configValues); + + CustomizeTable table; + + if (!cust_read_and_cache("sdmc:/atmosphere/kips/hoc.kip", &table)) { + FileUtils::LogLine("[clock_manager] Failed to read KIP file for GetKipData"); + writeNotification("Horizon OC\nKip read failed"); + return; + } + + if((u64)checksum_file("sdmc:/atmosphere/kips/hoc.kip") != this->config->GetConfigValue(KipCrc32) && !this->config->GetConfigValue(HocClkConfigValue_IsFirstLoad)) { + SetKipData(); + writeNotification("Horizon OC\nKIP has been updated"); + writeNotification("Horizon OC\nPlease reboot your console"); + writeNotification("Horizon OC\nto complete the update"); + return; + } + if(this->config->GetConfigValue(HocClkConfigValue_IsFirstLoad) == true) { + configValues.values[HocClkConfigValue_IsFirstLoad] = (u64)false; + writeNotification("Horizon OC has been installed"); + } + static bool writeBootConfigValues = true; + + configValues.values[KipCrc32] = (u64)checksum_file("sdmc:/atmosphere/kips/hoc.kip"); // write checksum + + + if(writeBootConfigValues) { + writeBootConfigValues = false; + + // initialConfigValues[KipConfigValue_mtcConf] = cust_get_mtc_conf(&table); + initialConfigValues[KipConfigValue_hpMode] = cust_get_hp_mode(&table); + + initialConfigValues[KipConfigValue_commonEmcMemVolt] = cust_get_common_emc_volt(&table); + initialConfigValues[KipConfigValue_eristaEmcMaxClock] = cust_get_erista_emc_max(&table); + initialConfigValues[KipConfigValue_eristaEmcMaxClock1] = cust_get_erista_emc_max1(&table); + initialConfigValues[KipConfigValue_eristaEmcMaxClock2] = cust_get_erista_emc_max2(&table); + initialConfigValues[KipConfigValue_marikoEmcMaxClock] = cust_get_mariko_emc_max(&table); + initialConfigValues[KipConfigValue_marikoEmcVddqVolt] = cust_get_mariko_emc_vddq(&table); + initialConfigValues[KipConfigValue_emcDvbShift] = cust_get_emc_dvb_shift(&table); + + initialConfigValues[KipConfigValue_t1_tRCD] = cust_get_tRCD(&table); + initialConfigValues[KipConfigValue_t2_tRP] = cust_get_tRP(&table); + initialConfigValues[KipConfigValue_t3_tRAS] = cust_get_tRAS(&table); + initialConfigValues[KipConfigValue_t4_tRRD] = cust_get_tRRD(&table); + initialConfigValues[KipConfigValue_t5_tRFC] = cust_get_tRFC(&table); + initialConfigValues[KipConfigValue_t6_tRTW] = cust_get_tRTW(&table); + initialConfigValues[KipConfigValue_t7_tWTR] = cust_get_tWTR(&table); + initialConfigValues[KipConfigValue_t8_tREFI] = cust_get_tREFI(&table); + initialConfigValues[KipConfigValue_mem_burst_read_latency] = cust_get_burst_read_lat(&table); + initialConfigValues[KipConfigValue_mem_burst_write_latency] = cust_get_burst_write_lat(&table); + + initialConfigValues[KipConfigValue_eristaCpuUV] = cust_get_erista_cpu_uv(&table); + initialConfigValues[KipConfigValue_eristaCpuVmin] = cust_get_eristaCpuVmin(&table); + initialConfigValues[KipConfigValue_eristaCpuMaxVolt] = cust_get_erista_cpu_max_volt(&table); + initialConfigValues[KipConfigValue_eristaCpuUnlock] = cust_get_eristaCpuUnlock(&table); + + initialConfigValues[KipConfigValue_marikoCpuUVLow] = cust_get_mariko_cpu_uv_low(&table); + initialConfigValues[KipConfigValue_marikoCpuUVHigh] = cust_get_mariko_cpu_uv_high(&table); + initialConfigValues[KipConfigValue_tableConf] = cust_get_table_conf(&table); + initialConfigValues[KipConfigValue_marikoCpuLowVmin] = cust_get_mariko_cpu_low_vmin(&table); + initialConfigValues[KipConfigValue_marikoCpuHighVmin] = cust_get_mariko_cpu_high_vmin(&table); + initialConfigValues[KipConfigValue_marikoCpuMaxVolt] = cust_get_mariko_cpu_max_volt(&table); + initialConfigValues[KipConfigValue_marikoCpuMaxClock] = cust_get_marikoCpuMaxClock(&table); + initialConfigValues[KipConfigValue_eristaCpuBoostClock] = cust_get_erista_cpu_boost(&table); + initialConfigValues[KipConfigValue_marikoCpuBoostClock] = cust_get_mariko_cpu_boost(&table); + + initialConfigValues[KipConfigValue_eristaGpuUV] = cust_get_erista_gpu_uv(&table); + initialConfigValues[KipConfigValue_eristaGpuVmin] = cust_get_erista_gpu_vmin(&table); + initialConfigValues[KipConfigValue_marikoGpuUV] = cust_get_mariko_gpu_uv(&table); + initialConfigValues[KipConfigValue_marikoGpuVmin] = cust_get_mariko_gpu_vmin(&table); + initialConfigValues[KipConfigValue_marikoGpuVmax] = cust_get_mariko_gpu_vmax(&table); + initialConfigValues[KipConfigValue_commonGpuVoltOffset] = cust_get_common_gpu_offset(&table); + initialConfigValues[KipConfigValue_gpuSpeedo] = cust_get_gpu_speedo(&table); + initialConfigValues[KipConfigValue_t6_tRTW_fine_tune] = cust_get_tRTW_fine_tune(&table); + initialConfigValues[KipConfigValue_t7_tWTR_fine_tune] = cust_get_tWTR_fine_tune(&table); + } + + // configValues.values[KipConfigValue_mtcConf] = cust_get_mtc_conf(&table); + configValues.values[KipConfigValue_hpMode] = cust_get_hp_mode(&table); + + configValues.values[KipConfigValue_commonEmcMemVolt] = cust_get_common_emc_volt(&table); + configValues.values[KipConfigValue_eristaEmcMaxClock] = cust_get_erista_emc_max(&table); + configValues.values[KipConfigValue_eristaEmcMaxClock1] = cust_get_erista_emc_max1(&table); + configValues.values[KipConfigValue_eristaEmcMaxClock2] = cust_get_erista_emc_max2(&table); + configValues.values[KipConfigValue_marikoEmcMaxClock] = cust_get_mariko_emc_max(&table); + configValues.values[KipConfigValue_marikoEmcVddqVolt] = cust_get_mariko_emc_vddq(&table); + configValues.values[KipConfigValue_emcDvbShift] = cust_get_emc_dvb_shift(&table); + + configValues.values[KipConfigValue_t1_tRCD] = cust_get_tRCD(&table); + configValues.values[KipConfigValue_t2_tRP] = cust_get_tRP(&table); + configValues.values[KipConfigValue_t3_tRAS] = cust_get_tRAS(&table); + configValues.values[KipConfigValue_t4_tRRD] = cust_get_tRRD(&table); + configValues.values[KipConfigValue_t5_tRFC] = cust_get_tRFC(&table); + configValues.values[KipConfigValue_t6_tRTW] = cust_get_tRTW(&table); + configValues.values[KipConfigValue_t7_tWTR] = cust_get_tWTR(&table); + configValues.values[KipConfigValue_t8_tREFI] = cust_get_tREFI(&table); + configValues.values[KipConfigValue_mem_burst_read_latency] = cust_get_burst_read_lat(&table); + configValues.values[KipConfigValue_mem_burst_write_latency] = cust_get_burst_write_lat(&table); + + configValues.values[KipConfigValue_eristaCpuUV] = cust_get_erista_cpu_uv(&table); + configValues.values[KipConfigValue_eristaCpuVmin] = cust_get_eristaCpuVmin(&table); + configValues.values[KipConfigValue_eristaCpuMaxVolt] = cust_get_erista_cpu_max_volt(&table); + configValues.values[KipConfigValue_eristaCpuUnlock] = cust_get_eristaCpuUnlock(&table); + + + configValues.values[KipConfigValue_marikoCpuUVLow] = cust_get_mariko_cpu_uv_low(&table); + configValues.values[KipConfigValue_marikoCpuUVHigh] = cust_get_mariko_cpu_uv_high(&table); + configValues.values[KipConfigValue_tableConf] = cust_get_table_conf(&table); + configValues.values[KipConfigValue_marikoCpuLowVmin] = cust_get_mariko_cpu_low_vmin(&table); + configValues.values[KipConfigValue_marikoCpuHighVmin] = cust_get_mariko_cpu_high_vmin(&table); + configValues.values[KipConfigValue_marikoCpuMaxVolt] = cust_get_mariko_cpu_max_volt(&table); + configValues.values[KipConfigValue_marikoCpuMaxClock] = cust_get_marikoCpuMaxClock(&table); + configValues.values[KipConfigValue_eristaCpuBoostClock] = cust_get_erista_cpu_boost(&table); + configValues.values[KipConfigValue_marikoCpuBoostClock] = cust_get_mariko_cpu_boost(&table); + + configValues.values[KipConfigValue_eristaGpuUV] = cust_get_erista_gpu_uv(&table); + configValues.values[KipConfigValue_eristaGpuVmin] = cust_get_erista_gpu_vmin(&table); + configValues.values[KipConfigValue_marikoGpuUV] = cust_get_mariko_gpu_uv(&table); + configValues.values[KipConfigValue_marikoGpuVmin] = cust_get_mariko_gpu_vmin(&table); + configValues.values[KipConfigValue_marikoGpuVmax] = cust_get_mariko_gpu_vmax(&table); + configValues.values[KipConfigValue_commonGpuVoltOffset] = cust_get_common_gpu_offset(&table); + configValues.values[KipConfigValue_gpuSpeedo] = Board::getSpeedo(HorizonOCSpeedo_GPU); // cust_get_gpu_speedo(&table); + + for (int i = 0; i < 24; i++) { + configValues.values[KipConfigValue_g_volt_76800 + i] = cust_get_mariko_gpu_volt(&table, i); + initialConfigValues[KipConfigValue_g_volt_76800 + i] = cust_get_mariko_gpu_volt(&table, i); + } + + for (int i = 0; i < 27; i++) { + configValues.values[KipConfigValue_g_volt_e_76800 + i] = cust_get_erista_gpu_volt(&table, i); + initialConfigValues[KipConfigValue_g_volt_e_76800 + i] = cust_get_erista_gpu_volt(&table, i); + } + + configValues.values[KipConfigValue_t7_tWTR_fine_tune] = cust_get_tWTR_fine_tune(&table); + configValues.values[KipConfigValue_t6_tRTW_fine_tune] = cust_get_tRTW_fine_tune(&table); + + // if(cust_get_cust_rev(&table) == KIP_CUST_REV) + // return; + + if (sizeof(SysClkConfigValueList) <= sizeof(configValues)) { + if (this->config->SetConfigValues(&configValues, false)) { + FileUtils::LogLine("[clock_manager] Successfully loaded KIP data into config"); + } else { + FileUtils::LogLine("[clock_manager] Warning: Failed to set config values from KIP"); + writeNotification("Horizon OC\nKip config set failed"); + } + } else { + FileUtils::LogLine("[clock_manager] Error: Config value list buffer size mismatch"); + writeNotification("Horizon OC\nConfig Buffer Mismatch"); + } + } else { + FileUtils::LogLine("[clock_manager] Config refresh error in GetKipData!"); + writeNotification("Horizon OC\nConfig refresh failed"); + } +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.h b/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.h new file mode 100644 index 00000000..b09d10e1 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/clock_manager.h @@ -0,0 +1,261 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ +#pragma once +#include +#include +#include +#include "config.h" +#include "board.h" +#include +#include "integrations.h" + +class SysDockIntegration; +class SaltyNXIntegration; +class ClockManager +{ + public: + /** + * Get instance + * @return Pointer to a ClockManager instance + */ + static ClockManager* GetInstance(); + static void Initialize(); + static void Exit(); + ClockManager(); + virtual ~ClockManager(); + + /** + * Get context object + * @return Context instance + */ + SysClkContext GetCurrentContext(); + + /** + * Get config object + * @return Pointer to a config instance + */ + Config* GetConfig(); + + /** + * Set clock manager running + * @param running Is running or not? + */ + void SetRunning(bool running); + + /** + * Is clock manager running + * @return running or not? + */ + bool Running(); + + /** + * Get frequency list from clkrst + * + * @param module Module to get frequency list for + * @param list List of frequencies + * @param maxCount How many entries to expect in list. Usually 32 + * @param outCount How many entries were retrived + */ + void GetFreqList(SysClkModule module, std::uint32_t* list, std::uint32_t maxCount, std::uint32_t* outCount); + + /** + * Handles safety features + * + */ + void HandleSafetyFeatures(); + + /** + * Handles misc features (currently only battery charge current). + * + */ + void HandleMiscFeatures(); + + /** + * Handles governor state resolution and applies CPU/GPU governor transitions. + * + * @param targetHz Governor override value for the current profile. + */ + void HandleGovernor(uint32_t targetHz); + + /** + * Handles DVFS logic before the frequency set + * + * @param targetHz Governor override value for the current profile. + */ + void DVFSBeforeSet(u32 targetHz); + + /** + * Handles DVFS logic after the frequency set + * + * @param targetHz Governor override value for the current profile. + */ + void DVFSAfterSet(u32 targetHz); + + /** + * Reset the GPU vMin + * + */ + void DVFSReset(); + + /** + * Handles the Live CPU UV Feature + * + */ + void HandleCpuUv(); + + /** + * Handles frequency resets + * + * @param module The module to reset frequency for + * @param isBoost Is in boost mode + */ + void HandleFreqReset(SysClkModule module, bool isBoost); + + /** + * Sets clocks + * + * @param isBoost Is in boost mode + */ + void SetClocks(bool isBoost); + + /** + * Main function, runs every 5s in sleep mode, and a user specified amount when awake + * + */ + void Tick(); + + /** + * Reset CPU/GPU to stock values + * + */ + void ResetToStockClocks(); + + /** + * Wait for the next tick event + * + */ + void WaitForNextTick(); + + /** + * Set the data in the KIP + * + */ + void SetKipData(); + + /** + * Get the data from the KIP + * + */ + void GetKipData(); + + /** + * Runs the CPU Governor + * + * @param arg Cast to ClockManager* for context + */ + static void CpuGovernorThread(void* arg); + + /** + * Runs the GPU Governor + * + * @param arg Cast to ClockManager* for context + */ + static void GovernorThread(void* arg); + + /** + * Runs the VRR Algorithm + * + * @param arg Cast to ClockManager* for context + */ + static void VRRThread(void* arg); + + /** + * Frequency table + * + */ + struct FreqTable { + std::uint32_t count; + std::uint32_t list[SYSCLK_FREQ_LIST_MAX]; + } freqTable[SysClkModule_EnumMax]; + + /** + * Gets the current GPU speedo bracket + * + * @param speedo GPU Speedo + */ + int GetSpeedoBracket (int speedo); + + /** + * Gets the required vMin for a ram frequency for a speedo + * + * @param freq RAM Freq in MHz + * @param speedo GPU Speedo + */ + unsigned int GetGpuVoltage (unsigned int freq, int speedo); + + /** + * Gets the required vMin for a ram frequency for a speedo + * + * @param util Utilization in percentile + * @param tableMaxHz Table Max Hz + */ + static u32 SchedutilTargetHz(u32 util, u32 tableMaxHz); + + /** + * Gets the required vMin for a ram frequency for a speedo + * + * @param table FreqTable for module + * @param targetHz Hz to search for + */ + static u32 TableIndexForHz(const FreqTable& table, u32 targetHz); + + /** + * Gets the required vMin for a ram frequency for a speedo + * + * @param mgr ClockManager instance (runs in a thread so must be passed) + * @param module Module for which to resolve target Hz + */ + static u32 ResolveTargetHz(ClockManager* mgr, SysClkModule module); + + protected: + bool IsAssignableHz(SysClkModule module, std::uint32_t hz); + inline std::uint32_t GetMaxAllowedHz(SysClkModule module, SysClkProfile profile); + std::uint32_t GetNearestHz(SysClkModule module, std::uint32_t inHz, std::uint32_t maxHz); + bool ConfigIntervalTimeout(SysClkConfigValue intervalMsConfigValue, std::uint64_t ns, std::uint64_t* lastLogNs); + void RefreshFreqTableRow(SysClkModule module); + bool RefreshContext(); + static ClockManager *instance; + std::atomic_bool running; + LockableMutex contextMutex; + Config* config; + SysClkContext* context; + std::uint64_t lastTempLogNs; + std::uint64_t lastFreqLogNs; + std::uint64_t lastPowerLogNs; + std::uint64_t lastCsvWriteNs; + SysDockIntegration *sysDockIntegration; + SaltyNXIntegration *saltyNXIntegration; +}; \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/config.cpp b/Source/rewrite-hoc-clk/sysmodule/src/config.cpp new file mode 100644 index 00000000..06722105 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/config.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "file_utils.h" + +Config::Config(std::string path) +{ + this->path = path; + this->loaded = false; + this->profileMHzMap = std::map, std::uint32_t>(); + this->profileCountMap = std::map(); + this->mtime = 0; + this->enabled = false; + for(unsigned int i = 0; i < SysClkModule_EnumMax; i++) + { + this->overrideFreqs[i] = 0; + } + + for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++) + { + this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i); + } +} + +Config::~Config() +{ + std::scoped_lock lock{this->configMutex}; + this->Close(); +} + +Config* Config::CreateDefault() +{ + return new Config(FILE_CONFIG_DIR "/config.ini"); +} + +void Config::Load() +{ + FileUtils::LogLine("[cfg] Reading %s", this->path.c_str()); + + this->Close(); + this->mtime = this->CheckModificationTime(); + if(!this->mtime) + { + FileUtils::LogLine("[cfg] Error finding file"); + } + else if (!ini_browse(&BrowseIniFunc, this, this->path.c_str())) + { + FileUtils::LogLine("[cfg] Error loading file"); + } + + this->loaded = true; +} + +void Config::Close() +{ + this->loaded = false; + this->profileMHzMap.clear(); + this->profileCountMap.clear(); + + for(unsigned int i = 0; i < SysClkConfigValue_EnumMax; i++) + { + this->configValues[i] = sysclkDefaultConfigValue((SysClkConfigValue)i); + } +} + +bool Config::Refresh() +{ + std::scoped_lock lock{this->configMutex}; + if (!this->loaded || this->mtime != this->CheckModificationTime()) + { + this->Load(); + return true; + } + return false; +} + +bool Config::HasProfilesLoaded() +{ + std::scoped_lock lock{this->configMutex}; + return this->loaded; +} + +time_t Config::CheckModificationTime() +{ + time_t mtime = 0; + struct stat st; + if (stat(this->path.c_str(), &st) == 0) + { + mtime = st.st_mtime; + } + + return mtime; +} + +std::uint32_t Config::FindClockMHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile) +{ + if (this->loaded) + { + std::map, std::uint32_t>::const_iterator it = this->profileMHzMap.find(std::make_tuple(tid, profile, module)); + if (it != this->profileMHzMap.end()) + { + return it->second; + } + } + + return 0; +} + +std::uint32_t Config::FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list profiles, u32 mhzMultiplier) +{ + std::uint32_t mhz = 0; + + if (this->loaded) + { + for(auto profile: profiles) + { + mhz = FindClockMHz(tid, module, profile); + + if(mhz) + { + break; + } + } + } + + return std::max((std::uint32_t)0, mhz * mhzMultiplier); +} + +std::uint32_t Config::GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile, bool returnRaw) +{ + std::scoped_lock lock{this->configMutex}; + switch(profile) + { + case SysClkProfile_Handheld: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_Handheld}, returnRaw ? 1 : 1000000); + case SysClkProfile_HandheldCharging: + case SysClkProfile_HandheldChargingUSB: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingUSB, SysClkProfile_HandheldCharging, SysClkProfile_Handheld}, returnRaw ? 1 : 1000000); + case SysClkProfile_HandheldChargingOfficial: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_HandheldChargingOfficial, SysClkProfile_HandheldCharging, SysClkProfile_Handheld}, returnRaw ? 1 : 1000000); + case SysClkProfile_Docked: + return FindClockHzFromProfiles(tid, module, {SysClkProfile_Docked}, returnRaw ? 1 : 1000000); + default: + ERROR_THROW("Unhandled SysClkProfile: %u", profile); + } + + return 0; +} + +void Config::GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles) +{ + std::scoped_lock lock{this->configMutex}; + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + out_profiles->mhzMap[profile][module] = FindClockMHz(tid, (SysClkModule)module, (SysClkProfile)profile); + } + } +} + +bool Config::SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate) +{ + std::scoped_lock lock{this->configMutex}; + uint8_t numProfiles = 0; + + char section[17] = {0}; + snprintf(section, sizeof(section), "%016lX", tid); + + std::vector keys; + std::vector values; + keys.reserve(SysClkProfile_EnumMax * SysClkModule_EnumMax); + values.reserve(SysClkProfile_EnumMax * SysClkModule_EnumMax); + + std::uint32_t* mhz = &profiles->mhz[0]; + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + if(*mhz) + { + numProfiles++; + + std::string key = std::string(Board::GetProfileName((SysClkProfile)profile, false)) + + "_" + + Board::GetModuleName((SysClkModule)module, false); + std::string value = std::to_string(*mhz); + + keys.push_back(key); + values.push_back(value); + } + mhz++; + } + } + + std::vector keyPointers; + std::vector valuePointers; + keyPointers.reserve(keys.size() + 1); + valuePointers.reserve(values.size() + 1); + + for(size_t i = 0; i < keys.size(); i++) { + keyPointers.push_back(keys[i].c_str()); + valuePointers.push_back(values[i].c_str()); + } + keyPointers.push_back(NULL); + valuePointers.push_back(NULL); + + if(!ini_putsection(section, keyPointers.data(), valuePointers.data(), this->path.c_str())) + { + return false; + } + + if(immediate) + { + mhz = &profiles->mhz[0]; + this->profileCountMap[tid] = numProfiles; + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + if(*mhz) + { + this->profileMHzMap[std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module)] = *mhz; + } + else + { + this->profileMHzMap.erase(std::make_tuple(tid, (SysClkProfile)profile, (SysClkModule)module)); + } + mhz++; + } + } + } + + return true; +} + +std::uint8_t Config::GetProfileCount(std::uint64_t tid) +{ + std::map::iterator it = this->profileCountMap.find(tid); + if (it == this->profileCountMap.end()) + { + return 0; + } + + return it->second; +} + +int Config::BrowseIniFunc(const char* section, const char* key, const char* value, void* userdata) +{ + Config* config = (Config*)userdata; + std::uint64_t input; + if(!strcmp(section, CONFIG_VAL_SECTION)) + { + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(!strcmp(key, sysclkFormatConfigValue((SysClkConfigValue)kval, false))) + { + input = strtoul(value, NULL, 0); + if(!sysclkValidConfigValue((SysClkConfigValue)kval, input)) + { + input = sysclkDefaultConfigValue((SysClkConfigValue)kval); + FileUtils::LogLine("[cfg] Invalid value for key '%s' in section '%s': using default %d", key, section, input); + } + config->configValues[kval] = input; + return 1; + } + } + + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized config value", key, section); + return 1; + } + + std::uint64_t tid = strtoul(section, NULL, 16); + + if(!tid || strlen(section) != 16) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid TitleID", key, section); + return 1; + } + + SysClkProfile parsedProfile = SysClkProfile_EnumMax; + SysClkModule parsedModule = SysClkModule_EnumMax; + + for(unsigned int profile = 0; profile < SysClkProfile_EnumMax; profile++) + { + const char* profileCode = Board::GetProfileName((SysClkProfile)profile, false); + size_t profileCodeLen = strlen(profileCode); + + if(!strncmp(key, profileCode, profileCodeLen) && key[profileCodeLen] == '_') + { + const char* subkey = key + profileCodeLen + 1; + + for(unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + const char* moduleCode = Board::GetModuleName((SysClkModule)module, false); + size_t moduleCodeLen = strlen(moduleCode); + if(!strncmp(subkey, moduleCode, moduleCodeLen) && subkey[moduleCodeLen] == '\0') + { + parsedProfile = (SysClkProfile)profile; + parsedModule = (SysClkModule)module; + } + } + } + } + + if(parsedModule == SysClkModule_EnumMax || parsedProfile == SysClkProfile_EnumMax) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Unrecognized key", key, section); + return 1; + } + + std::uint32_t mhz = strtoul(value, NULL, 10); + if(!mhz) + { + FileUtils::LogLine("[cfg] Skipping key '%s' in section '%s': Invalid value", key, section); + return 1; + } + + config->profileMHzMap[std::make_tuple(tid, parsedProfile, parsedModule)] = mhz; + std::map::iterator it = config->profileCountMap.find(tid); + if (it == config->profileCountMap.end()) + { + config->profileCountMap[tid] = 1; + } + else + { + it->second++; + } + + return 1; +} + +void Config::SetEnabled(bool enabled) +{ + this->enabled = enabled; +} + +bool Config::Enabled() +{ + return this->enabled; +} + +void Config::SetOverrideHz(SysClkModule module, std::uint32_t hz) +{ + ASSERT_ENUM_VALID(SysClkModule, module); + + std::scoped_lock lock{this->overrideMutex}; + + this->overrideFreqs[module] = hz; +} + +std::uint32_t Config::GetOverrideHz(SysClkModule module) +{ + ASSERT_ENUM_VALID(SysClkModule, module); + + std::scoped_lock lock{this->overrideMutex}; + + return this->overrideFreqs[module]; +} + +std::uint64_t Config::GetConfigValue(SysClkConfigValue kval) +{ + ASSERT_ENUM_VALID(SysClkConfigValue, kval); + + std::scoped_lock lock{this->configMutex}; + + return this->configValues[kval]; +} + +const char* Config::GetConfigValueName(SysClkConfigValue kval, bool pretty) +{ + ASSERT_ENUM_VALID(SysClkConfigValue, kval); + + const char* result = sysclkFormatConfigValue(kval, pretty); + + return result; +} + +void Config::GetConfigValues(SysClkConfigValueList* out_configValues) +{ + std::scoped_lock lock{this->configMutex}; + + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + out_configValues->values[kval] = this->configValues[kval]; + } +} + +bool Config::SetConfigValues(SysClkConfigValueList* configValues, bool immediate) +{ + std::scoped_lock lock{this->configMutex}; + + std::vector iniKeys; + std::vector iniValues; + + iniKeys.reserve(SysClkConfigValue_EnumMax + 1); + iniValues.reserve(SysClkConfigValue_EnumMax); + + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(!sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval]) || + configValues->values[kval] == sysclkDefaultConfigValue((SysClkConfigValue)kval)) + { + continue; + } + + iniValues.push_back(std::to_string(configValues->values[kval])); + iniKeys.push_back(sysclkFormatConfigValue((SysClkConfigValue)kval, false)); + } + + // Null terminate + iniKeys.push_back(NULL); + + // Build pointer array for ini function + std::vector valuePointers; + valuePointers.reserve(iniValues.size() + 1); + for(const auto& val : iniValues) { + valuePointers.push_back(val.c_str()); + } + valuePointers.push_back(NULL); + + if(!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), this->path.c_str())) + { + return false; + } + + // Only actually apply changes in memory after a successful save + if(immediate) + { + for(unsigned int kval = 0; kval < SysClkConfigValue_EnumMax; kval++) + { + if(sysclkValidConfigValue((SysClkConfigValue)kval, configValues->values[kval])) + { + this->configValues[kval] = configValues->values[kval]; + } + else + { + this->configValues[kval] = sysclkDefaultConfigValue((SysClkConfigValue)kval); + } + } + } + + return true; +} + +bool Config::ResetConfigValue(SysClkConfigValue kval) +{ + if (!SYSCLK_ENUM_VALID(SysClkConfigValue, kval)) { + FileUtils::LogLine("[cfg] Invalid SysClkConfigValue: %u", kval); + return false; + } + + std::scoped_lock lock{this->configMutex}; + + std::uint64_t defaultValue = sysclkDefaultConfigValue(kval); + + std::vector iniKeys; + std::vector iniValues; + + iniKeys.reserve(2); + iniValues.reserve(1); + + const char* keyStr = sysclkFormatConfigValue(kval, false); + + iniKeys.push_back(keyStr); + iniValues.push_back(""); + + iniKeys.push_back(NULL); + + std::vector valuePointers; + valuePointers.reserve(iniValues.size() + 1); + for (const auto& val : iniValues) { + valuePointers.push_back(val.c_str()); + } + valuePointers.push_back(NULL); + + if (!ini_putsection(CONFIG_VAL_SECTION, iniKeys.data(), valuePointers.data(), this->path.c_str())) { + FileUtils::LogLine("[cfg] Failed to reset config value %u in INI", kval); + return false; + } + + this->configValues[kval] = defaultValue; + FileUtils::LogLine("[cfg] Reset config value %u to default: %llu", kval, defaultValue); + + return true; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/config.h b/Source/rewrite-hoc-clk/sysmodule/src/config.h new file mode 100644 index 00000000..23a72acf --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/config.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "board.h" + +#define CONFIG_VAL_SECTION "values" + +class Config +{ + public: + Config(std::string path); + virtual ~Config(); + + static Config* CreateDefault(); + + bool Refresh(); + + bool HasProfilesLoaded(); + + std::uint8_t GetProfileCount(std::uint64_t tid); + void GetProfiles(std::uint64_t tid, SysClkTitleProfileList* out_profiles); + bool SetProfiles(std::uint64_t tid, SysClkTitleProfileList* profiles, bool immediate); + std::uint32_t GetAutoClockHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile, bool returnRaw); + + void SetEnabled(bool enabled); + bool Enabled(); + void SetOverrideHz(SysClkModule module, std::uint32_t hz); + std::uint32_t GetOverrideHz(SysClkModule module); + + std::uint64_t GetConfigValue(SysClkConfigValue val); + const char* GetConfigValueName(SysClkConfigValue val, bool pretty); + void GetConfigValues(SysClkConfigValueList* out_configValues); + bool SetConfigValues(SysClkConfigValueList* configValues, bool immediate); + bool ResetConfigValue(SysClkConfigValue kval); + bool SetInternalValues(bool immediate); + bool SetConfigValue(SysClkConfigValue kval, std::uint64_t value, bool immediate = true); + + uint64_t configValues[SysClkConfigValue_EnumMax]; + protected: + void Load(); + void Close(); + bool kipOverride[SysClkConfigValue_EnumMax]; + time_t CheckModificationTime(); + std::uint32_t FindClockMHz(std::uint64_t tid, SysClkModule module, SysClkProfile profile); + std::uint32_t FindClockHzFromProfiles(std::uint64_t tid, SysClkModule module, std::initializer_list profiles, u32 mhzMultiplier = 1000000); + static int BrowseIniFunc(const char* section, const char* key, const char* value, void* userdata); + + std::map, std::uint32_t> profileMHzMap; + std::map profileCountMap; + bool loaded; + std::string path; + time_t mtime; + LockableMutex configMutex; + LockableMutex overrideMutex; + std::atomic_bool enabled; + std::uint32_t overrideFreqs[SysClkModule_EnumMax]; +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/src/errors.cpp b/Source/rewrite-hoc-clk/sysmodule/src/errors.cpp new file mode 100644 index 00000000..b7949432 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/errors.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "errors.h" +#include +#include +#include "file_utils.h" +void Errors::ThrowException(const char* format, ...) +{ + va_list args; + va_start(args, format); + const char* msg = Errors::FormatMessage(format, args); + va_end(args); + FileUtils::LogLine(format, args); + throw std::runtime_error(msg); +} + +const char* Errors::FormatMessage(const char* format, va_list args) +{ + size_t len = vsnprintf(NULL, 0, format, args) * sizeof(char); + char* buf = (char*)malloc(len + 1); + if (buf == NULL) + { + return format; + } + + vsnprintf(buf, len + 1, format, args); + + return buf; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/errors.h b/Source/rewrite-hoc-clk/sysmodule/src/errors.h new file mode 100644 index 00000000..f7575cf1 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/errors.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include + +#define ERROR_THROW(format, ...) Errors::ThrowException(format "\n in %s:%u", ##__VA_ARGS__, __FILE__, __LINE__) +#define ERROR_RESULT_THROW(rc, format, ...) ERROR_THROW(format "\n RC: [0x%x] %04d-%04d", ##__VA_ARGS__, rc, R_MODULE(rc), R_DESCRIPTION(rc)) +#define ASSERT_RESULT_OK(rc, format, ...) \ + if (R_FAILED(rc)) \ + { \ + ERROR_RESULT_THROW(rc, "ASSERT_RESULT_OK: " format, ##__VA_ARGS__); \ + } +#define ASSERT_ENUM_VALID(n, v) \ + if(!SYSCLK_ENUM_VALID(n, v)) { \ + ERROR_THROW("No such %s: %u", #n, v); \ + } + +class Errors +{ + public: + static void ThrowException(const char* format, ...); + + protected: + static const char* FormatMessage(const char* format, va_list args); +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/src/file_utils.cpp b/Source/rewrite-hoc-clk/sysmodule/src/file_utils.cpp new file mode 100644 index 00000000..a99494e8 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/file_utils.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) Souldbminer and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "file_utils.h" +#include + +static LockableMutex g_log_mutex; +static LockableMutex g_csv_mutex; +static std::atomic_bool g_has_initialized = false; +static bool g_log_enabled = false; +static std::uint64_t g_last_flag_check = 0; + +extern "C" void __libnx_init_time(void); + +static void _FileUtils_InitializeThreadFunc(void* args) +{ + FileUtils::Initialize(); +} + +bool FileUtils::IsInitialized() +{ + return g_has_initialized; +} + +void FileUtils::LogLine(const char* format, ...) +{ + std::scoped_lock lock{g_log_mutex}; + + va_list args; + va_start(args, format); + if (g_has_initialized) + { + FileUtils::RefreshFlags(false); + + if(g_log_enabled) + { + FILE* file = fopen(FILE_LOG_FILE_PATH, "a"); + + if (file) + { + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + struct tm* nowTm = localtime(&now.tv_sec); + + fprintf(file, "[%04d-%02d-%02d %02d:%02d:%02d.%03ld] ", nowTm->tm_year+1900, nowTm->tm_mon+1, nowTm->tm_mday, nowTm->tm_hour, nowTm->tm_min, nowTm->tm_sec, now.tv_nsec / 1000000UL); + vfprintf(file, format, args); + fprintf(file, "\n"); + fclose(file); + } + } + } + va_end(args); +} + +void FileUtils::WriteContextToCsv(const SysClkContext* context) +{ + std::scoped_lock lock{g_csv_mutex}; + + FILE* file = fopen(FILE_CONTEXT_CSV_PATH, "a"); + + if (file) + { + // Print header + if(!ftell(file)) + { + fprintf(file, "timestamp,profile,app_tid"); + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%s_hz", sysclkFormatModule((SysClkModule)module, false)); + } + + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + fprintf(file, ",%s_milliC", sysclkFormatThermalSensor((SysClkThermalSensor)sensor, false)); + } + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%s_real_hz", sysclkFormatModule((SysClkModule)module, false)); + } + + for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++) + { + fprintf(file, ",%s_mw", sysclkFormatPowerSensor((SysClkPowerSensor)sensor, false)); + } + + fprintf(file, "\n"); + } + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + fprintf(file, "%ld%03ld,%s,%016lx", now.tv_sec, now.tv_nsec / 1000000UL, sysclkFormatProfile(context->profile, false), context->applicationId); + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%d", context->freqs[module]); + } + + for (unsigned int sensor = 0; sensor < SysClkThermalSensor_EnumMax; sensor++) + { + fprintf(file, ",%d", context->temps[sensor]); + } + + for (unsigned int module = 0; module < SysClkModule_EnumMax; module++) + { + fprintf(file, ",%d", context->realFreqs[module]); + } + + for (unsigned int sensor = 0; sensor < SysClkPowerSensor_EnumMax; sensor++) + { + fprintf(file, ",%d", context->power[sensor]); + } + + fprintf(file, "\n"); + fclose(file); + } +} + +void FileUtils::RefreshFlags(bool force) +{ + std::uint64_t now = armTicksToNs(armGetSystemTick()); + if(!force && (now - g_last_flag_check) < FILE_FLAG_CHECK_INTERVAL_NS) + { + return; + } + + FILE* file = fopen(FILE_LOG_FLAG_PATH, "r"); + if (file) + { + g_log_enabled = true; + fclose(file); + } else { + g_log_enabled = false; + } + + g_last_flag_check = now; +} + +void FileUtils::InitializeAsync() +{ + Thread initThread = {0}; + threadCreate(&initThread, _FileUtils_InitializeThreadFunc, NULL, NULL, 0x4000, 0x15, 0); + threadStart(&initThread); +} + +Result FileUtils::Initialize() +{ + Result rc = 0; + + if (R_SUCCEEDED(rc)) + { + rc = timeInitialize(); + } + + __libnx_init_time(); + timeExit(); + + if (R_SUCCEEDED(rc)) + { + rc = fsInitialize(); + } + + if (R_SUCCEEDED(rc)) + { + rc = fsdevMountSdmc(); + } + + if (R_SUCCEEDED(rc)) + { + FileUtils::RefreshFlags(true); + g_has_initialized = true; + FileUtils::LogLine("=== hoc-clk " TARGET_VERSION " ==="); + FileUtils::LogLine("by m4xw, natinusala, p-sam, Souldbminer and Lightos_"); + } + + return rc; +} + +void FileUtils::Exit() +{ + if (!g_has_initialized) + { + return; + } + + g_has_initialized = false; + g_log_enabled = false; + + fsdevUnmountAll(); + fsExit(); +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/file_utils.h b/Source/rewrite-hoc-clk/sysmodule/src/file_utils.h new file mode 100644 index 00000000..993420de --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/file_utils.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#define FILE_CONFIG_DIR "/config/" TARGET +#define FILE_FLAG_CHECK_INTERVAL_NS (10000ULL * 1000000000ULL) +#define FILE_CONTEXT_CSV_PATH FILE_CONFIG_DIR "/context.csv" +#define FILE_LOG_FLAG_PATH FILE_CONFIG_DIR "/log.flag" +#define FILE_LOG_FILE_PATH FILE_CONFIG_DIR "/log.txt" + +class FileUtils +{ + public: + static void Exit(); + static Result Initialize(); + static bool IsInitialized(); + static bool IsLogEnabled(); + static void InitializeAsync(); + static void LogLine(const char* format, ...); + static void WriteContextToCsv(const SysClkContext* context); + protected: + static void RefreshFlags(bool force); +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/src/integrations.cpp b/Source/rewrite-hoc-clk/sysmodule/src/integrations.cpp new file mode 100644 index 00000000..64077895 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/integrations.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#include "integrations.h" +#include +#include +#include "process_management.h" + +SysDockIntegration::SysDockIntegration() { +} + +bool SysDockIntegration::getCurrentSysDockState() { + struct stat st = {0}; + 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; +} + +u16 SaltyNXIntegration::GetResolutionHeight() { + if (!SharedMemoryUsed) + return 0; + + u64 tid = ProcessManagement::GetCurrentApplicationId(); + if (tid == 0) + return 0; + + if (prevTid != tid) { + NxFps = 0; + prevTid = tid; + } + + if (!NxFps) { + uintptr_t base = (uintptr_t)shmemGetAddr(&_sharedmemory); + searchSharedMemoryBlock(base); + } + if(NxFps) { + NxFps->renderCalls[0].calls = 0xFFFF; + svcSleepThread(10*1000); + + return NxFps->renderCalls[0].height == 0 ? NxFps->viewportCalls[0].height : NxFps->renderCalls[0].height; + } + return 0; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/integrations.h b/Source/rewrite-hoc-clk/sysmodule/src/integrations.h new file mode 100644 index 00000000..466e2f7d --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/integrations.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "errors.h" +#include "file_utils.h" + +#include "clock_manager.h" + +class SysDockIntegration { +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(); + u16 GetResolutionHeight(); +}; \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.cpp b/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.cpp new file mode 100644 index 00000000..4dea5685 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "ipc_service.h" +#include +#include +#include "file_utils.h" +#include "errors.h" +#include "clock_manager.h" +IpcService::IpcService(ClockManager* clockMgr) +{ + std::int32_t priority; + Result rc = svcGetThreadPriority(&priority, CUR_THREAD_HANDLE); + ASSERT_RESULT_OK(rc, "svcGetThreadPriority"); + rc = ipcServerInit(&this->server, SYSCLK_IPC_SERVICE_NAME, 42); + ASSERT_RESULT_OK(rc, "ipcServerInit"); + rc = threadCreate(&this->thread, &IpcService::ProcessThreadFunc, this, NULL, 0x2000, priority, -2); + ASSERT_RESULT_OK(rc, "threadCreate"); + + this->running = false; + this->clockMgr = clockMgr; + +} + +void IpcService::SetRunning(bool running) +{ + std::scoped_lock lock{this->threadMutex}; + if(this->running == running) + { + return; + } + + this->running = running; + + if(running) + { + Result rc = threadStart(&this->thread); + ASSERT_RESULT_OK(rc, "threadStart"); + } + else + { + svcCancelSynchronization(this->thread.handle); + threadWaitForExit(&this->thread); + } +} + +IpcService::~IpcService() +{ + this->SetRunning(false); + Result rc = threadClose(&this->thread); + ASSERT_RESULT_OK(rc, "threadClose"); + rc = ipcServerExit(&this->server); + ASSERT_RESULT_OK(rc, "ipcServerExit"); +} + +void IpcService::ProcessThreadFunc(void* arg) +{ + Result rc; + IpcService* ipcSrv = (IpcService*)arg; + while(true) + { + rc = ipcServerProcess(&ipcSrv->server, &IpcService::ServiceHandlerFunc, arg); + if(R_FAILED(rc)) + { + if(rc == KERNELRESULT(Cancelled)) + { + return; + } + if(rc != KERNELRESULT(ConnectionClosed)) + { + FileUtils::LogLine("[ipc] ipcServerProcess: [0x%x] %04d-%04d", rc, R_MODULE(rc), R_DESCRIPTION(rc)); + } + } + } +} + +Result IpcService::ServiceHandlerFunc(void* arg, const IpcServerRequest* r, u8* out_data, size_t* out_dataSize) +{ + IpcService* ipcSrv = (IpcService*)arg; + + switch(r->data.cmdId) + { + case SysClkIpcCmd_GetApiVersion: + *out_dataSize = sizeof(u32); + return ipcSrv->GetApiVersion((u32*)out_data); + + case SysClkIpcCmd_GetVersionString: + if(r->hipc.meta.num_recv_buffers >= 1) + { + return ipcSrv->GetVersionString( + (char*)hipcGetBufferAddress(r->hipc.data.recv_buffers), + hipcGetBufferSize(r->hipc.data.recv_buffers) + ); + } + break; + + case SysClkIpcCmd_GetCurrentContext: + if(r->data.size >= sizeof(std::uint64_t) && r->hipc.meta.num_recv_buffers >= 1) + { + size_t bufSize = hipcGetBufferSize(r->hipc.data.recv_buffers); + if(bufSize >= sizeof(SysClkContext)) + { + return ipcSrv->GetCurrentContext((SysClkContext*)hipcGetBufferAddress(r->hipc.data.recv_buffers)); + } + } + break; + case SysClkIpcCmd_Exit: + return ipcSrv->Exit(); + + case SysClkIpcCmd_GetProfileCount: + if(r->data.size >= sizeof(std::uint64_t)) + { + *out_dataSize = sizeof(std::uint8_t); + return ipcSrv->GetProfileCount((std::uint64_t*)r->data.ptr, (std::uint8_t*)out_data); + } + break; + + case SysClkIpcCmd_GetProfiles: + if(r->data.size >= sizeof(std::uint64_t) && r->hipc.meta.num_recv_buffers >= 1) + { + size_t bufSize = hipcGetBufferSize(r->hipc.data.recv_buffers); + if(bufSize >= sizeof(SysClkTitleProfileList)) + { + return ipcSrv->GetProfiles((std::uint64_t*)r->data.ptr, (SysClkTitleProfileList*)hipcGetBufferAddress(r->hipc.data.recv_buffers)); + } + } + break; + + case SysClkIpcCmd_SetProfiles: + if(r->data.size >= sizeof(SysClkIpc_SetProfiles_Args)) + { + return ipcSrv->SetProfiles((SysClkIpc_SetProfiles_Args*)r->data.ptr); + } + break; + + case SysClkIpcCmd_SetEnabled: + if(r->data.size >= sizeof(std::uint8_t)) + { + return ipcSrv->SetEnabled((std::uint8_t*)r->data.ptr); + } + break; + + case SysClkIpcCmd_SetOverride: + if(r->data.size >= sizeof(SysClkIpc_SetOverride_Args)) + { + return ipcSrv->SetOverride((SysClkIpc_SetOverride_Args*)r->data.ptr); + } + break; + + case SysClkIpcCmd_GetConfigValues: + if(r->hipc.meta.num_recv_buffers >= 1) + { + size_t bufSize = hipcGetBufferSize(r->hipc.data.recv_buffers); + if(bufSize >= sizeof(SysClkConfigValueList)) + { + return ipcSrv->GetConfigValues((SysClkConfigValueList*)hipcGetBufferAddress(r->hipc.data.recv_buffers)); + } + } + break; + + case SysClkIpcCmd_SetConfigValues: + if(r->hipc.meta.num_send_buffers >= 1) + { + size_t bufSize = hipcGetBufferSize(r->hipc.data.send_buffers); + if(bufSize >= sizeof(SysClkConfigValueList)) + { + return ipcSrv->SetConfigValues((SysClkConfigValueList*)hipcGetBufferAddress(r->hipc.data.send_buffers)); + } + } + break; + case SysClkIpcCmd_GetFreqList: + if(r->data.size >= sizeof(SysClkIpc_GetFreqList_Args) && r->hipc.meta.num_recv_buffers >= 1) + { + *out_dataSize = sizeof(std::uint32_t); + return ipcSrv->GetFreqList( + (SysClkIpc_GetFreqList_Args*)r->data.ptr, + (std::uint32_t*)hipcGetBufferAddress(r->hipc.data.recv_buffers), + hipcGetBufferSize(r->hipc.data.recv_buffers), + (std::uint32_t*)out_data + ); + } + break; + case HocClkIpcCmd_SetKipData: + if (r->data.size >= 0) { + return ipcSrv->SetKipData(); + } + break; + } + + return SYSCLK_ERROR(Generic); +} + +Result IpcService::GetApiVersion(u32* out_version) +{ + *out_version = SYSCLK_IPC_API_VERSION; + + return 0; +} + +Result IpcService::GetVersionString(char* out_buf, size_t bufSize) +{ + if(bufSize) + { + strncpy(out_buf, TARGET_VERSION, bufSize-1); + } + + return 0; +} + +Result IpcService::GetCurrentContext(SysClkContext* out_ctx) +{ + *out_ctx = this->clockMgr->GetCurrentContext(); + + return 0; +} + +Result IpcService::Exit() +{ + this->clockMgr->SetRunning(false); + + return 0; +} + +Result IpcService::GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count) +{ + Config* config = this->clockMgr->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + *out_count = config->GetProfileCount(*tid); + + return 0; +} + +Result IpcService::GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles) +{ + Config* config = this->clockMgr->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + config->GetProfiles(*tid, out_profiles); + + return 0; +} + +Result IpcService::SetProfiles(SysClkIpc_SetProfiles_Args* args) +{ + Config* config = this->clockMgr->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + SysClkTitleProfileList profiles = args->profiles; + + if(!config->SetProfiles(args->tid, &profiles, true)) + { + return SYSCLK_ERROR(ConfigSaveFailed); + } + + return 0; +} + +Result IpcService::SetEnabled(std::uint8_t* enabled) +{ + Config* config = this->clockMgr->GetConfig(); + config->SetEnabled(*enabled); + + return 0; +} + +Result IpcService::SetOverride(SysClkIpc_SetOverride_Args* args) +{ + SysClkModule module = args->module; + std::uint32_t hz = args->hz; + + if(!SYSCLK_ENUM_VALID(SysClkModule, args->module)) + { + return SYSCLK_ERROR(Generic); + } + + Config* config = this->clockMgr->GetConfig(); + config->SetOverrideHz(module, hz); + + return 0; +} + +Result IpcService::GetConfigValues(SysClkConfigValueList* out_configValues) +{ + Config* config = this->clockMgr->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + config->GetConfigValues(out_configValues); + + return 0; +} + +Result IpcService::SetConfigValues(SysClkConfigValueList* configValues) +{ + Config* config = this->clockMgr->GetConfig(); + if(!config->HasProfilesLoaded()) + { + return SYSCLK_ERROR(ConfigNotLoaded); + } + + SysClkConfigValueList configValuesCopy = *configValues; + + if(!config->SetConfigValues(&configValuesCopy, true)) + { + return SYSCLK_ERROR(ConfigSaveFailed); + } + + return 0; +} + +Result IpcService::GetFreqList(SysClkIpc_GetFreqList_Args* args, std::uint32_t* out_list, std::size_t size, std::uint32_t* out_count) +{ + if(!SYSCLK_ENUM_VALID(SysClkModule, args->module)) + { + return SYSCLK_ERROR(Generic); + } + + if(args->maxCount != size/sizeof(*out_list)) + { + return SYSCLK_ERROR(Generic); + } + + this->clockMgr->GetFreqList(args->module, out_list, args->maxCount, out_count); + + return 0; +} + +Result IpcService::SetKipData() { + this->clockMgr->SetKipData(); + + return 0; +} + +Result IpcService::GetKipData() { + this->clockMgr->GetKipData(); + + return 0; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.h b/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.h new file mode 100644 index 00000000..cded785c --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/ipc_service.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once + +#include +#include +#include +#include "clock_manager.h" + +class IpcService +{ + public: + + IpcService(ClockManager* clockMgr); + virtual ~IpcService(); + void SetRunning(bool running); + static void ProcessThreadFunc(void* arg); + static Result ServiceHandlerFunc(void* arg, const IpcServerRequest* r, std::uint8_t* out_data, size_t* out_dataSize); + + Result GetApiVersion(u32* out_version); + Result GetVersionString(char* out_buf, size_t bufSize); + Result GetCurrentContext(SysClkContext* out_ctx); + Result Exit(); + Result GetProfileCount(std::uint64_t* tid, std::uint8_t* out_count); + Result GetProfiles(std::uint64_t* tid, SysClkTitleProfileList* out_profiles); + Result SetProfiles(SysClkIpc_SetProfiles_Args* args); + Result SetEnabled(std::uint8_t* enabled); + Result SetOverride(SysClkIpc_SetOverride_Args* args); + Result GetConfigValues(SysClkConfigValueList* out_configValues); + Result SetConfigValues(SysClkConfigValueList* configValues); + Result GetFreqList(SysClkIpc_GetFreqList_Args* args, std::uint32_t* out_list, std::size_t size, std::uint32_t* out_count); + Result SetKipData(); + Result GetKipData(); + bool running; + Thread thread; + LockableMutex threadMutex; + IpcServer server; + ClockManager* clockMgr; + protected: + +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/src/kip.h b/Source/rewrite-hoc-clk/sysmodule/src/kip.h new file mode 100644 index 00000000..818b4937 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/kip.h @@ -0,0 +1,450 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + + +#pragma once +#include +#include +#include +#include +#include + +#pragma pack(push, 1) + +typedef struct { + u8 cust[4]; + u32 custRev; + u32 placeholder; + u32 hpMode; + u32 commonEmcMemVolt; + u32 eristaEmcMaxClock; + u32 eristaEmcMaxClock1; + u32 eristaEmcMaxClock2; + u32 marikoEmcMaxClock; + u32 marikoEmcVddqVolt; + u32 emcDvbShift; + // advanced config + u32 t1_tRCD; + u32 t2_tRP; + u32 t3_tRAS; + u32 t4_tRRD; + u32 t5_tRFC; + u32 t6_tRTW; + u32 t7_tWTR; + u32 t8_tREFI; + + u32 mem_burst_read_latency; + u32 mem_burst_write_latency; + + u32 eristaCpuUV; + u32 eristaCpuVmin; + u32 eristaCpuMaxVolt; + u32 eristaCpuUnlock; + + u32 marikoCpuUVLow; + u32 marikoCpuUVHigh; + u32 tableConf; + u32 marikoCpuLowVmin; + u32 marikoCpuHighVmin; + u32 marikoCpuMaxVolt; + u32 marikoCpuMaxClock; + + u32 eristaCpuBoostClock; + u32 marikoCpuBoostClock; + + u32 eristaGpuUV; + u32 eristaGpuVmin; + + u32 marikoGpuUV; + u32 marikoGpuVmin; + u32 marikoGpuVmax; + + u32 commonGpuVoltOffset; + + u32 gpuSpeedo; + + u32 eristaGpuVoltArray[27]; + u32 marikoGpuVoltArray[24]; + + u32 t6_tRTW_fine_tune; + u32 t7_tWTR_fine_tune; + + u32 reserved[60]; +} CustomizeTable; + +#pragma pack(pop) + +#define CUST_MAGIC "CUST" +#define CUST_MAGIC_LEN 4 + +typedef struct { + FILE* file; + long offset; + CustomizeTable cached_table; + bool has_cache; +} CustHandle; + +static inline bool cust_find_offset(FILE* f, long* out_offset) { + u8 buf[512]; + long pos = 0; + fseek(f, 0, SEEK_SET); + + while (1) { + size_t r = fread(buf, 1, sizeof(buf), f); + if (r < CUST_MAGIC_LEN) break; + + for (size_t i = 0; i <= r - CUST_MAGIC_LEN; i++) { + if (memcmp(&buf[i], CUST_MAGIC, CUST_MAGIC_LEN) == 0) { + *out_offset = pos + (long)i; + return true; + } + } + pos += (long)(r - (CUST_MAGIC_LEN - 1)); + fseek(f, pos, SEEK_SET); + } + return false; +} + +static inline bool cust_read_table(const char* path, CustomizeTable* out) { + FILE* f = fopen(path, "rb"); + if (!f) return false; + + long off; + if (!cust_find_offset(f, &off)) { + fclose(f); + return false; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + + if (off + (long)sizeof(CustomizeTable) > size) { + fclose(f); + return false; + } + + fseek(f, off, SEEK_SET); + bool ok = fread(out, 1, sizeof(CustomizeTable), f) == sizeof(CustomizeTable); + fclose(f); + + return ok && memcmp(out->cust, CUST_MAGIC, CUST_MAGIC_LEN) == 0; +} + +static inline bool cust_write_table(const char* path, const CustomizeTable* in) { + FILE* f = fopen(path, "r+b"); + if (!f) return false; + + long off; + if (!cust_find_offset(f, &off)) { + fclose(f); + return false; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + + if (off + (long)sizeof(CustomizeTable) > size) { + fclose(f); + return false; + } + + fseek(f, off, SEEK_SET); + bool ok = fwrite(in, 1, sizeof(CustomizeTable), f) == sizeof(CustomizeTable); + fflush(f); + fclose(f); + + return ok; +} + + +static inline bool cust_read_and_cache(const char* path, CustomizeTable* out) { + return cust_read_table(path, out); +} + +#define CUST_WRITE_FIELD_BATCH(table, field, val) \ + do { \ + (table)->field = (val); \ + } while (0) + +#define CUST_WRITE_FIELD(path, field, val) \ + do { \ + CustomizeTable t; \ + if (!cust_read_table(path, &t)) return false; \ + t.field = (val); \ + return cust_write_table(path, &t); \ + } while (0) + +static inline bool cust_set_cust_rev(const char* p, u32 v) { CUST_WRITE_FIELD(p, custRev, v); } +// static inline bool cust_set_mtc_conf(const char* p, u32 v) { CUST_WRITE_FIELD(p, mtcConf, v); } +static inline bool cust_set_hp_mode(const char* p, u32 v) { CUST_WRITE_FIELD(p, hpMode, v); } + +static inline bool cust_set_common_emc_volt(const char* p, u32 v) { CUST_WRITE_FIELD(p, commonEmcMemVolt, v); } +static inline bool cust_set_erista_emc_max(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaEmcMaxClock, v); } +static inline bool cust_set_erista_emc_max1(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaEmcMaxClock1, v); } +static inline bool cust_set_erista_emc_max2(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaEmcMaxClock2, v); } +static inline bool cust_set_mariko_emc_max(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoEmcMaxClock, v); } +static inline bool cust_set_mariko_emc_vddq(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoEmcVddqVolt, v); } +static inline bool cust_set_emc_dvb_shift(const char* p, u32 v) { CUST_WRITE_FIELD(p, emcDvbShift, v); } + +static inline bool cust_set_tRCD(const char* p, u32 v) { CUST_WRITE_FIELD(p, t1_tRCD, v); } +static inline bool cust_set_tRP(const char* p, u32 v) { CUST_WRITE_FIELD(p, t2_tRP, v); } +static inline bool cust_set_tRAS(const char* p, u32 v) { CUST_WRITE_FIELD(p, t3_tRAS, v); } +static inline bool cust_set_tRRD(const char* p, u32 v) { CUST_WRITE_FIELD(p, t4_tRRD, v); } +static inline bool cust_set_tRFC(const char* p, u32 v) { CUST_WRITE_FIELD(p, t5_tRFC, v); } +static inline bool cust_set_tRTW(const char* p, u32 v) { CUST_WRITE_FIELD(p, t6_tRTW, v); } +static inline bool cust_set_tWTR(const char* p, u32 v) { CUST_WRITE_FIELD(p, t7_tWTR, v); } +static inline bool cust_set_tREFI(const char* p, u32 v) { CUST_WRITE_FIELD(p, t8_tREFI, v); } +static inline bool cust_set_tRTW_fine_tune(const char* p, u32 v) { CUST_WRITE_FIELD(p, t6_tRTW_fine_tune, v); } +static inline bool cust_set_tWTR_fine_tune(const char* p, u32 v) { CUST_WRITE_FIELD(p, t7_tWTR_fine_tune, v); } +static inline bool cust_set_burst_read_lat(const char* p, u32 v) { CUST_WRITE_FIELD(p, mem_burst_read_latency, v); } +static inline bool cust_set_burst_write_lat(const char* p, u32 v) { CUST_WRITE_FIELD(p, mem_burst_write_latency, v); } + +static inline bool cust_set_erista_cpu_uv(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaCpuUV, v); } +static inline bool cust_set_eristaCpuVmin(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaCpuVmin, v); } +static inline bool cust_set_erista_cpu_max_volt(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaCpuMaxVolt, v); } +static inline bool cust_set_eristaCpuUnlock(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaCpuUnlock, v); } + +static inline bool cust_set_mariko_cpu_uv_low(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuUVLow, v); } +static inline bool cust_set_mariko_cpu_uv_high(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuUVHigh, v); } +static inline bool cust_set_mariko_cpu_low_vmin(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuLowVmin, v); } +static inline bool cust_set_mariko_cpu_high_vmin(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuHighVmin, v); } +static inline bool cust_set_mariko_cpu_max_volt(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuMaxVolt, v); } +static inline bool cust_set_erista_cpu_boost(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaCpuBoostClock, v); } +static inline bool cust_set_mariko_cpu_boost(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuBoostClock, v); } + +static inline bool cust_set_erista_gpu_uv(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaGpuUV, v); } +static inline bool cust_set_erista_gpu_vmin(const char* p, u32 v) { CUST_WRITE_FIELD(p, eristaGpuVmin, v); } +static inline bool cust_set_mariko_gpu_uv(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoGpuUV, v); } +static inline bool cust_set_mariko_gpu_vmin(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoGpuVmin, v); } +static inline bool cust_set_mariko_gpu_vmax(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoGpuVmax, v); } +static inline bool cust_set_common_gpu_offset(const char* p, u32 v) { CUST_WRITE_FIELD(p, commonGpuVoltOffset, v); } +static inline bool cust_set_gpu_speedo(const char* p, u32 v) { CUST_WRITE_FIELD(p, gpuSpeedo, v); } +static inline bool cust_set_marikoCpuMaxClock(const char* p, u32 v) { CUST_WRITE_FIELD(p, marikoCpuMaxClock, v); } + +/* GPU VOLT ARRAY HELPERS */ +static inline bool cust_set_erista_gpu_volt(const char* p, int idx, u32 v) { + if (idx < 0 || idx >= 27) return false; + CustomizeTable t; + if (!cust_read_table(p, &t)) return false; + t.eristaGpuVoltArray[idx] = v; + return cust_write_table(p, &t); +} + +static inline bool cust_set_mariko_gpu_volt(const char* p, int idx, u32 v) { + if (idx < 0 || idx >= 24) return false; + CustomizeTable t; + if (!cust_read_table(p, &t)) return false; + t.marikoGpuVoltArray[idx] = v; + return cust_write_table(p, &t); +} + +static inline u32 cust_get_field(const CustomizeTable* t, u32 offset) { + if (!t) return 0; + return *(u32*)((u8*)t + offset); +} + +#define CUST_GET_FIELD(table, field) ((table) ? (table)->field : 0) + +static inline u32 cust_get_cust_rev(const CustomizeTable* t) { return CUST_GET_FIELD(t, custRev); } +// static inline u32 cust_get_mtc_conf(const CustomizeTable* t) { return CUST_GET_FIELD(t, mtcConf); } +static inline u32 cust_get_hp_mode(const CustomizeTable* t) { return CUST_GET_FIELD(t, hpMode); } + +static inline u32 cust_get_common_emc_volt(const CustomizeTable* t) { return CUST_GET_FIELD(t, commonEmcMemVolt); } +static inline u32 cust_get_erista_emc_max(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaEmcMaxClock); } +static inline u32 cust_get_erista_emc_max1(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaEmcMaxClock1); } +static inline u32 cust_get_erista_emc_max2(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaEmcMaxClock2); } +static inline u32 cust_get_mariko_emc_max(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoEmcMaxClock); } +static inline u32 cust_get_mariko_emc_vddq(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoEmcVddqVolt); } +static inline u32 cust_get_emc_dvb_shift(const CustomizeTable* t) { return CUST_GET_FIELD(t, emcDvbShift); } + +static inline u32 cust_get_tRCD(const CustomizeTable* t) { return CUST_GET_FIELD(t, t1_tRCD); } +static inline u32 cust_get_tRP(const CustomizeTable* t) { return CUST_GET_FIELD(t, t2_tRP); } +static inline u32 cust_get_tRAS(const CustomizeTable* t) { return CUST_GET_FIELD(t, t3_tRAS); } +static inline u32 cust_get_tRRD(const CustomizeTable* t) { return CUST_GET_FIELD(t, t4_tRRD); } +static inline u32 cust_get_tRFC(const CustomizeTable* t) { return CUST_GET_FIELD(t, t5_tRFC); } +static inline u32 cust_get_tRTW(const CustomizeTable* t) { return CUST_GET_FIELD(t, t6_tRTW); } +static inline u32 cust_get_tWTR(const CustomizeTable* t) { return CUST_GET_FIELD(t, t7_tWTR); } +static inline u32 cust_get_tREFI(const CustomizeTable* t) { return CUST_GET_FIELD(t, t8_tREFI); } +static inline u32 cust_get_tRTW_fine_tune(const CustomizeTable* t) { return CUST_GET_FIELD(t, t6_tRTW_fine_tune); } +static inline u32 cust_get_tWTR_fine_tune(const CustomizeTable* t) { return CUST_GET_FIELD(t, t7_tWTR_fine_tune); } +static inline u32 cust_get_burst_read_lat(const CustomizeTable* t) { return CUST_GET_FIELD(t, mem_burst_read_latency); } +static inline u32 cust_get_burst_write_lat(const CustomizeTable* t) { return CUST_GET_FIELD(t, mem_burst_write_latency); } + +static inline u32 cust_get_erista_cpu_uv(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaCpuUV); } +static inline u32 cust_get_eristaCpuVmin(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaCpuVmin); } +static inline u32 cust_get_erista_cpu_max_volt(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaCpuMaxVolt); } +static inline u32 cust_get_eristaCpuUnlock(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaCpuUnlock); } + +static inline u32 cust_get_mariko_cpu_uv_low(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuUVLow); } +static inline u32 cust_get_mariko_cpu_uv_high(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuUVHigh); } +static inline u32 cust_get_mariko_cpu_low_vmin(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuLowVmin); } +static inline u32 cust_get_mariko_cpu_high_vmin(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuHighVmin); } +static inline u32 cust_get_mariko_cpu_max_volt(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuMaxVolt); } +static inline u32 cust_get_erista_cpu_boost(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaCpuBoostClock); } +static inline u32 cust_get_mariko_cpu_boost(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuBoostClock); } +static inline u32 cust_get_table_conf(const CustomizeTable* t) { return CUST_GET_FIELD(t, tableConf); } + +static inline u32 cust_get_erista_gpu_uv(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaGpuUV); } +static inline u32 cust_get_erista_gpu_vmin(const CustomizeTable* t) { return CUST_GET_FIELD(t, eristaGpuVmin); } +static inline u32 cust_get_mariko_gpu_uv(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoGpuUV); } +static inline u32 cust_get_mariko_gpu_vmin(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoGpuVmin); } +static inline u32 cust_get_mariko_gpu_vmax(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoGpuVmax); } +static inline u32 cust_get_common_gpu_offset(const CustomizeTable* t) { return CUST_GET_FIELD(t, commonGpuVoltOffset); } +static inline u32 cust_get_gpu_speedo(const CustomizeTable* t) { return CUST_GET_FIELD(t, gpuSpeedo); } +static inline u32 cust_get_marikoCpuMaxClock(const CustomizeTable* t) { return CUST_GET_FIELD(t, marikoCpuMaxClock); } + +static inline u32 cust_get_erista_gpu_volt(const CustomizeTable* t, int idx) { + if (!t || idx < 0 || idx >= 27) return 0; + return t->eristaGpuVoltArray[idx]; +} + +static inline u32 cust_get_mariko_gpu_volt(const CustomizeTable* t, int idx) { + if (!t || idx < 0 || idx >= 24) return 0; + return t->marikoGpuVoltArray[idx]; +} + +#define DECL_ERISTA_GPU_VOLT_HELPER(freq, idx) \ +static inline bool cust_set_erista_gpu_volt_##freq( \ + const char* p, u32 v) { \ + return cust_set_erista_gpu_volt(p, idx, v); \ +} + +#define DECL_MARIKO_GPU_VOLT_HELPER(freq, idx) \ +static inline bool cust_set_mariko_gpu_volt_##freq( \ + const char* p, u32 v) { \ + return cust_set_mariko_gpu_volt(p, idx, v); \ +} + +DECL_ERISTA_GPU_VOLT_HELPER(76800, 0) +DECL_ERISTA_GPU_VOLT_HELPER(115200, 1) +DECL_ERISTA_GPU_VOLT_HELPER(153600, 2) +DECL_ERISTA_GPU_VOLT_HELPER(192000, 3) +DECL_ERISTA_GPU_VOLT_HELPER(230400, 4) +DECL_ERISTA_GPU_VOLT_HELPER(268800, 5) +DECL_ERISTA_GPU_VOLT_HELPER(307200, 6) +DECL_ERISTA_GPU_VOLT_HELPER(345600, 7) +DECL_ERISTA_GPU_VOLT_HELPER(384000, 8) +DECL_ERISTA_GPU_VOLT_HELPER(422400, 9) +DECL_ERISTA_GPU_VOLT_HELPER(460800, 10) +DECL_ERISTA_GPU_VOLT_HELPER(499200, 11) +DECL_ERISTA_GPU_VOLT_HELPER(537600, 12) +DECL_ERISTA_GPU_VOLT_HELPER(576000, 13) +DECL_ERISTA_GPU_VOLT_HELPER(614400, 14) +DECL_ERISTA_GPU_VOLT_HELPER(652800, 15) +DECL_ERISTA_GPU_VOLT_HELPER(691200, 16) +DECL_ERISTA_GPU_VOLT_HELPER(729600, 17) +DECL_ERISTA_GPU_VOLT_HELPER(768000, 18) +DECL_ERISTA_GPU_VOLT_HELPER(806400, 19) +DECL_ERISTA_GPU_VOLT_HELPER(844800, 20) +DECL_ERISTA_GPU_VOLT_HELPER(883200, 21) +DECL_ERISTA_GPU_VOLT_HELPER(921600, 22) +DECL_ERISTA_GPU_VOLT_HELPER(960000, 23) +DECL_ERISTA_GPU_VOLT_HELPER(998400, 24) +DECL_ERISTA_GPU_VOLT_HELPER(1036800, 25) +DECL_ERISTA_GPU_VOLT_HELPER(1075200, 26) + +DECL_MARIKO_GPU_VOLT_HELPER(76800, 0) +DECL_MARIKO_GPU_VOLT_HELPER(153600, 1) +DECL_MARIKO_GPU_VOLT_HELPER(230400, 2) +DECL_MARIKO_GPU_VOLT_HELPER(307200, 3) +DECL_MARIKO_GPU_VOLT_HELPER(384000, 4) +DECL_MARIKO_GPU_VOLT_HELPER(460800, 5) +DECL_MARIKO_GPU_VOLT_HELPER(537600, 6) +DECL_MARIKO_GPU_VOLT_HELPER(614400, 7) +DECL_MARIKO_GPU_VOLT_HELPER(691200, 8) +DECL_MARIKO_GPU_VOLT_HELPER(768000, 9) +DECL_MARIKO_GPU_VOLT_HELPER(844800, 10) +DECL_MARIKO_GPU_VOLT_HELPER(921600, 11) +DECL_MARIKO_GPU_VOLT_HELPER(998400, 12) +DECL_MARIKO_GPU_VOLT_HELPER(1075200, 13) +DECL_MARIKO_GPU_VOLT_HELPER(1152000, 14) +DECL_MARIKO_GPU_VOLT_HELPER(1228800, 15) +DECL_MARIKO_GPU_VOLT_HELPER(1267200, 16) +DECL_MARIKO_GPU_VOLT_HELPER(1305600, 17) +DECL_MARIKO_GPU_VOLT_HELPER(1344000, 18) +DECL_MARIKO_GPU_VOLT_HELPER(1382400, 19) +DECL_MARIKO_GPU_VOLT_HELPER(1420800, 20) +DECL_MARIKO_GPU_VOLT_HELPER(1459200, 21) +DECL_MARIKO_GPU_VOLT_HELPER(1497600, 22) +DECL_MARIKO_GPU_VOLT_HELPER(1536000, 23) + + +#define DECL_ERISTA_GPU_VOLT_GET(freq, idx) \ +static inline u32 cust_get_erista_gpu_volt_##freq##_val(const char* p) { \ + CustomizeTable t; \ + if (!cust_read_table(p, &t)) return 0; \ + return cust_get_erista_gpu_volt(&t, idx); \ +} +#define DECL_MARIKO_GPU_VOLT_GET(freq, idx) \ +static inline u32 cust_get_mariko_gpu_volt_##freq##_val(const char* p) { \ + CustomizeTable t; \ + if (!cust_read_table(p, &t)) return 0; \ + return cust_get_mariko_gpu_volt(&t, idx); \ +} + +DECL_ERISTA_GPU_VOLT_GET(76800, 0) +DECL_ERISTA_GPU_VOLT_GET(115200, 1) +DECL_ERISTA_GPU_VOLT_GET(153600, 2) +DECL_ERISTA_GPU_VOLT_GET(192000, 3) +DECL_ERISTA_GPU_VOLT_GET(230400, 4) +DECL_ERISTA_GPU_VOLT_GET(268800, 5) +DECL_ERISTA_GPU_VOLT_GET(307200, 6) +DECL_ERISTA_GPU_VOLT_GET(345600, 7) +DECL_ERISTA_GPU_VOLT_GET(384000, 8) +DECL_ERISTA_GPU_VOLT_GET(422400, 9) +DECL_ERISTA_GPU_VOLT_GET(460800, 10) +DECL_ERISTA_GPU_VOLT_GET(499200, 11) +DECL_ERISTA_GPU_VOLT_GET(537600, 12) +DECL_ERISTA_GPU_VOLT_GET(576000, 13) +DECL_ERISTA_GPU_VOLT_GET(614400, 14) +DECL_ERISTA_GPU_VOLT_GET(652800, 15) +DECL_ERISTA_GPU_VOLT_GET(691200, 16) +DECL_ERISTA_GPU_VOLT_GET(729600, 17) +DECL_ERISTA_GPU_VOLT_GET(768000, 18) +DECL_ERISTA_GPU_VOLT_GET(806400, 19) +DECL_ERISTA_GPU_VOLT_GET(844800, 20) +DECL_ERISTA_GPU_VOLT_GET(883200, 21) +DECL_ERISTA_GPU_VOLT_GET(921600, 22) +DECL_ERISTA_GPU_VOLT_GET(960000, 23) +DECL_ERISTA_GPU_VOLT_GET(998400, 24) +DECL_ERISTA_GPU_VOLT_GET(1036800, 25) +DECL_ERISTA_GPU_VOLT_GET(1075200, 26) + +DECL_MARIKO_GPU_VOLT_GET(76800, 0) +DECL_MARIKO_GPU_VOLT_GET(153600, 1) +DECL_MARIKO_GPU_VOLT_GET(230400, 2) +DECL_MARIKO_GPU_VOLT_GET(307200, 3) +DECL_MARIKO_GPU_VOLT_GET(384000, 4) +DECL_MARIKO_GPU_VOLT_GET(460800, 5) +DECL_MARIKO_GPU_VOLT_GET(537600, 6) +DECL_MARIKO_GPU_VOLT_GET(614400, 7) +DECL_MARIKO_GPU_VOLT_GET(691200, 8) +DECL_MARIKO_GPU_VOLT_GET(768000, 9) +DECL_MARIKO_GPU_VOLT_GET(844800, 10) +DECL_MARIKO_GPU_VOLT_GET(921600, 11) +DECL_MARIKO_GPU_VOLT_GET(998400, 12) +DECL_MARIKO_GPU_VOLT_GET(1075200, 13) +DECL_MARIKO_GPU_VOLT_GET(1152000, 14) +DECL_MARIKO_GPU_VOLT_GET(1228800, 15) +DECL_MARIKO_GPU_VOLT_GET(1267200, 16) +DECL_MARIKO_GPU_VOLT_GET(1305600, 17) +DECL_MARIKO_GPU_VOLT_GET(1344000, 18) +DECL_MARIKO_GPU_VOLT_GET(1382400, 19) +DECL_MARIKO_GPU_VOLT_GET(1420800, 20) +DECL_MARIKO_GPU_VOLT_GET(1459200, 21) +DECL_MARIKO_GPU_VOLT_GET(1497600, 22) +DECL_MARIKO_GPU_VOLT_GET(1536000, 23) \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/main.cpp b/Source/rewrite-hoc-clk/sysmodule/src/main.cpp new file mode 100644 index 00000000..6249172c --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/main.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +#include +#include +#include + +#include + +#include "errors.h" +#include "file_utils.h" +#include "board.h" +#include "process_management.h" +#include "clock_manager.h" +#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; + TimeServiceType __nx_time_service_type = TimeServiceType_System; + std::uint32_t __nx_fs_num_sessions = 1; + u32 __nx_nv_transfermem_size = 0x8000; + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + NvServiceType __nx_nv_service_type = NvServiceType_Factory; + + + void __libnx_initheap(void) + { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib Heap Management */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; + + virtmemSetup(); + } + + void __appInit(void) + { + if (R_FAILED(smInitialize())) + { + fatalThrow(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM)); + } + + Result rc = setsysInitialize(); + if (R_SUCCEEDED(rc)) + { + SetSysFirmwareVersion fw; + rc = setsysGetFirmwareVersion(&fw); + if (R_SUCCEEDED(rc)) + hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro)); + setsysExit(); + } + + // rc = fanInitialize(); + // if (R_FAILED(rc)) + // diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_ShouldNotHappen)); + + 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) + { + // CloseFanControllerThread(); + // fanExit(); + i2cExit(); + fsExit(); + fsdevUnmountAll(); + appletExit(); + } +} + +int main(int argc, char** argv) +{ + Result rc = FileUtils::Initialize(); + if (R_FAILED(rc)) + { + fatalThrow(rc); + return 1; + } + + try + { + Board::Initialize(); + ProcessManagement::Initialize(); + + ProcessManagement::WaitForQLaunch(); + + ClockManager* clockMgr = new ClockManager(); + IpcService* ipcSrv = new IpcService(clockMgr); + + FileUtils::LogLine("Ready"); + + clockMgr->SetRunning(true); + clockMgr->GetConfig()->SetEnabled(true); + ipcSrv->SetRunning(true); + // TemperaturePoint *table; + // ReadConfigFile(&table); + // InitFanController(table); + // StartFanControllerThread(); + + while (clockMgr->Running()) + { + clockMgr->Tick(); + clockMgr->WaitForNextTick(); + } + + ipcSrv->SetRunning(false); + delete ipcSrv; + delete clockMgr; + ProcessManagement::Exit(); + Board::Exit(); + } + catch (const std::exception &ex) + { + FileUtils::LogLine("[!] %s", ex.what()); + } + catch (...) + { + std::exception_ptr p = std::current_exception(); + FileUtils::LogLine("[!?] %s", p ? p.__cxa_exception_type()->name() : "..."); + } + + FileUtils::LogLine("Exit"); + svcSleepThread(1000000ULL); + FileUtils::Exit(); + + return 0; +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/old_board.cpp b/Source/rewrite-hoc-clk/sysmodule/src/old_board.cpp new file mode 100644 index 00000000..ef65b227 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/old_board.cpp @@ -0,0 +1,1329 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + +// Note: Hoag crashes on display refresh rate init while in sleep mode + +#include +#include "board.h" +#include "errors.h" +#include "rgltr.h" +#include "file_utils.h" +#include // for std::clamp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX(A, B) std::max(A, B) +#define MIN(A, B) std::min(A, B) +#define CEIL(A) std::ceil(A) +#define FLOOR(A) std::floor(A) +#define ROUND(A) std::lround(A) + + +#define FUSE_CPU_SPEEDO_0_CALIB 0x114 +//#define FUSE_CPU_SPEEDO_1_CALIB 0x12C +#define FUSE_CPU_SPEEDO_2_CALIB 0x130 + +#define FUSE_SOC_SPEEDO_0_CALIB 0x134 +//#define FUSE_SOC_SPEEDO_1_CALIB 0x138 +//#define FUSE_SOC_SPEEDO_2_CALIB 0x13C + +#define FUSE_CPU_IDDQ_CALIB 0x118 +#define FUSE_SOC_IDDQ_CALIB 0x140 +#define FUSE_GPU_IDDQ_CALIB 0x228 + +#define HOSSVC_HAS_CLKRST (hosversionAtLeast(8,0,0)) +#define HOSSVC_HAS_TC (hosversionAtLeast(5,0,0)) +#define NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD 0x80044715 +#define NVSCHED_CTRL_ENABLE 0x00000601 +#define NVSCHED_CTRL_DISABLE 0x00000602 + +constexpr u64 CpuTimeOutNs = 500'000'000; +constexpr double Systemtickfrequency = 19200000.0 * (static_cast(CpuTimeOutNs) / 1'000'000'000.0); +Result nvInitialize_rc; +Result nvCheck = 1; +Result nvCheck_sched = 1; + +LEvent threadexit; +Thread gpuLThread; +Thread cpuCore0Thread; +Thread cpuCore1Thread; +Thread cpuCore2Thread; +Thread cpuCore3Thread; +Thread miscThread; +double temp = 0; + +PwmChannelSession g_ICon; +Result pwmCheck = 1; +Result pwmDutyCycleCheck = 1; +double Rotation_Duty = 0; +u8 fanLevel; + +uint32_t GPU_Load_u = 0, fd = 0, fd2 = 0; +BatteryChargeInfo info; + +static SysClkSocType g_socType = SysClkSocType_Erista; +static HorizonOCConsoleType g_consoleType = HorizonOCConsoleType_Iowa; + +u64 idletick0 = 0; +u64 idletick1 = 0; +u64 idletick2 = 0; +// u64 idletick3 = 0; + +u32 cpu0, cpu1, cpu2, cpu3, cpuAvg; +u16 cpuSpeedo0, cpuSpeedo2, socSpeedo0; // CPU, GPU, SOC +u32 speedoBracket; +u16 cpuIDDQ, gpuIDDQ, socIDDQ; +u8 g_dramID = 0; +u64 cldvfs, cldvfs_temp; +u32 cachedEristaUvLowTune0 = 0, cachedEristaUvLowTune1 = 0, cachedMarikoUvHighTune0 = 0; + +static const u32 ramBrackets[][22] = { + { 2133, 2200, 2266, 2300, 2366, 2400, 2433, 2466, 2533, 2566, 2600, 2633, 2700, 2733, 2766, 2833, 2866, 2900, 2933, 3033, 3066, 3100, }, + { 2300, 2366, 2433, 2466, 2533, 2566, 2633, 2700, 2733, 2800, 2833, 2900, 2933, 2966, 3033, 3066, 3100, 3133, 3166, 3200, 3233, 3266, }, + { 2433, 2466, 2533, 2600, 2666, 2733, 2766, 2800, 2833, 2866, 2933, 2966, 3033, 3066, 3100, 3133, 3166, 3200, 3233, 3300, 3333, 3366, }, + { 2500, 2533, 2600, 2633, 2666, 2733, 2800, 2866, 2900, 2966, 3033, 3100, 3166, 3200, 3233, 3266, 3300, 3333, 3366, 3400, 3400, 3400, }, +}; + +static const u32 gpuDvfsArray[] = { 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, 800}; + +u32 dvfsTable[6][32] = {}; +u64 dvfsAddress; +u32 ramVmin; + +const char* Board::GetModuleName(SysClkModule module, bool pretty) +{ + ASSERT_ENUM_VALID(SysClkModule, module); + return sysclkFormatModule(module, pretty); +} + +const char* Board::GetProfileName(SysClkProfile profile, bool pretty) +{ + ASSERT_ENUM_VALID(SysClkProfile, profile); + return sysclkFormatProfile(profile, pretty); +} + +const char* Board::GetThermalSensorName(SysClkThermalSensor sensor, bool pretty) +{ + ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); + return sysclkFormatThermalSensor(sensor, pretty); +} + +const char* Board::GetPowerSensorName(SysClkPowerSensor sensor, bool pretty) +{ + ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); + return sysclkFormatPowerSensor(sensor, pretty); +} + +PcvModule Board::GetPcvModule(SysClkModule sysclkModule) +{ + switch(sysclkModule) + { + case SysClkModule_CPU: + return PcvModule_CpuBus; + case SysClkModule_GPU: + return PcvModule_GPU; + case SysClkModule_MEM: + return PcvModule_EMC; + default: + ASSERT_ENUM_VALID(SysClkModule, sysclkModule); + } + + return (PcvModule)0; +} + +PcvModuleId Board::GetPcvModuleId(SysClkModule sysclkModule) +{ + PcvModuleId pcvModuleId; + Result rc = pcvGetModuleId(&pcvModuleId, GetPcvModule(sysclkModule)); + ASSERT_RESULT_OK(rc, "pcvGetModuleId"); + + return pcvModuleId; +} + +void CheckCore(void *idletickPtr) { + u64* idletick = static_cast(idletickPtr); + while(true) { + u64 idletickA; + u64 idletickB; + svcGetInfo(&idletickB, InfoType_IdleTickCount, INVALID_HANDLE, -1); + svcWaitForAddress(&threadexit, ArbitrationType_WaitIfEqual, 0, CpuTimeOutNs); + svcGetInfo(&idletickA, InfoType_IdleTickCount, INVALID_HANDLE, -1); + *idletick = idletickA - idletickB; + } +} + +void gpuLoadThread(void*) { + #define gpu_samples_average 8 + uint32_t gpu_load_array[gpu_samples_average] = {0}; + size_t i = 0; + if (R_SUCCEEDED(nvCheck)) do { + u32 temp; + if (R_SUCCEEDED(nvIoctl(fd, NVGPU_GPU_IOCTL_PMU_GET_GPU_LOAD, &temp))) { + gpu_load_array[i++ % gpu_samples_average] = temp; + GPU_Load_u = std::accumulate(&gpu_load_array[0], &gpu_load_array[gpu_samples_average], 0) / gpu_samples_average; + } + svcSleepThread(16'666'000); // wait a bit (this is the perfect amount of time to keep the reading accurate) + } while(true); +} + +void miscThreadFunc(void*) { + for(;;) { + if (R_SUCCEEDED(pwmCheck)) { + if (R_SUCCEEDED(pwmChannelSessionGetDutyCycle(&g_ICon, &temp))) { + temp *= 10; + temp = trunc(temp); + temp /= 10; + Rotation_Duty = 100.0 - temp; + } + } + fanLevel = (u8)Rotation_Duty; + svcSleepThread(300'000'000); + } +} + +void Board::Initialize() +{ + Result rc = 0; + if(HOSSVC_HAS_CLKRST) + { + rc = clkrstInitialize(); + ASSERT_RESULT_OK(rc, "clkrstInitialize"); + } + else + { + rc = pcvInitialize(); + ASSERT_RESULT_OK(rc, "pcvInitialize"); + } + + rc = apmExtInitialize(); + ASSERT_RESULT_OK(rc, "apmExtInitialize"); + + rc = psmInitialize(); + ASSERT_RESULT_OK(rc, "psmInitialize"); + + if(HOSSVC_HAS_TC) + { + rc = tcInitialize(); + ASSERT_RESULT_OK(rc, "tcInitialize"); + } + + rc = max17050Initialize(); + ASSERT_RESULT_OK(rc, "max17050Initialize"); + + rc = tmp451Initialize(); + ASSERT_RESULT_OK(rc, "tmp451Initialize"); + nvInitialize_rc = nvInitialize(); + if (R_SUCCEEDED(nvInitialize_rc)) { + nvCheck = nvOpen(&fd, "/dev/nvhost-ctrl-gpu"); + nvCheck_sched = nvOpen(&fd2, "/dev/nvsched-ctrl"); + } + + rc = rgltrInitialize(); + ASSERT_RESULT_OK(rc, "rgltrInitialize"); + + // if (R_SUCCEEDED(fanInitialize())) { + // if (hosversionAtLeast(7,0,0)) fanCheck = fanOpenController(&fanController, 0x3D000001); + // else fanCheck = fanOpenController(&fanController, 1); + // } + + rc = pmdmntInitialize(); + ASSERT_RESULT_OK(rc, "pmdmntInitialize"); + + threadCreate(&gpuLThread, gpuLoadThread, NULL, NULL, 0x1000, 0x3F, -2); + threadStart(&gpuLThread); + + leventClear(&threadexit); + threadCreate(&cpuCore0Thread, CheckCore, &idletick0, NULL, 0x1000, 0x10, 0); + threadCreate(&cpuCore1Thread, CheckCore, &idletick1, NULL, 0x1000, 0x10, 1); + threadCreate(&cpuCore2Thread, CheckCore, &idletick2, NULL, 0x1000, 0x10, 2); + // threadCreate(&cpuCore3Thread, CheckCore, &idletick3, NULL, 0x1000, 0x10, 3); + threadCreate(&miscThread, miscThreadFunc, NULL, NULL, 0x1000, 0x10, 3); + + threadStart(&cpuCore0Thread); + threadStart(&cpuCore1Thread); + threadStart(&cpuCore2Thread); + // threadStart(&cpuCore3Thread); + threadStart(&miscThread); + batteryInfoInitialize(); + FetchHardwareInfos(); + + if (hosversionAtLeast(6,0,0) && R_SUCCEEDED(pwmInitialize())) { + pwmCheck = pwmOpenSession2(&g_ICon, 0x3D000001); + } + + if(!IsHoag()) { + u64 clkVirtAddr, dsiVirtAddr, outsize; + rc = svcQueryMemoryMapping(&clkVirtAddr, &outsize, 0x60006000, 0x1000); + ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (clk)"); + rc = svcQueryMemoryMapping(&dsiVirtAddr, &outsize, 0x54300000, 0x40000); + ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (dsi)"); + + DisplayRefreshConfig cfg = {.clkVirtAddr = clkVirtAddr, .dsiVirtAddr = dsiVirtAddr}; + + DisplayRefresh_Initialize(&cfg); + } + + rc = svcQueryMemoryMapping(&cldvfs, &cldvfs_temp, CLDVFS_REGION_BASE, CLDVFS_REGION_SIZE); + ASSERT_RESULT_OK(rc, "svcQueryMemoryMapping (cldvfs)"); + + if(Board::GetSocType() == SysClkSocType_Erista) { + cachedEristaUvLowTune0 = *(u32*)(cldvfs + CL_DVFS_TUNE0_0); + cachedEristaUvLowTune1 = *(u32*)(cldvfs + CL_DVFS_TUNE1_0); + } else { + Board::SetHz(SysClkModule_CPU, 1785000000); + cachedMarikoUvHighTune0 = *(u32*)(cldvfs + CL_DVFS_TUNE0_0); + Board::ResetToStockCpu(); + } + + +} + +void Board::fuseReadSpeedos() { + + u64 pid = 0; + if (R_FAILED(pmdmntGetProcessId(&pid, 0x0100000000000006))) { + return; + } + + Handle debug; + if (R_FAILED(svcDebugActiveProcess(&debug, pid))) { + return; + } + + MemoryInfo mem_info = {0}; + u32 pageinfo = 0; + u64 addr = 0; + + char stack[0x10] = {0}; + const char compare[0x10] = {0}; + char dump[0x400] = {0}; + + while (true) { + if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &pageinfo, debug, addr)) || mem_info.addr < addr) { + break; + } + + if (mem_info.type == MemType_Io && mem_info.size == 0x1000) { + if (R_FAILED(svcReadDebugProcessMemory(stack, debug, mem_info.addr, sizeof(stack)))) { + break; + } + + if (memcmp(stack, compare, sizeof(stack)) == 0) { + if (R_FAILED(svcReadDebugProcessMemory(dump, debug, mem_info.addr + 0x800, sizeof(dump)))) { + break; + } + + cpuSpeedo0 = *reinterpret_cast(dump + FUSE_CPU_SPEEDO_0_CALIB); + cpuSpeedo2 = *reinterpret_cast(dump + FUSE_CPU_SPEEDO_2_CALIB); + socSpeedo0 = *reinterpret_cast(dump + FUSE_SOC_SPEEDO_0_CALIB); + cpuIDDQ = *reinterpret_cast(dump + FUSE_CPU_IDDQ_CALIB); + gpuIDDQ = *reinterpret_cast(dump + FUSE_SOC_IDDQ_CALIB); + socIDDQ = *reinterpret_cast(dump + FUSE_GPU_IDDQ_CALIB); + + svcCloseHandle(debug); + return; + } + } + + addr = mem_info.addr + mem_info.size; + } + + svcCloseHandle(debug); +} + +u16 Board::getSpeedo(HorizonOCSpeedo speedoType) { + switch(speedoType) { + case HorizonOCSpeedo_CPU: + return cpuSpeedo0; + case HorizonOCSpeedo_GPU: + return cpuSpeedo2; + case HorizonOCSpeedo_SOC: + return socSpeedo0; + default: + ASSERT_ENUM_VALID(HorizonOCSpeedo, speedoType); + return 0; + } +} + +u16 Board::getIDDQ(HorizonOCSpeedo speedoType) { + switch(speedoType) { + case HorizonOCSpeedo_CPU: + return cpuIDDQ; + case HorizonOCSpeedo_GPU: + return gpuIDDQ; + case HorizonOCSpeedo_SOC: + return socIDDQ; + default: + ASSERT_ENUM_VALID(HorizonOCSpeedo, speedoType); + return 0; + } +} + + +void Board::Exit() +{ + if(HOSSVC_HAS_CLKRST) + { + clkrstExit(); + } + else + { + pcvExit(); + } + + apmExtExit(); + psmExit(); + + if(HOSSVC_HAS_TC) + { + tcExit(); + } + + max17050Exit(); + tmp451Exit(); + + threadClose(&gpuLThread); + threadClose(&cpuCore0Thread); + threadClose(&cpuCore1Thread); + threadClose(&cpuCore2Thread); + // threadClose(&cpuCore3Thread); + threadClose(&miscThread); + + pwmChannelSessionClose(&g_ICon); + pwmExit(); + rgltrExit(); + batteryInfoExit(); + pmdmntExit(); + nvExit(); + if(!IsHoag()) + DisplayRefresh_Shutdown(); +} + +SysClkProfile Board::GetProfile() +{ + std::uint32_t mode = 0; + Result rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + if(mode) + { + return SysClkProfile_Docked; + } + + PsmChargerType chargerType; + + rc = psmGetChargerType(&chargerType); + ASSERT_RESULT_OK(rc, "psmGetChargerType"); + + if(chargerType == PsmChargerType_EnoughPower) + { + return SysClkProfile_HandheldChargingOfficial; + } + else if(chargerType == PsmChargerType_LowPower) + { + return SysClkProfile_HandheldChargingUSB; + } + + return SysClkProfile_Handheld; +} + +void Board::SetHz(SysClkModule module, std::uint32_t hz) +{ + Result rc = 0; + if(module == HorizonOCModule_Display) { + if(!IsHoag()) + DisplayRefresh_SetRate(hz); + return; + } + if(module > SysClkModule_MEM) + return; + if(HOSSVC_HAS_CLKRST) + { + ClkrstSession session = {0}; + + rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); + ASSERT_RESULT_OK(rc, "clkrstOpenSession"); + rc = clkrstSetClockRate(&session, hz); + ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); + if (module == SysClkModule_CPU) { + svcSleepThread(200'000); + rc = clkrstSetClockRate(&session, hz); + ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); + } + clkrstCloseSession(&session); + } + else + { + rc = pcvSetClockRate(Board::GetPcvModule(module), hz); + ASSERT_RESULT_OK(rc, "pcvSetClockRate"); + if (module == SysClkModule_CPU) { + svcSleepThread(200'000); + rc = pcvSetClockRate(Board::GetPcvModule(module), hz); + ASSERT_RESULT_OK(rc, "pcvSetClockRate"); + } + } +} + +std::uint32_t Board::GetHz(SysClkModule module) +{ + Result rc = 0; + std::uint32_t hz = 0; + + if(module == HorizonOCModule_Display) { + if(!IsHoag()) + DisplayRefresh_GetRate(&hz, false); + else + hz = 60; + return hz; + } + + if(HOSSVC_HAS_CLKRST) + { + ClkrstSession session = {0}; + + rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); + ASSERT_RESULT_OK(rc, "clkrstOpenSession"); + + rc = clkrstGetClockRate(&session, &hz); + ASSERT_RESULT_OK(rc, "clkrstSetClockRate"); + + clkrstCloseSession(&session); + } + else + { + rc = pcvGetClockRate(Board::GetPcvModule(module), &hz); + ASSERT_RESULT_OK(rc, "pcvGetClockRate"); + } + + return hz; +} + +std::uint32_t Board::GetRealHz(SysClkModule module) +{ + u32 hz = 0; + switch(module) + { + case SysClkModule_CPU: + return t210ClkCpuFreq(); + case SysClkModule_GPU: + return t210ClkGpuFreq(); + case SysClkModule_MEM: + return t210ClkMemFreq(); + case HorizonOCModule_Display: + if(!IsHoag()) + DisplayRefresh_GetRate(&hz, false); + else + hz = 60; + return hz; + default: + ASSERT_ENUM_VALID(SysClkModule, module); + } + + return 0; +} + +void Board::GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount) +{ + Result rc = 0; + PcvClockRatesListType type; + s32 tmpInMaxCount = maxCount; + s32 tmpOutCount = 0; + + + + if(HOSSVC_HAS_CLKRST) + { + ClkrstSession session = {0}; + + rc = clkrstOpenSession(&session, Board::GetPcvModuleId(module), 3); + ASSERT_RESULT_OK(rc, "clkrstOpenSession"); + + rc = clkrstGetPossibleClockRates(&session, outList, tmpInMaxCount, &type, &tmpOutCount); + ASSERT_RESULT_OK(rc, "clkrstGetPossibleClockRates"); + + clkrstCloseSession(&session); + } + else + { + rc = pcvGetPossibleClockRates(Board::GetPcvModule(module), outList, tmpInMaxCount, &type, &tmpOutCount); + ASSERT_RESULT_OK(rc, "pcvGetPossibleClockRates"); + } + + if(type != PcvClockRatesListType_Discrete) + { + ERROR_THROW("Unexpected PcvClockRatesListType: %u (module = %s)", type, Board::GetModuleName(module, false)); + } + + *outCount = tmpOutCount; +} + +void Board::ResetToStock() +{ + Result rc = 0; + if(hosversionAtLeast(9,0,0)) + { + std::uint32_t confId = 0; + rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + SysClkApmConfiguration* apmConfiguration = NULL; + for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) + { + if(sysclk_g_apm_configurations[i].id == confId) + { + apmConfiguration = &sysclk_g_apm_configurations[i]; + break; + } + } + + if(!apmConfiguration) + { + ERROR_THROW("Unknown apm configuration: %x", confId); + } + + Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); + Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); + Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); + } + else + { + std::uint32_t mode = 0; + rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + rc = apmExtSysRequestPerformanceMode(mode); + ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); + } +} + +void Board::ResetToStockCpu() +{ + Result rc = 0; + if(hosversionAtLeast(9,0,0)) + { + std::uint32_t confId = 0; + rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + SysClkApmConfiguration* apmConfiguration = NULL; + for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) + { + if(sysclk_g_apm_configurations[i].id == confId) + { + apmConfiguration = &sysclk_g_apm_configurations[i]; + break; + } + } + + if(!apmConfiguration) + { + ERROR_THROW("Unknown apm configuration: %x", confId); + } + + Board::SetHz(SysClkModule_CPU, apmConfiguration->cpu_hz); + } + else + { + std::uint32_t mode = 0; + rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + rc = apmExtSysRequestPerformanceMode(mode); + ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); + } +} + +void Board::ResetToStockMem() +{ + Result rc = 0; + if(hosversionAtLeast(9,0,0)) + { + std::uint32_t confId = 0; + rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + SysClkApmConfiguration* apmConfiguration = NULL; + for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) + { + if(sysclk_g_apm_configurations[i].id == confId) + { + apmConfiguration = &sysclk_g_apm_configurations[i]; + break; + } + } + + if(!apmConfiguration) + { + ERROR_THROW("Unknown apm configuration: %x", confId); + } + + Board::SetHz(SysClkModule_MEM, apmConfiguration->mem_hz); + } + else + { + std::uint32_t mode = 0; + rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + rc = apmExtSysRequestPerformanceMode(mode); + ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); + } +} + +void Board::ResetToStockGpu() +{ + Result rc = 0; + if(hosversionAtLeast(9,0,0)) + { + std::uint32_t confId = 0; + rc = apmExtGetCurrentPerformanceConfiguration(&confId); + ASSERT_RESULT_OK(rc, "apmExtGetCurrentPerformanceConfiguration"); + + SysClkApmConfiguration* apmConfiguration = NULL; + for(size_t i = 0; sysclk_g_apm_configurations[i].id; i++) + { + if(sysclk_g_apm_configurations[i].id == confId) + { + apmConfiguration = &sysclk_g_apm_configurations[i]; + break; + } + } + + if(!apmConfiguration) + { + ERROR_THROW("Unknown apm configuration: %x", confId); + } + + Board::SetHz(SysClkModule_GPU, apmConfiguration->gpu_hz); + } + else + { + std::uint32_t mode = 0; + rc = apmExtGetPerformanceMode(&mode); + ASSERT_RESULT_OK(rc, "apmExtGetPerformanceMode"); + + rc = apmExtSysRequestPerformanceMode(mode); + ASSERT_RESULT_OK(rc, "apmExtSysRequestPerformanceMode"); + } +} + +void Board::ResetToStockDisplay() { + if(!IsHoag()) + DisplayRefresh_SetRate(60); +} + +u8 Board::GetHighestDockedDisplayRate() { + if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { + return DisplayRefresh_GetDockedHighestAllowed(); + } else + return 60; +} + +std::uint32_t Board::GetTemperatureMilli(SysClkThermalSensor sensor) +{ + std::int32_t millis = 0; + + if(sensor == SysClkThermalSensor_SOC) + { + millis = tmp451TempSoc(); + } + else if(sensor == SysClkThermalSensor_PCB) + { + millis = tmp451TempPcb(); + } + else if(sensor == SysClkThermalSensor_Skin) + { + if(HOSSVC_HAS_TC) + { + Result rc; + rc = tcGetSkinTemperatureMilliC(&millis); + ASSERT_RESULT_OK(rc, "tcGetSkinTemperatureMilliC"); + } + } + else if (sensor == HorizonOCThermalSensor_Battery) { + batteryInfoGetChargeInfo(&info); + millis = batteryInfoGetTemperatureMiliCelsius(&info); + } + else if (sensor == HorizonOCThermalSensor_PMIC) { + millis = 50000; + } + else + { + ASSERT_ENUM_VALID(SysClkThermalSensor, sensor); + } + + return std::max(0, millis); +} + +std::int32_t Board::GetPowerMw(SysClkPowerSensor sensor) +{ + switch(sensor) + { + case SysClkPowerSensor_Now: + return max17050PowerNow(); + case SysClkPowerSensor_Avg: + return max17050PowerAvg(); + default: + ASSERT_ENUM_VALID(SysClkPowerSensor, sensor); + } + + return 0; +} + +u32 GetMaxCpuLoad() { + float cpuUsage0 = std::clamp(((Systemtickfrequency - idletick0) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); + float cpuUsage1 = std::clamp(((Systemtickfrequency - idletick1) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); + float cpuUsage2 = std::clamp(((Systemtickfrequency - idletick2) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); + // float cpuUsage3 = std::clamp(((Systemtickfrequency - idletick3) / static_cast(Systemtickfrequency)) * 1000.0, 0.0, 1000.0); + + return std::round(std::max({cpuUsage0, cpuUsage1, cpuUsage2})); +} + +std::uint32_t Board::GetPartLoad(SysClkPartLoad loadSource) +{ + switch(loadSource) + { + case SysClkPartLoad_EMC: + return t210EmcLoadAll(); + case SysClkPartLoad_EMCCpu: + return t210EmcLoadCpu(); + case HocClkPartLoad_GPU: + return GPU_Load_u; + case HocClkPartLoad_CPUMax: + return GetMaxCpuLoad(); + case HocClkPartLoad_BAT: + batteryInfoGetChargeInfo(&info); + return info.RawBatteryCharge; + case HocClkPartLoad_FAN: + return fanLevel; + default: + ASSERT_ENUM_VALID(SysClkPartLoad, loadSource); + } + + return 0; +} + + +SysClkSocType Board::GetSocType() { + return g_socType; +} + +HorizonOCConsoleType Board::GetConsoleType() { + return g_consoleType; +} + +u8 Board::GetDramID() { + return g_dramID; +} + +void Board::FetchHardwareInfos() +{ + fuseReadSpeedos(); + SetSpeedoBracket(); + u64 sku = 0, dramID = 0; + Result rc = splInitialize(); + ASSERT_RESULT_OK(rc, "splInitialize"); + + rc = splGetConfig(SplConfigItem_HardwareType, &sku); + ASSERT_RESULT_OK(rc, "splGetConfig"); + + rc = splGetConfig(SplConfigItem_DramId, &dramID); + ASSERT_RESULT_OK(rc, "splGetConfig"); + + splExit(); + + switch(sku) + { + case 2: + case 3: + case 4: + case 5: + g_socType = SysClkSocType_Mariko; + break; + default: + g_socType = SysClkSocType_Erista; + } + + if (g_socType == SysClkSocType_Mariko) { + CacheDvfsTable(); + } + + g_consoleType = (HorizonOCConsoleType)sku; + g_dramID = (u8)dramID; +} + +/* +* Switch Power domains (max77620): +* Name | Usage | uV step | uV min | uV default | uV max | Init +*-------+---------------+---------+--------+------------+---------+------------------ +* sd0 | SoC | 12500 | 600000 | 625000 | 1400000 | 1.125V (pkg1.1) +* sd1 | SDRAM | 12500 | 600000 | 1125000 | 1125000 | 1.1V (pkg1.1) +* sd2 | ldo{0-1, 7-8} | 12500 | 600000 | 1325000 | 1350000 | 1.325V (pcv) +* sd3 | 1.8V general | 12500 | 600000 | 1800000 | 1800000 | +* ldo0 | Display Panel | 25000 | 800000 | 1200000 | 1200000 | 1.2V (pkg1.1) +* ldo1 | XUSB, PCIE | 25000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) +* ldo2 | SDMMC1 | 50000 | 800000 | 1800000 | 3300000 | +* ldo3 | GC ASIC | 50000 | 800000 | 3100000 | 3100000 | 3.1V (pcv) +* ldo4 | RTC | 12500 | 800000 | 850000 | 850000 | 0.85V (AO, pcv) +* ldo5 | GC Card | 50000 | 800000 | 1800000 | 1800000 | 1.8V (pcv) +* ldo6 | Touch, ALS | 50000 | 800000 | 2900000 | 2900000 | 2.9V (pcv) +* ldo7 | XUSB | 50000 | 800000 | 1050000 | 1050000 | 1.05V (pcv) +* ldo8 | XUSB, DP, MCU | 50000 | 800000 | 1050000 | 2800000 | 1.05V/2.8V (pcv) + +typedef enum { + PcvPowerDomainId_Max77620_Sd0 = 0x3A000080, + PcvPowerDomainId_Max77620_Sd1 = 0x3A000081, // vdd2 + PcvPowerDomainId_Max77620_Sd2 = 0x3A000082, + PcvPowerDomainId_Max77620_Sd3 = 0x3A000083, + PcvPowerDomainId_Max77620_Ldo0 = 0x3A0000A0, + PcvPowerDomainId_Max77620_Ldo1 = 0x3A0000A1, + PcvPowerDomainId_Max77620_Ldo2 = 0x3A0000A2, + PcvPowerDomainId_Max77620_Ldo3 = 0x3A0000A3, + PcvPowerDomainId_Max77620_Ldo4 = 0x3A0000A4, + PcvPowerDomainId_Max77620_Ldo5 = 0x3A0000A5, + PcvPowerDomainId_Max77620_Ldo6 = 0x3A0000A6, + PcvPowerDomainId_Max77620_Ldo7 = 0x3A0000A7, + PcvPowerDomainId_Max77620_Ldo8 = 0x3A0000A8, + PcvPowerDomainId_Max77621_Cpu = 0x3A000003, + PcvPowerDomainId_Max77621_Gpu = 0x3A000004, + PcvPowerDomainId_Max77812_Cpu = 0x3A000003, + PcvPowerDomainId_Max77812_Gpu = 0x3A000004, + PcvPowerDomainId_Max77812_Dram = 0x3A000005, // vddq +} PowerDomainId; + +*/ + +std::uint32_t Board::GetVoltage(HocClkVoltage voltage) +{ + RgltrSession session; + Result rc = 0; + u32 out = 0; + switch(voltage) + { + case HocClkVoltage_SOC: + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Sd0); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + break; + case HocClkVoltage_EMCVDD2: + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Sd1); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + break; + case HocClkVoltage_CPU: + if(Board::GetSocType() == SysClkSocType_Mariko) + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_Cpu); + else + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Cpu); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + break; + case HocClkVoltage_GPU: + if(Board::GetSocType() == SysClkSocType_Mariko) + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77621_Gpu); + else + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Gpu); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + break; + case HocClkVoltage_EMCVDDQ_MarikoOnly: + if(Board::GetSocType() == SysClkSocType_Mariko) { + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77812_Dram); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + } else { + out = Board::GetVoltage(HocClkVoltage_EMCVDD2); + } + break; + case HocClkVoltage_Display: + rc = rgltrOpenSession(&session, PcvPowerDomainId_Max77620_Ldo0); + ASSERT_RESULT_OK(rc, "rgltrOpenSession") + rgltrGetVoltage(&session, &out); + rgltrCloseSession(&session); + break; + case HocClkVoltage_Battery: + batteryInfoGetChargeInfo(&info); + out = info.VoltageAvg; + break; + default: + ASSERT_ENUM_VALID(HocClkVoltage, voltage); + } + + return out > 0 ? out : 0; +} + +void Board::SetSpeedoBracket() { + if (cpuSpeedo2 >= 1754) { + speedoBracket = 3; + } else if (cpuSpeedo2 >= 1690) { + speedoBracket = 2; + } else if (cpuSpeedo2 > 1625) { + speedoBracket = 1; + } else { + speedoBracket = 0; + } +} + +u32 Board::GetMinimumGpuVoltage(u32 freqMhz) { + if (freqMhz <= 1600) + return 0; + + for (u32 voltageIndex = 0; voltageIndex < 22; ++voltageIndex) { + if (freqMhz <= ramBrackets[speedoBracket][voltageIndex]) { + return gpuDvfsArray[voltageIndex]; + } + } + + return 800; +} + +Handle Board::GetPcvHandle() { + constexpr u64 PcvID = 0x10000000000001a; + u64 processIDList[80]{}; + s32 processCount = 0; + Handle handle = INVALID_HANDLE; + + DebugEventInfo debugEvent{}; + + /* Get all running processes. */ + Result resultGetProcessList = svcGetProcessList(&processCount, processIDList, std::size(processIDList)); + if (R_FAILED(resultGetProcessList)) { + return INVALID_HANDLE; + } + + /* Try to find pcv. */ + for (int i = 0; i < processCount; ++i) { + if (handle != INVALID_HANDLE) { + svcCloseHandle(handle); + handle = INVALID_HANDLE; + } + + /* Try to debug process, if it fails, try next process. */ + Result resultSvcDebugProcess = svcDebugActiveProcess(&handle, processIDList[i]); + if (R_FAILED(resultSvcDebugProcess)) { + continue; + } + + /* Try to get a debug event. */ + Result resultDebugEvent = svcGetDebugEvent(&debugEvent, handle); + if (R_SUCCEEDED(resultDebugEvent)) { + if (debugEvent.info.create_process.program_id == PcvID) { + return handle; + } + } + } + + /* Failed to get handle. */ + return INVALID_HANDLE; +} + +void Board::CacheDvfsTable() { + const u32 voltagePattern[] = { 600000, 12500, 1400000, }; + + Handle handle = GetPcvHandle(); + if (handle == INVALID_HANDLE) { + FileUtils::LogLine("[Board] Invalid handle!"); + return; + } + + MemoryInfo memoryInfo = {}; + u64 address = 0; + u32 pageInfo = 0; + constexpr u32 PageSize = 0x1000; + u8 buffer[PageSize]; + + /* Loop until failure. */ + while (true) { + /* Find pcv heap. */ + while (true) { + Result resultProcessMemory = svcQueryDebugProcessMemory(&memoryInfo, &pageInfo, handle, address); + address = memoryInfo.addr + memoryInfo.size; + + if (R_FAILED(resultProcessMemory) || !address) { + svcCloseHandle(handle); + FileUtils::LogLine("[Board] Failed to get process data. %u", R_DESCRIPTION(resultProcessMemory)); + handle = INVALID_HANDLE; + return; + } + + if (memoryInfo.size && (memoryInfo.perm & 3) == 3 && static_cast(memoryInfo.type) == 0x04) { + /* Found valid memory. */ + break; + } + } + + for (u64 base = 0; base < memoryInfo.size; base += PageSize) { + u32 memorySize = std::min(memoryInfo.size, static_cast(PageSize)); + if (R_FAILED(svcReadDebugProcessMemory(buffer, handle, base + memoryInfo.addr, memorySize))) { + break; + } + + u8 *resultPattern = static_cast(memmem_impl(buffer, sizeof(buffer), voltagePattern, sizeof(voltagePattern))); + u32 index = resultPattern - buffer; + + if (!resultPattern) { + continue; + } + + /* Assuming mariko. */ + const u32 vmax = 800; + constexpr u32 DvfsTableOffset = 312; + if (!std::memcmp(&buffer[index + DvfsTableOffset], &vmax, sizeof(vmax))) { + std::memcpy(dvfsTable, &buffer[index + DvfsTableOffset], sizeof(dvfsTable)); + dvfsAddress = base + memoryInfo.addr + DvfsTableOffset + index; + } + + svcCloseHandle(handle); + handle = INVALID_HANDLE; + return; + } + } + + svcCloseHandle(handle); + handle = INVALID_HANDLE; + return; +} + +void Board::PcvHijackDvfs(u32 vmin) { + u32 table[192]; + static_assert(sizeof(table) == sizeof(dvfsTable)); + std::memcpy(table, dvfsTable, sizeof(dvfsTable)); + + if (ramVmin == vmin) { + return; + } + + for (u32 i = 0; i < std::size(table); ++i) { + if (table[i] && table[i] <= vmin) { + table[i] = vmin; + } + } + + Handle handle = GetPcvHandle(); + if (handle == INVALID_HANDLE) { + FileUtils::LogLine("Invalid handle!"); + return; + } + + Result rc = svcWriteDebugProcessMemory(handle, table, dvfsAddress, sizeof(table)); + + if (R_SUCCEEDED(rc)) { + ramVmin = vmin; + } + + svcCloseHandle(handle); + FileUtils::LogLine("[dvfs] voltage set to %u mV", vmin); +} + +bool Board::IsDram8GB() { + SecmonArgs args = {}; + args.X[0] = 0xF0000002; + args.X[1] = MC_REGISTER_BASE + MC_EMEM_CFG_0; + svcCallSecureMonitor(&args); + + if(args.X[1] == (MC_REGISTER_BASE + MC_EMEM_CFG_0)) { // if param 1 is identical read failed + writeNotification("Horizon OC\nSecmon read failed!\n This may be a hardware issue!"); + return false; + } else + return args.X[1] == 0x00002000 ? true : false; +} + +void Board::SetGpuSchedulingMode(GpuSchedulingMode mode, GpuSchedulingOverrideMethod method) { + if (nvCheck_sched == 1 && method == GpuSchedulingOverrideMethod_NvService) { + return; + } + u32 temp; + bool enabled = false; + switch(mode) { + case GpuSchedulingMode_DoNotOverride: break; + case GpuSchedulingMode_Disabled: + if(method == GpuSchedulingOverrideMethod_NvService) + nvIoctl(fd2, NVSCHED_CTRL_DISABLE, &temp); + else + enabled = false; + break; + case GpuSchedulingMode_Enabled: + if(method == GpuSchedulingOverrideMethod_NvService) + nvIoctl(fd2, NVSCHED_CTRL_ENABLE, &temp); + else + enabled = true; + break; + default: + ASSERT_ENUM_VALID(GpuSchedulingMode, mode); + } + if(method == GpuSchedulingOverrideMethod_Ini) { + const char* ini_path = "sdmc:/atmosphere/config/system_settings.ini"; + const char* section = "am.gpu"; + const char* key = "gpu_scheduling_enabled"; + + const char* value = enabled ? "u8!0x1" : "u8!0x0"; + + ini_puts(section, key, value, ini_path); + } +} +void Board::SetDisplayRefreshDockedState(bool docked) { + if(Board::GetConsoleType() != HorizonOCConsoleType_Hoag) { + DisplayRefresh_SetDockedState(docked); + } +} + + +typedef struct EristaCpuUvEntry { + u32 tune0; + u32 tune1; +} EristaCpuUvEntry; +typedef struct MarikoCpuUvEntry { + u32 tune0_low; + u32 tune0_high; + u32 tune1_low; + u32 tune1_high; +} MarikoCpuUvEntry; + +EristaCpuUvEntry eristaCpuUvTable[5] = { + {0xffff, 0x27007ff}, + {0xefff, 0x27407ff}, + {0xdfff, 0x27807ff}, + {0xdfdf, 0x27a07ff}, + {0xcfdf, 0x37007ff}, +}; + +MarikoCpuUvEntry marikoCpuUvLow[12] = { + {0xffa0, 0xffff, 0x21107ff, 0}, + {0x0, 0xffdf, 0x21107ff, 0x27207ff}, + {0xffdf, 0xffdf, 0x21107ff, 0x27307ff}, + {0xffff, 0xffdf, 0x21107ff, 0x27407ff}, + {0x0, 0xffdf, 0x21607ff, 0x27707ff}, + {0x0, 0xffdf, 0x21607ff, 0x27807ff}, + {0x0, 0xdfff, 0x21607ff, 0x27b07ff}, + {0xdfff, 0xdfff, 0x21707ff, 0x27b07ff}, + {0xdfff, 0xdfff, 0x21707ff, 0x27c07ff}, + {0xdfff, 0xdfff, 0x21707ff, 0x27d07ff}, + {0xdfff, 0xdfff, 0x21707ff, 0x27e07ff}, + {0xdfff, 0xdfff, 0x21707ff, 0x27f07ff}, +}; + +MarikoCpuUvEntry marikoCpuUvHigh[12] = { + {0x0, 0xffff, 0, 0}, + {0x0, 0xffdf, 0, 0x27207ff}, + {0x0, 0xffdf, 0, 0x27307ff}, + {0x0, 0xffdf, 0, 0x27407ff}, + {0x0, 0xffdf, 0, 0x27707ff}, + {0x0, 0xffdf, 0, 0x27807ff}, + {0x0, 0xdfff, 0, 0x27b07ff}, + {0x0, 0xdfff, 0, 0x27c07ff}, + {0x0, 0xdfff, 0, 0x27d07ff}, + {0x0, 0xdfff, 0, 0x27e07ff}, + {0x0, 0xdfff, 0, 0x27f07ff}, + {0x0, 0xdfff, 0, 0x27f07ff}, +}; +void Board::SetCpuUvLevel(u32 levelLow, u32 levelHigh, u32 tbreakPoint) { + u32* tune0_ptr = (u32*)(cldvfs + CL_DVFS_TUNE0_0); + u32* tune1_ptr = (u32*)(cldvfs + CL_DVFS_TUNE1_0); + if(Board::GetSocType() == SysClkSocType_Mariko) { + if(Board::GetHz(SysClkModule_CPU) < tbreakPoint && (levelLow || levelHigh)) { + if(levelLow) { + *tune0_ptr = marikoCpuUvLow[levelLow-1].tune0_low; + *tune1_ptr = marikoCpuUvLow[levelLow-1].tune1_low; + } + return; + } else { + if(levelLow) { + *tune0_ptr = marikoCpuUvLow[levelLow-1].tune0_low; + *tune1_ptr = marikoCpuUvLow[levelLow-1].tune1_low; + } + if(levelHigh) { + *tune0_ptr = marikoCpuUvHigh[levelHigh-1].tune0_high; + *tune1_ptr = marikoCpuUvHigh[levelHigh-1].tune1_high; + } + return; + } + if(Board::GetHz(SysClkModule_CPU) < tbreakPoint || (!levelLow)) { // account for tbreak + *tune0_ptr = 0xCFFF; + *tune1_ptr = 0xFF072201; + return; + } else if (Board::GetHz(SysClkModule_CPU) >= tbreakPoint || (!levelHigh)) { + *tune0_ptr = cachedMarikoUvHighTune0; // per console? + *tune1_ptr = 0xFFF7FF3F; + return; + } + } else { + if(Board::GetHz(SysClkModule_CPU) < tbreakPoint || (!levelLow)) { // account for tbreak + *tune0_ptr = cachedEristaUvLowTune0; // I think each erista has a different tune0/tune1? + *tune1_ptr = cachedEristaUvLowTune1; + return; + } else { + if(levelLow) { + *tune0_ptr = eristaCpuUvTable[levelLow-1].tune0; + *tune1_ptr = eristaCpuUvTable[levelLow-1].tune1; + } else { + *tune0_ptr = 0x0; + *tune1_ptr = 0x0; + } + } + } +} +/* +enum TableConfig: u32 { + DEFAULT_TABLE = 1, + TBREAK_1581 = 2, + TBREAK_1683 = 3, + EXTREME_TABLE = 4, +}; +*/ +u32 Board::CalculateTbreak(u32 table) { + if(Board::GetSocType() == SysClkSocType_Erista) + return 1581000000; + else { + switch(table) { + case 1 ... 2: + case 4: + return 1581000000; + case 3: + return 1683000000; + default: + return 1581000000; + } + } + +} + +bool Board::IsHoag() { + return Board::GetConsoleType() == HorizonOCConsoleType_Hoag; +} \ No newline at end of file diff --git a/Source/rewrite-hoc-clk/sysmodule/src/old_board.h b/Source/rewrite-hoc-clk/sysmodule/src/old_board.h new file mode 100644 index 00000000..2a32c87b --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/old_board.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once +#include +#include +#include + +class Board +{ + public: + static void PcvHijackDvfs(u32 vmin); + static u32 GetMinimumGpuVoltage(u32 freqMhz); + static void fuseReadSpeedos(); + static u16 getSpeedo(HorizonOCSpeedo speedoType); + static u16 getIDDQ(HorizonOCSpeedo speedoType); + static const char* GetProfileName(SysClkProfile profile, bool pretty); + static const char* GetModuleName(SysClkModule module, bool pretty); + static const char* GetThermalSensorName(SysClkThermalSensor sensor, bool pretty); + static const char* GetPowerSensorName(SysClkPowerSensor sensor, bool pretty); + static void Initialize(); + static void Exit(); + static void ResetToStock(); + static void ResetToStockCpu(); + static void ResetToStockMem(); + static void ResetToStockGpu(); + static void ResetToStockDisplay(); + static u8 GetHighestDockedDisplayRate(); + static SysClkProfile GetProfile(); + static void SetHz(SysClkModule module, std::uint32_t hz); + static std::uint32_t GetHz(SysClkModule module); + static std::uint32_t GetRealHz(SysClkModule module); + static void GetFreqList(SysClkModule module, std::uint32_t* outList, std::uint32_t maxCount, std::uint32_t* outCount); + static std::uint32_t GetTemperatureMilli(SysClkThermalSensor sensor); + static std::int32_t GetPowerMw(SysClkPowerSensor sensor); + static std::uint32_t GetPartLoad(SysClkPartLoad load); + static SysClkSocType GetSocType(); + static HorizonOCConsoleType GetConsoleType(); + static std::uint32_t GetVoltage(HocClkVoltage voltage); + static u8 GetFanRotationLevel(); + static u8 GetDramID(); + static bool IsDram8GB(); + static void SetGpuSchedulingMode(GpuSchedulingMode mode, GpuSchedulingOverrideMethod method); + static void SetDisplayRefreshDockedState(bool docked); + static void SetCpuUvLevel(u32 levelLow, u32 levelHigh, u32 tbreakPoint); + static u32 CalculateTbreak(u32 table); + static bool IsHoag(); + protected: + static void FetchHardwareInfos(); + static PcvModule GetPcvModule(SysClkModule sysclkModule); + static PcvModuleId GetPcvModuleId(SysClkModule sysclkModule); + private: + static void SetSpeedoBracket(); + static void CacheDvfsTable(); + static Handle GetPcvHandle(); +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/src/process_management.cpp b/Source/rewrite-hoc-clk/sysmodule/src/process_management.cpp new file mode 100644 index 00000000..6c0f6953 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/process_management.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#include "process_management.h" +#include "file_utils.h" +#include "errors.h" +#define IS_QLAUNCH 0x20f + +void ProcessManagement::Initialize() +{ + Result rc = 0; + + rc = pmdmntInitialize(); + ASSERT_RESULT_OK(rc, "pmdmntInitialize"); + + rc = pminfoInitialize(); + ASSERT_RESULT_OK(rc, "pminfoInitialize"); +} + +void ProcessManagement::WaitForQLaunch() +{ + Result rc = 0; + std::uint64_t pid = 0; + do + { + rc = pmdmntGetProcessId(&pid, PROCESS_MANAGEMENT_QLAUNCH_TID); + svcSleepThread(50 * 1000000ULL); // 50ms + } while (R_FAILED(rc)); +} + +std::uint64_t ProcessManagement::GetCurrentApplicationId() +{ + Result rc = 0; + std::uint64_t pid = 0; + std::uint64_t tid = 0; + rc = pmdmntGetApplicationProcessId(&pid); + + if (rc == IS_QLAUNCH) + { + return PROCESS_MANAGEMENT_QLAUNCH_TID; + } + + ASSERT_RESULT_OK(rc, "pmdmntGetApplicationProcessId"); + + rc = pminfoGetProgramId(&tid, pid); + + if (rc == IS_QLAUNCH) + { + return PROCESS_MANAGEMENT_QLAUNCH_TID; + } + + ASSERT_RESULT_OK(rc, "pminfoGetProgramId"); + + return tid; +} + +void ProcessManagement::Exit() +{ + pmdmntExit(); + pminfoExit(); +} diff --git a/Source/rewrite-hoc-clk/sysmodule/src/process_management.h b/Source/rewrite-hoc-clk/sysmodule/src/process_management.h new file mode 100644 index 00000000..65abe792 --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/src/process_management.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Souldbminer, Lightos_ and Horizon OC Contributors + * + * 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 . + * + */ + +/* -------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * , , + * wrote this file. As long as you retain this notice you can do whatever you + * want with this stuff. If you meet any of us some day, and you think this + * stuff is worth it, you can buy us a beer in return. - The sys-clk authors + * -------------------------------------------------------------------------- + */ + + +#pragma once +#include +#include + +#define PROCESS_MANAGEMENT_QLAUNCH_TID 0x0100000000001000ULL + +class ProcessManagement +{ + public: + static void Initialize(); + static void WaitForQLaunch(); + static std::uint64_t GetCurrentApplicationId(); + static void Exit(); +}; diff --git a/Source/rewrite-hoc-clk/sysmodule/toolbox.json b/Source/rewrite-hoc-clk/sysmodule/toolbox.json new file mode 100644 index 00000000..83ac964c --- /dev/null +++ b/Source/rewrite-hoc-clk/sysmodule/toolbox.json @@ -0,0 +1,5 @@ +{ + "name" : "hoc-clk", + "tid" : "00FF0000636C6BFF", + "requires_reboot": false +} \ No newline at end of file