Compare commits

...
244 Commits
Author SHA1 Message Date
niklascfw 16cb8c1bbf overlay: remove Updates page for ams-pro
Build Horizon OC Zeus / build (push) Failing after 1m22s
2026-11-12 18:30:38 +01:00
niklascfw 9bc6756265 build-exosphere: copy emc01 access tables like upstream build.sh 2026-11-12 18:28:39 +01:00
niklascfw 2c5cf446f6 Merge upstream Horizon-OC main into Horizon-OC-pro. 2026-11-12 18:27:27 +01:00
Lightos1 d7c9ad8515 ldr: use the correct size and don't be stupid 2026-10-23 13:05:16 +02:00
Lightos1 03789095db ldr: properly truncate dvb freq tables using 64 lut. 2026-10-23 13:00:53 +02:00
Lightos1 8798b1de76 Update README.md 2026-10-17 20:38:27 +02:00
Lightos1 aafc8b1169 add Portuguese translation by Wivi 2026-10-17 20:37:14 +02:00
Lightos1 cc0245f15c update .gitignore 2026-10-17 19:19:16 +02:00
Lightos1 2fd78d6c30 hoc-clk: make logging toggle config based 2026-10-17 19:18:24 +02:00
Lightos1 abfe4774a3 add (experimental) uv13 2026-10-17 18:21:08 +02:00
Lightos1 f349afd00b lang: update translation template 2026-10-17 18:20:35 +02:00
Lightos1 a707d4f2c4 clk-mgr: properly apply gpu ram oc dvfs during GameStartMemWar 2026-10-17 18:16:58 +02:00
Souldbminer c5ded00cc5 Update README.md 2026-10-17 12:03:35 -04:00
Lightos1 6df88693fe Readme: Update clock table 2026-10-17 14:28:10 +02:00
Lightos1 b27701debb Readme: Clarify max clocks 2026-10-17 14:26:35 +02:00
Lightos1 fe90a1df14 Update README.md 2026-10-17 14:25:33 +02:00
Lightos1 9d73f7bd1e Update README.md 2026-10-17 14:24:30 +02:00
Lightos1 b1b4ead2e2 ldr: odt has always seemingly been used? 2026-10-16 20:55:06 +02:00
Lightos1 dbc37c6982 ldr: avoid out of cave memory with a warcrime (overwriting dead destructor) 2026-10-10 15:45:07 +02:00
Lightos1 69bd7e088f ldr: fix offset 2026-10-10 13:49:53 +02:00
Lightos1 740f9ca61a ldr: build 64 lut table ourselves instead of interpolating 2026-10-09 20:01:48 +02:00
Lightos1 9b1aab4128 fix 64 lut off by one 2026-10-09 17:13:08 +02:00
souldbminersmwc 6fa9e5ae29 Update board_volt.cpp 2026-10-08 18:48:53 -04:00
souldbminersmwc 9ca050d3b5 cpu lut patch fixes + crash fixes 2026-10-08 18:41:44 -04:00
souldbminersmwc 87d0214288 remove some unused logging variables 2026-10-07 19:49:11 -04:00
souldbminersmwc 8403386f1a 64lut 2026-10-07 19:43:05 -04:00
Lightos1 e28d7d1206 add back delay 2026-10-06 21:12:55 +02:00
Lightos1 2c4020e71f hoc-clk: auto cpu ram oc 2026-10-06 19:56:02 +02:00
Lightos1 2b3883998a hoc-clk: code style ocd or something 2026-10-06 19:49:15 +02:00
Lightos1 c3137f56d6 hoc-clk: better variable names for minLut 2026-10-06 19:46:13 +02:00
Lightos1 d98da35604 hoc-clk: properly adjust CL_DVFS_OUTPUT_CFG after lut write 2026-10-06 19:43:52 +02:00
Lightos1 010cdaafcc hoc-clk: update makefiles to recursively scan instead of hardcoding source paths 2026-10-06 18:42:30 +02:00
Lightos1 5b1cc026d1 add lto 2026-10-06 18:32:35 +02:00
Lightos1 1a0fddf7ea hoc-clk: remove unstuck delay and clarify workaround 2026-10-06 15:43:00 +02:00
Lightos1 5f973f60b2 Revert C++ libnx bindings and call-site conversion 2026-10-06 10:42:15 +02:00
souldbminersmwc fb46ac4199 Update display_refresh_rate.cpp 2026-10-05 19:48:36 -04:00
souldbminersmwc 127c6f947b Update process_management.cpp 2026-10-05 19:46:31 -04:00
souldbminersmwc 4efe27ca91 Update integrations.cpp 2026-10-05 19:45:25 -04:00
souldbminersmwc 4b9c4dfaf0 Update file_utils.cpp 2026-10-05 19:44:31 -04:00
souldbminersmwc 7d6f296fa0 c++ify more stuff 2026-10-05 19:43:36 -04:00
souldbminersmwc fb07924a3d c++ify ipc server 2026-10-05 19:36:13 -04:00
souldbminersmwc fa9f911620 more C++-ification 2026-10-05 19:35:07 -04:00
souldbminersmwc 062b52c607 more c++ification 2026-10-05 19:28:12 -04:00
souldbminersmwc 018325747d c++ bindings for libnx 2026-10-05 19:15:19 -04:00
souldbminersmwc b144c8d633 dvfs: fix bugs and edge cases with cpu dvfs 2026-10-05 17:45:36 -04:00
Lightos1 79ee323cbe hoc-clk: apply ram oc cpu dvfs unstuck fix when decreasing voltage 2026-10-05 22:53:30 +02:00
Lightos1 aec025fd9d hoc-clk: add (half finished) cpu dvfs 2026-10-05 22:16:44 +02:00
Lightos1 32fa50a252 Revert "add CPU DVFS skeleton"
This reverts commit 930f2efe63.
2026-10-05 11:30:29 +02:00
Lightos1 c9dafc0e2a Revert "hocclk: actually implement cpu dvfs"
This reverts commit a8f221aed8.
2026-10-05 11:30:15 +02:00
souldbminersmwc a8f221aed8 hocclk: actually implement cpu dvfs 2026-10-04 20:52:27 -04:00
souldbminersmwc befe98fa4b Fix watchdog mutexing hang
Due to how everything is on Core3 it should be good to not have mutexes
2026-10-04 18:45:40 -04:00
souldbminersmwc 930f2efe63 add CPU DVFS skeleton 2026-10-04 18:37:38 -04:00
souldbminersmwc e9d01a0878 add watchcat 2026-10-04 16:39:09 -04:00
souldbminersmwc eae39374a8 Reapply "hocclk: add watchdog"
This reverts commit 1f1c28875c.
2026-10-04 15:06:17 -04:00
Lightos1 1f1c28875c Revert "hocclk: add watchdog"
This watchdog prevents fatal errors which are *kinda* important.
This reverts commit c9e9b87bd0.
2026-10-04 13:55:01 +02:00
souldbminersmwc c9e9b87bd0 hocclk: add watchdog 2026-10-03 19:47:29 -04:00
souldbminersmwc 5c097363c9 update build script and add AMS 1.12.x patches 2026-10-03 19:01:25 -04:00
Lightos1 82d3f2a93c benchmark-toolbox: make cpu readings *actually* match memtoolkit 2026-10-03 17:01:21 +02:00
souldbminersmwc 119a9a6ea4 soctherm: workaround TSOSC potential hw bug 2026-09-29 20:33:15 -04:00
souldbminersmwc ee18a82424 Revert "add wip erista vmax"
This reverts commit 8953cedd50.
2026-09-29 20:31:44 -04:00
souldbminersmwc 8953cedd50 add wip erista vmax 2026-09-29 19:28:07 -04:00
Lightos1 c78010a1e5 hoc-clk: fix config low t8 typo 2026-09-29 20:17:17 +02:00
souldbminersmwc 16a1a5306f Revert "ldr_process_creation: update to latest"
This reverts commit bc2830932a.

Atmosphere 1.12 is not supported, even via custom compilation
2026-09-28 16:10:12 -04:00
Lightos1 bc2830932a ldr_process_creation: update to latest
ldr_process_creation: actually update to latest
2026-09-28 16:09:25 -04:00
Lightos1 3af1b89340 ldr: even more formating stuff 2026-09-25 18:01:59 +02:00
Lightos1 f2eadd8866 ldr: remove extra line 2026-09-25 17:56:22 +02:00
Lightos1 0393d59e57 hoc-clk: update soc volt array size in configurator 2026-09-25 17:48:39 +02:00
souldbminersmwc 7329ee2e80 add hazel to contributor list 2026-09-24 17:38:40 -04:00
Lightos1 0d476cab26 revert bpmp related exosphere patches 2026-09-24 17:11:59 +02:00
Lightos1 21390c05eb Remove FUNDING.yml 2026-09-24 11:09:17 +02:00
souldbminersmwc 94c1074a05 temporarily disable mrr due to crashes 2026-09-23 23:25:51 +02:00
souldbminersmwc ab60e55303 BPMP ABORT! 2026-09-23 23:25:51 +02:00
Lightos1 9428309ac3 swap ram settings order, move auto cpu oc to experimental settings (disabled auto cpu oc by default now), experimental setting cleanup 2026-09-20 20:36:46 +02:00
Lightos1 056396804f hoc-clk-overlay: update cpu/gpu/ram temp colors 2026-09-20 15:39:33 +02:00
Lightos1 0e8c138187 remove debug hardcode (yes I'm stupid) 2026-09-20 15:32:27 +02:00
souldbminersmwc 4fb9615653 benchmark-toolbox: CUDA95 integration 2026-09-12 17:17:21 -04:00
Lightos1 5dacd30dd3 ldr: code cleanup 2026-09-11 14:16:29 +02:00
Lightos1 8fc18ee3e8 fix namespace overusage 2026-09-11 13:11:04 +02:00
Lightos1 1741e27116 remove bss crap 2026-09-10 13:24:25 +02:00
souldbminersmwc b770f88b9b fix lightos Z 2026-09-07 15:40:18 -04:00
souldbminersmwc 9c892ee365 governor configuration options + gm20b driver fixes 2026-09-06 15:20:55 -04:00
souldbminersmwc 01d85ca9ff cleanup mfg identification as i dont need to support nonstandard ones 2026-09-06 13:27:03 -04:00
souldbminersmwc 080daeee5a Update board_sensor.cpp 2026-09-06 12:55:53 -04:00
souldbminersmwc 5c215fcd02 DRAM MR4 tsensor reading 2026-09-06 12:50:11 -04:00
Lightos1 bd6d615143 improve timings 2026-09-06 15:37:36 +02:00
souldbminersmwc 763ef09020 add x267 2026-09-05 21:25:22 -04:00
souldbminersmwc 21a21df4ba hocclk: DRAMID from MRR 2026-09-05 21:20:56 -04:00
souldbminersmwc e16735d16d Update config.cpp 2026-09-05 16:24:49 -04:00
souldbminersmwc 28265faedc complete config refactor 2026-09-05 15:58:08 -04:00
souldbminersmwc 0169e44b3b reduce heap size 2026-09-05 10:30:48 -04:00
souldbminersmwc 427e5cfded change defaults + comments 2026-09-04 17:53:27 -04:00
Lightos1 afde93e2b9 mariko timings: rw2pden cleanup 2026-09-02 16:40:18 +02:00
Lightos1 196121884f mariko timings: improve qrst_duration (brute forced values) 2026-09-02 16:13:46 +02:00
Lightos1 1b32d21860 mariko timings: einput_duration, seems reasonable? 2026-09-01 17:04:20 +02:00
Lightos1 349a9200c5 more accurate mariko timings 2026-09-01 16:51:57 +02:00
Lightos1 79c6b81da2 fix context size 2026-08-30 20:20:45 +02:00
Lightos1 ac31a3defa Revert "ipc: allow mismatached provided buffer size as long as the stable region is covered"
This reverts commit 40ec8dee8c.
2026-08-30 20:16:38 +02:00
souldbminersmwc 4ac7f472d9 bpmpfw uart changes 2026-08-28 14:45:37 -04:00
souldbminersmwc 6b88b43cb9 add bpmp-fw cmd interface 2026-08-28 13:43:50 -04:00
souldbminersmwc 39c612779b allow hoc-clk to panic 2026-08-27 20:10:50 -04:00
souldbminersmwc 63601e35fa allow hoc-clk to panic 2026-08-27 20:08:37 -04:00
souldbminersmwc 50ff965a6f remove hocmon 2026-08-27 18:57:40 -04:00
souldbminersmwc 3352dffc39 bpmpfw: cleanup makefile and split hpp/cpp 2026-08-27 18:57:15 -04:00
souldbminersmwc e5240d835f Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-27 18:48:53 -04:00
souldbminersmwc 6980a12324 bpmpfw: disable SMMU for FW 2026-08-27 18:48:52 -04:00
Lightos1 d06479af69 remove hoc-mon binaries 2026-08-28 00:08:46 +02:00
souldbminersmwc cbaa61385c Revert "refuse to adhere to SMMU for BPMP-FW"
This reverts commit fc10a53bda.
2026-08-27 16:31:41 -04:00
souldbminersmwc fc10a53bda refuse to adhere to SMMU for BPMP-FW 2026-08-27 16:14:19 -04:00
souldbminersmwc 19f040ece1 🐈 has told me to change this 2026-08-27 15:13:17 -04:00
souldbminersmwc ff333977ab Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-27 14:56:04 -04:00
souldbminersmwc 4023ae6838 clean up pattern matching 2026-08-27 14:56:02 -04:00
Lightos1 40ec8dee8c ipc: allow mismatached provided buffer size as long as the stable region is covered 2026-08-27 20:33:02 +02:00
souldbminersmwc b6fcd6072c move pllmb measurement to bpmp 2026-08-26 15:49:17 -04:00
souldbminersmwc 3141d0ea2e move AOTAG to BPMP 2026-08-26 15:29:41 -04:00
souldbminersmwc 0262236d2c bpmpfw: system panic on panic 2026-08-26 15:22:10 -04:00
souldbminersmwc 24dec1ffc3 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-26 15:19:00 -04:00
souldbminersmwc 3095135993 prevent exosphere patch from crashing nvsrv 2026-08-26 15:18:51 -04:00
Lightos1 2a60fe1a04 ldr: fix rext fallback 2026-08-26 21:04:00 +02:00
Lightos1 4cf2aab755 pcv: act2pden was correct 2026-08-26 20:46:39 +02:00
Lightos1 0a9f412c51 ldr: add erista rext timings 2026-08-26 20:40:09 +02:00
Lightos1 b16337df89 add secmon patch diff 2026-08-26 20:22:51 +02:00
Lightos1 638fe68535 fix wunused warnings 2026-08-26 20:03:41 +02:00
Lightos1 8514d3c95b add proper quse timing 2026-08-26 19:56:08 +02:00
Lightos1 04f2b9c6cc properly add bpmpfw to .gitignore 2026-08-26 19:55:20 +02:00
souldbminersmwc 3d2719f1b6 I REFUSE 2026-08-26 12:53:59 -04:00
souldbminersmwc a36d861e49 actually fix gpu real hz 2026-08-26 11:59:52 -04:00
souldbminersmwc d8c6a86bb0 real freqs + ram load on bpmp
gpu freq is excluded due to smmu issue (carveout?)
2026-08-26 11:56:56 -04:00
Souldbminer 8222fe6b0c Merge pull request #108 from Horizon-OC/bpmpfw
hocclk: add BPMP-Lite firmware
2026-08-25 19:21:30 -04:00
souldbminersmwc dca14e4767 Update about_gui.cpp 2026-08-25 19:21:06 -04:00
souldbminersmwc ac41a498e1 fix soctherm-BPMP 2026-08-25 19:19:19 -04:00
souldbminersmwc 209ff7afd8 unused comments + u64 2026-08-25 18:56:28 -04:00
souldbminersmwc 1b9156fbd6 soctherm coldboot fix 2026-08-25 18:54:58 -04:00
souldbminersmwc b8f728ac0a Update .gitignore 2026-08-25 16:08:32 -04:00
souldbminersmwc 49244b3baf move soctherm poll to bpmp 2026-08-25 15:59:52 -04:00
Lightos1 dd59deeefa clkmgr: (hack) prevent emc freq list mismatch with 64 lut freq hack 2026-08-25 21:25:01 +02:00
souldbminersmwc ba75b02c52 hopefully fix mariko bpmp panic issue once and for all(?) 2026-08-25 15:20:11 -04:00
souldbminersmwc cbdd35b0be stop ticking during intermittent sleep 2026-08-25 15:01:26 -04:00
souldbminersmwc 85bfed0604 lower fs reads during early warmboot 2026-08-25 14:54:06 -04:00
souldbminersmwc ba07273675 bpmpfw reliability fix 2026-08-25 14:49:57 -04:00
Lightos1 f9ed6c84b9 pcv: nvm, didn't like the alignment 2026-08-25 20:45:42 +02:00
Lightos1 eba5573711 Refactor busReloc to use hooks.
Also add sd logging and temporarily remove new Erista timings; they're still slightly too explosive.
Todo: Rename stuff and fix uart redirect.
2026-08-25 20:40:32 +02:00
souldbminersmwc 1990b1b66c hocclk bpmp: communication 2026-08-25 14:33:58 -04:00
souldbminersmwc b20d20edff more bpmpfw shenanigans 2026-08-25 13:37:05 -04:00
souldbminersmwc 7660650129 raise to 1.5S 2026-08-24 22:00:33 -04:00
souldbminersmwc ea288b1e9a attempt to fix fs journaling crashes 2026-08-24 22:00:10 -04:00
souldbminersmwc a3b8400477 make sleep mode work 2026-08-24 21:42:25 -04:00
souldbminersmwc bd468f087d hocclk: add bpmpfw loading 2026-08-24 21:21:50 -04:00
Lightos1 7fea98a72d Exos: add exception vectors and flow controller access patches 2026-08-24 23:25:50 +02:00
Lightos1 c5fcc6c051 pcv/erista: improve timings (rdv & qpop) 2026-08-21 23:52:56 +02:00
souldbminersmwc 160e2483a6 1574mhz gpu + 995mV vmax
refer to 2805mhz commit to reasoning
2026-08-21 15:11:01 -04:00
souldbminersmwc 90efad8dde erista dvb overlay changes 2026-08-21 12:42:45 -04:00
souldbminersmwc 7e67859c84 remove retro remake dispay support and more ui cohesion 2026-08-21 12:40:19 -04:00
souldbminersmwc e5765e70ab fix cpu read benchmark 2026-08-20 20:32:55 -04:00
souldbminersmwc e528efc6d1 vdd2/vddq ram display mode 2026-08-20 19:24:04 -04:00
souldbminersmwc 4c0370d4a6 add isFirstLoad to context 2026-08-20 18:44:22 -04:00
souldbminersmwc 1f5fbdfae4 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-20 18:41:27 -04:00
souldbminersmwc 50336ec687 add ram timing presets in the menu 2026-08-20 18:41:25 -04:00
Lightos1 850c333128 Update pcv_mariko.cpp 2026-08-20 23:57:59 +02:00
souldbminersmwc 9645159b55 Update misc_gui.cpp 2026-08-20 16:56:59 -04:00
souldbminersmwc fe9c4f1920 Update misc_gui.cpp 2026-08-20 16:53:30 -04:00
souldbminersmwc 15fbc69b37 lower max mem volt on mariko
it's unnessesary and useless on mariko
2026-08-20 16:52:15 -04:00
souldbminersmwc 2a505211aa gm20b driver bugfixes 2026-08-20 16:10:45 -04:00
souldbminersmwc 02026fa5fe Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-20 15:51:32 -04:00
souldbminersmwc c7ca572d71 improve update feature with changelogs 2026-08-20 15:51:25 -04:00
Lightos1 ecdfd417e3 use correct units 2026-08-20 21:45:54 +02:00
Lightos1 8174c6fb7a pcv::mariko: change rdv timing formular to match specifications according to t124 2026-08-20 21:43:58 +02:00
Lightos1 c0096686d8 refactor assembly patching 2026-08-20 15:48:02 +02:00
Lightos1 62a2cfd65a remove unneeded dependencies 2026-08-19 21:01:42 +02:00
souldbminersmwc 7d6868b943 Update main.cpp 2026-08-18 15:06:26 -04:00
souldbminersmwc 864d5d6aa8 config: erista doesn't have vmax (for now), so remove it 2026-08-18 15:03:27 -04:00
souldbminersmwc de2369023a update validators 2026-08-18 15:01:51 -04:00
souldbminersmwc de817b05a0 add 2805mhz cpu/1235 vmax
reasoning before anyone yells at us:
we did testing on very good speedo and found that they can hold 2805@1200-1235 so it's justifable to add it if people can do it.
INA current measurement of 2703 proves that it draws only 3A so 2805 wont be much different (even if it overdraws it draws from vddq which is fine)

1235mV is also not known to have caused degratation (even long therm we havent got reports from just 1235). 1275 was what was reported to cause cpu damage.
2026-08-18 15:01:23 -04:00
souldbminersmwc 60f1f4a695 Update ldr_process_creation.cpp 2026-08-17 11:50:53 -04:00
souldbminersmwc 0a4e2a5cdf Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-08-16 17:19:15 -04:00
souldbminersmwc caa92493d5 incorrect dram tsensor fix 2026-08-16 17:19:12 -04:00
Souldbminer 1e521b1a47 Merge pull request #107 from redraz/patch-3
Update Russian translation
2026-08-16 12:55:16 -04:00
redraz 73b1e397e6 Update Russian translation 2026-08-15 05:44:58 +03:00
souldbminersmwc 490a207bbb Update pcv_mariko_gpu.cpp 2026-08-14 16:34:48 -04:00
souldbminersmwc ffab12ccd3 Revert "whoops wrong branch"
This reverts commit 0f70e65d98.
2026-08-14 16:33:46 -04:00
souldbminersmwc 0f70e65d98 whoops wrong branch 2026-08-14 16:31:42 -04:00
souldbminersmwc e0ef50f7f5 fix nvservices patch compile error 2026-08-14 15:18:25 -04:00
souldbminersmwc 836e24c82e more debug feature 2026-08-13 20:03:53 -04:00
souldbminersmwc 539cd58464 verbosity patch rework 2026-08-13 19:11:00 -04:00
souldbminersmwc 62127a4383 quick fix 2026-08-13 18:44:22 -04:00
souldbminersmwc 5b70938f91 nvservices debug unlock + license change 2026-08-13 18:40:51 -04:00
souldbminersmwc e321a18479 Update registers.h 2026-08-11 16:47:28 -04:00
souldbminersmwc fb5b8c61c6 add GCPLL trim registers 2026-08-11 11:29:19 -04:00
souldbminersmwc 21f94d724a add GPU registers 2026-08-11 11:22:43 -04:00
Lightos1 0643e3c2bc Readme: don't forget the other hero 2026-08-06 16:35:47 +02:00
Lightos1 c870ee9f57 Readme: update credits 2026-08-06 16:34:47 +02:00
Lightos1 f0e1541d38 Readme: having clock table in the same file is prettier 2026-08-06 16:27:43 +02:00
Lightos1 8a3e34948a Readme: move clock table to a separate file + add missing frequencies 2026-08-06 16:25:46 +02:00
Lightos1 3b650952db overlay: jumpToItem fix 2026-08-06 16:13:15 +02:00
Lightos1 8429576545 ldr: slight w2r timing fix 2026-08-04 23:38:56 +02:00
Souldbminer eb51fa0ff9 Merge pull request #106 from XiaoYeeMoon/main
update zh-cn translations
2026-08-03 20:55:37 -04:00
souldbminersmwc eaad7b1d89 default JEDEC step mode 2026-08-03 17:42:55 -04:00
Lightos1 bfe8d3a913 ldr: major refactor part 2 2026-08-03 20:53:42 +02:00
Lightos1 2375210fee ldr: fix rext 2026-08-03 20:37:10 +02:00
XiaoYee Moon 55552b67ef update zh-cn translation 2026-08-03 23:13:59 +08:00
souldbminersmwc 8476969576 revert timings
more recalculation must be done
2026-07-30 18:22:47 -04:00
souldbminersmwc 4582e07d39 Update calculate_timings_mariko.cpp 2026-07-30 16:52:45 -04:00
souldbminersmwc f25f6a85db fix w2r 2026-07-30 16:42:49 -04:00
souldbminersmwc 4415749779 screw ram timings 2026-07-30 16:34:25 -04:00
souldbminersmwc aae77e0585 more timings 2026-07-30 16:29:19 -04:00
souldbminersmwc ff084e2f31 add uart log flag 2026-07-30 15:34:42 -04:00
souldbminersmwc df305437f7 Update calculate_timings_mariko.cpp 2026-07-30 15:09:03 -04:00
souldbminersmwc 969f548231 perfect quse_width 2026-07-30 12:31:24 -04:00
souldbminersmwc c5f4d65478 Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-07-30 11:24:55 -04:00
souldbminersmwc 497fc64d7c more timing improvement 2026-07-30 11:24:53 -04:00
Souldbminer 734ed0c954 Update README.md 2026-07-29 19:38:15 -04:00
souldbminersmwc 3150692690 Update calculate_timings_mariko.cpp 2026-07-29 19:26:18 -04:00
souldbminersmwc f76b737299 trrd "fix" revert 2026-07-29 19:25:11 -04:00
souldbminersmwc b093b32b29 perfect ram timings 2026-07-29 19:06:10 -04:00
souldbminersmwc 119099984d more timing improvements 2026-07-29 15:52:16 -04:00
souldbminersmwc e626d0c4f8 perfect pdex2rw 2026-07-29 11:22:54 -04:00
souldbminersmwc db5ae4efd4 fix compile error 2026-07-28 19:28:49 -04:00
souldbminersmwc f45c9b2933 perfect quse_width
also perfects quse, einput, einput_duration and qsafe
2026-07-28 19:24:40 -04:00
souldbminersmwc 770a544b8f improved dram timings 2026-07-28 19:01:47 -04:00
niklascfw e5c1f0faa2 Merge upstream Horizon-OC main into Horizon-OC-pro.
Build Horizon OC Zeus / build (push) Failing after 15s
2026-07-28 23:45:30 +02:00
niklascfw 3baa3e7b2e Point Atmosphere-Pro clones at the OmniNX git host. 2026-07-28 23:44:08 +02:00
souldbminersmwc da7f922782 fix boot crash 2026-07-28 14:53:32 -04:00
Lightos1 af8912321c ldr: major refactor part 1 2026-07-28 19:54:07 +02:00
souldbminersmwc 363d1f0e3c Merge branch 'main' of https://github.com/Horizon-OC/Horizon-OC 2026-07-27 15:12:57 -04:00
souldbminersmwc 7e4b4b5b1d Revert "mariko 64lut cleanup"
This reverts commit 88ca2e8688.
2026-07-27 15:12:29 -04:00
Lightos1 b8c006c702 Merge branch 'main' of https://github.com/horizon-OC/horizon-OC 2026-07-27 20:57:58 +02:00
Lightos1 b7d0e3c9fa add ccache 2026-07-27 20:57:48 +02:00
souldbminersmwc 1fc9afd6d1 fix typo 2026-07-27 14:48:16 -04:00
souldbminersmwc 88ca2e8688 mariko 64lut cleanup 2026-07-27 14:38:29 -04:00
Lightos1 02bbdcdb57 improve build system for uart 2026-07-27 19:57:41 +02:00
Lightos1 139721761a fix uart log newline 2026-07-27 19:17:53 +02:00
souldbminersmwc 88f0dda772 add debug flag to build.sh 2026-07-27 10:18:56 -04:00
Lightos1 38ab15a243 fix formating 2026-07-27 15:04:17 +02:00
Lightos1 6e4a28ee67 use hooking for erista mrf 2026-07-27 15:02:53 +02:00
Lightos1 0cf4927c47 add newline for formating 2026-07-26 21:53:06 +02:00
Lightos1 bfb2009dab ldr: fix pre processor hell 2026-07-26 21:52:24 +02:00
Lightos1 8c54df039f ldr: ifdef logging 2026-07-26 21:36:12 +02:00
Lightos1 9eb53f5a0b ldr: add hooking capabilities 2026-07-26 21:04:31 +02:00
Lightos1 a6f1732e1d clkmgr: don't apply gpudvfs during GameStartMemWar if gpu dvfs is disabled 2026-07-26 13:45:25 +02:00
souldbminersmwc 6f4b58b83e ldr: code cleanup and fix soc bus table bug 2026-07-25 14:50:56 -04:00
souldbminersmwc 9d12aa5cb1 add uart logging feature 2026-07-25 12:39:31 -04:00
niklascfw ff4fd37cdc Fix exosphere build after checkout path rename.
Build Horizon OC Zeus / build (push) Failing after 3s
Clean stale absolute dependency paths when the tree moved, and fail the script instead of copying an old binary.
2026-07-15 23:02:43 +02:00
niklascfw 618fae2532 Merge upstream main into Horizon-OC-pro fork.
Build Horizon OC Zeus / build (push) Failing after 6s
Keep Atmosphere-Pro boot-storage submodule; take remaining upstream updates.
2026-07-15 22:59:30 +02:00
niklascfw 5e9ede09a1 Switch Atmosphere clone to Atmosphere-Pro and add exosphere-only build script.
Build Horizon OC Zeus / build (push) Failing after 48s
Use the boot-storage branch required for Horizon OC exosphere patches, and provide build-exosphere.sh for compiling just exosphere.bin without a full project build.
2026-07-14 23:06:55 +02:00
niklascfw 40968377c5 Fix Atmosphere-libs submodule to point at Atmosphere-Pro boot-storage.
The previous Atmosphere-NX commit is not available in the new remote, which broke submodule update.
2026-07-14 23:06:55 +02:00
212 changed files with 18031 additions and 21639 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
# Donate to us!
https://ko-fi.com/souldbminer
+1
View File
@@ -8,3 +8,4 @@ build
Source/hekate
dist/*
!dist/.gitignore
Status-Monitor-Overlay.ovl*
+3
View File
@@ -5,3 +5,6 @@ Horizon OC Compilation Instructions
3. Clone the Horizon OC develop branch (``git clone https://github.com/Horizon-OC/Horizon-OC.git --recurse-submodules``)
4. Run ``./build.sh`` in the root directory
- If you want to compile with extensions, append ``--ext``
- To skip components, append ``-s``/``--skip`` followed by one or more of: ``ldr`` (loader), ``clk`` (hoc-clk), ``hek`` (hekate), ``btb`` (benchmark toolbox), ``exos`` (exosphere), ``sm`` (status-monitor download), ``zip`` (dist.zip packaging). Example: ``./build.sh --ext -s ldr hek``
- Building the custom Hekate (``--ext`` without skipping ``hek``) additionally requires devkitARM with ``DEVKITARM`` exported
- Installing ``ccache`` is optional; if it is on your PATH it is used automatically to speed up rebuilds
+48 -22
View File
@@ -34,9 +34,9 @@ It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration to
## Default clocks
* **CPU:** Up to 1963MHz (Mariko) / 1785MHz (Erista)
* **GPU:** Up to 1075MHz (Mariko) / 921MHz (Erista)
* **RAM:** Up to 1866/2133MHz (Mariko) / 1600MHz (Erista)
* **CPU:** 1963MHz (Mariko) / 1785MHz (Erista)
* **GPU:** 1152MHz (Mariko) / 921MHz (Erista)
* **RAM:** 2133MHz (Mariko) / 1600MHz (Erista)
* Over/undervolting support
* Built-in configurator
* Compatible with most homebrew
@@ -51,7 +51,7 @@ It enables advanced CPU, GPU, and RAM tuning with user-friendly configuration to
* [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere)
* [Ultrahand Overlay](https://github.com/ppkantorski/Ultrahand-Overlay)
2. Download and extract the **Horizon OC Package** to the root of your SD card.
2. Download and extract **Horizon OC** to the root of your SD card.
3. If using **Hekate**, edit `hekate_ipl.ini` to include:
```
@@ -83,13 +83,23 @@ We don't accept donations. Put your spare cash into a charity instead :)
Refer to COMPILATION.md
---
## Clock table
Higher clocks are achievable via undervolting or further configuration, refer to this table for more details.
The averages are only for reference. Each ram is different and must be tuned independently. Refer to the guide for more details!
<details>
<summary><h2 style="display:inline">Clock table</h2></summary>
### MEM clocks (mhz)
* 3200 → max on mariko, JEDEC.
* 3400 → max on mariko.
* 3366
* 3333
* 3300
* 3266
* 3233
* 3200 → JEDEC, Hynix NEE/x267 Average
* 3166
* 3133
* 3133 → Micron WT:B Average
* 3100
* 3066
* 3033
@@ -98,20 +108,20 @@ Refer to COMPILATION.md
* 2933 → JEDEC.
* 2900
* 2866
* 2833
* 2833 → Samsung AA-MGCL Average
* 2800
* 2766
* 2733
* 2733 → Samsung AM-MGCJ/Micron WT:E Average
* 2700
* 2666 → JEDEC.
* 2666 → JEDEC, Micron WT:F Average
* 2633
* 2600
* 2566
* 2566 → Samsung AB-MGCL Average
* 2533
* 2500
* 2466
* 2433
* 2400 → max on erista, JEDEC.
* 2400 → max on erista, JEDEC, Hynix NME Average
* 2366
* 2333
* 2300
@@ -124,7 +134,7 @@ Refer to COMPILATION.md
* 2066
* 2033
* 2000
* 1996 → JEDEC standard
* 1996 → JEDEC, Erista Average
* 1966
* 1933
* 1900
@@ -138,12 +148,18 @@ Refer to COMPILATION.md
* 1633
* 1600 → official docked, boost mode, Erista JEDEC standard max (3200 Modules), JEDEC.
* 1331 → official handheld, JEDEC.
* 1065
* 800
* 665
* 1065 → Erista only
* 800 → Erista only
* 665 → Erista only
* 408 → Erista only
* 204 → sleep mode
* 102 → Erista only
* 68 → Erista only
* 40 → Erista only
### CPU clocks (mhz)
* 2703 → mariko absolute max, dangerous
* 2805 → mariko absolute max, dangerous
* 2703
* 2601 → unsafe
* 2499
* 2397 → mariko safe max with UV (low speedo)
@@ -166,9 +182,13 @@ Refer to COMPILATION.md
* 612 → sleep mode
### GPU clocks (mhz)
* 1536 → absolute max clock on mariko. Dangerous
* 1497 → max mariko unsafe clock
* 1574 → absolute max clock on mariko. Dangerous
* 1536
* 1497 → max mariko unsafe clock
* 1459
* 1420
* 1382 → Max mariko "safe" clock
* 1344
* 1305
* 1267 → NVIDIA T214(mariko) rating
* 1228 → mariko High UV safe clock
@@ -195,12 +215,15 @@ Refer to COMPILATION.md
3. On Mariko, cap with hiOpt is 614MHz, with hiOpt-15mV it is 691MHz and with High UV it's 768MHz
4. Clocks higher than 768MHz on erista need the official charger is plugged in.
</details>
---
## Credits
* **Lightos's Cat** - Cat
* **Lightos's Cats** - Cats
* **Souldbminer** - hoc-clk and loader development
* **Lightos** - Loader patches development, hoc-clk development, guides
* **HAZEL** - Perfect DRAM timings help
* **TDRR** - HOC Logo Design
* **tetetete-ctrl** - Website design
* **SciresM** - Atmosphere CFW
@@ -217,6 +240,9 @@ Refer to COMPILATION.md
* **Dominatorul, Samybigio, Arcdelta, Miki, Happy, Winnerboi77, Blaise, Alvise, agjeococh, frost, letum00, and Xenshen** - Testing
* **Samybigio2011, Miki** - Italian translations
* **angelblaster** - Korean translations
* **q1332348216-glitch** - Chinese translations
* **q1332348216-glitch, nangongjing1, xinluochenjiang** - Chinese translations
* **th3-ne0undr5c0r** - French translations
* **redraz** - Russian translations
* **Wivi** - Portuguese translations
* **Niklas080208** - JSON language support for sysmodule notifications.
* **Nvidia** - [Tegra X1 Technical Reference Manual](https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual), soctherm driver, L4T
+1
View File
@@ -0,0 +1 @@
This contains a diff all modifications done to exosphere
@@ -0,0 +1,57 @@
131,156c131,159
< #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \
< HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
< HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
< HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
< HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
< HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
< HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(FuseKFuse, MemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \
< HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
< HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
< HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__)
---
> #define AMS_SECMON_FOREACH_DEVICE_REGION(HANDLER, ...) \
> HANDLER(GicDistributor, Empty, UINT64_C(0x50041000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(GicCpuInterface, GicDistributor, UINT64_C(0x50042000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
> HANDLER(Uart, GicCpuInterface, UINT64_C(0x70006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
> HANDLER(ClkRst, Uart, UINT64_C(0x60006000), UINT64_C(0x1000), false, ## __VA_ARGS__) \
> HANDLER(RtcPmc, ClkRst, UINT64_C(0x7000E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(Timer, RtcPmc, UINT64_C(0x60005000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(System, Timer, UINT64_C(0x6000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(SecurityEngine, System, UINT64_C(0x70012000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
> HANDLER(SecurityEngine2, SecurityEngine, UINT64_C(0x70412000), UINT64_C(0x2000), true, ## __VA_ARGS__) \
> HANDLER(SysCtr0, SecurityEngine2, UINT64_C(0x700F0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(MemoryController, SysCtr0, UINT64_C(0x70019000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(ExternalMemoryController, MemoryController, UINT64_C(0x7001b000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(FuseKFuse, ExternalMemoryController, UINT64_C(0x7000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(ApbMisc, FuseKFuse, UINT64_C(0x70000000), UINT64_C(0x4000), true, ## __VA_ARGS__) \
> HANDLER(FlowController, ApbMisc, UINT64_C(0x60007000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(BootloaderParams, FlowController, UINT64_C(0x40000000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(I2c5, BootloaderParams, UINT64_C(0x7000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(Gpio, I2c5, UINT64_C(0x6000D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(I2c1, Gpio, UINT64_C(0x7000C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(ExternalMemoryController0, MemoryController1, UINT64_C(0x7001E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(ExternalMemoryController1, ExternalMemoryController0, UINT64_C(0x7001F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(Sdmmc, ExternalMemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
> HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
> HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
@@ -0,0 +1,29 @@
1221c1221
< const auto target_fw = GetTargetFirmware();
---
> // const auto target_fw = GetTargetFirmware();
1223,1226c1223,1226
< if (target_fw >= TargetFirmware_2_0_0) {
< /* Set the PMC secure. */
< reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE));
< }
---
> // if (target_fw >= TargetFirmware_2_0_0) {
> // /* Set the PMC secure. */ I REFUSE
> // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, ENABLE));
> // }
1228,1233c1228,1234
< if (target_fw >= TargetFirmware_4_0_0) {
< /* Set the MC secure. */
< reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE),
< SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE),
< SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE));
< }
---
> // if (target_fw >= TargetFirmware_4_0_0) {
> // /* Set the MC secure. */ I REFUSE
> // reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG1_0, SLAVE_SECURITY_REG_BITS_ENUM(1, MC0, ENABLE),
> // SLAVE_SECURITY_REG_BITS_ENUM(1, MC1, ENABLE),
> // SLAVE_SECURITY_REG_BITS_ENUM(1, MCB, ENABLE));
> // }
> return;
@@ -0,0 +1,2 @@
154a155
> { 0xF0000005, Restriction_None, SmcShowError },
@@ -0,0 +1,22 @@
100a101,105
> #include "secmon_define_emc_access_table.inc"
> #include "secmon_define_emc01_access_table.inc"
> #include "secmon_define_flow_controller_access_table.inc"
> #include "secmon_define_exception_vectors_access_table.inc"
> #include "secmon_define_rtc_pmc_access_table.inc"
104,107c109,118
< { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
< { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
< { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
< { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
---
> { PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
> { McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
> { EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
> { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Size, },
> { Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Size, },
> { FlowControllerAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceFlowController.GetAddress(), FlowControllerAccessTable::Address, FlowControllerAccessTable::Size, },
> { ExceptionVectorsAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExceptionVectors.GetAddress(), ExceptionVectorsAccessTable::Address, ExceptionVectorsAccessTable::Size, },
> { RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
> { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
> { Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
@@ -0,0 +1,25 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define __ACCESS_TABLE_NAME__ Emc01AccessTable
#define __ACCESS_TABLE_ADDRESS__ 0
#define __ACCESS_TABLE_INC__ "secmon_emc01_access_table_data.inc"
#include "secmon_define_access_table.inc"
#undef __ACCESS_TABLE_INC__
#undef __ACCESS_TABLE_ADDRESS__
#undef __ACCESS_TABLE_NAME__
File diff suppressed because it is too large. Load diff
@@ -151,7 +151,9 @@ namespace ams::secmon {
HANDLER(ExceptionVectors, I2c1, UINT64_C(0x6000F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MemoryController0, ExceptionVectors, UINT64_C(0x7001C000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MemoryController1, MemoryController0, UINT64_C(0x7001D000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(Sdmmc, MemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(ExternalMemoryController0, MemoryController1, UINT64_C(0x7001E000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(ExternalMemoryController1, ExternalMemoryController0, UINT64_C(0x7001F000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(Sdmmc, ExternalMemoryController1, UINT64_C(0x700B0000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(Disp1, Sdmmc, UINT64_C(0x54200000), UINT64_C(0x3000), true, ## __VA_ARGS__) \
HANDLER(Dsi, Disp1, UINT64_C(0x54300000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
HANDLER(MipiCal, Dsi, UINT64_C(0x700E3000), UINT64_C(0x1000), true, ## __VA_ARGS__) \
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,567 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <exosphere.hpp>
#include "../secmon_cache.hpp"
#include "../secmon_cpu_context.hpp"
#include "../secmon_error.hpp"
#include "../secmon_misc.hpp"
#include "secmon_smc_power_management.hpp"
#include "secmon_smc_se_lock.hpp"
#include "sc7fw_bin.h"
namespace ams::secmon {
/* Declare assembly functionality. */
void *GetCoreExceptionStackVirtual();
}
namespace ams::secmon::smc {
/* Declare assembly power-management functionality. */
void PivotStackAndInvoke(void *stack, void (*function)());
void FinalizePowerOff();
namespace {
constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress();
constexpr inline const uintptr_t APB_MISC = MemoryRegionVirtualDeviceApbMisc.GetAddress();
constexpr inline const uintptr_t GPIO = MemoryRegionVirtualDeviceGpio.GetAddress();
constexpr inline const uintptr_t CLK_RST = MemoryRegionVirtualDeviceClkRst.GetAddress();
constexpr inline const uintptr_t EVP = secmon::MemoryRegionVirtualDeviceExceptionVectors.GetAddress();
constexpr inline const uintptr_t FLOW_CTLR = MemoryRegionVirtualDeviceFlowController.GetAddress();
constexpr inline const uintptr_t AHB_ARBC = MemoryRegionVirtualDeviceSystem.GetAddress();
constexpr inline uintptr_t CommonSmcStackTop = MemoryRegionVirtualTzramVolatileData.GetEndAddress() - (0x80 * (NumCores - 1));
enum PowerStateType {
PowerStateType_StandBy = 0,
PowerStateType_PowerDown = 1,
};
enum PowerStateId {
PowerStateId_Sc7 = 27,
};
/* http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_Coordination_Interface_PDD_v1_1_DEN0022D.pdf Page 46 */
struct SuspendCpuPowerState {
using StateId = util::BitPack32::Field< 0, 16, PowerStateId>;
using StateType = util::BitPack32::Field<16, 1, PowerStateType>;
using PowerLevel = util::BitPack32::Field<24, 2, u32>;
};
constinit bool g_charger_hi_z_mode_enabled = false;
constinit const reg::BitsMask CpuPowerGateStatusMasks[NumCores] = {
PMC_REG_BITS_MASK(PWRGATE_STATUS_CE0),
PMC_REG_BITS_MASK(PWRGATE_STATUS_CE1),
PMC_REG_BITS_MASK(PWRGATE_STATUS_CE2),
PMC_REG_BITS_MASK(PWRGATE_STATUS_CE3),
};
constinit const APBDEV_PMC_PWRGATE_TOGGLE_PARTID CpuPowerGateTogglePartitionIds[NumCores] = {
APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE0,
APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE1,
APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE2,
APBDEV_PMC_PWRGATE_TOGGLE_PARTID_CE3,
};
bool IsCpuPoweredOn(const reg::BitsMask mask) {
return reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, REG_BITS_VALUE_FROM_MASK(mask, APBDEV_PMC_PWRGATE_STATUS_STATUS_ON));
}
void PowerOnCpu(const reg::BitsMask mask, u32 toggle_partid) {
/* If the cpu is already on, we have nothing to do. */
if (IsCpuPoweredOn(mask)) {
return;
}
/* Wait until nothing is being powergated. */
int timeout = 5000;
while (true) {
if (reg::HasValue(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM(PWRGATE_TOGGLE_START, DISABLE))) {
break;
}
util::WaitMicroSeconds(1);
if ((--timeout) < 0) {
/* NOTE: Nintendo doesn't do any error handling here... */
return;
}
}
/* Toggle on the cpu partition. */
reg::Write(PMC + APBDEV_PMC_PWRGATE_TOGGLE, PMC_REG_BITS_ENUM (PWRGATE_TOGGLE_START, ENABLE),
PMC_REG_BITS_VALUE(PWRGATE_TOGGLE_PARTID, toggle_partid));
/* Wait up to 5000 us for the powergate to complete. */
timeout = 5000;
while (true) {
if (IsCpuPoweredOn(mask)) {
break;
}
util::WaitMicroSeconds(1);
if ((--timeout) < 0) {
/* NOTE: Nintendo doesn't do any error handling here... */
return;
}
}
}
void ResetCpu(int which_core) {
reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
}
void StartCpu(int which_core) {
reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR, REG_BITS_VALUE(which_core + 0x00, 1, 1), /* CPURESETn */
REG_BITS_VALUE(which_core + 0x10, 1, 1)); /* CORERESETn */
}
void PowerOffCpuImpl() {
/* Get the current core id. */
const auto core_id = hw::GetCurrentCoreId();
/* Configure the flow controller to prepare for shutting down the current core. */
flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_ONLY);
flow::SetHaltCpuEvents(core_id, false);
flow::SetCc4Ctrl(core_id, 0);
/* Save the core's context for restoration on next power-on. */
SaveDebugRegisters();
SetCoreOff();
/* Ensure there are no pending memory transactions prior to our power-down. */
FlushEntireDataCache();
/* Finalize our powerdown and wait for an interrupt. */
FinalizePowerOff();
}
void ValidateSocStateForSuspend() {
/* Validate that all other cores are off. */
AMS_ABORT_UNLESS(reg::HasValue(PMC + APBDEV_PMC_PWRGATE_STATUS, PMC_REG_BITS_VALUE(PWRGATE_STATUS_CE123, 0)));
/* Validate that the bpmp is appropriately halted. */
const bool jtag = IsJtagEnabled();
AMS_ABORT_UNLESS(reg::Read(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS) == reg::Encode(FLOW_REG_BITS_ENUM (HALT_COP_EVENTS_MODE, FLOW_MODE_STOP),
FLOW_REG_BITS_ENUM_SEL(HALT_COP_EVENTS_JTAG, jtag, ENABLED, DISABLED)));
/* Further validations aren't guaranteed on < 6.0.0. */
if (GetTargetFirmware() < TargetFirmware_6_0_0) {
return;
}
/* Validate that USB2, APB-DMA, AHB-DMA are held in reset. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_H, CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_USB2_RST, ENABLE),
CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_APBDMA_RST, ENABLE),
CLK_RST_REG_BITS_ENUM(RST_DEVICES_H_SWR_AHBDMA_RST, ENABLE)));
/* Validate that USBD is held in reset. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_USBD_RST, ENABLE)));
/* Validate that AHB-DMA, USB, USB2, COP are not allowed to arbitrate on the AHB. */
AMS_ABORT_UNLESS(reg::HasValue(AHB_ARBC + AHB_ARBITRATION_DISABLE, AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_COP, DISABLE),
AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_AHBDMA, DISABLE),
AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB, DISABLE),
AHB_REG_BITS_ENUM(ARBITRATION_DISABLE_USB2, DISABLE)));
/* Validate that the GPIO controller has clock enabled. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_GPIO, ENABLE)));
/* Validate that both FUSE and KFUSE have clock enabled. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_H, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_FUSE, ENABLE),
CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_H_CLK_ENB_KFUSE, ENABLE)));
/* Validate that all of IRAM has clock enabled. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMA, ENABLE),
CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMB, ENABLE),
CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMC, ENABLE),
CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_IRAMD, ENABLE)));
/* Validate that ACTMON has clock enabled. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_V, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_V_CLK_ENB_ACTMON, ENABLE)));
/* Validate that ENTROPY has clock enabled. */
AMS_ABORT_UNLESS(reg::HasValue(CLK_RST + CLK_RST_CONTROLLER_CLK_OUT_ENB_W, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_W_CLK_ENB_ENTROPY, ENABLE)));
}
void GenerateCryptographicallyRandomBytes(void * const dst, int size) {
/* Flush the region we're about to fill to ensure consistency with the SE. */
hw::FlushDataCache(dst, size);
hw::DataSynchronizationBarrierInnerShareable();
/* Generate random bytes. */
se::GenerateRandomBytes(dst, size);
hw::DataSynchronizationBarrierInnerShareable();
/* Flush to ensure the CPU sees consistent data for the region. */
hw::FlushDataCache(dst, size);
hw::DataSynchronizationBarrierInnerShareable();
}
void SaveSecureContextForErista() {
/* Generate a random key source. */
util::AlignedBuffer<hw::DataCacheLineSize, se::AesBlockSize> key_source;
GenerateCryptographicallyRandomBytes(key_source, se::AesBlockSize);
const u32 * const key_source_32 = reinterpret_cast<const u32 *>(static_cast<u8 *>(key_source));
/* Ensure that the key source registers are not locked. */
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) != pmc::LockState::Locked);
/* Write the key source, lock writes to the key source, and verify that the key source is write-locked. */
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH24, key_source_32[0]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH25, key_source_32[1]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH26, key_source_32[2]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH27, key_source_32[3]);
pmc::LockSecureRegister(pmc::SecureRegister_KeySourceWrite);
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceWrite) == pmc::LockState::Locked);
/* Verify the key source is correct in registers, and read-lock the key source registers. */
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH24) == key_source_32[0]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH25) == key_source_32[1]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH26) == key_source_32[2]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH27) == key_source_32[3]);
pmc::LockSecureRegister(pmc::SecureRegister_KeySourceRead);
/* Ensure that the key source registers are locked. */
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_KeySourceReadWrite) == pmc::LockState::Locked);
/* Generate a random kek into keyslot 2. */
se::SetRandomKey(pkg1::AesKeySlot_TzramSaveKek);
/* Verify that the se is in a validate state, context save, and validate again. */
{
se::ValidateErrStatus();
ON_SCOPE_EXIT { se::ValidateErrStatus(); };
{
/* Transition to non-secure mode for the duration of the context save operation. */
se::SetSecure(false);
ON_SCOPE_EXIT { se::SetSecure(true); };
/* Get a pointer to the context storage. */
se::Context * const context = MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetPointer<se::Context>();
static_assert(MemoryRegionVirtualDramSecureDataStoreSecurityEngineState.GetSize() == sizeof(*context));
/* Save the context. */
se::SaveContext(context);
/* Ensure that the cpu sees consistent data. */
hw::FlushDataCache(context, sizeof(*context));
hw::DataSynchronizationBarrierInnerShareable();
/* Write the context pointer to pmc scratch, so that the bootrom will restore it on wake. */
reg::Write(PMC + APBDEV_PMC_SCRATCH43, MemoryRegionPhysicalDramSecureDataStoreSecurityEngineState.GetAddress());
}
}
/* Clear keyslot 3, and then derive the save key. */
se::ClearAesKeySlot(pkg1::AesKeySlot_TzramSaveKey);
se::SetEncryptedAesKey256(pkg1::AesKeySlot_TzramSaveKey, pkg1::AesKeySlot_TzramSaveKek, key_source, se::AesBlockSize);
/* Declare a temporary block to be used as both iv and mac. */
u32 temp_block[se::AesBlockSize / sizeof(u32)] = {};
/* Ensure that the SE sees consistent data for tzram. */
const void * const tzram_save_src = MemoryRegionVirtualTzramReadOnlyAlias.GetPointer<u8>() + MemoryRegionVirtualTzramVolatileData.GetSize() + MemoryRegionVirtualTzramVolatileStack.GetSize();
void * const tzram_save_dst = MemoryRegionVirtualIramSc7Work.GetPointer<void>();
constexpr size_t TzramSaveSize = MemoryRegionVirtualDramSecureDataStoreTzram.GetSize();
hw::FlushDataCache(tzram_save_src, TzramSaveSize);
hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
hw::DataSynchronizationBarrierInnerShareable();
/* Encrypt tzram using our random key. */
se::EncryptAes256Cbc(tzram_save_dst, TzramSaveSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize, temp_block, se::AesBlockSize);
hw::FlushDataCache(tzram_save_dst, TzramSaveSize);
hw::DataSynchronizationBarrierInnerShareable();
/* Copy the data from work space to the secure storage destination. */
void * const tzram_store_dst = MemoryRegionVirtualDramSecureDataStoreTzram.GetPointer<void>();
std::memcpy(tzram_store_dst, tzram_save_dst, TzramSaveSize);
hw::FlushDataCache(tzram_store_dst, TzramSaveSize);
hw::DataSynchronizationBarrierInnerShareable();
/* Compute cmac of tzram into our temporary block. */
se::ComputeAes256Cmac(temp_block, se::AesBlockSize, pkg1::AesKeySlot_TzramSaveKey, tzram_save_src, TzramSaveSize);
/* Ensure that the cmac registers are not locked. */
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) != pmc::LockState::Locked);
/* Write the cmac, lock writes to the cmac, and verify that the cmac is write-locked. */
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH112, temp_block[0]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH113, temp_block[1]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH114, temp_block[2]);
reg::Write(PMC + APBDEV_PMC_SECURE_SCRATCH115, temp_block[3]);
pmc::LockSecureRegister(pmc::SecureRegister_CmacWrite);
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacWrite) == pmc::LockState::Locked);
/* Verify the key source is correct in registers, and read-lock the key source registers. */
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH112) == temp_block[0]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH113) == temp_block[1]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH114) == temp_block[2]);
AMS_ABORT_UNLESS(reg::Read(PMC + APBDEV_PMC_SECURE_SCRATCH115) == temp_block[3]);
pmc::LockSecureRegister(pmc::SecureRegister_CmacRead);
/* Ensure that the key source registers are locked. */
AMS_ABORT_UNLESS(pmc::GetSecureRegisterLockState(pmc::SecureRegister_CmacReadWrite) == pmc::LockState::Locked);
}
void SaveSecureContextForMariko() {
/* Save security engine context to TZRAM SE carveout (inaccessible to cpu). */
se::SaveContextAutomatic();
/* Save TZRAM to shadow-TZRAM in always-on power domain. */
se::SaveTzramAutomatic();
}
void SaveSecureContext() {
/* Save the appropriate secure context. */
const auto soc_type = GetSocType();
if (soc_type == fuse::SocType_Erista) {
SaveSecureContextForErista();
} else /* if (soc_type == fuse::SocType_Mariko) */ {
SaveSecureContextForMariko();
}
/* Save the debug code. */
#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
{
const void * const debug_code_src = MemoryRegionVirtualDebugCode.GetPointer<void>();
void * const debug_code_dst = MemoryRegionVirtualDramDebugDataStore.GetPointer<void>();
std::memcpy(debug_code_dst, debug_code_src, MemoryRegionVirtualDebugCode.GetSize());
hw::FlushDataCache(debug_code_dst, MemoryRegionVirtualDebugCode.GetSize());
hw::DataSynchronizationBarrierInnerShareable();
}
#endif
}
void LoadAndStartSc7BpmpFirmware() {
/* Set BPMP reset. */
reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SET_SET_COP_RST, ENABLE));
/* Set the PMC as insecure, so that the BPMP firmware can access it. */
reg::ReadWrite(APB_MISC + APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0, SLAVE_SECURITY_REG_BITS_ENUM(0, PMC, DISABLE));
/* Set the exception vectors for the bpmp. RESET should point to RESET, all others should point to generic exception/panic. */
constexpr const u32 Sc7FirmwareResetVector = static_cast<u32>(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x0);
constexpr const u32 Sc7FirmwarePanicVector = static_cast<u32>(MemoryRegionPhysicalIramSc7Firmware.GetAddress() + 0x4);
reg::Write(EVP + EVP_COP_RESET_VECTOR, Sc7FirmwareResetVector);
reg::Write(EVP + EVP_COP_UNDEF_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_SWI_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_PREFETCH_ABORT_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_DATA_ABORT_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_RSVD_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_IRQ_VECTOR, Sc7FirmwarePanicVector);
reg::Write(EVP + EVP_COP_FIQ_VECTOR, Sc7FirmwarePanicVector);
/* Disable activity monitor bpmp monitoring, so that we don't panic upon bpmp wake. */
actmon::StopMonitoringBpmp();
/* Load the bpmp firmware. */
void * const sc7fw_load_address = MemoryRegionVirtualIramSc7Firmware.GetPointer<void>();
std::memcpy(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size);
hw::FlushDataCache(sc7fw_load_address, sc7fw_bin_size);
hw::DataSynchronizationBarrierInnerShareable();
/* Ensure that the bpmp firmware was loaded. */
AMS_ABORT_UNLESS(crypto::IsSameBytes(sc7fw_load_address, sc7fw_bin, sc7fw_bin_size));
/* Clear BPMP reset. */
reg::Write(CLK_RST + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_CLR_CLR_COP_RST, ENABLE));
/* Start the bpmp. */
reg::Write(FLOW_CTLR + FLOW_CTLR_HALT_COP_EVENTS, FLOW_REG_BITS_ENUM(HALT_COP_EVENTS_MODE, FLOW_MODE_NONE));
}
void SaveSecureContextAndSuspend() {
/* Ensure there are no pending memory transactions before we continue */
FlushEntireDataCache();
hw::DataSynchronizationBarrierInnerShareable();
/* Save all secure context (security engine state + tzram). */
SaveSecureContext();
/* Load and start the sc7 firmware on the bpmp. */
if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
LoadAndStartSc7BpmpFirmware();
}
/* Log our suspension. */
/* NOTE: Nintendo only does this on dev, but we will always do it. */
if (true /* !pkg1::IsProduction() */) {
log::Initialize(secmon::GetLogPort(), secmon::GetLogBaudRate(), secmon::GetLogFlags());
log::SendText("OYASUMI\n", 8);
log::Flush();
}
/* If we're on erista, configure the bootrom to allow our custom warmboot firmware. */
if (GetSocType() == fuse::SocType_Erista) {
reg::Write(PMC + APBDEV_PMC_SCRATCH31, 0x2202E012);
reg::Write(PMC + APBDEV_PMC_SCRATCH32, 0x6001DC28);
}
/* Finalize our powerdown and wait for an interrupt. */
FinalizePowerOff();
}
SmcResult SuspendCpuImpl(SmcArguments &args) {
/* Decode arguments. */
const util::BitPack32 power_state = { static_cast<u32>(args.r[1]) };
const uintptr_t entry_point = args.r[2];
const uintptr_t context_id = args.r[3];
const auto state_type = power_state.Get<SuspendCpuPowerState::StateType>();
const auto state_id = power_state.Get<SuspendCpuPowerState::StateId>();
const auto core_id = hw::GetCurrentCoreId();
/* Validate arguments. */
SMC_R_UNLESS(state_type == PowerStateType_PowerDown, PsciDenied);
SMC_R_UNLESS(state_id == PowerStateId_Sc7, PsciDenied);
/* Orchestrate charger transition to Hi-Z mode if needed. */
if (IsChargerHiZModeEnabled()) {
/* Ensure we can do comms over i2c-1. */
clkrst::EnableI2c1Clock();
/* If the charger isn't in hi-z mode, perform a transition. */
if (!charger::IsHiZMode()) {
charger::EnterHiZMode();
/* Wait up to 50ms for the transition to complete. */
const auto start_time = util::GetMicroSeconds();
auto current_time = start_time;
while ((current_time - start_time) <= 50'000) {
if (auto intr_status = reg::Read(GPIO + 0x634); (intr_status & 1) == 0) {
/* Wait 256 us to ensure the transition completes. */
util::WaitMicroSeconds(256);
break;
}
current_time = util::GetMicroSeconds();
}
}
/* Disable i2c-1, since we're done communicating over it. */
clkrst::DisableI2c1Clock();
}
/* Enable wake event detection. */
pmc::EnableWakeEventDetection();
/* Ensure that i2c-5 is usable for communicating with the pmic. */
clkrst::EnableI2c5Clock();
i2c::Initialize(i2c::Port_5);
/* Orchestrate sleep entry with the pmic. */
pmic::EnableSleep();
/* Ensure that the soc is in a state valid for us to suspend. */
if (GetTargetFirmware() >= TargetFirmware_2_0_0) {
ValidateSocStateForSuspend();
}
/* Configure the pmc for sc7 entry. */
pmc::ConfigureForSc7Entry();
/* Configure the flow controller for sc7 entry. */
flow::SetCc4Ctrl(core_id, 0);
flow::SetHaltCpuEvents(core_id, false);
flow::ClearL2FlushControl();
flow::SetCpuCsr(core_id, FLOW_CTLR_CPUN_CSR_ENABLE_EXT_POWERGATE_CPU_TURNOFF_CPURAIL);
/* Save the entry context. */
SetEntryContext(core_id, entry_point, context_id);
/* Configure the cpu context for reset. */
SaveDebugRegisters();
SetCoreOff();
SetResetExpected(true);
/* Switch to use the common smc stack (all other cores are off), and perform suspension. */
PivotStackAndInvoke(reinterpret_cast<void *>(CommonSmcStackTop), SaveSecureContextAndSuspend);
/* This code will never be reached. */
__builtin_unreachable();
}
}
void PowerOffCpu() {
/* Get the current core id. */
const auto core_id = hw::GetCurrentCoreId();
/* Note that we're expecting a reset for the current core. */
SetResetExpected(true);
/* If we're on the final core, shut down directly. Otherwise, invoke with special stack. */
if (core_id == NumCores - 1) {
PowerOffCpuImpl();
} else {
PivotStackAndInvoke(GetCoreExceptionStackVirtual(), PowerOffCpuImpl);
}
/* This code will never be reached. */
__builtin_unreachable();
}
SmcResult SmcPowerOffCpu(SmcArguments &args) {
AMS_UNUSED(args);
PowerOffCpu();
}
SmcResult SmcPowerOnCpu(SmcArguments &args) {
/* Get and validate the core to power on. */
const int which_core = args.r[1];
SMC_R_UNLESS(0 <= which_core && which_core < NumCores, PsciInvalidParameters);
/* Ensure the core isn't already on. */
SMC_R_UNLESS(!IsCoreOn(which_core), PsciAlreadyOn);
/* Save the entry context. */
SetEntryContext(which_core, args.r[2], args.r[3]);
/* Reset the cpu. */
ResetCpu(which_core);
/* Turn on the core. */
PowerOnCpu(CpuPowerGateStatusMasks[which_core], CpuPowerGateTogglePartitionIds[which_core]);
/* Start the core. */
StartCpu(which_core);
return SmcResult::PsciSuccess;
}
SmcResult SmcSuspendCpu(SmcArguments &args) {
return LockSecurityEngineAndInvoke(args, SuspendCpuImpl);
}
bool IsChargerHiZModeEnabled() {
return g_charger_hi_z_mode_enabled;
}
void SetChargerHiZModeEnabled(bool en) {
g_charger_hi_z_mode_enabled = en;
}
}
@@ -99,16 +99,19 @@ namespace ams::secmon::smc {
#include "secmon_define_pmc_access_table.inc"
#include "secmon_define_mc_access_table.inc"
#include "secmon_define_emc_access_table.inc"
#include "secmon_define_emc01_access_table.inc"
#include "secmon_define_rtc_pmc_access_table.inc"
#include "secmon_define_mc01_access_table.inc"
constexpr const AccessTableEntry AccessTables[] = {
{ PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
{ McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
{ EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
{ RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
{ Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
{ Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
{ PmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDevicePmc.GetAddress(), PmcAccessTable::Address, PmcAccessTable::Size, },
{ McAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceMemoryController.GetAddress(), McAccessTable::Address, McAccessTable::Size, },
{ EmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceExternalMemoryController.GetAddress(), EmcAccessTable::Address, EmcAccessTable::Size, },
{ Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController0.GetAddress(), Emc01AccessTable::Size, },
{ Emc01AccessTable::ReducedAccessTable.data(), Emc01AccessTable::Address + MemoryRegionVirtualDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Address + MemoryRegionPhysicalDeviceExternalMemoryController1.GetAddress(), Emc01AccessTable::Size, },
{ RtcPmcAccessTable::ReducedAccessTable.data(), MemoryRegionVirtualDeviceRtcPmc.GetAddress(), RtcPmcAccessTable::Address, RtcPmcAccessTable::Size, },
{ Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController0.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController0.GetAddress(), Mc01AccessTable::Size, },
{ Mc01AccessTable::ReducedAccessTable.data(), Mc01AccessTable::Address + MemoryRegionVirtualDeviceMemoryController1.GetAddress(), Mc01AccessTable::Address + MemoryRegionPhysicalDeviceMemoryController1.GetAddress(), Mc01AccessTable::Size, },
};
constexpr bool IsAccessAllowed(const AccessTableEntry &entry, uintptr_t address) {
@@ -170,12 +173,12 @@ namespace ams::secmon::smc {
/* This is "probably" to fuck with hackers who got access to the SMC and are trying to get control of the */
/* BPMP to exploit jamais vu, deja vu, or other related DMA/wake-from-sleep vulnerabilities. */
constexpr uintptr_t MC = MemoryRegionPhysicalDeviceMemoryController.GetAddress();
SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument);
// SMC_R_UNLESS((address == (MC + MC_SMMU_AVPC_ASID) || address == (MC + MC_SMMU_PPCS1_ASID)), InvalidArgument);
/* For backwards compatibility, we'll allow access to these devices on 1.0.0. */
if (GetTargetFirmware() < TargetFirmware_2_0_0) {
// /* For backwards compatibility, we'll allow access to these devices on 1.0.0. */
// if (GetTargetFirmware() < TargetFirmware_2_0_0) {
virtual_address = MemoryRegionVirtualDeviceMemoryController.GetAddress() + (address - MC);
}
// }
}
/* Perform the read or write, if we should. */
@@ -13,6 +13,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
@@ -126,8 +127,7 @@ namespace ams::ldr {
bool g_is_ptm;
/* Global Zstd decompression context. */
constexpr size_t ZstdDctxWorkspaceSize = 0x176E8;
alignas(8) u8 g_zstd_dctx_workspace[ZstdDctxWorkspaceSize];
alignas(8) u8 g_zstd_dctx_workspace[util::DecompressZstdWithBicWorkBufferSizeDefault];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
@@ -181,8 +181,8 @@ namespace ams::ldr {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
Npdm::ProcessAddressSpace GetProcessAddressSpace(const Meta *meta) {
return static_cast<Npdm::ProcessAddressSpace>((meta->npdm->flags0 & Npdm::MetaFlag0_ProcessAddressSpaceMask) >> Npdm::MetaFlag0_ProcessAddressSpaceShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
@@ -225,7 +225,7 @@ namespace ams::ldr {
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, g_nso_headers + ctx.nso_count, sizeof(NsoHeader)));
R_UNLESS(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso());
R_UNLESS_LOG(read_size == sizeof(NsoHeader), ldr::ResultInvalidNso(), "[ldr] NSO header truncated!\n");
/* Note nso is present. */
switch (i) {
@@ -264,7 +264,7 @@ namespace ams::ldr {
Result CheckAutoLoad(const AutoLoadModuleContext &ctx, u32 acid_flags) {
/* We must always have a main. */
R_UNLESS(ctx.ali.has_main, ldr::ResultInvalidNso());
R_UNLESS_LOG(ctx.ali.has_main, ldr::ResultInvalidNso(), "[ldr] Missing main!\n");
/* Validate flags and extents for all present NSOs. */
for (int i = 0; i < ctx.nso_count; ++i) {
@@ -272,11 +272,11 @@ namespace ams::ldr {
/* All NSOs must not be --X. */
/* This is "probably" not checked on Ounce? */
R_UNLESS((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso());
R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_PreventCodeReads) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] --x not allowed!\n", i);
/* Zstd compression only allowed on main, and only when both rtld+sdk are present. */
if (i != ctx.main_nso_idx || ctx.rtld_idx < 0 || ctx.sdk_nso_idx < 0) {
R_UNLESS((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso());
R_UNLESS_LOG((hdr.flags & NsoHeader::Flag_UseZbicCompression) == 0, ldr::ResultInvalidNso(), "[ldr] NSO[%d] zbic not allowed!\n", i);
}
/* NSOs must have page-aligned segments. */
@@ -299,13 +299,13 @@ namespace ams::ldr {
const bool has_browser_dll = (acid_flags & Acid::AcidFlag_LoadBrowserCoreDll) != 0;
if (ctx.ali.has_rtld || ctx.ali.has_sdk) {
/* If we have sdk we must have rtld. */
R_UNLESS(ctx.ali.has_rtld, ldr::ResultInvalidNso());
R_UNLESS_LOG(ctx.ali.has_rtld, ldr::ResultInvalidNso(), "[ldr] Missing rtld!\n");
/* If we have rtld, we must not have browser core dll. */
R_UNLESS(!has_browser_dll, ldr::ResultInvalidNso());
R_UNLESS_LOG(!has_browser_dll, ldr::ResultInvalidNso(), "[ldr] BrowserCoreDll must not be present!\n");
} else {
/* We must not have both subsdk and browser dll. */
R_UNLESS(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso());
R_UNLESS_LOG(!(ctx.ali.has_subsdk && has_browser_dll), ldr::ResultInvalidNso(), "[ldr] Can't have both subsdk and BrowserCoreDll!\n");
}
R_SUCCEED();
@@ -394,6 +394,8 @@ namespace ams::ldr {
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
AMS_LOG("[ldr] invalid signature!\n");
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
@@ -407,52 +409,56 @@ namespace ams::ldr {
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
Result GetCreateProcessParameterFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags0 = meta->npdm->flags0;
const u8 meta_flags1 = meta->npdm->flags1;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
if (meta_flags0 & Npdm::MetaFlag0_Is64BitInstruction) {
flags |= svc::CreateProcessParameterFlag_64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
/* Set ProcessAddressSpace. */
switch (GetProcessAddressSpace(meta)) {
case Npdm::ProcessAddressSpace_32Bit:
flags |= svc::CreateProcessParameterFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
case Npdm::ProcessAddressSpace_64Bit36:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit36;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
case Npdm::ProcessAddressSpace_32BitNoReserved:
flags |= svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
case Npdm::ProcessAddressSpace_64Bit39:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit39;
break;
case Npdm::ProcessAddressSpace_64Bit42:
flags |= svc::CreateProcessParameterFlag_AddressSpace64Bit42;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
if (ldr_flags & CreateProcessParameterFlag_EnableJitDebug) {
flags |= svc::CreateProcessParameterFlag_EnableJitDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
if (!(ldr_flags & CreateProcessParameterFlag_DisableAslr)) {
flags |= svc::CreateProcessParameterFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
flags |= svc::CreateProcessParameterFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
if (meta_flags0 & Npdm::MetaFlag0_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessParameterFlag_OptimizeMemoryAllocation;
}
}
}
@@ -464,19 +470,19 @@ namespace ams::ldr {
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
flags |= svc::CreateProcessParameterFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
flags |= svc::CreateProcessParameterFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
flags |= svc::CreateProcessParameterFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
@@ -484,18 +490,23 @@ namespace ams::ldr {
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
flags |= svc::CreateProcessParameterFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
/* 11.0.0+/meso Set Disable DAS Merge. */
if (meta_flags0 & Npdm::MetaFlag0_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessParameterFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
/* 18.0.0+/meso Set Enable Address Sanitizer . */
if (meta_flags0 & Npdm::MetaFlag0_EnableAddressSanitizer) {
flags |= svc::CreateProcessParameterFlag_EnableAddressSanitizer;
}
/* 23.0.0+/meso Set Enable Shadow Stack. */
if (meta_flags1 & Npdm::MetaFlag1_EnableShadowStack) {
flags |= svc::CreateProcessParameterFlag_EnableShadowStack;
}
*out = flags;
@@ -513,7 +524,7 @@ namespace ams::ldr {
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
R_TRY(GetCreateProcessParameterFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
@@ -523,7 +534,7 @@ namespace ams::ldr {
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
R_UNLESS((out->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit39), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
@@ -567,6 +578,11 @@ namespace ams::ldr {
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] += static_cast<size_t>(ctx.headers[i].bss_size);
/* Reserve hook arena memory past pcv's bss. */
if (g_is_pcv && i == ctx.main_nso_idx) {
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize) + hoc::pcv::PcvDataArenaSize;
}
const size_t aligned_up_size = util::AlignUp(out->nso_size[i], os::MemoryPageSize) & (AutoLoadModuleSizeMax - 1);
R_UNLESS(out->nso_size[i] <= aligned_up_size, ldr::ResultInvalidNso());
R_UNLESS(aligned_up_size > 0, ldr::ResultInvalidNso());
@@ -591,25 +607,29 @@ namespace ams::ldr {
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
switch (out_param->flags & svc::CreateProcessParameterFlag_AddressSpaceMask) {
case svc::CreateProcessParameterFlag_AddressSpace32Bit:
case svc::CreateProcessParameterFlag_AddressSpace32BitNoReserved:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
case svc::CreateProcessParameterFlag_AddressSpace64Bit36:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
case svc::CreateProcessParameterFlag_AddressSpace64Bit39:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
case svc::CreateProcessParameterFlag_AddressSpace64Bit42:
aslr_start = svc::AddressMap42Start;
aslr_size = svc::AddressMap42Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
if (out_param->flags & svc::CreateProcessParameterFlag_AddressSpace64Bit36) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
@@ -622,7 +642,7 @@ namespace ams::ldr {
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
if (out_param->flags & svc::CreateProcessParameterFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
@@ -644,10 +664,13 @@ namespace ams::ldr {
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zstd, uintptr_t map_base, uintptr_t map_end) {
Result LoadAutoLoadModuleSegment(fs::FileHandle file, size_t file_offset, size_t compressed_size, size_t segment_size, bool is_compressed, bool is_zbic, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
size_t file_size = is_compressed ? compressed_size : segment_size;
AMS_LOG("[ldr] Loading segment @ 0x%016lx: compressed=%d, file_size=0x%08lx, compressed_size=0x%08lx, segment_size=0x%08lx\n",
map_base, is_compressed, file_size, compressed_size, segment_size);
/* Validate size. */
R_UNLESS(file_size <= segment_size, ldr::ResultInvalidNso());
R_UNLESS(file_size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
@@ -657,19 +680,19 @@ namespace ams::ldr {
uintptr_t load_address = is_compressed ? map_end - compressed_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
R_UNLESS_LOG(read_size == file_size, ldr::ResultInvalidNso(), "[ldr] Couldn't read segment from file!\n");
/* Uncompress if necessary. */
R_SUCCEED_IF(!is_compressed);
auto compressed_data_buf = reinterpret_cast<const void *>(load_address);
if (is_zstd) {
bool decompressed = util::DecompressZstdForLoader(reinterpret_cast<void *>(g_zstd_dctx_workspace), ZstdDctxWorkspaceSize, reinterpret_cast<void *>(map_base), static_cast<size_t>(map_end - map_base), segment_size, compressed_data_buf, file_size);
R_UNLESS(decompressed, ldr::ResultInvalidNso());
if (is_zbic) {
bool decompressed = util::DecompressZstdWithBic(reinterpret_cast<void *>(g_zstd_dctx_workspace), sizeof(g_zstd_dctx_workspace), reinterpret_cast<void *>(map_base), static_cast<size_t>(map_end - map_base), segment_size, compressed_data_buf, file_size);
R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with zbic!\n");
} else {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment_size, compressed_data_buf, file_size) == static_cast<int>(segment_size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
R_UNLESS_LOG(decompressed, ldr::ResultInvalidNso(), "[ldr] Failed to decompress segment with lz4!\n");
}
R_SUCCEED();
@@ -684,12 +707,15 @@ namespace ams::ldr {
crypto::GenerateSha256(hash, sizeof(hash),
reinterpret_cast<void *>(map_address + nso_header->segments[segment].dst_offset),
nso_header->segments[segment].size);
R_UNLESS(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso());
R_UNLESS_LOG(std::memcmp(hash, nso_header->segment_hashes[segment], sizeof(hash)) == 0, ldr::ResultInvalidNso(), "[ldr] Invalid segment hash!\n");
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, size_t map_size) {
const bool is_zstd = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
const bool is_zbic = (nso_header->flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t module_size = static_cast<size_t>(nso_header->rw_dst_offset) + util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t arena_size = (nso_size > module_size) ? (nso_size - module_size) : 0;
/* Map and read data from file. */
{
@@ -703,11 +729,11 @@ namespace ams::ldr {
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Text].file_offset, nso_header->text_compressed_size, nso_header->text_size,
(nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zstd, map_address + nso_header->text_dst_offset, map_end));
(nso_header->flags & NsoHeader::Flag_CompressedText) != 0, is_zbic, map_address + nso_header->text_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Ro].file_offset, nso_header->ro_compressed_size, nso_header->ro_size,
(nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zstd, map_address + nso_header->ro_dst_offset, map_end));
(nso_header->flags & NsoHeader::Flag_CompressedRo) != 0, is_zbic, map_address + nso_header->ro_dst_offset, map_end));
R_TRY(LoadAutoLoadModuleSegment(file, nso_header->segments[NsoHeader::Segment_Rw].file_offset, nso_header->rw_compressed_size, nso_header->rw_size,
(nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zstd, map_address + nso_header->rw_dst_offset, map_end));
(nso_header->flags & NsoHeader::Flag_CompressedRw) != 0, is_zbic, map_address + nso_header->rw_dst_offset, map_end));
/* Clear unused space to zero. */
const size_t text_end = static_cast<size_t>(nso_header->text_dst_offset) + static_cast<size_t>(nso_header->text_size);
@@ -730,7 +756,15 @@ namespace ams::ldr {
/* Apply PCV and PTM patches */
if (g_is_pcv) {
hoc::pcv::Patch(map_address, nso_size);
const size_t text_end = static_cast<size_t>(nso_header->text_size);
const size_t rx_end = util::AlignUp(text_end, os::MemoryPageSize);
const size_t ro_start = static_cast<size_t>(nso_header->ro_dst_offset);
const size_t cave_end = (rx_end < ro_start) ? rx_end : ro_start;
const uintptr_t cave = map_address + text_end;
const size_t cave_size = (cave_end > text_end) ? (cave_end - text_end) : 0;
/* module_size is used rather than nso_size to exclude the extra data section. */
hoc::pcv::Patch(map_address, module_size, cave, cave_size, nso_address, arena_size ? (map_address + module_size) : 0);
}
if (g_is_ptm) {
@@ -741,7 +775,7 @@ namespace ams::ldr {
/* Set permissions. */
const size_t text_size = util::AlignUp(nso_header->text_size, os::MemoryPageSize);
const size_t ro_size = util::AlignUp(nso_header->ro_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize);
const size_t rw_size = util::AlignUp(nso_header->rw_size + nso_header->bss_size, os::MemoryPageSize) + arena_size;
if (text_size) {
const bool prevent_code_reads = (nso_header->flags & NsoHeader::Flag_PreventCodeReads);
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
@@ -762,14 +796,15 @@ namespace ams::ldr {
for (int i = 0; i < ctx.nso_count; i++) {
const NsoIndex nso_idx = static_cast<NsoIndex>(ctx.ali.nso_indices[i]);
const bool is_zbic = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t map_size = is_zbic ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
AMS_LOG("[ldr] module[%d]: idx=%d, path='%s', zbic=%d\n", i, (int)nso_idx, GetNsoPath(nso_idx), is_zbic);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(nso_idx), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
const bool is_zstd = (ctx.headers[i].flags & NsoHeader::Flag_UseZbicCompression) != 0;
const size_t map_size = is_zstd ? (total_end - process_info->nso_address[i]) : process_info->nso_size[i];
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, ctx.headers + i,
process_info->nso_address[i], process_info->nso_size[i], map_size));
}
@@ -798,11 +833,6 @@ namespace ams::ldr {
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const AutoLoadModuleContext &ctx, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Append extra .bss for 64LUT */
if (g_is_pcv && ctx.main_nso_idx >= 0) {
g_nso_headers[ctx.main_nso_idx].bss_size += static_cast<u32>(hoc::HocPcvScratchSize);
}
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
@@ -817,7 +847,7 @@ namespace ams::ldr {
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
out->code_address = param.code_address;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
ON_RESULT_FAILURE { R_DISCARD(svc::CloseHandle(process_handle)); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, ctx, argument));
@@ -875,8 +905,8 @@ namespace ams::ldr {
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
const auto as_type = GetProcessAddressSpace(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::ProcessAddressSpace_64Bit39 || as_type == Npdm::ProcessAddressSpace_64Bit36);
/* Register all NSOs. */
for (int i = 0; i < ctx.nso_count; i++) {
@@ -887,7 +917,7 @@ namespace ams::ldr {
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
R_DISCARD(RedirectHtmlDocumentPathForHbl(loc));
}
/* Clear the external code for the program. */
@@ -26,7 +26,7 @@
#define DISABLED 0
#define DEACTIVATED_GPU_FREQ 2000
#define GPU_MIN_MIN_VOLT 480000
#define CPU_MAX_MAX_VOLT 1200000
#define CPU_MAX_MAX_VOLT 1235000
namespace ams::ldr::hoc {
@@ -36,14 +36,14 @@ volatile CustomizeTable C = {
.hpMode = DISABLED,
.commonEmcMemVolt = 1175000, /* LPDDR4(X) JEDEC Specification */
.eristaEmcMaxClock = 1600000, /* Maximum HB-MGCH ram rating */
.eristaEmcMaxClock = 1600000, /* Erista ram rating*/
/* Available: 33MHz step rate, 66MHz step rate, 100MHz step rate, 133MHz step rate and jedec. */
/* Jedec freqs are 1333MHz, 1600MHz, 1866MHz, 2133MHz, 2400MHz, 2666MHz, 2933MHz, 3200MHz. */
.stepMode = StepMode_33MHz,
.stepMode = StepMode_Jedec,
.marikoEmcMaxClock = 2133000, /* Requires the EMC DVFS 63-entry patches (EMC DVFS Count / EMC SoC LUT). */
.marikoEmcVddqVolt = 600000,
.marikoEmcMaxClock = 2133000, /* Should work for every ram*/
.marikoEmcVddqVolt = 640000, /* Optimal for DRAM OC */
.emcDvbShift = 0,
.marikoSocVmax = 0, /* 0 = stock limits (1450 - 1597 is 1050mV, 1598-1708 is 1025mV, 1709+ is 1000mV). */
@@ -100,13 +100,14 @@ volatile CustomizeTable C = {
/* 1120mV is NVIDIA rating */
.marikoCpuMaxVolt = 1120,
/* Supported values: 1963500, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000. */
/* 1963500 is official rating of T214/Mariko, fully safe. */
/* 2091000-2295000 is a slight OC which should work on all units, but no guarantees. */
/* 2397000 is the max safe OC for most average units with tuned undervolt. */
/* 2499000 should be used with caution. */
/* 2601000 exceeds pmic limit on most consoles. */
/* 2703000 is potentially dangerous and not advised. */
/* Supported values: 1963500, 2091000, 2193000, 2295000, 2397000, 2499000, 2601000, 2703000, 2805000. */
/* 1963500 is official rating of T214/Mariko, fully safe. */
/* 2091000-2295000 is a slight OC which should work on all units, but no guarantees. */
/* 2397000 is the max safe OC for most average units with tuned undervolt. */
/* 2499000 should be used with caution. */
/* 2601000 exceeds pmic limit on most consoles. */
/* 2703000 is potentially dangerous and not advised. */
/* 2805000 can only be ran on the best of consoles and may be dangerous due to the voltage requirement */
.marikoCpuMaxClock = 1963500,
.eristaCpuBoostClock = 1785000, /* Default boost clock */
@@ -132,7 +133,7 @@ volatile CustomizeTable C = {
AUTO /* 153 */,
AUTO /* 192 */,
AUTO /* 230 */,
AUTO /* 269 */,
AUTO /* 268 */,
AUTO /* 307 */,
AUTO /* 346 */,
AUTO /* 384 */,
@@ -181,6 +182,7 @@ volatile CustomizeTable C = {
DEACTIVATED_GPU_FREQ /* 1459 (Disabled by default) */,
DEACTIVATED_GPU_FREQ /* 1497 (Disabled by default) */,
DEACTIVATED_GPU_FREQ /* 1536 (Disabled by default) */,
DEACTIVATED_GPU_FREQ /* 1574 (Disabled by default) */,
},
/* Override soc voltage brackets. */
@@ -219,6 +221,16 @@ volatile CustomizeTable C = {
.fineTune_t6_tRTW = 0,
.fineTune_t7_tWTR = 0,
/* Debugging options */
/* PCV Log Verbosity */
/* 0xFF: Nothing */
/* 0: Basic logging */
/* 1: More advanced logging */
/* 2: All logging excluding MMIO */
/* 3: All logging including MMIO */
.pcvLogVerbosity = 2,
/* You shouldn't have to anything past here. */
.eristaCpuDvfsTable = {
{ 204000, { 721094, }, { } },
@@ -318,6 +330,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1580725, -35815, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1702903, -36675, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1770375, -37515, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2805000, { 1838820, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTable1581Tbreak {
@@ -346,6 +359,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1736856, -35286, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1787838, -35967, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1838820, -36648, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2805000, { 1908891, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTable1683Tbreak {
@@ -374,6 +388,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1736856, -35286, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1787838, -35967, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1838820, -36648, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2805000, { 1908891, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.marikoCpuDvfsTableExtreme {
@@ -402,6 +417,7 @@ volatile CustomizeTable C = {
{ 2499000, { 1580725, -35815, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2601000, { 1702903, -36675, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2703000, { 1775375, -37515, 113, }, { CPU_MAX_MAX_VOLT, } },
{ 2805000, { 1838820, -37707, 113, }, { CPU_MAX_MAX_VOLT, } },
},
.eristaGpuDvfsTable = {
@@ -571,13 +587,14 @@ volatile CustomizeTable C = {
{ 1152000, { }, { 1180029, -14534, -830, 0, 1469, 110 } },
{ 1228800, { }, { 1238293, -16383, -859, 0, 3722, 313 } },
{ 1267200, { }, { 1276399, -17475, -867, 0, 3681, 559 } },
{ 1305600, { }, { } },
{ 1344000, { }, { } },
{ 1382400, { }, { } },
{ 1420800, { }, { } },
{ 1459200, { }, { } },
{ 1497600, { }, { } },
{ 1536000, { }, { } },
{ 1305600, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1344000, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1382400, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1420800, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1459200, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1497600, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1536000, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1574400, { }, { 0, 0, 0, 0, 0, 0 } },
},
.marikoGpuDvfsTableHighUV = {
@@ -605,6 +622,7 @@ volatile CustomizeTable C = {
{ 1459200, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1497600, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1536000, { }, { 0, 0, 0, 0, 0, 0 } },
{ 1574400, { }, { 0, 0, 0, 0, 0, 0 } },
},
};
@@ -47,14 +47,14 @@ enum ReadLatency: u32 {
RL_2133 = 40,
RL_1866 = 36,
RL_1600 = 32,
RL_1331 = 28,
RL_1333 = 28,
};
enum WriteLatency: u32 {
WL_2133 = 18,
WL_1866 = 16,
WL_1600 = 14,
WL_1331 = 12,
WL_1333 = 12,
};
using CustomizeCpuDvfsTable = pcv::cvb_entry_t[pcv::DvfsTableEntryLimit];
@@ -126,13 +126,15 @@ struct CustomizeTable {
s32 commonGpuVoltOffset;
u32 eristaGpuVoltArray[27];
u32 marikoGpuVoltArray[24];
u32 marikoGpuVoltArray[25];
s32 marikoSocVoltArray[28];
u32 fineTune_t6_tRTW;
u32 fineTune_t7_tWTR;
u32 reserved[60];
u32 pcvLogVerbosity;
u32 reserved[57];
CustomizeCpuDvfsTable eristaCpuDvfsTable;
CustomizeCpuDvfsTable eristaCpuDvfsTableSLT;
@@ -1,144 +0,0 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv::erista {
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == 32) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
HandleLatency(freq);
CalculateMrw2();
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (C.t6_tRTW * 3) + finetRTW;
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
const double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
}
}
@@ -1,204 +0,0 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../mtc_timing_value.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
void GetRext() {
if (auto r = FindRext()) {
rext = r->rext;
return;
}
/* > 3200 */
rext = 0x1E;
}
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1862'400) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
void CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
mrw2 = static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1331;
WL = WL_1331;
HandleLatency(freq);
GetRext();
/* At 1333WL, for some reason (incorrect ram timing config in mtc table?), tRP causes crashes at high reductions - 2 seems to be the most common limit. */
/* This is a lazy workaround until I find the issue... */
const bool lowFreq = freq < C.timingEmcTbreak;
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetRTW = C.fineTune_t6_tRTW;
s32 finetWTR = C.fineTune_t7_tWTR;
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = CEIL(tREFpb_values[tREFI] / tCK_avg) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
tRC = tRAS + tRPpb;
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
tFAW = static_cast<u32>(tRRD * 4.0);
tRPab = tRPpb + 3;
tR2P = CEIL((RL * 0.426) - 2.0);
tR2W = FLOOR(FLOOR((5.0 / tCK_avg) + ((FLOOR(48.0 / WL) - 0.478) * 3.0)) / 1.501) + RL - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + FLOOR(7.489 / tCK_avg);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
rdv = RL + FLOOR((5.105 / tCK_avg) + 17.017);
qpop = rdv - 14;
quse_width = CEIL(((4.897 / tCK_avg) - FLOOR(2.538 / tCK_avg)) + 3.782);
quse = FLOOR(RL + ((5.082 / tCK_avg) + FLOOR(2.560 / tCK_avg))) - CEIL(4.820 / tCK_avg);
einput_duration = FLOOR(9.936 / tCK_avg) + 5.0 + quse_width;
einput = quse - CEIL(9.928 / tCK_avg);
u32 qrst_duration = FLOOR(8.399 - tCK_avg);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
qrst = PACK_U32(qrst_duration, qrstLow);
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.803 / tCK_avg) + MAX(RL + (2.694 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
tW2R = FLOOR(MAX((5.020 / tCK_avg) + 1.130, WL - MAX(-CEIL(0.258 * (WL - RL)), 1.964)) * 1.964) + WL - CEIL(tWTR / tCK_avg) + finetWTR;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = 0xA + (WL - 14);
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
u32 tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
CalculateMrw2();
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../oc_common.hpp"
#include "timing_tables.hpp"
namespace ams::ldr::hoc::pcv::mariko {
const ReplacePatch g_rext_table[] = {
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
};
const u32 g_rext_table_size = sizeof(g_rext_table) / sizeof(g_rext_table[0]);
const ReplacePatch *FindRext() {
for (u32 i = 0; i < g_rext_table_size; i++) {
if (g_rext_table[i].freq >= C.marikoEmcMaxClock) {
return &g_rext_table[i];
}
}
return nullptr;
}
}
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) 2025 Lightos_
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
@@ -20,7 +20,7 @@
#include "oc_common.hpp"
namespace ams::ldr::hoc {
namespace ams::ldr::hoc::pcv {
#define MAX(A, B) std::max(A, B)
#define MIN(A, B) std::min(A, B)
#define CEIL(A) std::ceil(A)
@@ -30,6 +30,9 @@ namespace ams::ldr::hoc {
#define PACK_U32(high, low) ((static_cast<u32>(high) << 16) | (static_cast<u32>(low) & 0xFFFF))
#define PACK_U32_NIBBLE_HIGH_BYTE_LOW(high, low) ((static_cast<u32>(high & 0xF) << 28) | (static_cast<u32>(low) & 0xFF))
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
#define GET_CYCLE_FLOOR(PARAM) u32(FLOOR(double(PARAM) / tCK_avg))
/* Burst latency, not to be confused with base latency (tWRL). */
const u32 BL = 16;
@@ -38,6 +41,10 @@ namespace ams::ldr::hoc {
/* DQS output access time from CK_t/CK_c. */
const double tDQSCK_max = 3.5;
/* Turn-around time RDQS to WDQS for WDQS control case. */
const double tDQSTA = 0.25;
/* Write preamble. (tCK) */
const u32 tWPRE = 2;
@@ -56,10 +63,24 @@ namespace ams::ldr::hoc {
/* DQ-to-DQS offset(max) (ns) */
const double tDQS2DQ_max = 0.8;
/* Internal READ to PRECHARGE command delay */
const double tRTP = 7.5;
/* Write recovery time. */
const u32 tWR = 18;
namespace pcv::erista {
/* Write-to-Read delay. */
const u32 tWTRStock = 10;
/* Round trace routing delay(?) Guessed based on tK1 (ns) */
const double roundTripDelay = 0.5;
const double tODTon = 1.5;
inline u32 RL;
inline u32 WL;
namespace erista {
const std::array<u32, 8> tRCD_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array<u32, 8> tRP_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array<u32, 10> tRAS_values = { 42, 36, 34, 32, 30, 28, 26, 24, 22, 20 };
@@ -83,13 +104,20 @@ namespace ams::ldr::hoc {
const u32 tFAW = static_cast<u32>(tRRD * 4.0);
const double tRPab = tRPpb + 3;
inline u32 RL;
inline u32 WL;
inline u32 tR2P;
inline u32 tR2W;
inline u32 rext;
inline u32 rdv;
inline u32 qpop;
inline u32 quse_width;
inline u32 quse;
inline u32 einput_duration;
inline u32 einput;
inline u32 qrst;
inline u32 ibdly;
inline u32 qsafe;
inline u32 tW2P;
inline u32 tWTPDEN;
inline u32 tW2R;
@@ -107,9 +135,17 @@ namespace ams::ldr::hoc {
inline double pdex2mrr;
inline u8 mrw2;
namespace {
/* Delay of entering soc? Guessed based on tK1. (ns) */
double chipDelay = 1.655;
}
/* Soc->dram->soc delay(?) */
const double flyByTime = chipDelay + roundTripDelay;
}
namespace pcv::mariko {
namespace mariko {
const std::array<u32, 8> tRCD_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array<u32, 8> tRP_values = { 18, 17, 16, 15, 14, 13, 12, 11 };
const std::array<u32, 10> tRAS_values = { 42, 36, 34, 32, 30, 28, 26, 24, 22, 20 };
@@ -132,9 +168,6 @@ namespace ams::ldr::hoc {
inline u32 refresh_raw;
inline u32 RL;
inline u32 WL;
inline u32 tR2P;
inline u32 tR2W;
inline u32 tRTM;
@@ -167,9 +200,17 @@ namespace ams::ldr::hoc {
inline u32 tCLKSTOP;
inline u32 pdex2mrr;
inline double pdex2mrr;
inline u8 mrw2;
namespace {
/* Delay of entering soc? Guessed based on tK1. (ns) */
const double chipDelay = 1.109;
}
/* Soc->dram->soc delay(?) */
const double flyByTime = chipDelay + roundTripDelay;
}
}
@@ -0,0 +1,102 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "nvsrv.hpp"
namespace ams::ldr::hoc::nvsrv {
namespace {
/* NvServices device names */
constexpr char DbgGpuPath[] = "/dev/nvhost-dbg-gpu";
constexpr char ProfGpuPath[] = "/dev/nvhost-prof-gpu";
/* Permission offset to patch */
constexpr size_t DevTablePermOffset = 0x68;
uintptr_t FindBytes(uintptr_t start, uintptr_t end, const void *needle, size_t len) {
const u8 *pat = reinterpret_cast<const u8 *>(needle);
for (uintptr_t addr = start; addr + len <= end; ++addr) {
if (std::memcmp(reinterpret_cast<const void *>(addr), pat, len) == 0) {
return addr;
}
}
return 0;
}
/* Unlock the devices */
Result UnlockDevicePermission(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address, const char *path, [[maybe_unused]] const char *description) {
const size_t path_len = std::strlen(path) + 1;
const uintptr_t str_addr = FindBytes(mapped_nso, mapped_nso + nso_size, path, path_len);
if (str_addr == 0) {
LOGGING("nvsrv: %s path string not found (nso_size=%lx)", description, static_cast<unsigned long>(nso_size));
R_THROW(ldr::ResultInvalidNvsrvDevTable());
}
const uintptr_t str_offset = str_addr - mapped_nso;
const uintptr_t candidates[] = { str_offset, nso_address + str_offset };
uintptr_t entry_addr = 0;
for (uintptr_t ptr = mapped_nso; entry_addr == 0 && ptr + sizeof(uintptr_t) <= mapped_nso + nso_size; ptr += sizeof(uintptr_t)) {
const uintptr_t val = *reinterpret_cast<const uintptr_t *>(ptr);
for (uintptr_t cand : candidates) {
if (val == cand) {
entry_addr = ptr;
break;
}
}
}
if (entry_addr == 0) {
LOGGING("nvsrv: %s str@+%lx has no referencing table entry", description,
static_cast<unsigned long>(str_offset));
R_THROW(ldr::ResultInvalidNvsrvDevTable());
}
const uintptr_t perm_addr = entry_addr + DevTablePermOffset;
if (perm_addr + sizeof(uintptr_t) > mapped_nso + nso_size) {
LOGGING("nvsrv: %s entry@+%lx perm field out of bounds", description, static_cast<unsigned long>(entry_addr - mapped_nso));
R_THROW(ldr::ResultInvalidNvsrvDevTable());
}
uintptr_t *perm = reinterpret_cast<uintptr_t *>(perm_addr);
LOGGING("nvsrv: %s entry@+%lx perm@+%lx was %lx", description,
static_cast<unsigned long>(entry_addr - mapped_nso),
static_cast<unsigned long>(perm_addr - mapped_nso),
static_cast<unsigned long>(*perm));
PATCH_OFFSET(perm, static_cast<uintptr_t>(0));
R_SUCCEED();
}
}
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address) {
struct {
const char *path;
const char *description;
} targets[] = {
{ DbgGpuPath, "dbg-gpu" },
{ ProfGpuPath, "prof-gpu" },
};
for (auto &t : targets) {
[[maybe_unused]] const Result res = UnlockDevicePermission(mapped_nso, nso_size, nso_address, t.path, t.description);
LOGGING("nvsrv: %s unlock%s", t.description, R_SUCCEEDED(res) ? "ed" : " skipped");
}
}
}
@@ -0,0 +1,23 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
namespace ams::ldr::hoc::nvsrv {
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t nso_address);
}
@@ -20,45 +20,88 @@
#include <stratosphere.hpp>
#include <vapours/results/results_common.hpp>
#define LOGGING(fmt, ...) ((void)0)
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#ifndef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#if HOC_UART_LOG && !(defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_UART_LOG
#define HOC_UART_LOG 0
#endif
#include "customize.hpp"
#include "oc_log.hpp"
#define HOC_IRAM_LOG 0
#if (!HOC_UART_LOG) && (defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING))
#undef HOC_IRAM_LOG
#define HOC_IRAM_LOG 1
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#define LOGGING(...) Log(__VA_ARGS__)
#elif HOC_UART_LOG
#define LOGGING(fmt, ...) AMS_LOG(fmt "\n", ##__VA_ARGS__)
#endif
#else
#define LOGGING(...) ((void)0)
#endif
#define CRASH(msg, ...) { ams::diag::AbortImpl(msg, __PRETTY_FUNCTION__, "", 0); __builtin_unreachable(); }
#define PATCH_OFFSET(offset, value) \
static_assert(sizeof(__typeof__(offset)) <= sizeof(u64)); \
*(offset) = value;
namespace ams::ldr {
R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
R_DEFINE_ERROR_RESULT(OutOfRange, 1000);
R_DEFINE_ERROR_RESULT(InvalidMemPllmEntry, 1001);
R_DEFINE_ERROR_RESULT(InvalidMtcMagic, 1002);
R_DEFINE_ERROR_RESULT(InvalidMtcTable, 1003);
R_DEFINE_ERROR_RESULT(InvalidDvbTable, 1004);
R_DEFINE_ERROR_RESULT(InvalidCpuFreqVddEntry, 1005);
R_DEFINE_ERROR_RESULT(InvalidCpuVoltDfllEntry, 1006);
R_DEFINE_ERROR_RESULT(InvalidCpuDvfs, 1007);
R_DEFINE_ERROR_RESULT(InvalidCpuMinVolt, 1008);
R_DEFINE_ERROR_RESULT(InvalidGpuDvfs, 1009);
R_DEFINE_ERROR_RESULT(InvalidGpuFreqMaxPattern, 1010);
R_DEFINE_ERROR_RESULT(InvalidGpuPllEntry, 1011);
R_DEFINE_ERROR_RESULT(InvalidRegulatorEntry, 1012);
R_DEFINE_ERROR_RESULT(UninitializedPatcher, 1013);
R_DEFINE_ERROR_RESULT(UnsuccessfulPatcher, 1014);
R_DEFINE_ERROR_RESULT(SafetyCheckFailure, 1015);
R_DEFINE_ERROR_RESULT(InvalidMtcTablePattern, 1016);
R_DEFINE_ERROR_RESULT(InvalidSocVoltPattern, 1017);
R_DEFINE_ERROR_RESULT(InvalidSocVoltLimit, 1018);
R_DEFINE_ERROR_RESULT(InvalidEmcDvfsCount, 1019);
R_DEFINE_ERROR_RESULT(InvalidEmcSocLut, 1020);
R_DEFINE_ERROR_RESULT(InvalidEmcRateList, 1021);
R_DEFINE_ERROR_RESULT(InvalidNvLogRedirect, 1022);
R_DEFINE_ERROR_RESULT(InvalidBusFreqReloc, 1023);
R_DEFINE_ERROR_RESULT(HookArenaOutOfMemory, 1024);
R_DEFINE_ERROR_RESULT(HookPayloadTooLarge, 1025);
R_DEFINE_ERROR_RESULT(HookRelocationUnsupported, 1026);
R_DEFINE_ERROR_RESULT(HookSiteInvalid, 1027);
R_DEFINE_ERROR_RESULT(HookPayloadEscapes, 1028);
R_DEFINE_ERROR_RESULT(HookDataOutOfMemory, 1029);
R_DEFINE_ERROR_RESULT(HookUnavailable, 1030);
R_DEFINE_ERROR_RESULT(InvalidNvsrvDevTable, 1031);
R_DEFINE_ERROR_RESULT(InvalidForceVerbosityPattern, 1032);
R_DEFINE_ERROR_RESULT(InvalidCpuLutMaxAsmPattern, 1033);
R_DEFINE_ERROR_RESULT(InvalidCpuLutHook, 1034);
R_DEFINE_ERROR_RESULT(InvalidDvcoRateConfig, 1035);
R_DEFINE_ERROR_RESULT(InvalidVoltCode, 1036);
R_DEFINE_ERROR_RESULT(InvalidVoltLutIndex, 1037);
R_DEFINE_ERROR_RESULT(InvalidHookCave, 1038);
}
namespace ams::ldr::hoc {
/* Extra pcv .bss for 64LUT */
constexpr size_t HocPcvScratchSize = 0x1000;
template<typename Pointer>
struct PatcherEntry {
using patternFn = bool(*)(Pointer *ptr);
@@ -70,6 +113,7 @@ namespace ams::ldr::hoc {
patternFn pattern_search_fn = nullptr;
Pointer value_search;
size_t patched_count = 0;
bool optional = false;
Result Apply(Pointer *ptr) {
Result res = patcher_fn(ptr);
@@ -100,7 +144,7 @@ namespace ams::ldr::hoc {
}
Result CheckResult() {
R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher());
R_UNLESS(optional || patched_count > 0, ldr::ResultUnsuccessfulPatcher());
if (maximum_patched_count) {
R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher());
@@ -21,3 +21,4 @@
#include "oc_common.hpp"
#include "pcv/pcv.hpp"
#include "ptm/ptm.hpp"
#include "nvsrv/nvsrv.hpp"
@@ -19,7 +19,7 @@
#include "oc_common.hpp"
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
#include "fatal_handler_bin.h"
#endif
@@ -70,6 +70,32 @@ namespace ams::ldr::hoc {
return rc;
}
#if HOC_UART_LOG
/* Remove this? */
void UartLog(const char *fmt, ...) {
char line[256];
constexpr size_t PrefixLen = 13; /* "[HOC] " */
std::memcpy(line, "[Horizon OC] ", PrefixLen);
va_list args;
va_start(args, fmt);
int n = vsnprintf(line + PrefixLen, sizeof(line) - PrefixLen - 1, fmt, args);
va_end(args);
if (n < 0) {
return;
}
size_t body = static_cast<size_t>(n);
if (body > sizeof(line) - PrefixLen - 2) { /* vsnprintf returns the untruncated length */
body = sizeof(line) - PrefixLen - 2;
}
size_t len = PrefixLen + body;
line[len++] = '\n';
svc::OutputDebugString(line, len);
}
#endif
struct log_ctx_t {
u32 magic;
u32 sz;
@@ -81,7 +107,7 @@ namespace ams::ldr::hoc {
#define IRAM_LOG_CTX_ADDR 0x4003C000
#define IRAM_LOG_MAX_SZ 4096
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
#if HOC_IRAM_LOG
void Log(const char *data, ...) {
static const u32 max_log_sz = sizeof(working_buf) - sizeof(log_ctx_t);
static bool initDone = false;
@@ -112,8 +138,8 @@ namespace ams::ldr::hoc {
}
#endif
#if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
void ViewLog() {
#if HOC_IRAM_LOG
if (spl::GetSocType() == spl::SocType_Mariko) {
return;
}
@@ -127,6 +153,65 @@ namespace ams::ldr::hoc {
SmcRebootToIramPayload();
while(true) { }
#endif
}
#endif
namespace {
constexpr size_t SdLogBufferSize = 0x1000;
char sdLogBuffer[SdLogBufferSize];
size_t workingLength = 0;
}
void SdLog(const char *data, ...) {
if (workingLength >= SdLogBufferSize - 1) {
return;
}
va_list args;
va_start(args, data);
s32 res = vsnprintf(sdLogBuffer + workingLength, SdLogBufferSize - workingLength, data, args);
va_end(args);
if (res < 0) {
return;
}
workingLength = std::min(workingLength + static_cast<size_t>(res), SdLogBufferSize - 1);
}
void SaveLog() {
static bool saved = false;
if (workingLength == 0 || saved) {
return;
}
if (!cfg::IsSdCardInitialized()) {
return;
}
if (R_FAILED(fs::MountSdCard("sdmc"))) {
return;
}
ON_SCOPE_EXIT { fs::Unmount("sdmc"); };
constexpr const char *Path = "sdmc:/config/horizon-oc/ldr_log.txt";
R_DISCARD(fs::DeleteFile(Path));
if (R_FAILED(fs::CreateFile(Path, workingLength))) {
return;
}
fs::FileHandle file;
if (R_FAILED(fs::OpenFile(std::addressof(file), Path, fs::OpenMode_Write))) {
return;
}
ON_SCOPE_EXIT { fs::CloseFile(file); };
if (R_FAILED(fs::WriteFile(file, 0, sdLogBuffer, workingLength, fs::WriteOption::Flush))) {
return;
}
saved = true;
}
}
@@ -23,6 +23,12 @@ namespace ams::ldr::hoc {
Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size);
void Log(const char *data, ...);
void SdLog(const char *data, ...);
void SaveLog();
void ViewLog();
/* Emit a formatted line over the kernel debug UART via svcOutputDebugString.
No-op unless the running kernel enables debug logging (audit/debug mesosphere). */
void UartLog(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
}
@@ -0,0 +1,185 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../oc_common.hpp"
#include "../../mtc_timing_value.hpp"
namespace ams::ldr::hoc::pcv {
namespace {
void SwitchLatency(volatile u32 &latency, u32 index, u32 latencyStep) {
latency += index * latencyStep;
}
void AutoLatency(volatile u32 &latency, u32 freq, u32 latencyStep) {
if (freq > 1600'000 && freq <= 1866'000) { /* 1866tRWL */
latency += latencyStep * 2;
} else { /* 2133tRWL */
latency += latencyStep * 3;
}
}
static s32 GetMaxLatencyIndex(volatile u32 *latencyArray, u32 latencySize) {
s32 maxIndex = -1;
for (u32 i = 0; i < latencySize; ++i) {
if (latencyArray[i]) {
maxIndex = i;
}
}
return maxIndex;
}
void HandleLatency(u32 freq, volatile u32 &latency, volatile u32 *latencyArray, u32 indexMax, u32 latencyStep) {
for (u32 i = 0; i <= indexMax; ++i) {
if (latencyArray[i] != 0 && freq <= latencyArray[i]) {
SwitchLatency(latency, i, latencyStep);
return;
}
}
SwitchLatency(latency, indexMax, latencyStep);
}
u32 RoundUpToEven(u32 value) {
return value + (value % 2);
}
double GetTckAvg(u32 khz) {
return 1000'000.0 / static_cast<double>(khz);
}
}
void HandleLatency(u32 freq) {
static s32 rlIndexMax = GetMaxLatencyIndex(C.readLatency, std::size(C.readLatency));
static s32 wlIndexMax = GetMaxLatencyIndex(C.writeLatency, std::size(C.writeLatency));
constexpr u32 ReadLatencyStep = 4;
constexpr u32 WriteLatencyStep = 2;
bool autoLatencyRead = false, autoLatencyWrite = false;
if (rlIndexMax == -1) {
AutoLatency(RL, freq, ReadLatencyStep);
autoLatencyRead = true;
}
if (wlIndexMax == -1) {
AutoLatency(WL, freq, WriteLatencyStep);
autoLatencyWrite = true;
}
if (autoLatencyRead && autoLatencyWrite) {
return;
}
if (!autoLatencyRead) {
HandleLatency(freq, RL, C.readLatency, rlIndexMax, ReadLatencyStep);
}
if (!autoLatencyWrite) {
HandleLatency(freq, WL, C.writeLatency, wlIndexMax, WriteLatencyStep);
}
}
u32 GetRlFrequency() {
switch (RL) {
case RL_1333:
return 1333'000;
case RL_1600:
return 1600'000;
case RL_1866:
return 1866'000;
case RL_2133:
return 2133'000;
default:
return 2133'000;
}
}
u32 GetWlFrequency() {
switch (WL) {
case WL_1333:
return 1333'000;
case WL_1600:
return 1600'000;
case WL_1866:
return 1866'000;
case WL_2133:
return 2133'000;
default:
return 2133'000;
}
}
u32 GetOdtLon() {
/* Set A. */
switch (WL) {
case WL_1333:
return 4;
case WL_1600:
case WL_1866:
return 6;
case WL_2133:
return 8;
default:
return 8;
}
}
u32 CalculateNWr() {
const u32 latencyFrequency = GetWlFrequency();
const double tCK = GetTckAvg(latencyFrequency);
const u32 nWr = CEIL(tWR / tCK);
return RoundUpToEven(nWr);
}
u32 CalculateNTRTP() {
const u32 latencyFrequency = GetRlFrequency();
const double tCK = GetTckAvg(latencyFrequency);
return CEIL(tRTP / tCK);
}
u8 CalculateMrw2() {
static const u8 rlMapDBI[8] = {
6, 12, 16, 22, 28, 32, 36, 40
};
static const u8 wlMapSetA[8] = {
4, 6, 8, 10, 12, 14, 16, 18
};
u32 rlIndex = 0;
u32 wlIndex = 0;
for (u32 i = 0; i < std::size(rlMapDBI); ++i) {
if (rlMapDBI[i] == RL) {
rlIndex = i;
break;
}
}
for (u32 i = 0; i < std::size(wlMapSetA); ++i) {
if (wlMapSetA[i] == WL) {
wlIndex = i;
break;
}
}
/* DBI is always enabled. */
return static_cast<u8>(((rlIndex & 0x7) | ((wlIndex & 0x7) << 3) | ((0 & 0x1) << 6)));
}
}
@@ -1,31 +1,34 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::ldr::hoc::pcv::mariko {
struct ReplacePatch {
u32 freq;
u32 rext;
};
extern const ReplacePatch g_rext_table[];
extern const u32 g_rext_table_size;
const ReplacePatch *FindRext();
}
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace ams::ldr::hoc::pcv {
struct RextPatch {
u32 freq;
u32 rext;
};
void HandleLatency(u32 freq);
u32 GetRlFrequency();
u32 GetWlFrequency();
u32 GetOdtLon();
u32 CalculateNWr();
u32 CalculateNTRTP();
u8 CalculateMrw2();
}
@@ -0,0 +1,186 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../mtc_timing_value.hpp"
#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::erista {
namespace {
/* Dumped from bpmp fw mtc table (l4t). */
const RextPatch gRextTable[] = {
{1'633'000, 0x17}, {1'728'000, 0x17}, {1'795'200, 0x19},
{1'862'400, 0x1A}, {1'894'400, 0x1A}, {1'932'800, 0x1A},
{1'958'400, 0x1A}, {1'996'800, 0x1A}, {2'035'200, 0x1A},
{2'064'000, 0x19}, {2'099'200, 0x19}, {2'131'200, 0x1A},
{2'163'200, 0x1A}, {2'188'800, 0x1A}, {2'227'200, 0x1A},
{2'265'600, 0x1B}, {2'291'200, 0x1B}, {2'329'600, 0x1A},
{2'361'600, 0x1A},
};
const RextPatch *LookupRext(u32 freq) {
for (u32 i = 0; i < std::size(gRextTable); i++) {
if (gRextTable[i].freq >= freq) {
return &gRextTable[i];
}
}
return nullptr;
}
void GetRext(u32 freq) {
if (auto r = LookupRext(freq)) {
rext = r->rext;
return;
}
/* Fallback */
rext = 0x1A;
}
void CalculateReadTimings(u32 freq, double tCK_avg) {
tR2P = CalculateNTRTP();
double fullRoundDelay = flyByTime + tDQSCK_max;
/* Read to write turnaround. */
/* Switch seemingly uses odt. */
tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(tODTon) - GetOdtLon() + tWPRE - (C.t6_tRTW * 3) + finetRTW;
/* Read data valid: Read command -> data ready to be latched into the registers. */
rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 18;
/* Read command -> data poppable at the pad macros, 16 cycles ahead of rdv. */
qpop = rdv - 16;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 95.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
/* readEyeEndDerated, nv call this quseExtra. */
u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
/* The amount of deration from the perspective of a full round trip. */
u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
u32 readEyeStart = tQuse + RL + 1;
u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
/* einputMinimal satisfies this. */
quse = einputMinimal;
/* Difference between the two eye edges. */
quse_width = quseExtra - tQuse + 4;
/* input receiver takes time to enable. */
u32 einputLatency = GET_CYCLE_CEIL(10.0);
/* Enable input receiver. */
einput = einputMinimal - einputLatency;
/* Duration of input receiver being enabled? */
einput_duration = einputLatency + quse_width + 4;
/* qrst_duration is CEIL(a / tCK_avg + b) */
/* a and b were derived through a brute forcer. */
u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
/* quse reset? */
qrst = PACK_U32(qrst_duration, qrstLow);
/* Qsafe, safe to reset quse? */
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
/* Input buffer delay? */
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
GetRext(freq);
}
void CalculateWriteTimings(double tCK_avg) {
/* Write to read turnaround. */
/* According to jedec specifications: nWR >= tWTR + nRTP */
/* Therefore tWTR >= nWR - tRTP. */
const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
const u32 nWr = CalculateNWr();
u32 autoPrecharcetWTRMin = nWr - tR2P;
/* tWTR reduction is applied in a weird way to not break existing setups. */
tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
wdv = WL;
wsv = WL - 2;
wev = WL - 4;
/* Output buffer delay? */
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
void CalculatePowerDownTimings(double tCK_avg) {
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
const double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
}
}
u8 GetNWrIndex() {
static const u8 nWrMap[8] = {
6, 10, 16, 20, 24, 30, 34, 40
};
const u32 nWr = CalculateNWr();
for (u8 i = 0; i < std::size(nWrMap); ++i) {
if (nWrMap[i] >= nWr) {
return i;
}
}
return static_cast<u8>(std::size(nWrMap) - 1);
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1333;
WL = WL_1333;
/* I don't understand why, I don't want to know why, but for some fucking reason erista doesn't handle RL 40. */
/* This should not be required but for these fuck damn reasons any type of dram oc will crash instantly with RL 40. */
/* But it works fine in l4t? Even when copying the tables it still doesn't work??? WTF */
C.readLatency[std::size(C.readLatency) - 1] = 0;
HandleLatency(freq);
RL = std::min(static_cast<u32>(RL_1866), RL);
CalculateReadTimings(freq, tCK_avg);
CalculateWriteTimings(tCK_avg);
CalculatePowerDownTimings(tCK_avg);
mrw2 = CalculateMrw2();
}
}
@@ -18,6 +18,7 @@
namespace ams::ldr::hoc::pcv::erista {
u8 GetNWrIndex();
void CalculateTimings(double tCK_avg, u32 freq);
}
@@ -0,0 +1,97 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "pcv_erista_cpu.hpp"
#include "pcv_erista_gpu.hpp"
#include "pcv_erista_mtc.hpp"
#include "calculate_timings_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
u32 *nsoStart;
Result InstallHooks() {
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
auto *data = Hooks().BindData(e_HookPayloadData);
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
R_TRY(MtcInstallHooks(data));
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 1, nullptr, CpuCvbDefaultMaxFreq },
{"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
{"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq },
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{"GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
// {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../../oc_common.hpp"
#include "../pcv_common.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::erista {
struct HookPayloadData {
struct {
EristaMtcTable *mtcTable;
u32 mtcCount;
} mtcTableAsm;
};
DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
extern u32 *nsoStart;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -0,0 +1,108 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
namespace ams::ldr::hoc::pcv::erista {
Result CpuVoltDvfs(u32 *ptr) {
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET(ptr, C.eristaCpuVmin);
}
if (C.eristaCpuUV) {
PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET( ptr, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32* ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
if (!C.eristaCpuUV) {
R_SKIP();
}
switch (C.eristaCpuUV) {
case 1:
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xefff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
break;
default:
break;
}
R_SUCCEED();
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "pcv_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuTune0Low = 0xFFEAD0FF;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
Result CpuVoltDvfs(u32 *ptr);
Result CpuVoltThermals(u32 *ptr);
Result CpuVoltDfll(u32* ptr);
}
@@ -0,0 +1,123 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::erista {
Result GpuVoltDVFS(u32 *ptr) {
if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = _asm::Get(ins1, _asm::field::Rd);
if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.eristaGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
_asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
_asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr) {
// u32 prev_freq = *(ptr - 1);
// if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
// R_THROW(ldr::ResultInvalidGpuPllEntry());
// }
// PATCH_OFFSET(ptr, 3600000);
// R_SUCCEED();
// }
}
@@ -0,0 +1,82 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "pcv_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
Result GpuVoltDVFS(u32 *ptr);
Result GpuVoltThermals(u32 *ptr);
Result GpuFreqMaxAsm(u32 *ptr32);
Result GpuFreqPllMax(u32 *ptr);
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr);
}
@@ -21,163 +21,20 @@
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../erista/calculate_timings_erista.hpp"
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_erista.hpp"
namespace ams::ldr::hoc::pcv::erista {
std::vector<u32> newEmcList;
u32 *nsoStart;
u32 *nsoEnd;
namespace {
std::vector<u32> newEmcList;
Result CpuVoltDvfs(u32 *ptr) {
if (std::memcmp(ptr + 5, cpuVoltDvfsPattern, sizeof(cpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET(ptr, C.eristaCpuVmin);
}
if (C.eristaCpuUV) {
PATCH_OFFSET(ptr - 2, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr + 5, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 6, cpuVoltageThermalPattern, sizeof(cpuVoltageThermalPattern))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.eristaCpuVmin) {
PATCH_OFFSET( ptr, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaCpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaCpuVmin);
}
if (C.eristaCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.eristaCpuMaxVolt);
PATCH_OFFSET(ptr + 7, C.eristaCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32* ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFEAD0FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
if (!C.eristaCpuUV) {
R_SKIP();
}
switch(C.eristaCpuUV) {
case 1:
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_high), 0x27007ff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xefff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xdfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27a07ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xcfdf);
PATCH_OFFSET(&(entry->tune1_high), 0x37007ff);
break;
default:
break;
}
R_SUCCEED();
}
Result GpuVoltDVFS(u32 *ptr) {
if (std::memcmp(ptr, gpuVoltDvfsPattern, sizeof(gpuVoltDvfsPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
if (C.eristaGpuVmin) {
PATCH_OFFSET(ptr, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 3, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 6, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 9, C.eristaGpuVmin);
PATCH_OFFSET(ptr + 12, C.eristaGpuVmin);
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.eristaGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHiOPT)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
struct {
u32 *getEristaMtcTableFnSite = nullptr;
EristaMtcTable *mtcTable = nullptr;
bool foundMtcTablePattern = false;
} getMtcTableCache;
}
/* Note: This does not have proper timings, so base latency adjustment will not work. */
@@ -189,10 +46,9 @@ namespace ams::ldr::hoc::pcv::erista {
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
TABLE->burst_regs.PARAM = VALUE; \
TABLE->shadow_regs_ca_train.PARAM = VALUE; \
TABLE->shadow_regs_quse_train.PARAM = VALUE; \
TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
/* Ram power down */
/* B31: DRAM_CLKSTOP_PD */
/* B30: DRAM_CLKSTOP_SR */
@@ -209,14 +65,6 @@ namespace ams::ldr::hoc::pcv::erista {
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
if (table->rate_khz > 3200000) {
rext = 30;
} else if (table->rate_khz >= 2133001) {
rext = 28;
} else {
rext = 26;
}
u32 trefbw = refresh_raw + 0x40;
trefbw = MIN(trefbw, static_cast<u32>(0x3FFF));
@@ -255,37 +103,37 @@ namespace ams::ldr::hoc::pcv::erista {
WRITE_PARAM_ALL_REG(table, emc_pchg2pden, GET_CYCLE_CEIL(1.763));
WRITE_PARAM_ALL_REG(table, emc_ar2pden, GET_CYCLE_CEIL(1.75));
WRITE_PARAM_ALL_REG(table, emc_pdex2cke, GET_CYCLE_CEIL(1.05));
WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(14.0));
WRITE_PARAM_ALL_REG(table, emc_act2pden, GET_CYCLE_CEIL(1.75));
WRITE_PARAM_ALL_REG(table, emc_cke2pden, GET_CYCLE_CEIL(8.499));
WRITE_PARAM_ALL_REG(table, emc_pdex2mrr, GET_CYCLE_CEIL(pdex2mrr));
WRITE_PARAM_ALL_REG(table, emc_rw2pden, tWTPDEN);
/* Accept imperfection or prepare for suffering. */
// #if defined(AMS_BUILD_FOR_AUDITING) || defined(AMS_BUILD_FOR_DEBUGGING)
// WRITE_PARAM_ALL_REG(table, emc_einput, einput);
// WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
WRITE_PARAM_ALL_REG(table, emc_einput, einput);
WRITE_PARAM_ALL_REG(table, emc_einput_duration, einput_duration);
WRITE_PARAM_ALL_REG(table, emc_obdly, obdly);
// WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
// WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
// WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
// WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_ibdly, ibdly);
WRITE_PARAM_ALL_REG(table, emc_wdv_mask, wdv);
WRITE_PARAM_ALL_REG(table, emc_quse_width, quse_width);
WRITE_PARAM_ALL_REG(table, emc_quse, quse);
WRITE_PARAM_ALL_REG(table, emc_wdv, wdv);
WRITE_PARAM_ALL_REG(table, emc_wsv, wsv);
WRITE_PARAM_ALL_REG(table, emc_wev, wev);
// WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
// WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
// WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
// WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
// WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
// WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
// WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
WRITE_PARAM_ALL_REG(table, emc_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_tr_qrst, qrst);
WRITE_PARAM_ALL_REG(table, emc_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qsafe, qsafe);
WRITE_PARAM_ALL_REG(table, emc_tr_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_qpop, qpop);
WRITE_PARAM_ALL_REG(table, emc_rdv, rdv);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early, rdv - 2);
WRITE_PARAM_ALL_REG(table, emc_rdv_early_mask, rdv);
WRITE_PARAM_ALL_REG(table, emc_rdv_mask, rdv + 2);
WRITE_PARAM_ALL_REG(table, emc_tr_rdv, rdv);
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
// table->dram_timings.rl = RL;
table->dram_timings.rl = RL;
// #endif
constexpr double MC_ARB_DIV = 4.0;
@@ -376,8 +224,7 @@ namespace ams::ldr::hoc::pcv::erista {
const u32 tRFCabStock = tRFC_values[0] * 2;
table->dram_timings.t_rfc = tRFCabStock;
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
table->emc_mrw = (table->emc_mrw & ~0x70u) | (GetNWrIndex() << 4);
table->emc_cfg_2 = 0x11083D;
table->min_volt = std::clamp(900 + (C.emcDvbShift * 25), 900, 1050);
}
@@ -447,39 +294,6 @@ namespace ams::ldr::hoc::pcv::erista {
}
}
/* Relocate the table */
/* Rescanning is simpler than trying to extract a bunch of data from the asm patch, performance impact is negligable */
/* Also, this is more stable :P */
u32 RepointEristaEmcTablePtr(uintptr_t fromSlot, uintptr_t toTable) {
constexpr u32 RetIns = 0xD65F03C0; /* ret */
u32 patched = 0;
for (u32 *p = nsoStart; p + 4 < nsoEnd; ++p) {
const u32 ins = *p;
if (!AsmIsAdrX0(ins)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (AsmAdrTarget(ins, pc) != fromSlot) {
continue;
}
if (!(AsmIsLdpX(p[1]) && AsmIsLdpX(p[2]) && p[3] == RetIns)) {
continue;
}
/* adr only reaches +-1MB */
const s64 delta = static_cast<s64>(toTable) - static_cast<s64>(pc);
if (delta > 0xFFFFF || delta < -0x100000) {
continue;
}
PATCH_OFFSET(p, AsmSetAdrTarget(ins, pc, toTable));
++patched;
}
return patched;
}
/* The silicon instructs; the children obey... */
void MtcGenerateFreqTables() {
newEmcList.clear();
@@ -548,24 +362,20 @@ namespace ams::ldr::hoc::pcv::erista {
constexpr u32 StartAdjustment = offsetof(EristaMtcTable, rate_khz) + sizeof(EristaMtcTable) * (erista::MtcTableCountDefault - 1);
u8 *startPtr = reinterpret_cast<u8 *>(ptr) - StartAdjustment;
const uintptr_t usedSlot = reinterpret_cast<uintptr_t>(startPtr) + mtcOffset;
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(usedSlot);
EristaMtcTable *table = reinterpret_cast<EristaMtcTable *>(startPtr + mtcOffset);
R_TRY(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault));
PrepareMtcMemoryRegion(startPtr, table);
table = reinterpret_cast<EristaMtcTable *>(startPtr);
/* We must do this as the NLE tables don't have enough space past them for our extended ones */
if (usedSlot != reinterpret_cast<uintptr_t>(startPtr)) {
if (RepointEristaEmcTablePtr(usedSlot, reinterpret_cast<uintptr_t>(startPtr)) == 0) {
AbortInvalidMtc("Failed to repoint emc table");
}
}
if (R_FAILED(MtcValidateAllTables(table, EmcListDefault, EmcListSizeDefault))) {
AbortInvalidMtc("Failed mtc validation");
}
/* Cache the table for hooks. */
getMtcTableCache.mtcTable = table;
if (C.eristaEmcMaxClock <= EmcClkOSLimit) {
R_SKIP();
}
@@ -593,125 +403,68 @@ namespace ams::ldr::hoc::pcv::erista {
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count) {
const HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, e_HookPayloadData);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
*count = data->mtcTableAsm.mtcCount;
return data->mtcTableAsm.mtcTable;
}
// patch out 1305MHz limit on erista, don't use this!
// Result GpuFreqPllLimit(u32 *ptr) {
// u32 prev_freq = *(ptr - 1);
Result MtcInstallHooks(HookPayloadData *data) {
R_UNLESS(getMtcTableCache.getEristaMtcTableFnSite != nullptr && getMtcTableCache.mtcTable != nullptr, ldr::ResultInvalidMtcTablePattern());
// if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
// R_THROW(ldr::ResultInvalidGpuPllEntry());
// }
/* Copy the data to the payload. */
data->mtcTableAsm.mtcTable = reinterpret_cast<EristaMtcTable *>(Hooks().ToVa(getMtcTableCache.mtcTable));
data->mtcTableAsm.mtcCount = newEmcList.size();
// PATCH_OFFSET(ptr, 3600000);
// R_SUCCEED();
// }
R_TRY(INSTALL_IMPL_HOOK(getMtcTableCache.getEristaMtcTableFnSite, GetEristaMtcTableImpl));
R_SUCCEED();
}
Result MemMtcTableAsm(u32 *ptr) {
/* Return if the pattern was already found. */
/* This pattern happens multiple times in this function., we only need to find it once. */
R_UNLESS(!getMtcTableCache.foundMtcTablePattern, ldr::ResultInvalidMtcTablePattern());
/* This is a mess but the compiler made this painful to patch so we must do it this way */
constexpr s32 GoodAdrpOffset = -1;
constexpr s32 GoodMovOffset = -7;
constexpr s32 GoodBlOffset = 1;
constexpr u32 MtcGoodBlOpcode = 0x97fe6cfc;
constexpr u32 MtcBadBlOpcode0 = 0x97ffae64; // bl nn::pcv::GetHardwareType
constexpr u32 MtcBadBlOpcode1 = 0x940036d5; // bl strcmp
constexpr u32 MtcBadAdrpAsm = 0xd00000a1; // adrp x1, s_ModuleResetStatus_
constexpr s32 MtcBadBlOffset0 = 2;
constexpr s32 MtcBadBlOffset1 = -1;
constexpr s32 MtcBadAdrpOffset = 1;
constexpr u32 AddrpOffset = 1;
constexpr u32 MovOffset = 7;
constexpr u32 BlOffset = 5;
constexpr u32 MovOffsetOld = 8;
/* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr + GoodMovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
R_UNLESS(ptr - MovOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
/* Check for GetHardwareType asm and skip if it is found */
/* The pattern will match on the first time, but the location is bad, so it must be skipped */
if(AsmCompareAdrpNoImm(*(ptr + MtcBadAdrpOffset), MtcBadAdrpAsm) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset0), MtcBadBlOpcode0) && AsmBlCompareOpcodeOnly(*(ptr + MtcBadBlOffset1), MtcBadBlOpcode1)) {
R_SKIP();
}
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
u32 adrp = *(ptr + GoodAdrpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
/* Check for the branch instruction above the cbz to ensure we are patching the right location*/
u32 bl = *(ptr + GoodBlOffset);
R_UNLESS(AsmBlCompareOpcodeOnly(bl, MtcGoodBlOpcode), ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddrpOffset);
R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
/* Check for the branch instruction above the cbz to ensure we are patching the right location. */
/* Only the opcode is checked; the call target varies by firmware (0x97ffae64 on one build). */
u32 bl = *(ptr - BlOffset);
R_UNLESS(_asm::IsOp(bl, _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidMtcTablePattern());
/* Check for the mov that actually sets the mtc table count. */
u32 mov = *(ptr + GoodMovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
u32 mov = *(ptr - MovOffset);
bool foundMov = false;
foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
/* Patch out the count of the mov to our custom mtc table amount*/
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), asm_get_rd(mov));
if (!foundMov) {
mov = *(ptr + MovOffsetOld);
/* Check old firmware offset. */
foundMov = _asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd);
}
PATCH_OFFSET(ptr + GoodMovOffset, movCountPatch);
R_UNLESS(foundMov, ldr::ResultInvalidMtcTablePattern());
constexpr u32 PrologueWindow = 140;
u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueWindow, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidMtcTablePattern());
getMtcTableCache.getEristaMtcTableFnSite = functionPrologue;
getMtcTableCache.foundMtcTablePattern = true;
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
nsoEnd = reinterpret_cast<u32 *>(mapped_nso + nso_size);
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{"CPU Freq Table", CpuFreqCvbTable<false>, 1, nullptr, CpuCvbDefaultMaxFreq },
{"CPU Volt DVFS", &CpuVoltDvfs, 1, nullptr, CpuVminOfficial },
{"CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{"GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{"GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq },
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{"GPU PLL Max", & GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
// {"GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{"MEM Table Asm", &MemMtcTableAsm, 4, &MemMtcGetGetTablePatternFn },
{"MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(EristaMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu\n", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
// ViewLog();
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
}
}
@@ -0,0 +1,91 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "pcv_erista.hpp"
#include "../../mtc_timing_table.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
constexpr u32 MtcTableCountDefault = 10;
constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 3;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId {
ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
};
enum MtcTableIndex {
T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
MtcTableIndex_Invalid = 3,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
/* TODO: Test 6gb and 8gb. */
const inline MtcDramIndex mtcIndexTable[] = {
{ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
};
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
}
Result MemFreqMtcTable(u32 *ptr);
void MtcGenerateFreqTables();
Result MemFreqMax(u32 *ptr);
HOOK_PAYLOAD_FN EristaMtcTable *GetEristaMtcTableImpl(u32 *count);
Result MemMtcTableAsm(u32 *ptr);
Result MtcInstallHooks(HookPayloadData *data);
}
@@ -0,0 +1,220 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../mtc_timing_value.hpp"
#include "../common/calculate_timings_common.hpp"
namespace ams::ldr::hoc::pcv::mariko {
namespace {
const RextPatch gRextTable[] = {
{1'633'000, 0x17}, {1'666'000, 0x18}, {1'700'000, 0x18},
{1'733'000, 0x18}, {1'766'000, 0x18}, {1'800'000, 0x1A},
{1'833'000, 0x1A}, {1'866'000, 0x1A}, {1'900'000, 0x19},
{1'933'000, 0x19}, {1'966'000, 0x19}, {2'100'000, 0x1A},
{2'133'000, 0x1A}, {2'166'000, 0x19}, {2'200'000, 0x19},
{2'233'000, 0x19}, {2'266'000, 0x1A}, {2'300'000, 0x1B},
{2'333'000, 0x1B}, {2'366'000, 0x1B}, {2'400'000, 0x1B},
{2'433'000, 0x1B}, {2'466'000, 0x1B}, {2'500'000, 0x1A},
{2'533'000, 0x1C}, {2'566'000, 0x1B}, {2'600'000, 0x1B},
{2'633'000, 0x1B}, {2'666'000, 0x1B}, {2'700'000, 0x1C},
{2'733'000, 0x1C}, {2'766'000, 0x1D}, {2'800'000, 0x1D},
{2'833'000, 0x1D}, {2'866'000, 0x1D}, {2'900'000, 0x1D},
{2'933'000, 0x1C}, {2'966'000, 0x1D}, {3'000'000, 0x1D},
{3'033'000, 0x1D}, {3'066'000, 0x1D}, {3'100'000, 0x1D},
{3'133'000, 0x1D}, {3'166'000, 0x1C}, {3'200'000, 0x1C},
{3'233'000, 0x1E}, {3'266'000, 0x1F}, {3'300'000, 0x1E},
{3'333'000, 0x1F}, {3'366'000, 0x1F}, {3'400'000, 0x1F},
{3'433'000, 0x1F}, {3'466'000, 0x1F}, {3'500'000, 0x1E},
{3'533'000, 0x1E}, {3'566'000, 0x1E}, {3'600'000, 0x1E},
{3'633'000, 0x1E}, {3'666'000, 0x1E}, {3'700'000, 0x1F},
};
const RextPatch *LookupRext(u32 freq) {
for (u32 i = 0; i < std::size(gRextTable); i++) {
if (gRextTable[i].freq >= freq) {
return &gRextTable[i];
}
}
return nullptr;
}
void GetRext(u32 freq) {
if (auto r = LookupRext(freq)) {
rext = r->rext;
return;
}
/* Fallback */
rext = 0x1E;
}
void CalculateActivatePrechargeTimings(bool lowFreq) {
tRCD = tRCD_values[lowFreq ? C.low_t1_tRCD : C.t1_tRCD];
tRPpb = tRP_values[lowFreq ? C.low_t2_tRP : C.t2_tRP];
tRAS = tRAS_values[lowFreq ? C.low_t3_tRAS : C.t3_tRAS];
tRC = tRAS + tRPpb;
tRPab = tRPpb + 3;
}
void CalculateActivateWindowTimings(bool lowFreq) {
tRRD = tRRD_values[lowFreq ? C.low_t4_tRRD : C.t4_tRRD];
tFAW = static_cast<u32>(tRRD * 4.0);
}
void CalculateReadTimings(u32 freq, double tCK_avg, bool lowFreq) {
u32 tRTW = lowFreq ? C.low_t6_tRTW : C.t6_tRTW;
s32 finetRTW = C.fineTune_t6_tRTW;
double fullRoundDelay = flyByTime + tDQSCK_max;
/* Read to precharge delay. */
tR2P = CalculateNTRTP();
/* Read to write turnaround. */
/* Switch seemingly uses odt. */
tR2W = RL + (BL / 2) + CEIL((fullRoundDelay / tCK_avg) + tDQSTA) - GET_CYCLE_FLOOR(tODTon) - GetOdtLon() - (tRTW * 3) + finetRTW;
tRTM = FLOOR((10.0 + RL) + (3.502 / tCK_avg)) + GET_CYCLE_FLOOR(7.489);
tRATM = CEIL((tRTM - 10.0) + (RL * 0.426));
/* Read data valid: Read command -> data ready to be latched into the registers. */
rdv = RL + GET_CYCLE_CEIL(fullRoundDelay) + 16;
/* Read command -> data poppable at the pad macros, 14 cycles ahead of rdv. */
qpop = rdv - 14;
constexpr double TrimmerStepPs = 3.0;
constexpr double MaxTrimmerSteps = 96.0;
double tCkAvgPs = tCK_avg * 1000.0;
double tckDerated = tCkAvgPs * 0.85;
u32 derateStepCount = MIN(FLOOR(tckDerated / TrimmerStepPs), MaxTrimmerSteps);
double deratedNsDelay = (derateStepCount * TrimmerStepPs) / 1000.0;
/* readEyeEndDerated, nv call this quseExtra. */
u32 quseExtra = GET_CYCLE_CEIL(fullRoundDelay - deratedNsDelay);
/* The amount of deration from the perspective of a full round trip. */
u32 readEyeEndDerateMargin = GET_CYCLE_CEIL(fullRoundDelay) - quseExtra;
u32 tQuse = FLOOR((flyByTime + 0.94) / tCK_avg);
u32 readEyeStart = tQuse + RL + 1;
u32 einputMinimal = readEyeStart + readEyeEndDerateMargin - 2;
/* Data valid window, quse aligns with the start of the read eye (probably). */
/* DQS needs to be logically and'ed with quse in order to be valid. */
/* einputMinimal satisfies this. */
quse = einputMinimal;
/* input receiver takes time to enable. */
u32 einputLatency = GET_CYCLE_CEIL(10.0);
/* Enable input receiver. */
einput = einputMinimal - einputLatency;
/* Difference between the two eye edges. */
quse_width = quseExtra - tQuse + 4;
/* Duration of input receiver being enabled? */
einput_duration = einputLatency + quse_width + 4;
/* qrst_duration is CEIL(a / tCK_avg + b) */
/* a and b were derived through a brute forcer. */
u32 qrst_duration = CEIL(1.4 / tCK_avg + 3.5);
u32 qrstLow = MAX(static_cast<s32>(einput - qrst_duration - 2), static_cast<s32>(0));
/* quse reset? */
qrst = PACK_U32(qrst_duration, qrstLow);
/* Input buffer delay? */
ibdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(1, quse - qrst_duration - 2.0);
/* Qsafe, safe to reset quse? */
qsafe = (einput_duration + 3) + MAX(MIN(qrstLow * rdv, qrst_duration + qrst_duration), einput);
GetRext(freq);
}
void CalculateWriteTimings(double tCK_avg, bool lowFreq) {
u32 tWTR = 10 - tWTR_values[lowFreq ? C.low_t7_tWTR : C.t7_tWTR];
s32 finetWTR = C.fineTune_t7_tWTR;
/* Write to read turnaround. */
/* According to jedec specifications: nWR >= tWTR + nRTP */
/* Therefore tWTR >= nWR - tRTP. */
const u32 nTWTR = GET_CYCLE_CEIL(tWTRStock);
const u32 nWr = CalculateNWr();
u32 autoPrecharcetWTRMin = nWr - tR2P;
/* tWTR reduction is applied in a weird way to not break existing setups. */
tW2R = WL + 1 + MAX(autoPrecharcetWTRMin, nTWTR) - GET_CYCLE_CEIL(tWTR) + finetWTR;
tW2P = (CEIL(WL * 1.7303) * 2) - 5;
tWTM = CEIL(WL + ((7.570 / tCK_avg) + 8.753));
tWATM = (tWTM + (FLOOR(WL / 0.816) * 2.0)) - 4.0;
wdv = WL;
wsv = WL - 2;
wev = WL - 4;
/* Output buffer delay? */
u32 obdlyHigh = 3 / FLOOR(MIN(static_cast<double>(2), tCK_avg * (WL - 7)));
u32 obdlyLow = MAX(WL - FLOOR((126.0 / CEIL(tCK_avg + 8.601))), 0.0);
obdly = PACK_U32_NIBBLE_HIGH_BYTE_LOW(obdlyHigh, obdlyLow);
}
void CalculateRefreshTimings(double tCK_avg, bool lowFreq) {
tRFCpb = tRFC_values[lowFreq ? C.low_t5_tRFC : C.t5_tRFC];
tRFCab = tRFCpb * 2;
tXSR = static_cast<double>(tRFCab + 7.5);
u32 tREFI = lowFreq ? C.low_t8_tREFI : C.t8_tREFI;
refresh_raw = 0xFFFF;
if (tREFI != 6) {
refresh_raw = GET_CYCLE_CEIL(tREFpb_values[tREFI]) - 0x40;
refresh_raw = MIN(refresh_raw, static_cast<u32>(0xFFFF));
}
}
void CalculatePowerDownTimings(double tCK_avg) {
tWTPDEN = CEIL(((1.8 / tCK_avg) + MAX(RL + (2.7 / tCK_avg), static_cast<double>(tW2P))) + (BL / 2));
pdex2rw = CEIL((CEIL(12.335 - tCK_avg) + (7.430 / tCK_avg) - CEIL(tCK_avg * 11.361)));
tCLKSTOP = FLOOR(MIN(8.488 / tCK_avg, 23.0)) + 8.0;
double tMMRI = tRCD + (tCK_avg * 3);
pdex2mrr = tMMRI + 10;
}
}
void CalculateTimings(double tCK_avg, u32 freq) {
RL = RL_1333;
WL = WL_1333;
HandleLatency(freq);
const bool lowFreq = freq < C.timingEmcTbreak;
CalculateActivatePrechargeTimings(lowFreq);
CalculateActivateWindowTimings(lowFreq);
CalculateReadTimings(freq, tCK_avg, lowFreq);
CalculateWriteTimings(tCK_avg, lowFreq);
CalculateRefreshTimings(tCK_avg, lowFreq);
CalculatePowerDownTimings(tCK_avg);
mrw2 = CalculateMrw2();
}
}
@@ -1,24 +1,23 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace ams::ldr::hoc::pcv::mariko {
void CalculateTimings(double tCK_avg, u32 freq);
}
@@ -0,0 +1,134 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "pcv_mariko.hpp"
#include "pcv_mariko_cpu.hpp"
#include "pcv_mariko_gpu.hpp"
#include "pcv_mariko_mtc.hpp"
#include "pcv_mariko_soc.hpp"
#include "pcv_mariko_log.hpp"
#include "pcv_mariko_cave.hpp"
#include "calculate_timings_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
u32 *nsoStart;
size_t g_nso_size = 0;
DEFINE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
Result InstallHooks() {
R_TRY(CaveInstall());
R_TRY(Hooks().CheckEnabled());
R_TRY(Hooks().CopyPayload());
auto *data = Hooks().BindData(m_HookPayloadData);
R_UNLESS(data != nullptr, ldr::ResultHookDataOutOfMemory());
R_TRY(SharedClkBusInstallHooks(data));
R_TRY(LutWriterInstallHooks(data));
#if HOC_UART_LOG
R_TRY(ForceVebosityInstallHooks(data));
#endif
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
g_nso_size = nso_size;
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq },
{ "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{ "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{ "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{ "GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq },
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
{ "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
{ "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
{ "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
{ "Bus Freq Reloc", &BusFreqReloc, 1, &BusFreqRelocPatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
{ "CPU Lut Max Asm", &CpuLutMaxAsm, 1, &CpuLutAsmPatternFn },
{ "CPU Lut Max Asm2", &CpuLutMaxAsm2, 1, &CpuLutAsmPatternFn2 },
{ "CPU Lut Writer", &LutWriterFind, 1, &LutWriterPatternFn },
{ "CPU Dvco Rate Cfg", &CpuLutDvcoRateCfg, 1, &CpuLutDvcoRateCfgPatternFn },
{ "Hook Cave", &DeadDestructorCaveFind, 0, &CaveFlagRunPatternFn, 0, 0, true },
/* Debugging patches */
#if HOC_UART_LOG
{ "NvLog Redirect", &NvLogUartRedirect, 1, &NvLogVsnprintfPatternFn, 0, 0, true },
{ "Force Verbosity", &ForceVerbosity, 3, &ForceVerbosityPatternFn, 0, 0, true },
#endif
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
if (R_FAILED(InstallHooks())) {
panic::SmcError(panic::Patch);
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../../oc_common.hpp"
#include "../pcv_common.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
constexpr u32 BusFreqTableCount = 2;
struct HookPayloadData {
struct {
u32 *next; /* Save the bytes. */
uintptr_t originalFnCallback;
u32 table[BusFreqTableCount][EmcDvfsTableEntryCount]; /* Original bus table size: 32. */
} busData;
struct {
uintptr_t orig; /* ClDvfsLut64ExpandImpl to CLDVFS LUT writer trampoline. */
u32 table[ClDvfsLutSize];
} lut64;
#if HOC_UART_LOG
u32 verbosityLevel;
#endif
};
DECLARE_HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "pcv_mariko_cave.hpp"
namespace ams::ldr::hoc::pcv::mariko {
namespace {
struct {
u32 *site = nullptr;
u32 *end = nullptr;
} caveCache;
bool HasDirectCaller(const u32 *target, const u32 *begin, const u32 *end) {
for (const u32 *ptr = begin; ptr < end; ++ptr) {
const u32 top6 = *ptr & 0xFC000000u;
if (top6 != _asm::op::B && top6 != _asm::op::Bl) {
continue;
}
if (_asm::Target26(*ptr, reinterpret_cast<uintptr_t>(ptr)) == reinterpret_cast<uintptr_t>(target)) {
return true;
}
}
return false;
}
}
Result DeadDestructorCaveFind(u32 *ptr) {
u32 *textEnd = reinterpret_cast<u32 *>(g_pcv_cave);
u32 *site = _asm::FindFnPrologue(ptr, CavePrologueMargin, nsoStart);
R_UNLESS(site != nullptr, ldr::ResultInvalidHookCave());
u32 *end = _asm::FindFnEnd(site, textEnd);
R_UNLESS(end != nullptr, ldr::ResultInvalidHookCave());
/* Ensure this function is never called. */
R_UNLESS(!HasDirectCaller(site, nsoStart, textEnd), ldr::ResultInvalidHookCave());
caveCache.site = site;
caveCache.end = end;
R_SUCCEED();
}
Result CaveInstall() {
if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
R_SUCCEED();
}
R_UNLESS(caveCache.site != nullptr, ldr::ResultInvalidHookCave());
const uintptr_t start = util::AlignUp(reinterpret_cast<uintptr_t>(caveCache.site + 1), sizeof(u64));
const uintptr_t end = reinterpret_cast<uintptr_t>(caveCache.end);
R_UNLESS(start < end && end - start >= HookContext::RequiredCaveSize(), ldr::ResultHookPayloadTooLarge());
/* Insert a break in case this function gets called for whatever reason. */
*caveCache.site = _asm::BrkIns;
Hooks().SetCave(start, end - start);
R_SUCCEED();
}
}
@@ -0,0 +1,77 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
constexpr u32 CaveFlagChunkCount = 4;
constexpr u32 CaveFlagChunkInsCount = 4;
constexpr u32 CaveFlagLdrbDelta = 0x30;
constexpr u32 CavePrologueMargin = 8;
/* This function consists of multiple chunks. */
/* Each chunk does a load, a comparison and a conditional branch. The next chunk's address setup (adrp) follows right after it. */
inline bool IsFlagCheckChunk(const u32 *ptr) {
return _asm::IsOp(ptr[0], _asm::op::LdrbImm, _asm::field::Rt, _asm::field::Rn, _asm::field::Off1)
&& _asm::Ignoring(ptr[1], _asm::CmpImm32(0, 1), _asm::field::Rn)
&& _asm::Ignoring(ptr[2], _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Eq}), _asm::field::Imm19);
}
inline bool IsChunkDeltaValid(const u32 *prev, const u32 *next) {
return IsFlagCheckChunk(next) && _asm::Get(*next, _asm::field::Off1) == _asm::Get(*prev, _asm::field::Off1) + CaveFlagLdrbDelta;
}
inline bool ValidateChunkConsistency(const u32 *first) {
for (u32 i = 1; i < CaveFlagChunkCount; ++i) {
if (!IsChunkDeltaValid(first + (i - 1) * CaveFlagChunkInsCount, first + i * CaveFlagChunkInsCount)) {
return false;
}
}
return true;
}
inline bool CaveFlagRunPatternFn(u32 *ptr) {
if (Hooks().CaveFree() >= HookContext::RequiredCaveSize()) {
return false;
}
/* In case we don't have enough cave memory, we need to commit this warcrime. */
/* Ensure we don't dereference invalid memory. */
u32 *textEnd = reinterpret_cast<u32 *>(g_pcv_cave);
if (ptr - CaveFlagChunkInsCount < nsoStart || ptr + CaveFlagChunkCount * CaveFlagChunkInsCount > textEnd) {
return false;
}
/* Validate the current chunk and the previous chunk and ensure the delta of the ldrb instructions is consistent. */
if (!IsFlagCheckChunk(ptr) || IsChunkDeltaValid(ptr - CaveFlagChunkInsCount, ptr)) {
return false;
}
return ValidateChunkConsistency(ptr);
}
Result DeadDestructorCaveFind(u32 *ptr);
Result CaveInstall();
}
@@ -0,0 +1,617 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "pcv_mariko_cpu.hpp"
namespace ams::ldr::hoc::pcv::mariko {
namespace {
u32 CapCpuClock() {
u32 cpuCap = allowedCpuMaxFrequencies[0];
for (u32 freq : allowedCpuMaxFrequencies) {
if (C.marikoCpuMaxClock >= freq) {
cpuCap = freq;
} else {
break;
}
}
return cpuCap;
}
/* TODO: Reduce code duplication. */
cvb_entry_t *GetDvfsTable() {
switch (C.tableConf) {
case TBREAK_1683: return const_cast<cvb_entry_t *>(C.marikoCpuDvfsTable1683Tbreak);
case TBREAK_1581: return const_cast<cvb_entry_t *>(C.marikoCpuDvfsTable1581Tbreak);
case EXTREME_TABLE: return const_cast<cvb_entry_t *>(C.marikoCpuDvfsTableExtreme);
case DEFAULT_TABLE:
default:
return const_cast<cvb_entry_t *>(mariko::CpuCvbTableDefault);
}
}
/* Todo: Reduce code duplication of this. */
Result GetCpuSpeedo(u32 &cpuSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 CpuSpeedoOffset = 0x114;
cpuSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + CpuSpeedoOffset);
R_SUCCEED();
}
u32 CalculateCvbVoltage(cvb_entry_t *entry, u32 speedo, u32 speedoScale) {
float speedDiv = static_cast<float>(speedo) / speedoScale;
return ((entry->cvb_dfll_param.c2 * speedDiv + entry->cvb_dfll_param.c1) * speedDiv + entry->cvb_dfll_param.c0);
}
u32 DivRoundUp(u32 n, u32 d) {
return (n + d - 1) / d;
}
u32 RoundCvbVoltage(u32 mv, u32 vScale, u32 stepUv) {
u32 step = (stepUv ? stepUv : 1000) * vScale;
u32 uv = mv * 1000;
uv = DivRoundUp(uv, step) * stepUv;
return uv / 1000;
}
u32 EffVmin() { return C.marikoCpuLowVmin ? C.marikoCpuLowVmin : CpuVminOfficial; }
u32 EffVmax() { return C.marikoCpuMaxVolt ? C.marikoCpuMaxVolt : CpuVoltOfficial; }
u32 ClampVoltage(u32 mv) {
return std::clamp(mv, EffVmin(), EffVmax());
}
constexpr u32 PmicBaseMv = 250;
constexpr u32 PmicStepMv = 5;
constexpr u32 PmicVoltCount = 256;
u32 GetExactVoltCode(u32 mv) {
u32 alignMult = mv / PmicStepMv;
for (u32 i = 0; i < PmicVoltCount; ++i) {
u32 regMult = (PmicBaseMv + i * PmicStepMv) / PmicStepMv;
if (alignMult == regMult) {
return i;
}
}
return UINT32_MAX;
}
u32 GetVoltCode(u32 mv) {
u32 alignMult = DivRoundUp(mv, PmicStepMv);
for (u32 i = 0; i < PmicVoltCount; i++) {
u32 regMult = (PmicBaseMv + i * PmicStepMv) / PmicStepMv;
if (alignMult <= regMult) {
return i;
}
}
return UINT32_MAX;
}
Result BuildLut(u32 *lut, const u32 *voltArray) {
const u32 vMin = EffVmin();
const u32 vMax = *std::max_element(voltArray, voltArray + ClDvfsLutSize);
R_UNLESS(vMin <= vMax, ldr::ResultInvalidCpuMinVolt());
u32 voltage = vMin;
u32 code = GetExactVoltCode(voltage);
R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
lut[0] = code;
u32 j = 1;
for (u32 i = 0; i < ClDvfsLutSize; ++i) {
for (;;) {
R_UNLESS(j < ClDvfsLutSize, ldr::ResultInvalidVoltLutIndex());
voltage += std::max<u32>(1u, (vMax - voltage) / (ClDvfsLutSize - j));
if (voltage >= voltArray[i]) {
break;
}
code = GetVoltCode(voltage);
R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
if (code != lut[j - 1]) {
lut[j++] = code;
}
}
voltage = (j == ClDvfsLutSize - 1) ? vMax : voltArray[i];
code = GetExactVoltCode(voltage);
R_UNLESS(code != UINT32_MAX, ldr::ResultInvalidVoltCode());
if (code != lut[j - 1]) {
R_UNLESS(j < ClDvfsLutSize, ldr::ResultInvalidVoltLutIndex());
lut[j++] = code;
}
if (voltage >= vMax) {
break;
}
}
for (u32 k = j; k < ClDvfsLutSize; ++k) {
lut[k] = lut[j - 1];
}
R_SUCCEED();
}
/* Since doing the proper lut creation in pcv is wasteful, we'll do it ourselves */
Result FillLutTable(HookPayloadData *data) {
/* Step 1: Get the speedo. */
u32 cpuSpeedo = 0;
R_TRY(GetCpuSpeedo(cpuSpeedo));
/* Step 2: Select the appropriate table. */
cvb_entry_t *table = GetDvfsTable();
constexpr u32 SpeedoScale = 100;
constexpr u32 VoltageScale = 1000;
constexpr u32 StepUv = 5000;
/* Step 3: Fill the voltage array. */
u32 voltArray[ClDvfsLutSize] = {};
for (u32 i = 0; i < DvfsTableEntryLimit; ++i) {
u32 voltage = CalculateCvbVoltage(&table[i], cpuSpeedo, SpeedoScale);
voltage = RoundCvbVoltage(voltage, VoltageScale, StepUv);
voltage = ClampVoltage(voltage);
voltArray[i] = voltage;
}
/* Step 4: Build the lut table. */
u32 lut[ClDvfsLutSize] = {};
R_TRY(BuildLut(lut, voltArray));
/* Step 5: Copy it. */
std::memcpy(data->lut64.table, lut, sizeof(lut));
/* Step 6: Finally done. */
R_SUCCEED();
}
}
Result CpuFreqVdd(u32 *ptr) {
dvfs_rail *entry = reinterpret_cast<dvfs_rail *>(reinterpret_cast<u8 *>(ptr) - offsetof(dvfs_rail, freq));
R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry());
if (C.marikoCpuUVHigh) {
PATCH_OFFSET(ptr, CapCpuClock());
} else {
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
R_SUCCEED();
}
Result CpuVoltDVFS(u32 *ptr) {
CvbMeta *cpuCvbMeta = reinterpret_cast<CvbMeta *>(reinterpret_cast<u8 *>(ptr) - offsetof(CvbMeta, vmin));
R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
}
if (C.marikoCpuHighVmin) {
PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32 *ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
switch (C.marikoCpuUVLow) {
case 1:
PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x0);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_low), 0xffff);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 8:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
switch (C.marikoCpuUVHigh) {
case 1:
PATCH_OFFSET(&(entry->tune1_high), 0x0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
case 8:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
case 13:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27FF7FF);
break;
default:
break;
}
R_SUCCEED();
}
/* Little helper for ORR:LSL pairs which are found in here */
inline bool IsOrrLsl(u32 w) {
if (!_asm::IsOp(w, _asm::op::OrrShifted32, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6))
return false;
return _asm::Get(w, _asm::field::Shift) == 0; /* 0=LSL, 1=LSR, 2=ASR, 3=ROR */
}
/* Probably overcomplicated but I wanted to have a rock-solid way to do this regardless of FW version */
Result CpuLutMaxAsm(u32* ptr) {
/* Check for the dual ORR-LSL pairs*/
R_UNLESS(IsOrrLsl(ptr[2]), ldr::ResultInvalidCpuLutMaxAsmPattern());
R_UNLESS(IsOrrLsl(ptr[3]), ldr::ResultInvalidCpuLutMaxAsmPattern());
/* We can safely assume that we are in the right place now */
/* Safety check */
R_UNLESS(ptr - 10 >= nsoStart, ldr::ResultInvalidCpuLutMaxAsmPattern());
/* Search for the STRB */
u32* strb = _asm::ScanAssembly(ptr - 10, 10, _asm::Encode(_asm::op::StrbImm,
{_asm::field::Rt, 0}, {_asm::field::Rn, 0}, {_asm::field::Off1, 0x25D}), _asm::field::Rt,
_asm::field::Rn);
R_UNLESS(strb != nullptr, ldr::ResultInvalidCpuLutMaxAsmPattern());
/* Probably a cleaner way to do this?*/
u32 *prod = strb + 2;
const u32 rd = _asm::Get(*prod, _asm::field::Rd);
if (_asm::IsOp(*prod, _asm::op::Ubfm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Immr, _asm::field::Imm6)) {
/* replacement must carry LSL#16 to make up for ubfiz's remoal */
const u32 nw = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F}, {_asm::field::Hw, 1});
PATCH_OFFSET(prod, nw); /* movz wD,#0x3F,LSL#16 */
} else {
/* Imm12 is 7 for 0xff*/
R_UNLESS(_asm::IsOp(*prod, _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(*prod, _asm::field::Imm12) == 7, ldr::ResultInvalidCpuLutMaxAsmPattern());
/* No need to do the lsl */
const u32 nw = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F});
PATCH_OFFSET(prod, nw); /* movz wD,#0x3F */
}
/* Set SAFE to entry 32 */
PATCH_OFFSET(ptr + 1, _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, _asm::Get(ptr[1], _asm::field::Rd)}, {_asm::field::Imm16, 32}));
R_SUCCEED();
}
namespace {
struct {
u32 *site = nullptr; /* LUT writer prologue. */
} lutWriterCache;
}
HOOK_PAYLOAD_FN void CpuLutWriterExpandImpl(u64 param) {
HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
reinterpret_cast<void (*)(u64)>(data->lut64.orig)(param);
/* *(param+8) = CLDVFS mapping */
const u64 base = *reinterpret_cast<u64 *>(param + 8);
u32 *lut = reinterpret_cast<u32 *>(base + 0x200);
for (u32 i = 0; i < ClDvfsLutSize; ++i) {
lut[i] = data->lut64.table[i];
}
__asm__ volatile("dsb st" ::: "memory");
}
Result LutWriterFind(u32 *ptr) {
R_UNLESS(LutWriterPatternFn(ptr), ldr::ResultInvalidCpuLutHook());
u32 *prog = _asm::FindFnPrologue(ptr, 140, nsoStart);
R_UNLESS(prog != nullptr, ldr::ResultInvalidCpuLutHook());
/* Prolouge must hold MMIO base, so obtain it. */
bool ok = false;
for (u32 i = 0; i < 8; ++i) {
if (_asm::IsOp(prog[i], _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(prog[i], _asm::field::Rn) == 0
&& _asm::Get(prog[i], _asm::field::Off8) == 0x8) {
ok = true;
break;
}
}
R_UNLESS(ok, ldr::ResultInvalidCpuLutHook());
lutWriterCache.site = prog;
R_SUCCEED();
}
Result LutWriterInstallHooks(HookPayloadData *data) {
R_UNLESS(lutWriterCache.site != nullptr, ldr::ResultInvalidCpuLutHook());
R_TRY(FillLutTable(data));
uintptr_t orig = 0;
R_TRY(INSTALL_IMPL_HOOK_ORIG(lutWriterCache.site, CpuLutWriterExpandImpl, &orig));
data->lut64.orig = orig;
R_SUCCEED();
}
Result CpuLutMaxAsm2(u32* ptr) {
/* movk w?,#0xFFC0,LSL#16 */
R_UNLESS(_asm::IsOp(ptr[1], _asm::op::MovkW, _asm::field::Rd, _asm::field::Imm16, _asm::field::Hw)
&& _asm::Get(ptr[1], _asm::field::Imm16) == 0xFFC0
&& _asm::Get(ptr[1], _asm::field::Hw) == 1, ldr::ResultInvalidCpuLutMaxAsmPattern());
/* and x, x, #0xff */
R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) ||
_asm::Get(ptr[2], _asm::field::Imm12) != 7, ldr::ResultInvalidCpuLutMaxAsmPattern());
const u32 rd = _asm::Get(ptr[2], _asm::field::Rd);
PATCH_OFFSET(ptr + 2, _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, 0x3F})); /* movz wD,#0x3F */
R_SUCCEED();
}
/* FW 20.4.0
71000571dc 01 01 00 d0 adrp param_2=>aCldvfssetdvcor,aSSRequestedDKh+0x1f = "ClDvfsSetDvcoRateMin"
= " %s divider %#x diff %d\n"
71000571e0 21 40 27 91 add param_2=>aCldvfssetdvcor,param_2,#0x9d0 = "ClDvfsSetDvcoRateMin"
71000571e4 c3 9f ff 97 bl nn::pcv::NvLog undefined NvLog(char * fmt, ...)
loc_71000571E8 XREF[1]: 71000571b4(j)
71000571e8 69 aa 42 a9 ldp x9,x10,[x19, #0x28]
71000571ec 4a fd 41 d3 lsr x10,x10,#0x1
71000571f0 68 26 41 f9 ldr x8,[x19, #0x248] Anchor
71000571f4 0b 0d 0a cb sub x11,x8,x10, LSL #0x3 Target
71000571f8 6b 4e 01 f9 str x11,[x19, #0x298] Verify1
71000571fc 2c 0d 40 f9 ldr x12,[x9, #0x18] Verify2
7100057200 7f 01 0c eb cmp x11,x12
7100057204 6b 81 8c 9a csel x11,x11,x12,hi
7100057208 6b 4e 01 f9 str x11,[x19, #0x298]
710005720c 2b c9 40 f9 ldr x11,[x9, #0x190]
7100057210 6b 52 01 f9 str x11,[x19, #0x2a0]
7100057214 8b 00 00 b4 cbz x11,loc_7100057224
7100057218 f4 4f 41 a9 ldp x20,x19,[sp, #local_10]
710005721c fd 7b c2 a8 ldp x29=>local_20,x30,[sp], #0x20
7100057220 c0 03 5f d6 ret
loc_7100057224 XREF[1]: 7100057214(j)
7100057224 08 15 0a 8b add x8,x8,x10, LSL #0x5 Target
7100057228 2a 55 81 b9 ldrsw x10,[x9, #0x154] Anchor
710005722c 68 52 01 f9 str x8,[x19, #0x2a0] Verify1
*/
Result CpuLutDvcoRateCfg(u32* ptr) {
/* Anchor via scanning to prevent ordering issues */
u32 *sub = _asm::ScanAssembly(ptr + 1, 4,
_asm::Encode(_asm::op::SubShifted64, {_asm::field::Rd, 0}, {_asm::field::Rn, 0}, {_asm::field::Rm, 0},
{_asm::field::Shift, 0}, {_asm::field::Imm6, 3}),
_asm::field::Rd, _asm::field::Rn, _asm::field::Rm);
R_UNLESS(sub != nullptr, ldr::ResultInvalidDvcoRateConfig());
/* Verify */
R_UNLESS(_asm::IsOp(sub[1], _asm::op::StrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(sub[1], _asm::field::Off8) == 0x298, ldr::ResultInvalidDvcoRateConfig());
/* 22.x uses pre-index ldr x?,[x?,#0x18]! here (0xF8418D6D class). */
const bool isLdr18 = _asm::IsOp(sub[2], _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(sub[2], _asm::field::Off8) == 0x18;
const bool isLdrPre18 = _asm::IsOp(sub[2], _asm::op::LdrPreImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Imm9)
&& _asm::Get(sub[2], _asm::field::Imm9) == 0x18;
R_UNLESS(isLdr18 || isLdrPre18, ldr::ResultInvalidDvcoRateConfig());
/* Patch the target */
PATCH_OFFSET(sub, _asm::Encode(_asm::op::SubShifted64,
{_asm::field::Rd, _asm::Get(*sub, _asm::field::Rd)},
{_asm::field::Rn, _asm::Get(*sub, _asm::field::Rn)},
{_asm::field::Rm, _asm::Get(*sub, _asm::field::Rm)},
{_asm::field::Shift, 0}, {_asm::field::Imm6, 4}));
/* Search for the second anchor */
u32* ldrsw = _asm::ScanAssembly(sub, 20,
_asm::Encode(_asm::op::LdrsWImm64, {_asm::field::Rt, 0}, {_asm::field::Rn, 0}, {_asm::field::Off4, 0x154}),
_asm::field::Rt, _asm::field::Rn);
R_UNLESS(ldrsw != nullptr, ldr::ResultInvalidDvcoRateConfig());
/* Validate the str #0x2a0 */
auto isCorrectStr = [](u32 w) {
return _asm::IsOp(w, _asm::op::StrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(w, _asm::field::Off8) == 0x2a0;
};
R_UNLESS(isCorrectStr(ldrsw[1]) || isCorrectStr(ldrsw[2]), ldr::ResultInvalidDvcoRateConfig());
/* Validate the target in both directuins */
/* Due to codegen difference */
u32 *add = nullptr;
if (_asm::IsOp(ldrsw[-1], _asm::op::AddShifted64, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6)
&& _asm::Get(ldrsw[-1], _asm::field::Shift) == 0
&& _asm::Get(ldrsw[-1], _asm::field::Imm6) == 5) {
add = ldrsw - 1;
}
else if (_asm::IsOp(ldrsw[1], _asm::op::AddShifted64, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm, _asm::field::Shift, _asm::field::Imm6)
&& _asm::Get(ldrsw[1], _asm::field::Shift) == 0
&& _asm::Get(ldrsw[1], _asm::field::Imm6) == 5) {
add = ldrsw + 1;
}
R_UNLESS(add != nullptr, ldr::ResultInvalidDvcoRateConfig());
/* Patch the second target */
PATCH_OFFSET(add, _asm::Encode(_asm::op::AddShifted64,
{_asm::field::Rd, _asm::Get(*add, _asm::field::Rd)},
{_asm::field::Rn, _asm::Get(*add, _asm::field::Rn)},
{_asm::field::Rm, _asm::Get(*add, _asm::field::Rm)},
{_asm::field::Shift, 0}, {_asm::field::Imm6, 6}));
R_SUCCEED();
}
}
@@ -0,0 +1,111 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
#include "../pcv_hook.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
/* Maybe a cleaner way to do this? */
inline bool LutWriterPatternFn(u32 *ptr) {
if (_asm::IsOp(*ptr, _asm::op::AndImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Immr, _asm::field::Imm6)
&& _asm::Get(*ptr, _asm::field::Immr) == 61 && _asm::Get(*ptr, _asm::field::Imm6) == 7)
return true;
if (_asm::IsOp(*ptr, _asm::op::AndImm32, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
&& _asm::Get(*ptr, _asm::field::Imm12) == 0x747)
return true;
return false;
}
HOOK_PAYLOAD_FN void CpuLutWriterExpandImpl(u64 param);
Result LutWriterFind(u32 *ptr);
Result LutWriterInstallHooks(HookPayloadData *data);
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
{ 408000, { 776324, -15705, 27 }, { } },
{ 510000, { 809160, -17205, 27 }, { } },
{ 612000, { 845641, -18715, 27 }, { } },
{ 714000, { 885768, -20215, 27 }, { } },
{ 816000, { 929540, -21725, 27 }, { } },
{ 918000, { 976958, -23225, 27 }, { } },
{ 1020000, { 1028021, -24725, 27 }, { 1120000 } },
{ 1122000, { 1082730, -26235, 27 }, { 1120000 } },
{ 1224000, { 1141084, -27735, 27 }, { 1120000 } },
{ 1326000, { 1203084, -29245, 27 }, { 1120000 } },
{ 1428000, { 1268729, -30745, 27 }, { 1120000 } },
{ 1581000, { 1374032, -33005, 27 }, { 1120000 } },
{ 1683000, { 1448791, -34505, 27 }, { 1120000 } },
{ 1785000, { 1527196, -36015, 27 }, { 1120000 } },
{ 1887000, { 1609246, -37515, 27 }, { 1120000 } },
{ 1963500, { 1675751, -38635, 27 }, { 1120000 } },
{ },
};
constexpr u32 CpuClkOfficial = 1963'500;
constexpr u32 CpuVoltOfficial = 1120;
constexpr u32 CpuHighVminOfficial = 850;
constexpr u32 CpuVminOfficial = 620;
constexpr u32 CpuTune0Low = 0xFFCF;
static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
/* Refer to customize.cpp for more information */
static const u32 allowedCpuMaxFrequencies[] = { 1'963'500, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, 2'805'000 };
Result CpuFreqVdd(u32 *ptr);
Result CpuVoltDVFS(u32 *ptr);
Result CpuVoltThermals(u32 *ptr);
Result CpuVoltDfll(u32 *ptr);
Result CpuLutMaxAsm(u32* ptr);
Result CpuLutMaxAsm2(u32* ptr);
Result CpuLutDvcoRateCfg(u32* ptr);
ALWAYS_INLINE bool CpuLutAsmPatternFn(u32* ptr) {
/* Check if it is a DSB ST, a common indication of CL-DVFS related functions */
return *ptr == _asm::Dsb(_asm::barrier::St);
}
ALWAYS_INLINE bool CpuLutAsmPatternFn2(u32* ptr) {
/* Note: We are doing multiple checks here as it is the cleanest method */
return _asm::IsOp(*ptr, _asm::op::MovzW, _asm::field::Rd, _asm::field::Imm16, _asm::field::Hw)
&& _asm::Get(*ptr, _asm::field::Imm16) == 0xC0FF
&& _asm::Get(*ptr, _asm::field::Hw) == 0;
}
ALWAYS_INLINE bool CpuLutDvcoRateCfgPatternFn(u32* ptr) {
/* Again, cleaner to ensure we are on the correct instruction here */
return _asm::IsOp(*ptr, _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(*ptr, _asm::field::Off8) == 0x248;
}
}
@@ -0,0 +1,150 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "../pcv_asm.hpp"
#include "pcv_mariko_gpu.hpp"
namespace ams::ldr::hoc::pcv::mariko {
Result GpuVoltDVFS(u32 *ptr) {
/* Check for valid pattern. */
for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
}
/* Default value is 1050mV. */
if (C.marikoGpuVmax) {
PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
}
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
// if (C.marikoGpuBootVolt) {
// PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
// }
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
}
// if (C.marikoGpuVmax) {
// PATCH_OFFSET(ptr - 2, C.marikoGpuVmax);
// PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
// PATCH_OFFSET(ptr + 4, C.marikoGpuVmax);
// PATCH_OFFSET(ptr + 7, C.marikoGpuVmax);
// PATCH_OFFSET(ptr + 10, C.marikoGpuVmax);
// PATCH_OFFSET(ptr + 13, C.marikoGpuVmax);
// }
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(_asm::Ignoring(ins1, GpuAsmPattern[0], _asm::field::Rd) && _asm::Ignoring(ins2, GpuAsmPattern[1], _asm::field::Rd))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = _asm::Get(ins1, _asm::field::Rd);
if (rd != _asm::Get(ins2, _asm::field::Rd)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
case 3:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
break;
case 4:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
}
u32 asm_patch[2] = {
_asm::Encode(_asm::op::MovzW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock}),
_asm::Encode(_asm::op::MovkW, {_asm::field::Rd, rd}, {_asm::field::Imm16, max_clock >> 16}, {_asm::field::Hw, 1})
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
Result GpuFreqPllLimit(u32 *ptr) {
u32 prev_freq = *(ptr - 1);
if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
R_THROW(ldr::ResultInvalidGpuPllEntry());
}
PATCH_OFFSET(ptr, 3600000);
R_SUCCEED();
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
{ 76800, {}, { 610000, } },
{ 153600, {}, { 610000, } },
{ 230400, {}, { 610000, } },
{ 307200, {}, { 610000, } },
{ 384000, {}, { 610000, } },
{ 460800, {}, { 610000, } },
{ 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
{ 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
{ 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
{ 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
{ 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
{ 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
{ 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
{ 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
{ 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
{ 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
{ 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
{ },
};
constexpr u32 GpuClkPllMax = 1300'000'000;
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuVminOfficial = 610;
static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return _asm::Ignoring(*ptr32, GpuAsmPattern[0], _asm::field::Rd);
}
Result GpuVoltDVFS(u32 *ptr);
Result GpuVoltThermals(u32 *ptr);
Result GpuFreqMaxAsm(u32 *ptr32);
Result GpuFreqPllMax(u32 *ptr);
Result GpuFreqPllLimit(u32 *ptr);
}
@@ -0,0 +1,239 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "pcv_mariko_log.hpp"
namespace ams::ldr::hoc::pcv::mariko {
/* Todo: Remove this bs. */
[[maybe_unused]] static uintptr_t CaveReserve(size_t count) {
return reinterpret_cast<uintptr_t>(Hooks().Reserve(count));
}
#if HOC_UART_LOG
namespace {
struct {
uintptr_t vsnprintf_addr = 0;
uintptr_t nvlog_addr = 0;
} nvLogCache;
}
Result NvLogUartRedirect(u32 *ptr) {
const uintptr_t mapped_nso = reinterpret_cast<uintptr_t>(nsoStart);
const size_t nso_size = g_nso_size;
const uintptr_t textEnd = g_pcv_cave; /* .text ends where the cave begins */
const uintptr_t vsnprintf_addr = reinterpret_cast<uintptr_t>(ptr);
{
using namespace _asm::field;
R_UNLESS(_asm::Ignoring(ptr[1], NvLogStpFpLrAsm, PairOff8), ldr::ResultInvalidNvLogRedirect()); /* stp x29,x30,[sp,#imm] */
R_UNLESS(_asm::Ignoring(ptr[2], NvLogStrSpillAsm, Rt, Off8), ldr::ResultInvalidNvLogRedirect()); /* str x?,[sp,#imm] */
R_UNLESS(_asm::Ignoring(ptr[3], NvLogMovFpAsm, Imm12), ldr::ResultInvalidNvLogRedirect()); /* add x29,sp,#imm */
R_UNLESS(ptr[4] == NvLogCmpSizeAsm, ldr::ResultInvalidNvLogRedirect()); /* cmp x1,#0 */
}
/* NvLog via the VDD_SOC log */
static const char Fmt[] = "%s(%s): DVFS request VDD_SOC %d mV\n";
constexpr size_t FmtLen = sizeof(Fmt) - 1;
uintptr_t strAddr = 0;
{
const char *hay = reinterpret_cast<const char *>(mapped_nso);
for (size_t i = 0; i + FmtLen <= nso_size; ++i) {
if (std::memcmp(hay + i, Fmt, FmtLen) == 0) { strAddr = mapped_nso + i; break; }
}
}
if (strAddr == 0) {
LOGGING("NvLogRedirect: fmt string not found (vsnprintf@+%lx)", vsnprintf_addr - mapped_nso);
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
uintptr_t nvlog_addr = 0;
for (u32 *p = nsoStart; reinterpret_cast<uintptr_t>(p + 2) <= textEnd; ++p) {
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (!_asm::IsOp(p[0], _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo)) {
continue;
}
if (_asm::TargetAdrp(p[0], pc) != (strAddr & ~static_cast<uintptr_t>(0xFFFu))) {
continue;
}
const u32 adrpReg = _asm::Get(p[0], _asm::field::Rd);
if (!(_asm::IsOp(p[1], _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
&& _asm::Get(p[1], _asm::field::Rd) == adrpReg && _asm::Get(p[1], _asm::field::Rn) == adrpReg
&& _asm::Get(p[1], _asm::field::Imm12) == (strAddr & 0xFFFu))) {
continue;
}
for (u32 k = 2; k <= 12 && (pc + (k + 1) * 4) <= textEnd; ++k) {
if (_asm::IsOp(p[k], _asm::op::Bl, _asm::field::Imm26)) { nvlog_addr = _asm::Target26(p[k], pc + k * 4); break; }
}
if (nvlog_addr != 0) {
break;
}
}
if (nvlog_addr == 0 || nvlog_addr < mapped_nso || nvlog_addr >= textEnd) {
LOGGING("NvLogRedirect: NvLog entry not found (fmt@+%lx)", strAddr - mapped_nso);
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
nvLogCache.vsnprintf_addr = vsnprintf_addr;
nvLogCache.nvlog_addr = nvlog_addr;
R_SUCCEED();
}
namespace {
Result InstallNvLogRedirect() {
const uintptr_t mapped_nso = reinterpret_cast<uintptr_t>(nsoStart);
const uintptr_t textEnd = g_pcv_cave;
const uintptr_t vsnprintf_addr = nvLogCache.vsnprintf_addr;
const uintptr_t nvlog_addr = nvLogCache.nvlog_addr;
const uintptr_t helper = CaveReserve(40);
if (helper == 0) {
LOGGING("NvLogRedirect: cave unavailable (cave=%lx size=%lx)",
static_cast<unsigned long>(g_pcv_cave), static_cast<unsigned long>(g_pcv_cave_size));
R_THROW(ldr::ResultInvalidNvLogRedirect());
}
u32 *t = reinterpret_cast<u32 *>(helper);
size_t n = 0;
auto emit = [&](u32 ins) { t[n] = ins; ++n; };
using namespace _asm::field;
constexpr u32 SP = _asm::reg::Sp;
emit(_asm::Encode(_asm::op::SubImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200}));
emit(_asm::Encode(_asm::op::StpImm64, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff8, 0x100}));
emit(_asm::Encode(_asm::op::StpImm64, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff8, 0x110}));
emit(_asm::Encode(_asm::op::StpImm64, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff8, 0x120}));
emit(_asm::Encode(_asm::op::StpImm64, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff8, 0x130}));
emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 0}, {Rt2, 1}, {Rn, SP}, {PairOff16, 0x140}));
emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 2}, {Rt2, 3}, {Rn, SP}, {PairOff16, 0x160}));
emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 4}, {Rt2, 5}, {Rn, SP}, {PairOff16, 0x180}));
emit(_asm::Encode(_asm::op::StpImmQ, {Rt, 6}, {Rt2, 7}, {Rn, SP}, {PairOff16, 0x1A0}));
emit(_asm::Encode(_asm::op::StrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0}));
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x200})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C0})); /* __stack */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x140})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1C8})); /* __gr_top */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 9}, {Rn, SP}, {Imm12, 0x1C0})); emit(_asm::Encode(_asm::op::StrImm64, {Rt, 9}, {Rn, SP}, {Off8, 0x1D0})); /* __vr_top */
emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x37})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1D8})); /* __gr_offs = -56 */
emit(_asm::Encode(_asm::op::MovnW, {Rd, 9}, {Imm16, 0x7F})); emit(_asm::Encode(_asm::op::StrImm32, {Rt, 9}, {Rn, SP}, {Off4, 0x1DC})); /* __vr_offs = -128 */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (buf) */
emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0x100})); /* size = 0x100 */
emit(_asm::Encode(_asm::op::LdrImm64, {Rt, 2}, {Rn, SP}, {Off8, 0x100})); /* fmt (saved x0) */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 3}, {Rn, SP}, {Imm12, 0x1C0})); /* ap */
emit(_asm::Retarget26(_asm::op::Bl, helper + n * 4, vsnprintf_addr));
emit(_asm::MovReg64(1, 0)); /* len = retval */
emit(_asm::CmpImm32(1, 0x100));
{ /* b.lo +2 */
const size_t at = n;
emit(_asm::Retarget19(_asm::Encode(_asm::op::BCond, {BCond, _asm::cond::Lo}), helper + at * 4, helper + (at + 2) * 4));
}
emit(_asm::Encode(_asm::op::MovzW, {Rd, 1}, {Imm16, 0xFF})); /* clamp len */
emit(_asm::Encode(_asm::op::AddImm64, {Rd, 0}, {Rn, SP}, {Imm12, 0x00})); /* mov x0,sp (str) */
emit(_asm::Encode(_asm::op::Svc, {Imm16, 0x27})); /* svcOutputDebugString */
emit(_asm::Encode(_asm::op::LdrImm64, {Rt, _asm::reg::Lr}, {Rn, SP}, {Off8, 0x1E0}));
emit(_asm::Encode(_asm::op::AddImm64, {Rd, SP}, {Rn, SP}, {Imm12, 0x200}));
emit(_asm::RetIns);
/* Redirect the call sites as patching the actual function causes crash */
const uintptr_t roStart = g_pcv_cave + g_pcv_cave_size; /* module .rodata start */
size_t patchedSites = 0;
for (u32 *p = nsoStart; reinterpret_cast<uintptr_t>(p + 1) <= textEnd; ++p) {
if (!_asm::IsOp(*p, _asm::op::Bl, _asm::field::Imm26)) {
continue;
}
const uintptr_t pc = reinterpret_cast<uintptr_t>(p);
if (_asm::Target26(*p, pc) != nvlog_addr) {
continue;
}
bool isFmtCall = false;
for (u32 j = 1; j <= 8 && reinterpret_cast<uintptr_t>(p - j) >= reinterpret_cast<uintptr_t>(nsoStart); ++j) {
const u32 w = *(p - j);
if (_asm::IsOp(w, _asm::op::Adrp, _asm::field::Rd, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo) && _asm::Get(w, _asm::field::Rd) == 0) { /* adrp x0,<page> */
const uintptr_t tgtPage = _asm::TargetAdrp(w, pc - j * 4);
if (tgtPage >= (roStart & ~static_cast<uintptr_t>(0xFFFu))) { isFmtCall = true; break; }
}
}
if (isFmtCall) {
PATCH_OFFSET(p, _asm::Retarget26(_asm::op::Bl, pc, helper));
++patchedSites;
}
}
LOGGING("NvLogRedirect: stub@+%lx vsnprintf@+%lx helper@+%lx instr=%zu sites=%zu", nvlog_addr - mapped_nso, vsnprintf_addr - mapped_nso, helper - mapped_nso, n, patchedSites);
R_SUCCEED();
}
}
namespace {
struct {
u32 *sites[3] = {};
u32 count = 0;
} forceVerbosityCache;
}
HOOK_PAYLOAD_FN u32 ForceVerbosityImpl() {
HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
return data->verbosityLevel;
}
/* Force GetEffectiveVerbosityLevel to return a non-zero level so all NvLog runs. */
Result ForceVerbosity(u32 *ptr) {
using namespace _asm::field;
R_UNLESS(_asm::IsOp(ptr[1], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[1], Rn) == _asm::reg::Sp,
ldr::ResultInvalidForceVerbosityPattern()); /* str x?,[sp,#imm] */
R_UNLESS(_asm::IsOp(ptr[2], _asm::op::AddImm64, Rd, Rn, Imm12)
&& _asm::Get(ptr[2], Rd) == _asm::reg::Fp && _asm::Get(ptr[2], Rn) == _asm::reg::Sp,
ldr::ResultInvalidForceVerbosityPattern()); /* add x29,sp,#imm */
R_UNLESS(_asm::IsOp(ptr[3], _asm::op::AddImm64, Rn, Imm12)
&& _asm::Get(ptr[3], Rd) == 0 && _asm::Get(ptr[3], Rn) == _asm::reg::Fp,
ldr::ResultInvalidForceVerbosityPattern()); /* add x0,x29,#imm */
R_UNLESS(_asm::IsOp(ptr[4], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[4], Rn) == _asm::reg::Fp,
ldr::ResultInvalidForceVerbosityPattern()); /* add x?,x29,#imm */
R_UNLESS(_asm::Get(ptr[3], Imm12) == _asm::Get(ptr[4], Imm12)
&& _asm::IsOp(ptr[5], _asm::op::Bl, Imm26),
ldr::ResultInvalidForceVerbosityPattern());
bool foundCmp = false;
for (u32 j = 6; j <= 10; ++j) {
if (ptr[j] == 0x7100001Fu) { /* cmp w0,#0 */
foundCmp = true;
break;
}
}
R_UNLESS(foundCmp, ldr::ResultInvalidForceVerbosityPattern());
R_UNLESS(forceVerbosityCache.count < std::size(forceVerbosityCache.sites), ldr::ResultInvalidForceVerbosityPattern());
forceVerbosityCache.sites[forceVerbosityCache.count++] = ptr;
R_SUCCEED();
}
Result ForceVebosityInstallHooks(HookPayloadData *data) {
if (nvLogCache.nvlog_addr != 0) {
R_TRY(InstallNvLogRedirect());
}
if (forceVerbosityCache.count != 0 && C.pcvLogVerbosity != 0xff) {
data->verbosityLevel = C.pcvLogVerbosity;
for (u32 i = 0; i < forceVerbosityCache.count; ++i) {
R_DISCARD(INSTALL_IMPL_HOOK(forceVerbosityCache.sites[i], ForceVerbosityImpl));
}
}
R_SUCCEED();
}
#endif
}
@@ -0,0 +1,64 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
extern size_t g_nso_size;
inline bool ForceVerbosityPatternFn(u32 *ptr) {
if (g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 11) > g_pcv_cave) { /* .text only */
return false;
}
return _asm::IsFramePushPre(*ptr);
}
/*
sub sp, sp, #imm
stp x29,x30,[sp,#imm]
str x?, [sp,#imm]
add x29,sp, #imm
subs xzr, x1, #0
*/
constexpr u32 NvLogSubSpAsm = 0xD10003FFu; /* sub sp,sp,#0 */
constexpr u32 NvLogStpFpLrAsm = 0xA9007BFDu; /* stp x29,x30,[sp,#0] */
constexpr u32 NvLogStrSpillAsm = 0xF90003E0u; /* str x?,[sp,#0] */
constexpr u32 NvLogMovFpAsm = 0x910003FDu; /* add x29,sp,#0 */
constexpr u32 NvLogCmpSizeAsm = 0xF100003Fu; /* subs xzr,x1,#0 */
inline bool NvLogVsnprintfPatternFn(u32 *ptr) {
if (HOC_UART_LOG == 0 || g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 5) > g_pcv_cave) { /* must sit in .text */
return false;
}
return _asm::Ignoring(*ptr, NvLogSubSpAsm, _asm::field::Imm12);
}
Result NvLogUartRedirect(u32 *ptr);
HOOK_PAYLOAD_FN u32 ForceVerbosityImpl();
Result ForceVerbosity(u32 *ptr);
Result ForceVebosityInstallHooks(HookPayloadData *data);
}
@@ -5,7 +5,7 @@
*
* Copyright (c) B3711
*
* Copyright (c) Souldbminer and Horizon OC Contributors
* 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,
@@ -20,333 +20,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include "pcv.hpp"
#include "../mtc_timing_value.hpp"
#include "../mariko/calculate_timings.hpp"
#include "../pcv.hpp"
#include "../../mtc_timing_value.hpp"
#include "calculate_timings_mariko.hpp"
#include "pcv_mariko_mtc.hpp"
namespace ams::ldr::hoc::pcv::mariko {
Result GpuVoltDVFS(u32 *ptr) {
/* Check for valid pattern. */
for (size_t i = 0; i < std::size(gpuDVFSPattern); ++i) {
if (*(ptr + i + 1) != gpuDVFSPattern[i]) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
}
/* Default value is 1050mV. */
if (C.marikoGpuVmax) {
PATCH_OFFSET(ptr + 1, C.marikoGpuVmax);
}
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
}
R_SUCCEED();
}
Result GpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr - 3, gpuVoltThermalPattern, sizeof(gpuVoltThermalPattern))) {
R_THROW(ldr::ResultInvalidGpuDvfs());
}
// if (C.marikoGpuBootVolt) {
// PATCH_OFFSET(ptr - 3, C.marikoGpuBootVolt);
// }
if (C.marikoGpuVmin) {
PATCH_OFFSET(ptr, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 3, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 6, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 9, C.marikoGpuVmin);
PATCH_OFFSET(ptr + 12, C.marikoGpuVmin);
}
R_SUCCEED();
}
u32 CapCpuClock() {
u32 cpuCap = allowedCpuMaxFrequencies[0];
for (u32 freq : allowedCpuMaxFrequencies) {
if (C.marikoCpuMaxClock >= freq) {
cpuCap = freq;
} else {
break;
}
}
return cpuCap;
}
Result CpuFreqVdd(u32 *ptr) {
dvfs_rail *entry = reinterpret_cast<dvfs_rail *>(reinterpret_cast<u8 *>(ptr) - offsetof(dvfs_rail, freq));
R_UNLESS(entry->id == 1, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->min_mv == 250'000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->step_mv == 5000, ldr::ResultInvalidCpuFreqVddEntry());
R_UNLESS(entry->max_mv == 1525'000, ldr::ResultInvalidCpuFreqVddEntry());
if (C.marikoCpuUVHigh) {
PATCH_OFFSET(ptr, CapCpuClock());
} else {
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
R_SUCCEED();
}
Result CpuVoltDVFS(u32 *ptr) {
CvbMeta *cpuCvbMeta = reinterpret_cast<CvbMeta *>(reinterpret_cast<u8 *>(ptr) - offsetof(CvbMeta, vmin));
R_UNLESS(cpuCvbMeta->highVmin == CpuHighVminOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkStepMaybe == 38, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->vmax == CpuVoltOfficial, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkScale2 == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->speedoScale == 100, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->voltageScale == 1000, ldr::ResultInvalidCpuMinVolt());
R_UNLESS(cpuCvbMeta->unkZero5 == 0, ldr::ResultInvalidCpuMinVolt());
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(&(cpuCvbMeta->vmin), C.marikoCpuLowVmin);
}
if (C.marikoCpuHighVmin) {
PATCH_OFFSET(&(cpuCvbMeta->highVmin), C.marikoCpuHighVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(&(cpuCvbMeta->vmax), C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltThermals(u32 *ptr) {
if (std::memcmp(ptr, cpuVoltThermalData, sizeof(cpuVoltThermalData))) {
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
if (C.marikoCpuLowVmin) {
PATCH_OFFSET(ptr, C.marikoCpuLowVmin);
PATCH_OFFSET(ptr + 3, C.marikoCpuLowVmin);
}
if (C.marikoCpuMaxVolt) {
PATCH_OFFSET(ptr - 2, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr - 5, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 1, C.marikoCpuMaxVolt);
PATCH_OFFSET(ptr + 4, C.marikoCpuMaxVolt);
}
R_SUCCEED();
}
Result CpuVoltDfll(u32 *ptr) {
CvbCpuDfllData *entry = reinterpret_cast<CvbCpuDfllData *>(ptr);
R_UNLESS(entry->tune0_low == 0xFFCF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x0, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x12207FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x3FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
switch (C.marikoCpuUVLow) {
case 1:
PATCH_OFFSET(&(entry->tune0_low), 0xffa0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x0);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_low), 0xffdf);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_low), 0xffff);
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21107ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21607ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 8:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_low), 0xdfff);
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_low), 0x21707ff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
switch (C.marikoCpuUVHigh) {
case 1:
PATCH_OFFSET(&(entry->tune1_high), 0x0);
PATCH_OFFSET(&(entry->tune0_high), 0xffff);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27207ff);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27307ff);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27407ff);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27707ff);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_high), 0xffdf);
PATCH_OFFSET(&(entry->tune1_high), 0x27807ff);
break;
case 7:
case 8:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27b07ff);
break;
case 9:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27c07ff);
break;
case 10:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27d07ff);
break;
case 11:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27e07ff);
break;
case 12:
PATCH_OFFSET(&(entry->tune0_high), 0xdfff);
PATCH_OFFSET(&(entry->tune1_high), 0x27f07ff);
break;
default:
break;
}
R_SUCCEED();
}
Result GpuFreqMaxAsm(u32 *ptr32) {
// Check if both two instructions match the pattern
u32 ins1 = *ptr32, ins2 = *(ptr32 + 1);
if (!(asm_compare_no_rd(ins1, GpuAsmPattern[0]) && asm_compare_no_rd(ins2, GpuAsmPattern[1]))) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
// Both instructions should operate on the same register
u8 rd = asm_get_rd(ins1);
if (rd != asm_get_rd(ins2)) {
R_THROW(ldr::ResultInvalidGpuFreqMaxPattern());
}
u32 max_clock;
switch (C.marikoGpuUV) {
case 0:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
case 1:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq;
break;
case 2:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
case 3:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT15)->freq;
break;
case 4:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHighUV)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(GpuAsmPattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(GpuAsmPattern[1], max_clock >> 16), rd)
};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllMax(u32 *ptr) {
clk_pll_param *entry = reinterpret_cast<clk_pll_param *>(ptr);
// All zero except for freq
for (size_t i = 1; i < sizeof(clk_pll_param) / sizeof(u32); i++) {
R_UNLESS(*(ptr + i) == 0, ldr::ResultInvalidGpuPllEntry());
}
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
R_SUCCEED();
}
Result GpuFreqPllLimit(u32 *ptr) {
u32 prev_freq = *(ptr - 1);
if (prev_freq != 128000 && prev_freq != 1300000 && prev_freq != 76800) {
R_THROW(ldr::ResultInvalidGpuPllEntry());
}
PATCH_OFFSET(ptr, 3600000);
R_SUCCEED();
namespace {
std::vector<u32> newEmcList;
}
void MemMtcTableAutoAdjust(MarikoMtcTable *table) {
@@ -354,6 +36,8 @@ namespace ams::ldr::hoc::pcv::mariko {
* https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual
* Section 18.11.1: MC Registers
* Section 18.11.2: EMC Registers
*
* Additional information from reverse-engineering public Tegra K1 (T124) memory characterization tools.
*/
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
@@ -363,8 +47,6 @@ namespace ams::ldr::hoc::pcv::mariko {
const double tCK_avg = 1000'000.0 / table->rate_khz;
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
/* Ram power down */
/* B31: DRAM_CLKSTOP_PD */
/* B30: DRAM_CLKSTOP_SR */
@@ -375,13 +57,12 @@ namespace ams::ldr::hoc::pcv::mariko {
WRITE_PARAM_ALL_REG(table, emc_cfg, 0xF3200000);
}
CalculateTimings(tCK_avg, table->rate_khz);
u32 trefbw = refresh_raw + 0x40;
trefbw = MIN(trefbw, static_cast<u32>(0x3FFF));
const u32 dyn_self_ref_control = (static_cast<u32>(7605.0 / tCK_avg) + 260) | (table->burst_regs.emc_dyn_self_ref_control & 0xffff0000);
CalculateTimings(tCK_avg, table->rate_khz);
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_rc, MIN(GET_CYCLE_CEIL(tRC), static_cast<u32>(0xB9)));
@@ -554,6 +235,9 @@ namespace ams::ldr::hoc::pcv::mariko {
const u32 tRFCabStock = tRFC_values[0] * 2;
table->dram_timings.t_rfc = tRFCabStock;
/* dram_timings.rl is used on Erista to calculate specific RL related timings during memory training for a workaround. */
/* See fusee or minerva for more details. */
/* This is unused on Mariko but we will set it regardless for completion. */
table->dram_timings.rl = RL;
table->emc_mrw2 = (table->emc_mrw2 & ~0xFFu) | static_cast<u32>(mrw2);
table->emc_mrw = (table->emc_mrw & ~0x70u) | 0x40; /* nWR */
@@ -617,89 +301,18 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
namespace {
std::vector<u32> newEmcList;
u32 *nsoStart;
}
/* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
Result EmcSocLutReloc(u32 *ptr) {
R_UNLESS(EmcSocLutPatternFn(ptr), ldr::ResultInvalidEmcSocLut()); /* str lut,[rail+0x120] ; str n,[rail+0x154] */
constexpr u32 Window = 48;
u32 *freqStore = ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, asm_compare_no_rd); /* str x?,[x8,#0x18] */
u32 *voltStore = ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, asm_compare_no_rd); /* str w?,[x8,#0x48] */
u32 *readLoad = ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, asm_compare_no_rd); /* ldr w?,[x9,#0x48] */
R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
R_UNLESS(AsmIsAddShiftedReg64(*voltBase) && asm_get_rd(*voltBase) == AsmGetRn(*voltStore),
ldr::ResultInvalidEmcSocLut());
/* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
const u32 lutReg = asm_get_rd(ptr[0]);
const u32 railReg = AsmGetRn(ptr[0]);
R_UNLESS(AsmIsAdrp(ptr[-3]) && asm_get_rd(ptr[-3]) == lutReg, ldr::ResultInvalidEmcSocLut());
R_UNLESS(AsmIsAddImm64(ptr[-2]) && asm_get_rd(ptr[-2]) == lutReg && AsmGetRn(ptr[-2]) == lutReg,
ldr::ResultInvalidEmcSocLut());
const u32 srcBase = AsmGetRn(*voltBase); /* rail ptr at +0x20 */
const u32 wBase = asm_get_rd(*voltBase); /* base reg */
const u32 wIdx = AsmGetRm(*voltBase); /* loop index */
PATCH_OFFSET(freqStore, NopIns); /* Unneeded */
PATCH_OFFSET(voltBase, AsmMakeLdrImm64(wBase, srcBase, 0x20)); /* ldr Xb,[Xsrc,#0x20] (rail) */
PATCH_OFFSET(voltStore - 1, AsmMakeAddImm64(wBase, wBase, 0x18)); /* add Xb,Xb,#0x18 (was cmn) */
PATCH_OFFSET(voltStore, AsmSetLdStRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
PATCH_OFFSET(voltStore + 1, NopIns); /* Unneeded */
/* rail+0x18 as the socMinLut pointer. */
PATCH_OFFSET(ptr - 3, NopIns);
PATCH_OFFSET(ptr - 2, AsmMakeAddImm64(lutReg, railReg, 0x18));/* add Xl,rail,#0x18 */
/* Drop the abort branch in case of a bad read */
for (u32 i = 1; i <= 4; ++i) {
if (AsmIsBCond(readLoad[i])) {
PATCH_OFFSET(&readLoad[i], NopIns);
break;
}
void AppendMaxEmc(std::vector<u32> &emcList) {
if (emcList.back() != C.marikoEmcMaxClock) {
emcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
R_SUCCEED();
}
Result EmcDvfsCountLimit(u32 *ptr) {
R_UNLESS(EmcDvfsCountPatternFn(ptr), ldr::ResultInvalidEmcDvfsCount());
/* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount)));
R_SUCCEED();
}
Result EmcRateListLimit(u32 *ptr) {
/* ptr = cmp w?,#0x20 ; ptr[1] = csel w?,w?,w?,lt (w? = min(maxCount, 32)) ; ptr[2] = bl */
R_UNLESS(EmcRateListPatternFn(ptr), ldr::ResultInvalidEmcRateList());
/* The csel's Rm holds the 32 cap. */
const u32 capReg = AsmGetRm(ptr[1]);
const u32 capMov = AsmMakeMovzW(capReg, 0x20); /* movz w<Rm>,#0x20 */
u32 *movPtr = nullptr;
for (u32 i = 1; i <= 16; ++i) {
if (*(ptr - i) == capMov) {
movPtr = ptr - i;
break;
}
}
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(movPtr, asm_set_imm16(*movPtr, static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
void TruncateToLutLimit(std::vector<u32> &emcList, size_t limit) {
emcList.resize(std::min(emcList.size(), limit));
}
void MtcGenerateJedecTable() {
const u32 jedecFreqs[] = { 1866000, 1996000, 2133000, 2400000, 2666000, 2933000, 3200000 };
const u32 jedecFreqs[] = { 1866000, 1996800, 2133000, 2400000, 2666000, 2933000, 3200000 };
constexpr u32 JedecFreqCount = std::size(jedecFreqs);
for (u32 i = 0; i < JedecFreqCount; ++i) {
@@ -710,11 +323,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
AppendMaxEmc(newEmcList);
TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerate133StepTable() {
@@ -729,11 +339,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
AppendMaxEmc(newEmcList);
TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerate33StepTable() {
@@ -756,11 +363,8 @@ namespace ams::ldr::hoc::pcv::mariko {
}
}
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
AppendMaxEmc(newEmcList);
TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
void MtcGenerateFreqTables() {
@@ -799,15 +403,13 @@ namespace ams::ldr::hoc::pcv::mariko {
u32 newFreq = EmcClkOSLimit + stepIndex * stepRate;
newFreq = (newFreq / RoundHz) * RoundHz;
if (newFreq > C.marikoEmcMaxClock) {
if (newEmcList.back() != C.marikoEmcMaxClock) {
newEmcList.push_back(static_cast<u32>(C.marikoEmcMaxClock));
}
AppendMaxEmc(newEmcList);
break;
}
newEmcList.push_back(newFreq);
}
newEmcList.resize(std::min(newEmcList.size(), EmcDvfsTableEntryLimit));
TruncateToLutLimit(newEmcList, EmcDvfsTableEntryLimit);
}
Result VerifyMtcTable(MarikoMtcTable *tableStart, u32 expectedFreq) {
@@ -921,6 +523,24 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
void Get32LutEmcList(std::vector<u32> &emcList) {
/* No need to reduce outside of 33MHz stepmode. */
if (C.stepMode != StepMode_33MHz || emcList.size() <= DvfsTableEntryLimit) {
return;
}
/* Add every other oc entry. */
std::vector<u32> reduced(emcList.begin(), emcList.begin() + mariko::MtcTableCountDefault);
for (size_t i = mariko::MtcTableCountDefault; i < emcList.size(); i += 2) {
reduced.push_back(emcList[i]);
}
TruncateToLutLimit(reduced, DvfsTableEntryLimit);
AppendMaxEmc(reduced);
emcList = std::move(reduced);
}
Result MemFreqDvbTable(u32 *ptr) {
DvbEntry *default_end = reinterpret_cast<DvbEntry *>(ptr);
DvbEntry *new_start = default_end + 1;
@@ -934,9 +554,9 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SKIP();
}
s32 max0 = 1050;
s32 max1 = 1025;
s32 max2 = 1000;
s32 max0 = 1050;
s32 max1 = 1025;
s32 max2 = 1000;
if (C.marikoSocVmax && C.marikoSocVmax > 1000) {
max0 = C.marikoSocVmax;
@@ -1011,12 +631,15 @@ namespace ams::ldr::hoc::pcv::mariko {
#undef DVB
#undef DVB_OC
const size_t dvbCount = std::min(newEmcList.size(), DvbTableCapacity);
DvbEntry emcDvbTableOc[DvbTableCapacity] = {};
std::vector<u32> reducedEmcList = newEmcList;
Get32LutEmcList(reducedEmcList);
const size_t dvbCount = std::min(reducedEmcList.size(), DvfsTableEntryCount);
DvbEntry emcDvbTableOc[DvfsTableEntryCount] = {};
u32 bracketIndex = 0;
for (size_t i = 0; i < dvbCount; ++i) {
const u32 freq = (i == dvbCount - 1) ? static_cast<u32>(newEmcList.back()) : newEmcList[i];
const u32 freq = reducedEmcList[i];
while (freq >= emcDvbOcTableBrackets[bracketIndex + 1].freq) {
++bracketIndex;
}
@@ -1026,7 +649,7 @@ namespace ams::ldr::hoc::pcv::mariko {
}
/* Clear the entire 32-entry region */
std::memset(mem_dvb_table_head, 0, DvbTableCapacity * sizeof(DvbEntry));
std::memset(mem_dvb_table_head, 0, DvfsTableEntryCount * sizeof(DvbEntry));
std::memcpy(mem_dvb_table_head, emcDvbTableOc, dvbCount * sizeof(DvbEntry));
R_SUCCEED();
@@ -1041,6 +664,37 @@ namespace ams::ldr::hoc::pcv::mariko {
R_SUCCEED();
}
Result MemMtcTableAsm(u32 *ptr) {
constexpr u32 AddpOffset = 1;
constexpr u32 BrOffset = 12;
constexpr u32 MovOffset = 10;
/* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddpOffset);
R_UNLESS(_asm::Ignoring(adrp, MtcAdrpAsm, _asm::field::ImmAdrpHi, _asm::field::ImmAdrpLo), ldr::ResultInvalidMtcTablePattern());
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
/* Pray this does not break. */
u32 br = *(ptr - BrOffset);
R_UNLESS(_asm::Ignoring(br, MtcBrAsm, _asm::field::Rn), ldr::ResultInvalidMtcTablePattern());
/* Pray this does not break either. */
u32 mov = *(ptr - MovOffset);
R_UNLESS(_asm::Ignoring(mov, MtcMovAsm, _asm::field::Rd), ldr::ResultInvalidMtcTablePattern());
u8 movRd = _asm::Get(mov, _asm::field::Rd);
u32 movCountPatch = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, movRd}, {_asm::field::Imm16, newEmcList.size()});
PATCH_OFFSET(ptr - BrOffset, _asm::NopIns);
PATCH_OFFSET(ptr - MovOffset, movCountPatch);
R_SUCCEED();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val) {
struct {
u8 reg;
@@ -1054,6 +708,7 @@ namespace ams::ldr::hoc::pcv::mariko {
cmd.val = val;
Result res = i2csessionSendAuto(&_session, &cmd, sizeof(cmd), I2cTransactionOption_All);
i2csessionClose(&_session);
return res;
}
@@ -1092,257 +747,4 @@ namespace ams::ldr::hoc::pcv::mariko {
return resultI2C;
}
Result MemMtcTableAsm(u32 *ptr) {
constexpr u32 AddpOffset = 1;
constexpr u32 BrOffset = 12;
constexpr u32 MovOffset = 10;
/* Ensure we don't dereference memory before nso start. */
R_UNLESS(ptr - BrOffset >= nsoStart, ldr::ResultInvalidMtcTablePattern());
u32 adrp = *(ptr - AddpOffset);
R_UNLESS(AsmCompareAdrpNoImm(adrp, MtcAdrpAsm), ldr::ResultInvalidMtcTablePattern());
/* We don't check for matching register because both registers must be x0 in order to pass the previous checks. */
/* The correct instructions will always be x0 since the mtcTable pointer is returned. */
/* Pray this does not break. */
u32 br = *(ptr - BrOffset);
R_UNLESS(AsmCompareBrNoRd(br, MtcBrAsm), ldr::ResultInvalidMtcTablePattern());
/* Pray this does not break either. */
u32 mov = *(ptr - MovOffset);
R_UNLESS(asm_compare_no_rd(mov, MtcMovAsm), ldr::ResultInvalidMtcTablePattern());
u8 movRd = asm_get_rd(mov);
u32 movCountPatch = asm_set_rd(asm_set_imm16(MtcMovAsm, newEmcList.size()), movRd);
PATCH_OFFSET(ptr - BrOffset, NopIns);
PATCH_OFFSET(ptr - MovOffset, movCountPatch);
R_SUCCEED();
}
Result GetSocSpeedo(u32 &socSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 SocSpeedoOffset = 308;
socSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
Result SocVoltAsm(u32 *compareSpeedos) {
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, asm_compare_no_rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = asm_get_rd(*writeProcessId);
/* This writes 1050mV. */
u32 *writeVoltage = ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, asm_compare_no_rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = asm_get_rd(*writeVoltage);
/* A csel instruction is used to select the soc voltage limit register. */
/* We care about its destination register since that is used for verification. */
constexpr u32 VoltageSelectScanLimit = 24;
u32 *selectVoltage = ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, AsmCompareCselNoReg);
R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
/* Todo: check rm and rn? */
u8 selectVoltageRd = asm_get_rd(*selectVoltage);
/* rdCsel is then multiplied by 1000 to convert to uV. */
/* This is pretty far down the function. */
constexpr u32 MultiplierScanLimit = 200;
u32 *multiplier = ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, AsmCompareMullNoReg);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = AsmGetMullRn(*multiplier);
u8 multiplierRm = AsmGetMullRm(*multiplier);
/* One of the two registers has to be rdCsel. */
R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
u8 multiplierRd = asm_get_rd(*multiplier);
/* Subs instruction is then used to verify against absolute limit. */
u32 limitValidationPattern = AsmSubsSetRn(SocVoltValidateLimitAsm, multiplierRd);
u32 *limitValidation = ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, AsmSubsCompareNoReg);
R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
/* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
u32 *branchToAbort = limitValidation + 1;
R_UNLESS(AsmCompareBrConNoImm19(*branchToAbort, SocVoltBranchToAbortAsm), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
}
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = AsmSubsSetImm12(*compareSpeedos, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
R_TRY(GetSocSpeedo(socSpeedo));
/* Adjust processId from 0 to [process id of switch booting this]. */
/* We're overwriting the orr instruction entirly. */
u32 processId = GetSocProcessId(socSpeedo);
u32 writeProcessIdPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, processId), writeProcessIdRd);
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = asm_set_rd(asm_set_imm16(SocVoltWriteVoltageAsm, C.marikoSocVmax), writeVoltageRd);
PATCH_OFFSET(writeVoltage, voltageLimitPatch);
/* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
PATCH_OFFSET(branchToAbort, NopIns);
R_SUCCEED();
}
Result SocVoltLimit(u32 *ptr) {
R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
R_SKIP();
}
constexpr u32 Step = 25;
u32 maxVolt = C.marikoSocVmax;
if (maxVolt % Step) {
maxVolt = maxVolt / Step * Step; /* Round. */
}
u32 volt = SocVoltLimitOfficial;
for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
volt += Step;
PATCH_OFFSET(ptr + i, volt);
}
R_SUCCEED();
}
Result EmcRateSessLimit(u32 *ptr) {
u32 movzI = 0;
R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
/* Reject cmd11 GetDvfsTable. */
for (u32 i = 1; i <= 24; ++i) {
const u32 w = ptr[i];
if (AsmIsSubX29Imm(w) && AsmGetImm12(w) >= 0x20u) { /* sub x?,x29,#>=0x20 */
R_THROW(ldr::ResultInvalidEmcRateList());
}
}
/* mov x<desc>,x2 */
u32 descReg = 0xFFu;
for (u32 i = 1; i <= 24; ++i) {
if (AsmIsMovReg(ptr[i], 2)) { descReg = asm_get_rd(ptr[i]); break; }
}
R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
/* Repoint the duplicated-imm pair */
u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
const u32 w = ptr[i];
if (AsmIsAddSpImm(w)) { /* add x?,sp,#imm12 (shift 0) */
adds[nAdds] = ptr + i;
addImm[nAdds] = AsmGetImm12(w);
++nAdds;
}
}
u32 patched = 0;
for (u32 a = 0; a < nAdds; ++a) {
bool dup = false;
for (u32 b = 0; b < nAdds; ++b) {
if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
}
if (dup) {
PATCH_OFFSET(adds[a], AsmMakeLdrImm64(asm_get_rd(*adds[a]), descReg, 0)); /* ldr x?,[x<desc>] */
++patched;
}
}
R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
PATCH_OFFSET(ptr, AsmSubsSetImm12(*ptr, static_cast<u16>(EmcDvfsTableEntryCount))); /* cmp w?,#64 */
PATCH_OFFSET(ptr + movzI, asm_set_imm16(*(ptr + movzI), static_cast<u16>(EmcDvfsTableEntryCount))); /* movz w?,#64 */
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
nsoStart = reinterpret_cast<u32 *>(mapped_nso);
g_pcv_scratch = mapped_nso + nso_size - HocPcvScratchSize;
MtcGenerateFreqTables();
u32 CpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(CpuCvbTableDefault)->freq);
u32 GpuCvbDefaultMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(GpuCvbTableDefault)->freq);
PatcherEntry<u32> patches[] = {
{ "CPU Freq Vdd", &CpuFreqVdd, 1, nullptr, CpuClkOSLimit },
{ "CPU Freq Table", CpuFreqCvbTable<true>, 1, nullptr, CpuCvbDefaultMaxFreq },
{ "CPU Volt DVFS", &CpuVoltDVFS, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Thermals", &CpuVoltThermals, 1, nullptr, CpuVminOfficial },
{ "CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, CpuTune0Low },
{ "GPU Volt DVFS", &GpuVoltDVFS, 1, nullptr, GpuVminOfficial },
{ "GPU Volt Thermals", &GpuVoltThermals, 1, nullptr, GpuVminOfficial },
{ "GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq },
{ "GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn },
{ "GPU PLL Max", &GpuFreqPllMax, 1, nullptr, GpuClkPllMax },
{ "GPU PLL Limit", &GpuFreqPllLimit, 4, nullptr, GpuClkPllLimit },
{ "MEM Freq Mtc", &MemFreqMtcTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit },
{ "MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit },
{ "MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit },
{ "MEM Vddq", &EmcVddqVolt, 2, nullptr, EmcVddqDefault },
{ "MEM Vdd2", &MemVoltHandler, 2, nullptr, MemVdd2Default },
{ "MEM Table Asm", &MemMtcTableAsm, 1, &MemMtcGetGetTablePatternFn },
{ "EMC DVFS Count", &EmcDvfsCountLimit, 1, &EmcDvfsCountPatternFn },
{ "EMC SoC LUT", &EmcSocLutReloc, 1, &EmcSocLutPatternFn },
{ "EMC Rate List", &EmcRateListLimit, 0, &EmcRateListPatternFn },
{ "EMC Rate Sess", &EmcRateSessLimit, 1, &EmcRateSessPatternFn },
{ "SOC Volt Asm", &SocVoltAsm, 1, &SocVoltPatternFn },
{ "SOC Volt Limit", &SocVoltLimit, 1, nullptr, SocVoltLimitOfficial },
};
for (uintptr_t ptr = mapped_nso; ptr <= mapped_nso + nso_size - sizeof(MarikoMtcTable); ptr += sizeof(u32)) {
u32 *ptr32 = reinterpret_cast<u32 *>(ptr);
for (auto &entry : patches) {
if (R_SUCCEEDED(entry.SearchAndApply(ptr32))) {
break;
}
}
}
for (auto &entry : patches) {
LOGGING("%s Count: %zu", entry.description, entry.patched_count);
if (R_FAILED(entry.CheckResult())) {
panic::SmcError(panic::Patch);
CRASH(entry.description);
}
}
}
}
@@ -0,0 +1,206 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* Copyright (c) B3711
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../pcv.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
struct DvbEntry {
u64 freq;
u32 volt[4] = {};
};
constexpr DvbEntry EmcDvbTableDefault[] = {
{ 204000, { 637, 637, 637, } },
{ 408000, { 637, 637, 637, } },
{ 800000, { 637, 637, 637, } },
{ 1065600, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
};
constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 EmcClkOSAlt = 1331'200;
constexpr u32 EmcClkPllmLimit = 2133'000'000;
constexpr u32 EmcVddqDefault = 600'000;
constexpr u32 MemVdd2Default = 1100'000;
constexpr u32 MTC_TABLE_REV = 3;
constexpr u32 MtcTableCountDefault = 3;
constexpr size_t MtcFullTableSize = sizeof(MarikoMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 17;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId : u64 {
HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 3,
AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 5,
IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 6,
IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 8,
IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 9,
IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 10,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE = 11,
HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 12,
HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 13,
HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 14,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE = 15,
IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 17,
IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 18,
HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 19,
IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 20,
HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 21,
AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 22,
HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 23,
AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 24,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 25,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF = 26,
AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 27,
AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 28,
IOWA_4GB_HYNIX_H54G46CYRBX267 = 29,
HOAG_4GB_HYNIX_H54G46CYRBX267 = 30,
AULA_4GB_HYNIX_H54G46CYRBX267 = 31,
IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 32,
HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB = 33,
AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 34,
};
enum MtcTableIndex {
T210b0SdevEmcDvfsTableS4gb01 = 0, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS4gb03 = 1, /* Samsung AM-MGCJ 4Gb */
T210b0SdevEmcDvfsTableS8gb03 = 2, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableH4gb03 = 3, /* Hynix NME 4Gb */
T210b0SdevEmcDvfsTableM4gb03 = 4, /* Micron WT:F 4Gb */
T210b0SdevEmcDvfsTableS4gbY01 = 5, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbY01 = 6, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y8gbY01 = 7, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbX03 = 8, /* Samsung AA-MGCL 4Gb */
T210b0SdevEmcDvfsTableS1y8gbX03 = 9, /* Samsung AA-MGCL 8Gb */
T210b0SdevEmcDvfsTableS1y4gb01 = 10, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableM1y4gb01 = 11, /* Micron WT:E 4Gb */
T210b0SdevEmcDvfsTableH1y4gb01 = 12, /* Hynix NEE 4Gb */
T210b0SdevEmcDvfsTableS1y8gb04 = 13, /* Samsung AM-MGCJ 8Gb */
T210b0SdevEmcDvfsTableS1z4gb01 = 14, /* Samsung AB-MGCL 4Gb */
T210b0SdevEmcDvfsTableH1a4gb01 = 15, /* Hynix x267 4Gb */
T210b0SdevEmcDvfsTableM1a4gb01 = 16, /* Micron WT:B 8Gb */
MtcTableIndex_Invalid = 17,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
const inline MtcDramIndex mtcIndexTable[] = {
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1z4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ IOWA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ HOAG_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ AULA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
};
/*
710006abfc 40 01 1f d6 br x10
*/
/*
710006ac28 a0 03 00 90 adrp x0,0x71000de000
710006ac2c 00 80 16 91 add x0=>SdevEmcDvfsTableS4gb01,x0,#0x5a0
*/
/* Br */
/*
| Z | OP | Fixed | A | M | RN | RM
31 30 29 28 27 26 25 | 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 |11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
1 1 0 1 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 Rn 0 0 0 0 0
Z op A M Rm
*/
/* Adrp */
/*
OP | ImmLow | | ImmHigh | RD
31 | 30 29 | 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
/* ADD (immediate) */
/*
SF | OP | S | Fixed value | Sh | Imm12 | RN | RD
31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800068;
constexpr u32 MtcAdrpAsm = 0x900003A0;
constexpr u32 MtcAddAsm = 0x91168000;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return _asm::Ignoring(*ptr, MtcAddAsm, _asm::field::Imm12);
}
void MtcGenerateFreqTables();
Result MemFreqMtcTable(u32 *ptr);
Result MemFreqDvbTable(u32 *ptr);
Result MemFreqMax(u32 *ptr);
Result MemMtcTableAsm(u32 *ptr);
Result EmcVddqVolt(u32 *ptr);
}
@@ -0,0 +1,325 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "../pcv.hpp"
#include "pcv_mariko_soc.hpp"
namespace ams::ldr::hoc::pcv::mariko {
namespace {
struct {
u32 *site = nullptr;
} busInitCache;
Result GetSocSpeedo(u32 &socSpeedo) {
constexpr u64 FusePhysicalAddress = 0x7000F000;
u64 virtualAddress = 0;
constexpr u64 Size = 0x1000;
u64 outSize;
/* TODO: use svc::QueryMemoryMapping instead. */
R_TRY(svcQueryMemoryMapping(&virtualAddress, &outSize, FusePhysicalAddress, Size));
constexpr u32 FuseOffset = 2048;
constexpr u32 SocSpeedoOffset = 0x134;
socSpeedo = *reinterpret_cast<u32 *>(virtualAddress + FuseOffset + SocSpeedoOffset);
R_SUCCEED();
}
u32 GetSocProcessId(u32 socSpeedo) {
if (socSpeedo <= 1597) {
return 0;
}
if (socSpeedo <= 1708) {
return 1;
}
/* >= 1709. */
return 2;
}
}
Result SocVoltAsm(u32 *compareSpeedos) {
using namespace _asm::field;
constexpr u32 VoltageScanLimit = 10;
/* Might actually be speedo id. */
u32 *writeProcessId = _asm::ScanAssembly(compareSpeedos, VoltageScanLimit, SocVoltWriteProcessIdAsm, Rd);
R_UNLESS(writeProcessId != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeProcessIdRd = _asm::Get(*writeProcessId, Rd);
/* This writes 1050mV. */
u32 *writeVoltage = _asm::ScanAssembly(writeProcessId, VoltageScanLimit, SocVoltWriteVoltageAsm, Rd);
R_UNLESS(writeVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 writeVoltageRd = _asm::Get(*writeVoltage, Rd);
/* A csel instruction is used to select the soc voltage limit register. */
/* We care about its destination register since that is used for verification. */
constexpr u32 VoltageSelectScanLimit = 24;
u32 *selectVoltage = _asm::ScanAssembly(writeVoltage, VoltageSelectScanLimit, SocVoltSelectRegisterAsm, Rd, Rn, Rm);
R_UNLESS(selectVoltage != nullptr, ldr::ResultInvalidSocVoltPattern());
/* Todo: check rm and rn? */
u8 selectVoltageRd = _asm::Get(*selectVoltage, Rd);
/* rdCsel is then multiplied by 1000 to convert to uV. */
/* This is pretty far down the function. */
constexpr u32 MultiplierScanLimit = 200;
u32 *multiplier = _asm::ScanAssembly(selectVoltage, MultiplierScanLimit, SocVoltMultiplyVoltsAsm, Rd, Rn, Rm);
R_UNLESS(multiplier != nullptr, ldr::ResultInvalidSocVoltPattern());
u8 multiplierRn = _asm::Get(*multiplier, Rn);
u8 multiplierRm = _asm::Get(*multiplier, Rm);
/* One of the two registers has to be rdCsel. */
R_UNLESS((multiplierRn == selectVoltageRd) || (multiplierRm == selectVoltageRd), ldr::ResultInvalidSocVoltPattern());
u8 multiplierRd = _asm::Get(*multiplier, Rd);
/* Subs instruction is then used to verify against absolute limit. */
/* NB: Rn is written into the pattern and then ignored by the comparison; only Rm is matched. */
u32 limitValidationPattern = _asm::Set(SocVoltValidateLimitAsm, Rn, multiplierRd);
u32 *limitValidation = _asm::ScanAssembly(multiplier, VoltageScanLimit, limitValidationPattern, Rd, Rn);
R_UNLESS(limitValidation != nullptr, ldr::ResultInvalidSocVoltPattern());
/* There is a b.gt instruction right after (checks for socVoltageCap < socVoltageMax). */
u32 *branchToAbort = limitValidation + 1;
R_UNLESS(_asm::Ignoring(*branchToAbort, SocVoltBranchToAbortAsm, Imm19), ldr::ResultInvalidSocVoltPattern());
if (!C.marikoSocVmax || C.marikoSocVmax <= 1000) {
R_SKIP();
}
/* Adjust 1598 speedo minimum to ensure it always goes down process id 0 branch. */
/* 2200 should be high enough :D */
u32 compareSpeedosPatch = _asm::Set(*compareSpeedos, Imm12, 2200);
PATCH_OFFSET(compareSpeedos, compareSpeedosPatch);
u32 socSpeedo = 0;
R_TRY(GetSocSpeedo(socSpeedo));
/* Adjust processId from 0 to [process id of switch booting this]. */
/* We're overwriting the orr instruction entirly. */
u32 processId = GetSocProcessId(socSpeedo);
u32 writeProcessIdPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeProcessIdRd}, {Imm16, processId});
PATCH_OFFSET(writeProcessId, writeProcessIdPatch);
/* Adjust voltage limit. */
u32 voltageLimitPatch = _asm::Encode(_asm::op::MovzW, {Rd, writeVoltageRd}, {Imm16, C.marikoSocVmax});
PATCH_OFFSET(writeVoltage, voltageLimitPatch);
/* Branches to an abort if limits are invalid -- we patch the branch instruction with NOP. */
PATCH_OFFSET(branchToAbort, _asm::NopIns);
R_SUCCEED();
}
Result SocVoltLimit(u32 *ptr) {
R_UNLESS(!std::memcmp(ptr - SocVoltLimitMaxDefaultIndex, socVoltLimitArray, sizeof(socVoltLimitArray)), ldr::ResultInvalidSocVoltLimit());
if (!C.marikoSocVmax || C.marikoSocVmax <= SocVoltLimitOfficial) {
R_SKIP();
}
constexpr u32 Step = 25;
u32 maxVolt = C.marikoSocVmax;
if (maxVolt % Step) {
maxVolt = maxVolt / Step * Step; /* Round. */
}
u32 volt = SocVoltLimitOfficial;
for (u32 i = 1; i < DvfsTableEntryCount - SocVoltLimitMaxDefaultIndex && volt < maxVolt; ++i) {
volt += Step;
PATCH_OFFSET(ptr + i, volt);
}
R_SUCCEED();
}
HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus) {
HookPayloadData *data = HOOK_PAYLOAD_PTR(HookPayloadData, m_HookPayloadData);
u32 *table = data->busData.next;
bus->allowedFreqTable = table;
data->busData.next = table + EmcDvfsTableEntryCount;
/* Call the original function. */
return reinterpret_cast<u64 (*)(SharedClkBus *)>(data->busData.originalFnCallback)(bus);
}
/* Relocate and extend C2/C3Bus to avoid issues (likely buffer overflow) */
Result BusFreqReloc(u32 *ptr) {
using namespace _asm::field;
const u32 bus = _asm::Get(ptr[0], Rn);
R_UNLESS(_asm::IsOp(ptr[1], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[1], Imm12) == 0x18 && _asm::Get(ptr[1], Rn) == bus,
ldr::ResultInvalidBusFreqReloc()); /* add Xcnt,Xbus,#0x18 */
R_UNLESS(_asm::IsOp(ptr[2], _asm::op::StrImm64, Rt, Rn, Off8) && _asm::Get(ptr[2], Off8) == 0x50 && _asm::Get(ptr[2], Rn) == bus,
ldr::ResultInvalidBusFreqReloc()); /* str Xrail,[Xbus,#0x50] */
R_UNLESS(_asm::IsOp(ptr[3], _asm::op::Bl, Imm26), ldr::ResultInvalidBusFreqReloc()); /* bl GetDvfsRailUniqueFreqList */
constexpr u32 PrologueMargin = 140;
u32 *functionPrologue = _asm::FindFnPrologue(ptr, PrologueMargin, nsoStart);
R_UNLESS(functionPrologue != nullptr, ldr::ResultInvalidBusFreqReloc());
busInitCache.site = functionPrologue;
R_SUCCEED();
}
/* Widen InitDram for a >32-entry EMC DVFS list. Freq array can be dropped to free 264 bytes, relocate the Soc LUT to that space */
Result EmcSocLutReloc(u32 *ptr) {
constexpr u32 Window = 48;
using namespace _asm::field;
R_UNLESS(_asm::Ignoring(ptr[1], EmcSocLutCountStoreAsm, Rt), ldr::ResultInvalidEmcSocLut()); /* str w?,[x0,#0x154] */
u32 *freqStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocFreqStoreAsm, Rt); /* str x?,[x8,#0x18] */
u32 *voltStore = _asm::ScanAssembly(ptr - Window, Window, EmcSocVoltStoreAsm, Rt); /* str w?,[x8,#0x48] */
u32 *readLoad = _asm::ScanAssembly(ptr - Window, Window, EmcSocReadLoadAsm, Rt); /* ldr w?,[x9,#0x48] */
R_UNLESS(freqStore && voltStore && readLoad, ldr::ResultInvalidEmcSocLut());
u32 *voltBase = voltStore - 2; /* `add Xb,Xsrc,Xi,LSL#2` (a cmn sits between it and store) */
R_UNLESS(_asm::IsOp(*voltBase, _asm::op::AddShifted64, Rd, Rn, Rm, Imm6) && _asm::Get(*voltBase, Rd) == _asm::Get(*voltStore, Rn), ldr::ResultInvalidEmcSocLut());
/* adrp Xl ; add Xl,Xl,#off ; ... ; str Xl,[rail,#0x120] */
const u32 lutReg = _asm::Get(ptr[0], Rt);
const u32 railReg = _asm::Get(ptr[0], Rn);
R_UNLESS(_asm::IsOp(ptr[-3], _asm::op::Adrp, Rd, ImmAdrpHi, ImmAdrpLo) && _asm::Get(ptr[-3], Rd) == lutReg, ldr::ResultInvalidEmcSocLut());
R_UNLESS(_asm::IsOp(ptr[-2], _asm::op::AddImm64, Rd, Rn, Imm12) && _asm::Get(ptr[-2], Rd) == lutReg && _asm::Get(ptr[-2], Rn) == lutReg, ldr::ResultInvalidEmcSocLut());
const u32 srcBase = _asm::Get(*voltBase, Rn); /* rail ptr at +0x20 */
const u32 wBase = _asm::Get(*voltBase, Rd); /* base reg */
const u32 wIdx = _asm::Get(*voltBase, Rm); /* loop index */
PATCH_OFFSET(freqStore, _asm::NopIns); /* Unneeded */
PATCH_OFFSET(voltBase, _asm::Encode(_asm::op::LdrImm64, {Rt, wBase}, {Rn, srcBase}, {Off8, 0x20})); /* ldr Xb,[Xsrc,#0x20] (rail) */
PATCH_OFFSET(voltStore - 1, _asm::Encode(_asm::op::AddImm64, {Rd, wBase}, {Rn, wBase}, {Imm12, 0x18})); /* add Xb,Xb,#0x18 (was cmn) */
PATCH_OFFSET(voltStore, _asm::ToRegOffset(*voltStore, wIdx)); /* str Wv,[Xb,Xi,LSL#2] -> rail+0x18+i*4 */
PATCH_OFFSET(voltStore + 1, _asm::NopIns); /* Unneeded */
/* rail+0x18 as the socMinLut pointer. */
PATCH_OFFSET(ptr - 3, _asm::NopIns);
PATCH_OFFSET(ptr - 2, _asm::Encode(_asm::op::AddImm64, {Rd, lutReg}, {Rn, railReg}, {Imm12, 0x18})); /* add Xl,rail,#0x18 */
/* Drop the abort branch in case of a bad read */
for (u32 i = 1; i <= 4; ++i) {
if (_asm::IsOp(readLoad[i], _asm::op::BCond, Imm19, BCond)) {
PATCH_OFFSET(&readLoad[i], _asm::NopIns);
break;
}
}
R_SUCCEED();
}
Result EmcDvfsCountLimit(u32 *ptr) {
R_UNLESS(_asm::IsOp(*(ptr - 1), _asm::op::Cbz, _asm::field::Imm19, _asm::field::Rt), ldr::ResultInvalidEmcDvfsCount()); /* cbz w?,<skip> */
R_UNLESS(_asm::Ignoring(*(ptr + 1), _asm::Encode(_asm::op::BCond, {_asm::field::BCond, _asm::cond::Cs}), _asm::field::Imm19),
ldr::ResultInvalidEmcDvfsCount()); /* b.cs <abort> */
/* cmp w?,#0x21 -> cmp w?,#EmcDvfsTableEntryCount */
PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount));
R_SUCCEED();
}
Result EmcRateListLimit(u32 *ptr) {
R_UNLESS(_asm::Ignoring(ptr[1], EmcRateCapCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm)
&& _asm::Get(ptr[0], _asm::field::Rn) == _asm::Get(ptr[1], _asm::field::Rn),
ldr::ResultInvalidEmcRateList()); /* csel w?,w?,w?,lt, min(reg, 0x20) */
R_UNLESS(_asm::IsOp(ptr[2], _asm::op::Bl, _asm::field::Imm26), ldr::ResultInvalidEmcRateList());
/* The csel's Rm holds the 32 cap. */
const u32 capReg = _asm::Get(ptr[1], _asm::field::Rm);
const u32 capMov = _asm::Encode(_asm::op::MovzW, {_asm::field::Rd, capReg}, {_asm::field::Imm16, 0x20}); /* movz w<Rm>,#0x20 */
u32 *movPtr = nullptr;
for (u32 i = 1; i <= 16; ++i) {
if (*(ptr - i) == capMov) {
movPtr = ptr - i;
break;
}
}
R_UNLESS(movPtr, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount). */
PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */
PATCH_OFFSET(movPtr, _asm::Set(*movPtr, _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */
R_SUCCEED();
}
Result EmcRateSessLimit(u32 *ptr) {
u32 movzI = 0;
R_UNLESS(EmcRateSessFindClamp(ptr, nullptr, nullptr, &movzI), ldr::ResultInvalidEmcRateList());
/* Reject cmd11 GetDvfsTable. */
for (u32 i = 1; i <= 24; ++i) {
const u32 w = ptr[i];
if (_asm::IsOp(w, _asm::op::SubImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12)
&& _asm::Get(w, _asm::field::Rn) == _asm::reg::Fp && _asm::Get(w, _asm::field::Imm12) >= 0x20u) { /* sub x?,x29,#>=0x20 */
R_THROW(ldr::ResultInvalidEmcRateList());
}
}
/* mov x<desc>,x2 */
u32 descReg = 0xFFu;
for (u32 i = 1; i <= 24; ++i) {
if (_asm::Ignoring(ptr[i], _asm::MovReg64(0, 2), _asm::field::Rd)) { descReg = _asm::Get(ptr[i], _asm::field::Rd); break; }
}
R_UNLESS(descReg != 0xFFu, ldr::ResultInvalidEmcRateList());
/* Repoint the duplicated-imm pair */
u32 *adds[8]; u32 addImm[8]; u32 nAdds = 0;
for (u32 i = 1; i <= 24 && nAdds < 8; ++i) {
const u32 w = ptr[i];
if (_asm::IsOp(w, _asm::op::AddImm64, _asm::field::Rd, _asm::field::Rn, _asm::field::Imm12) && _asm::Get(w, _asm::field::Rn) == _asm::reg::Sp) { /* add x?,sp,#imm12 (shift 0) */
adds[nAdds] = ptr + i;
addImm[nAdds] = _asm::Get(w, _asm::field::Imm12);
++nAdds;
}
}
u32 patched = 0;
for (u32 a = 0; a < nAdds; ++a) {
bool dup = false;
for (u32 b = 0; b < nAdds; ++b) {
if (a != b && addImm[a] == addImm[b]) { dup = true; break; }
}
if (dup) {
PATCH_OFFSET(adds[a], _asm::Encode(_asm::op::LdrImm64, {_asm::field::Rt, _asm::Get(*adds[a], _asm::field::Rd)},
{_asm::field::Rn, descReg},
{_asm::field::Off8, 0})); /* ldr x?,[x<desc>] */
++patched;
}
}
R_UNLESS(patched == 2, ldr::ResultInvalidEmcRateList());
/* min(maxCount, 32) -> min(maxCount, EmcDvfsTableEntryCount) */
PATCH_OFFSET(ptr, _asm::Set(*ptr, _asm::field::Imm12, EmcDvfsTableEntryCount)); /* cmp w?,#64 */
PATCH_OFFSET(ptr + movzI, _asm::Set(*(ptr + movzI), _asm::field::Imm16, EmcDvfsTableEntryCount)); /* movz w?,#64 */
R_SUCCEED();
}
Result SharedClkBusInstallHooks(HookPayloadData *data) {
R_UNLESS(busInitCache.site != nullptr, ldr::ResultInvalidBusFreqReloc());
uintptr_t originalFn = 0;
R_TRY(INSTALL_IMPL_HOOK_ORIG(busInitCache.site, BusFreqExtendImpl, &originalFn));
data->busData.originalFnCallback = originalFn;
data->busData.next = reinterpret_cast<u32 *>(Hooks().ToVa(data->busData.table[0]));
R_SUCCEED();
}
}
@@ -0,0 +1,132 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "pcv_mariko.hpp"
namespace ams::ldr::hoc::pcv::mariko {
extern u32 *nsoStart;
/* Movz */
/*
SF | OPC | HW | Imm16 | RD
31 | 30 29 28 27 26 25 24 23 | 22 21 | 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 SocVoltCompareSpeedoAsm = 0x7118FAFF; /* subs imm, compares to >=1598 max speedo and then goes down process id 1 route. */
constexpr u32 SocVoltWriteProcessIdAsm = 0x2A1F03F4; /* orr, writes id 0. */
constexpr u32 SocVoltWriteVoltageAsm = 0x52808358; /* Movz imm, writes 1050mV. */
constexpr u32 SocVoltSelectRegisterAsm = 0x1A9A3118; /* Csel, selects the voltage -- we need the register of this. */
constexpr u32 SocVoltMultiplyVoltsAsm = 0x1B1A7F0B; /* Mul, converts from mV -> uV */
constexpr u32 SocVoltValidateLimitAsm = 0x6B0A017F; /* Subs, checks limits */
constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
return _asm::Ignoring(*ptr, SocVoltCompareSpeedoAsm, _asm::field::Rd);
}
constexpr u32 SocVoltLimitOfficial = 1050;
constexpr u32 SocVoltLimitMaxDefaultIndex = 17;
static const u32 socVoltLimitArray[DvfsTableEntryCount] = { 637, 650, 675, 700, 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, };
constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
/*
str <lut>,[<rail>,#0x120] ; volt-array pointer
str w?, [<rail>,#0x154] ; num_freqs
*/
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut) */
inline bool EmcDvfsCountPatternFn(u32 *ptr) {
return _asm::Ignoring(*ptr, EmcCountCmpAsm, _asm::field::Rd);
}
inline bool EmcSocLutPatternFn(u32 *ptr) {
return _asm::Ignoring(*ptr, EmcSocLutPtrStoreAsm, _asm::field::Rt); /* str x?,[x0,#0x120] */
}
/*
mov w?,#0x20 ; the 32 cap
cmp w?,#0x20
csel w?,w?,w?,lt ; w? = min(maxCount, 32)
bl TegraGetEmcDvfsFreqTable
cmp w?,#0x20 ; (maxCount)
csel w?,<same>,<cap>,lt ; min(maxCount, 32)
bl <_asm::Get*DvfsFreqTable>
*/
constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */
inline bool EmcRateListPatternFn(u32 *ptr) {
return _asm::Ignoring(*ptr, EmcRateCapCmpAsm, _asm::field::Rd, _asm::field::Rn);
}
constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateSessMovAsm = 0x52800400; /* movz w?,#0x20 */
constexpr u32 EmcRateSessCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt (opcode + cond) */
inline bool EmcRateSessFindClamp(u32 *ptr, u32 *out_c, u32 *out_cap, u32 *out_movz_i) {
const u32 c = _asm::Get(ptr[0], _asm::field::Rn);
for (u32 i = 1; i <= 14; ++i) {
const u32 w = ptr[i];
if (_asm::Ignoring(w, EmcRateSessCselAsm, _asm::field::Rd, _asm::field::Rn, _asm::field::Rm) && _asm::Get(w, _asm::field::Rn) == c && _asm::Get(w, _asm::field::Rd) == c) {
const u32 cap = _asm::Get(w, _asm::field::Rm);
for (u32 j = 1; j < i; ++j) {
if (_asm::Ignoring(ptr[j], EmcRateSessMovAsm, _asm::field::Rd) && _asm::Get(ptr[j], _asm::field::Rd) == cap) {
if (out_c) *out_c = c;
if (out_cap) *out_cap = cap;
if (out_movz_i) *out_movz_i = j;
return true;
}
}
return false;
}
}
return false;
}
inline bool EmcRateSessPatternFn(u32 *ptr) {
return _asm::Ignoring(*ptr, EmcRateSessCmpAsm, _asm::field::Rd, _asm::field::Rn);
}
inline bool BusFreqRelocPatternFn(u32 *ptr) {
if (g_pcv_cave == 0) {
return false;
}
if (reinterpret_cast<uintptr_t>(ptr + 4) > g_pcv_cave) { /* the call site lives in .text */
return false;
}
return _asm::IsOp(*ptr, _asm::op::LdrImm64, _asm::field::Rt, _asm::field::Rn, _asm::field::Off8)
&& _asm::Get(*ptr, _asm::field::Off8) == 0x10; /* ldr Xbuf,[Xbus,#0x10] */
}
Result SocVoltAsm(u32 *compareSpeedos);
Result SocVoltLimit(u32 *ptr);
HOOK_PAYLOAD_FN u64 BusFreqExtendImpl(SharedClkBus *bus);
Result BusFreqReloc(u32 *ptr);
Result EmcSocLutReloc(u32 *ptr);
Result EmcDvfsCountLimit(u32 *ptr);
Result EmcRateListLimit(u32 *ptr);
Result EmcRateSessLimit(u32 *ptr);
Result SharedClkBusInstallHooks(HookPayloadData *data);
}
@@ -82,6 +82,7 @@ namespace ams::ldr::hoc::pcv {
u32 max;
u32 panic;
bool value_required = false;
const char *name = "unnamed";
Result Check() {
if (!value_required && !value) {
@@ -107,6 +108,7 @@ namespace ams::ldr::hoc::pcv {
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
u32 eristaGpuDvfsMaxFreq;
switch (C.eristaGpuUV) {
case 0:
@@ -146,26 +148,28 @@ namespace ams::ldr::hoc::pcv {
}
Validator validators[] = {
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true },
{ C.marikoCpuBoostClock, 1020'000, 2703'000, panic::Cpu, true },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, },
{ C.marikoCpuMaxVolt, 1000, 1200, panic::Cpu, },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, },
{ marikoCpuDvfsMaxFreq, 1785'000, 2703'000, panic::Cpu, },
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, }, /* Official vmax for the RAMs is 1400-1500mV */
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, },
{ C.marikoEmcVddqVolt, 400'000, 750'000, panic::Emc, },
{ C.marikoSocVmax, 1000, 1200, panic::Emc, },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, panic::Gpu, },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000, panic::Gpu, },
{ C.marikoGpuVmax, 800, 960, panic::Gpu, },
{ C.eristaCpuBoostClock, 1020'000, 2397'000, panic::Cpu, true, "eristaCpuBoostClock" },
{ C.marikoCpuBoostClock, 1020'000, 2805'000, panic::Cpu, true, "marikoCpuBoostClock" },
{ C.eristaCpuMaxVolt, 1000, 1260, panic::Cpu, false, "eristaCpuMaxVolt" },
{ C.marikoCpuMaxVolt, 1000, 1235, panic::Cpu, false, "marikoCpuMaxVolt" },
{ eristaCpuDvfsMaxFreq, 1785'000, 2397'000, panic::Cpu, false, "eristaCpuDvfsMaxFreq"},
{ marikoCpuDvfsMaxFreq, 1785'000, 2805'000, panic::Cpu, false, "marikoCpuDvfsMaxFreq"},
{ C.commonEmcMemVolt, 912'500, 1350'000, panic::Emc, false, "commonEmcMemVolt" }, /* Official vmax for the RAMs is 1400-1500mV */
{ C.eristaEmcMaxClock, 1600'000, 2600'000, panic::Emc, false, "eristaEmcMaxClock" },
{ C.marikoEmcMaxClock, 1600'000, 3500'000, panic::Emc, false, "marikoEmcMaxClock" },
{ C.marikoEmcVddqVolt, 400'000, 750'000, panic::Emc, false, "marikoEmcVddqVolt" },
{ C.marikoSocVmax, 1000, 1200, panic::Emc, false, "marikoSocVmax" },
{ eristaGpuDvfsMaxFreq, 768'000, 1152'000, panic::Gpu, false, "eristaGpuDvfsMaxFreq"},
{ marikoGpuDvfsMaxFreq, 768'000, 1574'400, panic::Gpu, false, "marikoGpuDvfsMaxFreq"},
{ C.marikoGpuVmax, 800, 995, panic::Gpu, false, "marikoGpuVmax" }, /* Official GPU vMax is 1050mV */
};
for (auto &v : validators) {
if (R_FAILED(v.Check())) {
LOGGING("Validation FAIL: %s value=%u min=%u max=%u", v.name, static_cast<u32>(v.value), v.min, v.max);
panic::SmcError(v.panic);
CRASH("Validation FAIL");
}
}
}
@@ -176,9 +180,14 @@ namespace ams::ldr::hoc::pcv {
R_DISCARD(SmcCopyToIram(LoadMagicAddress, &hocMagic, sizeof(hocMagic)));
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena) {
SafetyCheck();
Hooks().Initialize(mapped_nso, nso_address, cave, cave_size, data_arena);
g_pcv_cave = cave;
g_pcv_cave_size = cave_size;
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko) {
mariko::Patch(mapped_nso, nso_size);
@@ -22,8 +22,18 @@
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_erista.hpp"
#include "pcv_mariko.hpp"
#include "erista/pcv_erista_cpu.hpp"
#include "erista/pcv_erista_gpu.hpp"
#include "erista/pcv_erista_mtc.hpp"
#include "erista/pcv_erista.hpp"
#include "mariko/pcv_mariko.hpp"
#include "mariko/pcv_mariko_cpu.hpp"
#include "mariko/pcv_mariko_gpu.hpp"
#include "mariko/pcv_mariko_mtc.hpp"
#include "pcv_hook.hpp"
namespace ams::ldr::hoc::pcv {
@@ -36,7 +46,8 @@ namespace ams::ldr::hoc::pcv {
return true;
};
template <bool isMariko> Result CpuFreqCvbTable(u32 *ptr) {
template <bool isMariko>
Result CpuFreqCvbTable(u32 *ptr) {
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault);
cvb_entry_t *customize_table = nullptr;
@@ -111,7 +122,8 @@ namespace ams::ldr::hoc::pcv {
PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0);
}
template <bool isMariko> Result GpuFreqCvbTable(u32 *ptr) {
template <bool isMariko>
Result GpuFreqCvbTable(u32 *ptr) {
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault);
cvb_entry_t *customize_table;
if (isMariko) {
@@ -185,6 +197,7 @@ namespace ams::ldr::hoc::pcv {
}
++entry;
}
if (C.commonGpuVoltOffset && !(isMariko ? C.marikoGpuUV : C.eristaGpuUV)) {
cvb_entry_t *entry = static_cast<cvb_entry_t *>(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; ++i) {
@@ -200,6 +213,6 @@ namespace ams::ldr::hoc::pcv {
Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2
void SafetyCheck();
void Patch(uintptr_t mapped_nso, size_t nso_size);
void Patch(uintptr_t mapped_nso, size_t nso_size, uintptr_t cave, size_t cave_size, uintptr_t nso_address, uintptr_t data_arena);
}
@@ -22,11 +22,246 @@
#include <stratosphere.hpp>
namespace ams::ldr::hoc::pcv {
namespace ams::ldr::hoc::pcv::_asm {
constexpr u32 NopIns = 0xD503201F;
constexpr u32 RetIns = 0xD65F03C0;
constexpr u32 BrkIns = 0xD4200000;
template <typename Compare>
constexpr u32 GetField(u32 ins, u8 lsb, u8 width) {
return (ins >> lsb) & ((1u << width) - 1u);
}
constexpr u32 SetField(u32 ins, u8 lsb, u8 width, u32 val) {
const u32 mask = ((1u << width) - 1u) << lsb;
return (ins & ~mask) | ((val << lsb) & mask);
}
constexpr u32 FieldMask(u8 lsb, u8 width) {
return ((1u << width) - 1u) << lsb;
}
constexpr s64 SignExtend(u64 v, u32 bits) {
const u32 pad = 64u - bits;
return static_cast<s64>(v << pad) >> pad;
}
namespace field {
struct Descriptor {
u8 lsb;
u8 width;
u8 shift = 0;
};
constexpr Descriptor Rd { 0, 5}; /* destination register */
constexpr Descriptor Rn { 5, 5}; /* first source register */
constexpr Descriptor Rm {16, 5}; /* second source register */
constexpr Descriptor Ra {10, 5}; /* accumulator register */
constexpr Descriptor Rt { 0, 5}; /* load/store transfer register */
constexpr Descriptor Rt2 {10, 5}; /* second transfer register of a pair */
constexpr Descriptor Cond {12, 4}; /* condition code (CSEL) */
constexpr Descriptor BCond { 0, 4}; /* condition code (B.cond) */
constexpr Descriptor Hw {21, 2}; /* MOVZ/MOVK/MOVN shift */
constexpr Descriptor Sh {22, 1};
constexpr Descriptor Shift {22, 2}; /* ADD/SUB (shifted register) shift type */
constexpr Descriptor Imm6 {10, 6}; /* ADD/SUB (shifted register) shift amount */
constexpr Descriptor Immr {16, 6}; /* UBFM immr */
constexpr Descriptor Imm16 { 5, 16}; /* MOVZ/MOVK/MOVN immediate */
constexpr Descriptor Imm12 {10, 12}; /* ADD/SUB (immediate) */
constexpr Descriptor Imm19 { 5, 19}; /* B.cond / CBZ / CBNZ immediate */
constexpr Descriptor Imm26 { 0, 26}; /* B / BL immediate */
constexpr Descriptor ImmAdrpHi { 5, 19};
constexpr Descriptor ImmAdrpLo {29, 2};
constexpr Descriptor Off1 {10, 12, 0}; /* LDRB/STRB */
constexpr Descriptor Off4 {10, 12, 2}; /* 32-bit LDR/STR */
constexpr Descriptor Imm9 {12, 9}; /* pre/post-index offset */
constexpr Descriptor Barrier { 8, 4}; /* DSB/DMB option (CRm) */
constexpr Descriptor Off8 {10, 12, 3}; /* 64-bit LDR/STR */
constexpr Descriptor PairOff8 {15, 7, 3}; /* X pairs */
constexpr Descriptor PairOff16 {15, 7, 4}; /* Q pairs */
}
/* Regs that read as a name in the encoding. */
namespace reg {
constexpr u32 Fp = 29;
constexpr u32 Lr = 30;
constexpr u32 Sp = 31;
constexpr u32 Wzr = 31;
constexpr u32 Xzr = 31;
}
namespace cond {
constexpr u32 Eq = 0x0, Ne = 0x1, Cs = 0x2, Cc = 0x3,
Mi = 0x4, Pl = 0x5, Vs = 0x6, Vc = 0x7,
Hi = 0x8, Ls = 0x9, Ge = 0xA, Lt = 0xB,
Gt = 0xC, Le = 0xD, Al = 0xE;
constexpr u32 Hs = Cs, Lo = Cc;
}
/* Base opcodes. */
/* Either pair with Encode or Ignoring. */
namespace op {
constexpr u32 AddImm32 = 0x11000000;
constexpr u32 AddImm64 = 0x91000000;
constexpr u32 SubImm64 = 0xD1000000;
constexpr u32 SubsImm32 = 0x71000000;
constexpr u32 SubsImm64 = 0xF1000000;
constexpr u32 MovzW = 0x52800000;
constexpr u32 MovkW = 0x72800000;
constexpr u32 MovnW = 0x12800000;
constexpr u32 AddShifted64 = 0x8B000000;
constexpr u32 SubShifted64 = 0xCB000000;
constexpr u32 AndImm32 = 0x12000000;
constexpr u32 AndImm64 = 0x92400000;
constexpr u32 Ubfm32 = 0x53000000; /* UBFIZ/UBFX/LSL aliases */
constexpr u32 OrrShifted32 = 0x2A000000;
constexpr u32 OrrShifted64 = 0xAA000000;
constexpr u32 SubsShifted32 = 0x6B000000;
constexpr u32 Csel32 = 0x1A800000;
constexpr u32 Madd32 = 0x1B000000;
constexpr u32 LdrbImm = 0x39400000;
constexpr u32 StrbImm = 0x39000000;
constexpr u32 LdrImm32 = 0xB9400000;
constexpr u32 LdrsWImm64 = 0xB9800000; /* LDRSW x, 32-bit mem, Off4-scaled */
constexpr u32 StrImm32 = 0xB9000000;
constexpr u32 LdrImm64 = 0xF9400000;
constexpr u32 LdrPreImm64 = 0xF8400C00; /* LDR (pre-index): bits 11:10 fixed */
constexpr u32 StrImm64 = 0xF9000000;
constexpr u32 StpImm64 = 0xA9000000;
constexpr u32 StpPreImm64 = 0xA9800000;
constexpr u32 StpImmQ = 0xAD000000;
constexpr u32 B = 0x14000000;
constexpr u32 Bl = 0x94000000;
constexpr u32 BCond = 0x54000000;
constexpr u32 Cbz = 0x34000000;
constexpr u32 Br = 0xD61F0000;
constexpr u32 Adrp = 0x90000000;
constexpr u32 Svc = 0xD4000001;
constexpr u32 Dsb = 0xD503309F; /* DSB, option cleared */
constexpr u32 Dmb = 0xD50330BF; /* DMB, option cleared */
constexpr u32 Isb = 0xD5033FDF; /* ISB SY (only valid option) */
}
/* Barrier options (CRm). */
namespace barrier {
constexpr u32 Sy = 0xFu, St = 0xEu, Ld = 0xDu;
constexpr u32 Ish = 0xBu, Ishst = 0xAu, Ishld = 0x9u;
constexpr u32 Nsh = 0x7u, Nshst = 0x6u, Nshld = 0x5u;
constexpr u32 Osh = 0x3u, Oshst = 0x2u, Oshld = 0x1u;
}
constexpr u32 Get(u32 ins, field::Descriptor f) {
return GetField(ins, f.lsb, f.width) << f.shift;
}
constexpr u32 Set(u32 ins, field::Descriptor f, u64 val) {
return SetField(ins, f.lsb, f.width, static_cast<u32>(val >> f.shift));
}
template <typename... Fs>
constexpr bool Ignoring(u32 a, u32 b, Fs... fields) {
const u32 mask = (0u | ... | FieldMask(fields.lsb, fields.width));
return (a & ~mask) == (b & ~mask);
}
inline constexpr auto IgnoringRd = [](u32 a, u32 b) {
return Ignoring(a, b, field::Rd);
};
template <typename... Fs>
constexpr bool IsOp(u32 ins, u32 baseOpcode, Fs... variableFields) {
return Ignoring(ins, baseOpcode, variableFields...);
}
struct FieldValue {
field::Descriptor f;
u64 val;
};
constexpr u32 Encode(u32 base, FieldValue a) {
return Set(base, a.f, a.val);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b) {
return Encode(Encode(base, a), b);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c) {
return Encode(Encode(base, a, b), c);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c, FieldValue d) {
return Encode(Encode(base, a, b, c), d);
}
constexpr u32 Encode(u32 base, FieldValue a, FieldValue b, FieldValue c, FieldValue d, FieldValue e) {
return Encode(Encode(base, a, b, c, d), e);
}
constexpr uintptr_t Target26(u32 ins, uintptr_t pc) {
return static_cast<uintptr_t>(static_cast<s64>(pc) + SignExtend(static_cast<u64>(Get(ins, field::Imm26)) << 2, 28));
}
constexpr u32 Retarget26(u32 ins, uintptr_t pc, uintptr_t target) {
return Set(ins, field::Imm26, static_cast<u32>((static_cast<s64>(target) - static_cast<s64>(pc)) >> 2));
}
constexpr uintptr_t Target19(u32 ins, uintptr_t pc) {
return static_cast<uintptr_t>(static_cast<s64>(pc) + SignExtend(static_cast<u64>(Get(ins, field::Imm19)) << 2, 21));
}
constexpr u32 Retarget19(u32 ins, uintptr_t pc, uintptr_t target) {
return Set(ins, field::Imm19, static_cast<u32>((static_cast<s64>(target) - static_cast<s64>(pc)) >> 2));
}
constexpr uintptr_t TargetAdrp(u32 ins, uintptr_t pc) {
const u64 imm21 = (static_cast<u64>(Get(ins, field::ImmAdrpHi)) << 2) | Get(ins, field::ImmAdrpLo);
return static_cast<uintptr_t>(static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF)) + SignExtend(imm21 << 12, 33));
}
constexpr u32 RetargetAdrp(u32 ins, uintptr_t pc, uintptr_t target) {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF)) - static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm21 = static_cast<u32>((delta >> 12) & 0x1FFFFF);
return Set(Set(ins, field::ImmAdrpHi, imm21 >> 2), field::ImmAdrpLo, imm21 & 0x3u);
}
constexpr u32 Dsb(u32 opt) {
return Encode(op::Dsb, {field::Barrier, opt});
}
constexpr u32 Dmb(u32 opt) {
return Encode(op::Dmb, {field::Barrier, opt});
}
constexpr u32 MovReg64(u32 rd, u32 rm) {
return Encode(op::OrrShifted64, {field::Rd, rd}, {field::Rn, reg::Xzr}, {field::Rm, rm});
}
constexpr u32 CmpImm32(u32 rn, u32 imm12) {
return Encode(op::SubsImm32, {field::Rd, reg::Wzr}, {field::Rn, rn}, {field::Imm12, imm12});
}
constexpr u32 ToRegOffset(u32 ldstImm, u32 rm) {
constexpr u32 Keep = 0xC0C00000u | FieldMask(field::Rt.lsb, field::Rt.width) | FieldMask(field::Rn.lsb, field::Rn.width);
return ((ldstImm & Keep) | 0x38207800u) | ((rm & 0x1Fu) << field::Rm.lsb);
}
template <typename Compare> requires (!std::is_same_v<std::remove_cvref_t<Compare>, field::Descriptor>)
u32 *ScanAssembly(u32 *ptr, u32 scanLimit, u32 pattern, Compare comp) {
for (u32 i = 0; i < scanLimit; ++i) {
if (comp(pattern, ptr[i])) {
@@ -36,245 +271,82 @@ namespace ams::ldr::hoc::pcv {
return nullptr;
}
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 5) == 0;
};
inline auto asm_get_rd = [](u32 ins) {
return ins & ((1 << 5) - 1);
};
/* Rn (bits 9:5) and Rm (bits 20:16) to get registers. */
inline auto AsmGetRn = [](u32 ins) -> u32 { return (ins >> 5) & 0x1Fu; };
inline auto AsmGetRm = [](u32 ins) -> u32 { return (ins >> 16) & 0x1Fu; };
/* Add (shifted register), 64-bit: sf=1 op=0 S=0 01011 shift(2) 0 Rm imm6 Rn Rd. */
inline auto AsmIsAddShiftedReg64 = [](u32 ins) {
return (ins & 0xFF200000u) == 0x8B000000u;
};
inline auto asm_set_rd = [](u32 ins, u8 rd) {
return (ins & 0xFFFFFFE0) | (rd & 0x1F);
};
inline auto asm_set_imm16 = [](u32 ins, u16 imm) {
return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5);
};
inline auto AsmGetImm16 = [](u32 ins) {
return static_cast<u16>((ins >> 5) & 0xFFFF);
};
inline auto AsmCompareBrNoRd = [](u32 ins1, u32 ins2) {
constexpr u32 RegMask = ~(((1 << 5) - 1) << 5);
return ((ins1 & RegMask) ^ (ins2 & RegMask)) == 0;
};
inline auto AsmCompareAddNoImm12 = [](u32 ins1, u32 ins2) {
constexpr u32 Imm12Mask = ~(((1 << 12) - 1) << 10);
return ((ins1 & Imm12Mask) ^ (ins2 & Imm12Mask)) == 0;
};
inline auto AsmCompareAdrpNoImm = [](u32 ins1, u32 ins2) {
constexpr u32 ImmMask = ~((((1 << 2) - 1) << 29) | (((1 << 19) - 1) << 5));
return ((ins1 & ImmMask) ^ (ins2 & ImmMask)) == 0;
};
/* Csel (Conditional Select) */
/*
SF | Op | S | | RM | Cond | 0 | 0 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 23 | 20 19 18 17 16 | 15 14 13 12 | 11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmCbzCompareOpcodeOnly = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 24) == 0;
};
inline auto AsmBlCompareOpcodeOnly = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 26) == 0;
};
inline auto AsmIsAdrX0 = [](u32 ins) {
return (ins & 0x9F00001Fu) == 0x10000000u;
};
inline auto AsmAdrTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43;
return static_cast<uintptr_t>(static_cast<s64>(pc) + imm);
};
/* adrp Rd, target */
inline auto AsmMakeAdrp = [](uintptr_t pc, uintptr_t target, u32 rd) -> u32 {
const s64 delta = static_cast<s64>(target & ~static_cast<uintptr_t>(0xFFF))
- static_cast<s64>(pc & ~static_cast<uintptr_t>(0xFFF));
const u32 imm = static_cast<u32>((delta >> 12) & 0x1FFFFF); /* 21-bit page */
const u32 immlo = imm & 0x3;
const u32 immhi = (imm >> 2) & 0x7FFFF;
return 0x90000000u | (immlo << 29) | (immhi << 5) | (rd & 0x1Fu);
};
inline auto AsmSetAdrTarget = [](u32 ins, uintptr_t pc, uintptr_t target) -> u32 {
const s64 delta = static_cast<s64>(target) - static_cast<s64>(pc);
const u32 immlo = static_cast<u32>(delta & 0x3);
const u32 immhi = static_cast<u32>((delta >> 2) & 0x7FFFF);
return (ins & ~((0x3u << 29) | (0x7FFFFu << 5))) | (immlo << 29) | (immhi << 5);
};
/* adrp: bit31=1, bits 28:24 = 10000. */
inline auto AsmIsAdrp = [](u32 ins) -> bool {
return (ins & 0x9F000000u) == 0x90000000u;
};
inline auto AsmAdrpPageOffset = [](u32 ins) -> s64 {
s64 imm = static_cast<s64>((((ins >> 5) & 0x7FFFFu) << 2) | ((ins >> 29) & 0x3u));
imm = (imm << 43) >> 43; /* sign-extend the 21-bit immediate */
return imm << 12;
};
/* add (immediate), 64-bit: sf=1 op=0 S=0 100010 sh imm12 Rn Rd. */
inline auto AsmIsAddImm64 = [](u32 ins) -> bool {
return (ins & 0xFF800000u) == 0x91000000u;
};
inline auto AsmGetImm12 = [](u32 ins) -> u32 {
return (ins >> 10) & 0xFFFu;
};
inline auto AsmMakeAddImm64 = [](u32 rd, u32 rn, u32 imm12) -> u32 {
return 0x91000000u | ((imm12 & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rd & 0x1Fu);
};
/* movz Wd,#imm16 (no shift). */
inline auto AsmMakeMovzW = [](u32 rd, u16 imm16) -> u32 {
return 0x52800000u | (static_cast<u32>(imm16) << 5) | (rd & 0x1Fu);
};
/* mov Xd,Xm == orr Xd,XZR,Xm. */
inline auto AsmMakeMovReg = [](u32 rd, u32 rm) -> u32 {
return 0xAA0003E0u | ((rm & 0x1Fu) << 16) | (rd & 0x1Fu);
};
/* ldr Xt,[Xn,#byteOff] */
inline auto AsmMakeLdrImm64 = [](u32 rt, u32 rn, u32 byteOff) -> u32 {
return 0xF9400000u | (((byteOff / 8u) & 0xFFFu) << 10) | ((rn & 0x1Fu) << 5) | (rt & 0x1Fu);
};
/* mov Xd,X<rm> == orr Xd,XZR,X<rm> (matches any Rd). */
inline auto AsmIsMovReg = [](u32 ins, u32 rm) -> bool {
return (ins & 0xFFFFFFE0u) == (0xAA0003E0u | ((rm & 0x1Fu) << 16));
};
/* add Xd,sp,#imm12 (shift 0). */
inline auto AsmIsAddSpImm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0x910003E0u;
};
/* sub Xd,x29,#imm12 (shift 0). */
inline auto AsmIsSubX29Imm = [](u32 ins) -> bool {
return (ins & 0xFFC003E0u) == 0xD10003A0u;
};
inline auto AsmIsB = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x14000000u; }; /* b */
inline auto AsmIsBl = [](u32 ins) -> bool { return (ins & 0xFC000000u) == 0x94000000u; }; /* bl */
inline auto AsmIsBCond = [](u32 ins) -> bool { return (ins & 0xFF000010u) == 0x54000000u; }; /* b.c */
/* Byte target address of a b/bl at pc. */
inline auto AsmBranchTarget = [](u32 ins, uintptr_t pc) -> uintptr_t {
s64 off = static_cast<s64>((ins & 0x03FFFFFFu) << 2);
off = (off << 36) >> 36; /* sign-extend the 28-bit branch offset */
return static_cast<uintptr_t>(static_cast<s64>(pc) + off);
};
/* Rewrite a scaled immediate-offset load/store (`op Wt,[Xn,#imm]`) into its register-offset form */
inline auto AsmSetLdStRegOffset = [](u32 ldstImm, u32 rm) -> u32 {
return (ldstImm & 0xC0C003FFu) | 0x38207800u | ((rm & 0x1Fu) << 16);
};
inline auto AsmIsLdpX = [](u32 ins) {
return (ins & 0xFE400000u) == 0xA8400000u;
};
inline bool AsmComparePrologue(u32 ins1, u32 ins2, u32 ins3, u32 cmp1, u32 cmp2, u32 cmp3) {
constexpr u32 StpImmMask = ~((((1u << 7) - 1u) << 15));
bool firstMatch = (ins1 & StpImmMask) == (cmp1 & StpImmMask);
constexpr u32 StpRegsImmMask = ~(((1u << 5) - 1u) |(((1u << 5) - 1u) << 10) | (((1u << 7) - 1u) << 15));
bool secondMatch = (ins2 & StpRegsImmMask) == (cmp2 & StpRegsImmMask);
constexpr u32 MovMask = ~((1u << 5) - 1u);
bool thirdMatch = (ins3 & MovMask) == (cmp3 & MovMask);
return firstMatch && secondMatch && thirdMatch;
template <typename... Fs>
u32 *ScanAssembly(u32 *ptr, u32 scanLimit, u32 pattern, Fs... ignoreFields) {
return ScanAssembly(ptr, scanLimit, pattern, [=](u32 a, u32 b) ALWAYS_INLINE_LAMBDA { return Ignoring(a, b, ignoreFields...); });
}
inline auto AsmCompareCselNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
constexpr bool IsFramePushPre(u32 ins) {
constexpr u32 Pattern = Encode(op::StpPreImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::PairOff8);
}
/* Mul */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 | 20 19 18 17 16 | 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmCompareMullNoReg = [](u32 ins1, u32 ins2) {
constexpr u32 ClearReg = ~(((1 << 10) - 1) | (((1 << 5) - 1) << 16));
return ((ins1 & ClearReg) ^ (ins2 & ClearReg)) == 0;
};
constexpr bool IsFramePushOffset(u32 ins) {
constexpr u32 Pattern = Encode(op::StpImm64, {field::Rt, reg::Fp}, {field::Rt2, reg::Lr}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::PairOff8);
}
/* Mul */
/* MUL W11, W24, W26 */
/* multiplies by 1000, mV -> uV */
/*
SF | Op54 | Op31 | RM | o0 | RA | RN | RD
31 | 30 29 28 27 26 25 24 | 23 22 21 | 20 19 18 17 16 | 15 | 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmGetMullRn = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 5;
return (ins & Mask) >> 5;
};
constexpr bool IsSpSubImm(u32 ins) {
constexpr u32 Pattern = Encode(op::SubImm64, {field::Rd, reg::Sp}, {field::Rn, reg::Sp});
return Ignoring(ins, Pattern, field::Imm12);
}
inline auto AsmGetMullRm = [](u32 ins) {
constexpr u32 Mask = ((1 << 5) - 1) << 16;
return (ins & Mask) >> 16;
};
constexpr bool IsFramePush(u32 ins) {
return IsFramePushPre(ins) || IsFramePushOffset(ins);
}
/* Subs (Shifted register) */
/*
SF | Op | S | | Shift | 0 | RM | Imm6 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 | 23 22 | 21 | 20 19 18 17 16 | 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmSubsSetRn = [](u32 ins, u8 rn) {
constexpr u32 RnMaskClear = ~(((1u << 5) - 1u) << 5);
constexpr u32 RnMaskSet = (1u << 5) - 1u;
constexpr bool IsCondBranch(u32 ins) {
return (ins & 0xFF000010u) == op::BCond;
}
return (ins & RnMaskClear) | ((static_cast<u32>(rn) & RnMaskSet) << 5);
};
constexpr bool IsCompareBranch(u32 ins) {
return (ins & 0x7E000000u) == op::Cbz;
}
/* Subs (Immediate) */
constexpr bool IsTestBranch(u32 ins) {
return (ins & 0x7E000000u) == 0x36000000u;
}
/*
SF | Op | S | | Sh | Imm12 | Rn | Rd
31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
inline auto AsmSubsSetImm12 = [](u32 ins, u16 imm12) {
constexpr u32 ClearMask = ~(((1u << 12) - 1) << 10);
constexpr u32 SetImm12Mask = ( 1u << 12) - 1;
inline u32 *FindFnEnd(u32 *prologue, u32 *limit) {
u32 *furthest = prologue;
for (u32 *ptr = prologue; ptr < limit; ++ptr) {
const u32 ins = *ptr;
if (ins == RetIns && ptr >= furthest) {
return ptr + 1;
}
return (ins & ClearMask) | ((imm12 & SetImm12Mask) << 10);
};
s64 delta = 0;
if (IsCondBranch(ins) || IsCompareBranch(ins)) {
delta = SignExtend(static_cast<u64>(Get(ins, field::Imm19)) << 2, 21);
} else if (IsTestBranch(ins)) {
delta = SignExtend(static_cast<u64>(GetField(ins, 5, 14)) << 2, 16);
}
inline auto AsmSubsCompareNoReg = [](u32 ins1, u32 ins2) {
return ((ins1 ^ ins2) >> 10) == 0;
};
if (ptr + (delta >> 2) > furthest) {
furthest = ptr + (delta >> 2);
}
}
inline auto AsmCompareBrConNoImm19 = [](u32 ins1, u32 ins2) {
constexpr u32 ClearImm19 = ~(((1 << 19) - 1) << 5);
return (ins1 & ClearImm19) == (ins2 & ClearImm19);
};
return nullptr;
}
inline u32 *FindFnPrologue(u32 *ptr, u32 margin, u32 *nsoStart) {
for (u32 i = 0; i <= margin; ++i) {
u32 *candidate = ptr - i;
if (candidate < nsoStart) {
break;
}
if (IsFramePushPre(*candidate)) {
return candidate;
}
if (IsFramePushOffset(*candidate) && candidate - 1 >= nsoStart && IsSpSubImm(candidate[-1])) {
return candidate - 1;
}
}
return nullptr;
}
}
@@ -163,6 +163,30 @@ namespace ams::ldr::hoc::pcv {
};
static_assert(sizeof(CvbMeta) == 0x78);
struct SubClockStub {
u8 _unknown[0x48];
};
struct SharedClkBus {
u64 moduleId;
u32 currentRateHz;
u32 padding0;
u32 *allowedFreqTable;
u32 allowedFreqCount;
u32 padding1;
SubClockStub *subClocksTable;
u32 subClockCount;
u32 padding2;
void *unk30;
void *clockSource;
u32 targetRateHz;
u32 saveDivFlag;
u32 idleRateHz; /* Unsure. */
u32 alwaysOnFlag;
u64 clockSession;
};
static_assert(sizeof(SharedClkBus) == 0x58);
constexpr u32 CpuClkOSLimit = 1785'000;
constexpr u32 GpuClkOsLimit = 921'600;
constexpr u32 EmcClkOSLimit = 1600'000;
@@ -170,6 +194,7 @@ namespace ams::ldr::hoc::pcv {
#define R_SKIP() R_SUCCEED()
// Count 32 / Index 31 is reserved to be empty
/* The actual lut table has a size of 33. */
constexpr size_t DvfsTableEntryCount = 32;
constexpr size_t DvfsTableEntryLimit = DvfsTableEntryCount - 1;
@@ -177,11 +202,11 @@ namespace ams::ldr::hoc::pcv {
constexpr size_t EmcDvfsTableEntryCount = 64;
constexpr size_t EmcDvfsTableEntryLimit = EmcDvfsTableEntryCount - 1;
// The pcv SoC-voltage DVB table is a fixed 32-entry region. (it doesn't need to be larger)
constexpr size_t DvbTableCapacity = 32;
/* Note: According to TRM the lut size is 33, but up to 64 LUT is possible. */
constexpr size_t ClDvfsLutSize = 64;
// extra .bss location, 0 until Patch() runs.
inline uintptr_t g_pcv_scratch = 0;
inline uintptr_t g_pcv_cave = 0;
inline size_t g_pcv_cave_size = 0;
template<typename T>
size_t GetDvfsTableEntryCount(T *table_head) {
@@ -1,165 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::erista {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPU_PLL_CVB_TABLE_ODN
{ 204000, {721094}, { } },
{ 306000, {754040}, { } },
{ 408000, {786986}, { } },
{ 510000, {819932}, { } },
{ 612000, {852878}, { } },
{ 714000, {885824}, { } },
{ 816000, {918770}, { } },
{ 918000, {951716}, { } },
{ 1020000, {984662}, { -2875621, 358099, -8585} },
{ 1122000, {1017608}, { -52225, 104159, -2816} },
{ 1224000, {1050554}, { 1076868, 8356, -727} },
{ 1326000, {1083500}, { 2208191, -84659, 1240} },
{ 1428000, {1116446}, { 2519460, -105063, 1611} },
{ 1581000, {1130000}, { 2889664, -122173, 1834} },
{ 1683000, {1168000}, { 5100873, -279186, 4747} },
{ 1785000, {1227500}, { 5100873, -279186, 4747} },
{ },
};
constexpr u32 CpuVoltOfficial = 1227;
constexpr u32 CpuVminOfficial = 825;
constexpr u32 CpuTune0Low = 0xFFEAD0FF;
constexpr u32 CpuVoltL4T = 1257'000;
static const u32 cpuVoltDvfsPattern[] = { 1227, 1000, 100, 1000, 0 };
static_assert(sizeof(cpuVoltDvfsPattern) == 0x14, "Invalid cpuVoltDvfsPattern size");
static const u32 cpuVoltageThermalPattern[] = { 950, 1132, 0, 950, 1227, 0, 825, 1227, 15000, 825, 1170, 60000, 825, 1132, 80000 };
static_assert(sizeof(cpuVoltageThermalPattern) == 0x3c, "Invalid cpuVoltageThermalPattern size");
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuClkPllMax = 921'600'000;
constexpr u32 GpuVminOfficial = 810;
constexpr u16 CpuMinVolts[] = { 950, 850, 825, 810 };
static const u32 gpuVoltDvfsPattern[] = { 810, 1150, 1000, 100, 1000, 10, };
static_assert(sizeof(gpuVoltDvfsPattern) == (sizeof(u32) * 6), "Invalid gpuVoltDvfsPattern");
static const u32 gpuVoltThermalPattern[] = { 950, 1132, 0, 810, 1132, 15000, 810, 1132, 30000, 810, 1132, 50000, 810, 1132, 70000, 810, 1132, 105000 };
static_assert(sizeof(gpuVoltThermalPattern) == 0x48, "Invalid gpuVoltageThermalPattern size");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
};
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// NA_FREQ_CVB_TABLE
{ 76800, {}, { 814294, 8144, -940, 808, -21583, 226, } },
{ 153600, {}, { 856185, 8144, -940, 808, -21583, 226, } },
{ 230400, {}, { 898077, 8144, -940, 808, -21583, 226, } },
{ 307200, {}, { 939968, 8144, -940, 808, -21583, 226, } },
{ 384000, {}, { 981860, 8144, -940, 808, -21583, 226, } },
{ 460800, {}, { 1023751, 8144, -940, 808, -21583, 226, } },
{ 537600, {}, { 1065642, 8144, -940, 808, -21583, 226, } },
{ 614400, {}, { 1107534, 8144, -940, 808, -21583, 226, } },
{ 691200, {}, { 1149425, 8144, -940, 808, -21583, 226, } },
{ 768000, {}, { 1191317, 8144, -940, 808, -21583, 226, } },
{ 844800, {}, { 1233208, 8144, -940, 808, -21583, 226, } },
{ 921600, {}, { 1275100, 8144, -940, 808, -21583, 226, } },
{ },
};
constexpr u32 EmcListDefault[] = { 40800, 68000, 102000, 204000, 408000, 665600, 800000, 1065600, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 MemVoltHOS = 1125'000;
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
constexpr u32 MtcTableCountDefault = 10;
constexpr size_t MtcFullTableSize = sizeof(EristaMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 3;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId {
ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH = 0,
ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE = 1,
ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC = 2,
ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH = 4,
ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX = 7,
};
enum MtcTableIndex {
T210SdevEmcDvfsTableS4gb01 = 0, /* HB-MGCH, WT:C */
T210SdevEmcDvfsTableS6gb01 = 1, /* HM-MGCH */
T210SdevEmcDvfsTableH4gb01 = 2, /* HR-NLE */
MtcTableIndex_Invalid = 3,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
/* TODO: Test 6gb and 8gb. */
const inline MtcDramIndex mtcIndexTable[] = {
{ ICOSA_4GB_SAMSUNG_K4F6E304HB_MGCH, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_4GB_MICRON_MT53B512M32D2NP_062_WTC, T210SdevEmcDvfsTableS4gb01, },
{ ICOSA_6GB_SAMSUNG_K4FHE3D4HM_MGCH, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_8GB_SAMSUNG_K4FBE3D4HM_MGXX, T210SdevEmcDvfsTableS6gb01, },
{ ICOSA_4GB_HYNIX_H9HCNNNBPUMLHR_NLE, T210SdevEmcDvfsTableH4gb01, },
};
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800148;
constexpr u32 MtcAdrpAsm = 0xD0000081;
constexpr u32 MtcBlIns = 0x97ffae64;
constexpr u32 MtcAddAsm = 0x91131821;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -0,0 +1,384 @@
/*
* Copyright (c) Lightos_
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
/* aligned(4) is the ARM64 instruction alignment. This saves some memory over 16 byte padding */
#define HOOK_PAYLOAD_FN __attribute__((section("hoc_hookpayload"), used, noinline, visibility("hidden"), aligned(4)))
/* Inline asm because fuck compilers: GCC refuses to put a variable and a function in the same section. */
/* It wants alloc+write for one and alloc+exec for the other. */
/* You are supposed to be able to override that by writing the flags into the section name yourself, */
/* like section("hoc_hookpayload,\"ax\",%progbits"), instead of letting GCC pick them. */
/* But GCC takes that whole string as the name and never reads the flags out of it, so it appends the */
/* ones it wanted anyway: the .section directive it emits ends up carrying two sets of flags, and the */
/* assembler rejects it. The failure lands in the generated assembly, not in anything we wrote. */
/* Therefore we must use inline assembly, which reaches the assembler exactly as written. */
#define DEFINE_HOOK_PAYLOAD_PTR(type, name) \
asm(".section hoc_hookpayload,\"ax\",%progbits\n" \
".balign 8\n" \
".global " #name "\n" \
".hidden " #name "\n" \
#name ": .zero 8\n" \
".text\n"); \
extern "C" __attribute__((visibility("hidden"))) type *name
#define DECLARE_HOOK_PAYLOAD_PTR(type, name) \
extern "C" __attribute__((visibility("hidden"))) type *name
#define HOOK_PAYLOAD_PTR(type, name) \
([]() -> type * { \
type **_hoc_pp; \
__asm__("adr %0, " #name : "=r"(_hoc_pp)); \
return *_hoc_pp; \
}())
extern "C" const u8 __start_hoc_hookpayload[];
extern "C" const u8 __stop_hoc_hookpayload[];
namespace ams::ldr::hoc::pcv {
constexpr size_t HookPageSize = 0x1000;
constexpr size_t PcvDataArenaSize = 0x1000;
constexpr size_t HookStubReserve = 0x80;
inline s64 SignExtend(u64 value, int bits) {
const int shift = 64 - bits;
return static_cast<s64>(value << shift) >> shift;
}
inline u32 EncodeRelBranch(u32 opc, uintptr_t site_va, uintptr_t target_va) {
const s64 delta = static_cast<s64>(target_va) - static_cast<s64>(site_va);
AMS_ABORT_UNLESS((delta & 0x3) == 0);
AMS_ABORT_UNLESS(delta >= -0x08000000 && delta <= 0x07FFFFFC);
return opc | (static_cast<u32>(delta >> 2) & 0x03FFFFFFu);
}
inline u32 EncodeB(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x14000000u, site_va, target_va); }
inline u32 EncodeBL(uintptr_t site_va, uintptr_t target_va) { return EncodeRelBranch(0x94000000u, site_va, target_va); }
inline u32 EncodePairSp(bool load, u32 rt1, u32 rt2, s32 imm) {
const u32 base = load ? 0xA9400000u : 0xA9000000u;
const u32 imm7 = static_cast<u32>((imm / 8) & 0x7F);
return base | (imm7 << 15) | (rt2 << 10) | (31u << 5) | rt1;
}
inline u32 EncodeSpAdjust(bool sub, u32 imm12) {
const u32 base = sub ? 0xD1000000u : 0x91000000u;
return base | ((imm12 & 0xFFFu) << 10) | (31u << 5) | 31u;
}
inline Result RelocateInstruction(u32 insn, uintptr_t old_site_va, uintptr_t new_site_va, u32 *out) {
const u32 top6 = insn & 0xFC000000u;
if (top6 == 0x14000000u || top6 == 0x94000000u) {
const s64 old_imm = SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
const uintptr_t target = old_site_va + old_imm;
*out = EncodeRelBranch(top6, new_site_va, target);
R_SUCCEED();
}
R_UNLESS((insn & 0xFF000010u) != 0x54000000u, ldr::ResultHookRelocationUnsupported()); /* b.cond */
R_UNLESS((insn & 0x7E000000u) != 0x34000000u, ldr::ResultHookRelocationUnsupported()); /* cbz/cbnz */
R_UNLESS((insn & 0x7E000000u) != 0x36000000u, ldr::ResultHookRelocationUnsupported()); /* tbz/tbnz */
R_UNLESS((insn & 0x1F000000u) != 0x10000000u, ldr::ResultHookRelocationUnsupported()); /* adr/adrp */
R_UNLESS((insn & 0x3B000000u) != 0x18000000u, ldr::ResultHookRelocationUnsupported()); /* ldr literal */
*out = insn;
R_SUCCEED();
}
class HookContext {
private:
uintptr_t m_map_base = 0; /* loader-side base of pcv's NSO mapping. */
uintptr_t m_va_base = 0; /* pcv-side base of the same memory. */
uintptr_t m_cave = 0; /* loader-side cave base. (0 if unavailable) */
size_t m_cave_size = 0;
uintptr_t m_payload = 0; /* loader-side base of the payload copy. */
size_t m_payload_sz = 0;
size_t m_used = 0;
uintptr_t m_data = 0; /* loader-side data arena base, 0 if none. */
size_t m_data_used = 0;
public:
constexpr HookContext() = default;
void Initialize(uintptr_t map_base, uintptr_t va_base, uintptr_t cave, size_t cave_size, uintptr_t data) {
m_map_base = map_base;
m_va_base = va_base;
m_cave = cave;
m_cave_size = cave_size;
m_payload = 0;
m_payload_sz = 0;
m_used = 0;
m_data = data;
m_data_used = 0;
}
void SetCave(uintptr_t cave, size_t cave_size) {
m_cave = cave;
m_cave_size = cave_size;
}
static size_t RequiredCaveSize() {
return util::AlignUp(static_cast<size_t>(__stop_hoc_hookpayload - __start_hoc_hookpayload), sizeof(u32)) + HookStubReserve;
}
bool IsEnabled() const { return m_cave != 0 && m_cave_size != 0; }
Result CheckEnabled() const {
R_UNLESS(this->IsEnabled(), ldr::ResultHookUnavailable());
R_SUCCEED();
}
size_t CaveSize() const { return m_cave_size; }
size_t CaveUsed() const { return m_used; }
size_t CaveFree() const { return m_cave_size > m_used ? m_cave_size - m_used : 0; }
size_t DataSize() const { return m_data != 0 ? PcvDataArenaSize : 0; }
size_t DataUsed() const { return m_data_used; }
size_t DataFree() const { return this->DataSize() - m_data_used; }
/* Convert loader mapped address to pcv-side address. */
uintptr_t ToVa(const void *loader_ptr) const {
return m_va_base + (reinterpret_cast<uintptr_t>(loader_ptr) - m_map_base);
}
uintptr_t CaveVa() const { return ToVa(reinterpret_cast<const void *>(m_cave)); }
uintptr_t CaveBase() const { return m_cave; }
/* Todo remove? */
u32 *Reserve(size_t words) { return this->AllocCode(words); }
Result CopyPayload() {
R_TRY(this->CheckEnabled());
const size_t size = static_cast<size_t>(__stop_hoc_hookpayload - __start_hoc_hookpayload);
/* Zero length: Linker garbage collected the payload .(happens when nothing references it) */
/* __start_ and _stop_ don't prevent this. */
/* Copying here would succeed at first but fail later by jumping to empty memory. */
R_UNLESS(size != 0, ldr::ResultUninitializedPatcher());
R_UNLESS(size <= m_cave_size, ldr::ResultHookPayloadTooLarge());
m_payload = m_cave;
m_payload_sz = size;
std::memcpy(reinterpret_cast<void *>(m_payload), __start_hoc_hookpayload, size);
m_used = util::AlignUp(size, sizeof(u32));
R_SUCCEED();
}
/* pcv-side address of a payload symbol's copy. */
uintptr_t PayloadVa(const void *loader_sym) const {
const uintptr_t offset = reinterpret_cast<uintptr_t>(loader_sym) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
return this->ToVa(reinterpret_cast<void *>(m_payload + offset));
}
/* Loader-side, writable pointer to a payload variable's copy in the cave. */
template<typename T>
T *PayloadCopyOf(T &loader_sym) const {
const uintptr_t offset = reinterpret_cast<uintptr_t>(std::addressof(loader_sym)) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload);
return reinterpret_cast<T *>(m_payload + offset);
}
/* Reserves space in the writable data arena past pcv's .bss. */
/* Returns a zeroed, loader-side pointer. */
/* The data arena is too far from the cave to be addressed directly by symbol, so we must store a pointer to it in the cave section. */
template<typename T>
T *DataAlloc() {
const size_t size = util::AlignUp(sizeof(T), alignof(T) > 8 ? alignof(T) : 8);
if (m_data == 0 || m_data_used + size > PcvDataArenaSize) {
return nullptr;
}
T *p = reinterpret_cast<T *>(m_data + m_data_used);
m_data_used += size;
std::memset(p, 0, sizeof(T));
return p;
}
template<typename T>
T *BindData(T *&loader_sym) {
T *block = this->DataAlloc<T>();
if (block != nullptr) {
*this->PayloadCopyOf(loader_sym) = reinterpret_cast<T *>(this->ToVa(block));
}
return block;
}
/* Replaces the function entirely starting at function prologue. */
/* Preserves the function arguments. */
Result InstallImpl(u32 *site, const void *fn, uintptr_t *out_orig = nullptr) {
R_TRY(this->CheckEnabled());
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
R_TRY(this->ValidatePayloadFn(fn));
if (out_orig != nullptr) {
u32 *tramp = this->AllocCode(2);
R_UNLESS(tramp != nullptr, ldr::ResultHookArenaOutOfMemory());
u32 relocated;
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&tramp[0]), std::addressof(relocated)));
tramp[0] = relocated;
tramp[1] = EncodeB(this->ToVa(&tramp[1]), this->ToVa(site) + sizeof(u32));
*out_orig = this->ToVa(tramp);
}
site[0] = EncodeB(this->ToVa(site), this->PayloadVa(fn));
R_SUCCEED();
}
/* Takes the same arguments as the hooked function, does not replace. */
Result InstallIntercept(u32 *site, const void *fn) {
R_TRY(this->CheckEnabled());
R_UNLESS(site != nullptr, ldr::ResultHookSiteInvalid());
R_UNLESS(m_payload != 0, ldr::ResultUninitializedPatcher());
R_TRY(this->ValidatePayloadFn(fn));
/* x0-x7: arguments */
/* x8: result pointer */
/* x18: platform register */
/* x30: return address */
/* x29: keeps pairs clean */
/* x9-x17: scratch */
constexpr u32 FrameSize = 0x60;
constexpr u32 Pairs[][2] = { {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 18}, {29, 30} };
constexpr u32 PairCount = sizeof(Pairs) / sizeof(Pairs[0]);
constexpr u32 StubWords = 1 + PairCount + 1 + PairCount + 1 + 1 + 1;
u32 *stub = this->AllocCode(StubWords);
R_UNLESS(stub != nullptr, ldr::ResultHookArenaOutOfMemory());
u32 i = 0;
stub[i++] = EncodeSpAdjust(true, FrameSize);
for (u32 p = 0; p < PairCount; ++p) {
stub[i++] = EncodePairSp(false, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
}
stub[i] = EncodeBL(this->ToVa(&stub[i]), this->PayloadVa(fn));
++i;
for (u32 p = 0; p < PairCount; ++p) {
stub[i++] = EncodePairSp(true, Pairs[p][0], Pairs[p][1], static_cast<s32>(p * 16));
}
stub[i++] = EncodeSpAdjust(false, FrameSize);
u32 relocated;
R_TRY(RelocateInstruction(site[0], this->ToVa(site), this->ToVa(&stub[i]), std::addressof(relocated)));
stub[i] = relocated;
++i;
stub[i] = EncodeB(this->ToVa(&stub[i]), this->ToVa(site) + sizeof(u32));
++i;
AMS_ABORT_UNLESS(i == StubWords);
site[0] = EncodeB(this->ToVa(site), this->ToVa(stub));
R_SUCCEED();
}
/* Checks entry to first ret that everything still points to valid data. */
Result ValidatePayloadFn(const void *fn) const {
constexpr u32 MaxInstructions = 512;
constexpr u32 RetInsn = 0xD65F03C0u;
constexpr u32 BrMask = 0xFFFFFC1Fu;
constexpr u32 BrInsn = 0xD61F0000u;
const uintptr_t lo = m_payload;
const uintptr_t hi = m_payload + m_payload_sz;
const u32 *insns = reinterpret_cast<const u32 *>(fn);
const uintptr_t base = m_payload + (reinterpret_cast<uintptr_t>(fn) - reinterpret_cast<uintptr_t>(__start_hoc_hookpayload));
auto check = [&](uintptr_t target) -> Result {
if (target < lo || target >= hi) {
R_THROW(ldr::ResultHookPayloadEscapes());
}
R_SUCCEED();
};
for (u32 i = 0; i < MaxInstructions; ++i) {
const u32 insn = insns[i];
const uintptr_t site = base + i * sizeof(u32);
if (insn == RetInsn || (insn & BrMask) == BrInsn) {
R_SUCCEED();
}
const u32 top6 = insn & 0xFC000000u;
if (top6 == 0x14000000u || top6 == 0x94000000u) { /* b / bl */
const uintptr_t target = site + SignExtend(static_cast<u64>(insn & 0x03FFFFFFu) << 2, 28);
R_TRY(check(target));
} else if ((insn & 0xFF000010u) == 0x54000000u || /* b.cond */
(insn & 0x7E000000u) == 0x34000000u || /* cbz / cbnz */
(insn & 0x3B000000u) == 0x18000000u) { /* ldr literal */
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2, 21);
R_TRY(check(target));
} else if ((insn & 0x7E000000u) == 0x36000000u) { /* tbz / tbnz */
const uintptr_t target = site + SignExtend(static_cast<u64>((insn >> 5) & 0x3FFFu) << 2, 16);
R_TRY(check(target));
} else if ((insn & 0x9F000000u) == 0x10000000u) { /* adr */
const u64 imm = (static_cast<u64>((insn >> 5) & 0x7FFFFu) << 2) | ((insn >> 29) & 0x3u);
const uintptr_t target = site + SignExtend(imm, 21);
R_TRY(check(target));
} else if ((insn & 0x9F000000u) == 0x90000000u) { /* adrp */
/* ADRP is page-relative and the cave is at an arbitrary offset, any adrp would point to potential garbage. */
R_THROW(ldr::ResultHookPayloadEscapes());
}
}
/* No return found. */
R_THROW(ldr::ResultHookPayloadEscapes());
}
private:
u32 *AllocCode(size_t words) {
const size_t bytes = words * sizeof(u32);
if (!this->IsEnabled() || m_payload == 0 || m_used + bytes > m_cave_size) {
return nullptr;
}
u32 *p = reinterpret_cast<u32 *>(m_cave + m_used);
m_used += bytes;
return p;
}
};
inline HookContext &Hooks() {
static HookContext s_context;
return s_context;
}
}
/* Hook custom impl. Original becomes unreachable. */
/* Starts at function entry. Preserves arguments. */
#define INSTALL_IMPL_HOOK(site, fn) \
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn))))
/* Maintains original function. */
/* Starts at function entry. Preserves arguments. */
#define INSTALL_IMPL_HOOK_ORIG(site, fn, out_orig) \
(::ams::ldr::hoc::pcv::Hooks().InstallImpl((site), reinterpret_cast<const void *>(&(fn)), (out_orig)))
/* Takes the same arguments as the hooked function, does not replace it and cannot change what it does */
/* but it can be placed anywhere, not just at a function entry. */
#define INSTALL_INTERC_HOOK(site, fn) \
(::ams::ldr::hoc::pcv::Hooks().InstallIntercept((site), reinterpret_cast<const void *>(&(fn))))
@@ -1,376 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "../oc_common.hpp"
#include "pcv_common.hpp"
#include "pcv_asm.hpp"
namespace ams::ldr::hoc::pcv::mariko {
constexpr cvb_entry_t CpuCvbTableDefault[] = {
{ 204000, { 721589, -12695, 27 }, { } },
{ 306000, { 747134, -14195, 27 }, { } },
{ 408000, { 776324, -15705, 27 }, { } },
{ 510000, { 809160, -17205, 27 }, { } },
{ 612000, { 845641, -18715, 27 }, { } },
{ 714000, { 885768, -20215, 27 }, { } },
{ 816000, { 929540, -21725, 27 }, { } },
{ 918000, { 976958, -23225, 27 }, { } },
{ 1020000, { 1028021, -24725, 27 }, { 1120000 } },
{ 1122000, { 1082730, -26235, 27 }, { 1120000 } },
{ 1224000, { 1141084, -27735, 27 }, { 1120000 } },
{ 1326000, { 1203084, -29245, 27 }, { 1120000 } },
{ 1428000, { 1268729, -30745, 27 }, { 1120000 } },
{ 1581000, { 1374032, -33005, 27 }, { 1120000 } },
{ 1683000, { 1448791, -34505, 27 }, { 1120000 } },
{ 1785000, { 1527196, -36015, 27 }, { 1120000 } },
{ 1887000, { 1609246, -37515, 27 }, { 1120000 } },
{ 1963500, { 1675751, -38635, 27 }, { 1120000 } },
{ },
};
constexpr u32 CpuClkOfficial = 1963'500;
constexpr u32 CpuVoltOfficial = 1120;
constexpr u32 CpuHighVminOfficial = 850;
constexpr u32 CpuVminOfficial = 620;
constexpr u32 CpuTune0Low = 0xFFCF;
static const u32 cpuVoltagePatchValues[] = { 850, 38, 1120, 1000, 100, 1000, 0 };
static const s32 cpuVoltagePatchOffsets[] = { -2, -1, 5, 6, 7, 8, 9 };
static_assert(sizeof(cpuVoltagePatchValues) == sizeof(cpuVoltagePatchOffsets), "Invalid cpuVoltagePatch size");
static const u32 cpuVoltThermalData[] = { 620, 1120, 20000, 620, 1120, 70000, 950, 1132, 0, 950, 1227, 0 };
static const u32 allowedCpuMaxFrequencies[] = { 1'963'500, 2'091'000, 2'193'000, 2'295'000, 2'397'000, 2'499'000, 2'601'000, 2'703'000, };
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
{ 76800, {}, { 610000, } },
{ 153600, {}, { 610000, } },
{ 230400, {}, { 610000, } },
{ 307200, {}, { 610000, } },
{ 384000, {}, { 610000, } },
{ 460800, {}, { 610000, } },
{ 537600, {}, { 801688, -10900, -163, 298, -10599, 162, } },
{ 614400, {}, { 824214, -5743, -452, 238, -6325, 81, } },
{ 691200, {}, { 848830, -3903, -552, 119, -4030, -2, } },
{ 768000, {}, { 891575, -4409, -584, 0, -2849, 39, } },
{ 844800, {}, { 940071, -5367, -602, -60, -63, -93, } },
{ 921600, {}, { 986765, -6637, -614, -179, 1905, -13, } },
{ 998400, {}, { 1098475, -13529, -497, -179, 3626, 9, } },
{ 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40, } },
{ 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110, } },
{ 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313, } },
{ 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559, } },
{ },
};
constexpr u32 GpuClkPllMax = 1300'000'000;
constexpr u32 GpuClkPllLimit = 2'600'000;
constexpr u32 GpuVminOfficial = 610;
static const u32 gpuDVFSPattern[] = { 1050, 1000, 100, 1000, 10, };
static const u32 gpuVoltThermalPattern[] = { 800, 1120, 0, 610, 1120, 20000, 610, 1120, 30000, 610, 1120, 50000, 610, 1120, 70000, 610, 1120, 90000, };
static_assert(sizeof(gpuVoltThermalPattern) == 72, "Invalid gpuVoltThermalPattern");
/* GPU Max Clock asm Pattern:
*
* MOV W11, #0x1000 MOV (wide immediate) 0x1000 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 0 | 1 0 0 1 0 1| 0 0| 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 |0 1 0 1 1
*
* MOVK W11, #0xE, LSL#16 <shift>16 0xE 0xB (11)
* sf | opc | | hw | imm16 | Rd
* #31 |30 29|28 27 26 25 24 23|22 21|20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 |4 3 2 1 0
* 0 | 1 1 | 1 0 0 1 0 1| 0 1| 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 |0 1 0 1 1
*/
inline constexpr u32 GpuAsmPattern[] = { 0x52820000, 0x72A001C0 };
inline bool GpuMaxClockPatternFn(u32 *ptr32) {
return asm_compare_no_rd(*ptr32, GpuAsmPattern[0]);
}
struct DvbEntry {
u64 freq;
u32 volt[4] = {};
};
constexpr DvbEntry EmcDvbTableDefault[] = {
{ 204000, { 637, 637, 637, } },
{ 408000, { 637, 637, 637, } },
{ 800000, { 637, 637, 637, } },
{ 1065600, { 637, 637, 637, } },
{ 1331200, { 650, 637, 637, } },
{ 1600000, { 675, 650, 637, } },
};
/* Movz */
/*
SF | OPC | HW | Imm16 | RD
31 | 30 29 28 27 26 25 24 23 | 22 21 | 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 SocVoltCompareSpeedoAsm = 0x7118FAFF; /* subs imm, compares to >=1598 max speedo and then goes down process id 1 route. */
constexpr u32 SocVoltWriteProcessIdAsm = 0x2A1F03F4; /* orr, writes id 0. */
constexpr u32 SocVoltWriteVoltageAsm = 0x52808358; /* Movz imm, writes 1050mV. */
constexpr u32 SocVoltSelectRegisterAsm = 0x1A9A3118; /* Csel, selects the voltage -- we need the register of this. */
constexpr u32 SocVoltMultiplyVoltsAsm = 0x1B1A7F0B; /* Mul, converts from mV -> uV */
constexpr u32 SocVoltValidateLimitAsm = 0x6B0A017F; /* Subs, checks limits */
constexpr u32 SocVoltBranchToAbortAsm = 0x540020AC; /* B.ge Branches to abort if limits are invalid. */
ALWAYS_INLINE bool SocVoltPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, SocVoltCompareSpeedoAsm);
}
constexpr u32 SocVoltLimitOfficial = 1050;
constexpr u32 SocVoltLimitMaxDefaultIndex = 17;
static const u32 socVoltLimitArray[DvfsTableEntryCount] = { 637, 650, 675, 700, 725, 750, 775, 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1025, 1050, };
constexpr u32 EmcListDefault[] = { 204000, 1331200, 1600000, };
constexpr u32 EmcListSizeDefault = std::size(EmcListDefault);
constexpr u32 EmcListEndDefault = EmcListSizeDefault - 1;
constexpr u32 EmcClkOSAlt = 1331'200;
constexpr u32 EmcClkPllmLimit = 2133'000'000;
constexpr u32 EmcVddqDefault = 600'000;
constexpr u32 MemVdd2Default = 1100'000;
constexpr u32 MTC_TABLE_REV = 3;
constexpr u32 MtcTableCountDefault = 3;
constexpr size_t MtcFullTableSize = sizeof(MarikoMtcTable) * MtcTableCountDefault;
constexpr u32 MtcFullTableCount = 17;
/* These dramids were copied from Hekate -- see /bdk/mem/sdram.h */
enum DramId : u64 {
HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 3,
AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 5,
IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE = 6,
IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 8,
IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 9,
IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 10,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE = 11,
HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ = 12,
HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ = 13,
HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME = 14,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE = 15,
IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 17,
IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 18,
HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 19,
IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 20,
HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 21,
AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL = 22,
HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 23,
AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL = 24,
IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 25,
HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF = 26,
AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF = 27,
AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL = 28,
IOWA_4GB_HYNIX_H54G46CYRBX267 = 29,
HOAG_4GB_HYNIX_H54G46CYRBX267 = 30,
AULA_4GB_HYNIX_H54G46CYRBX267 = 31,
IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 32,
HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB = 33,
AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB = 34,
};
enum MtcTableIndex {
T210b0SdevEmcDvfsTableS4gb01 = 0, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS4gb03 = 1, /* Samsung AM-MGCJ 4Gb */
T210b0SdevEmcDvfsTableS8gb03 = 2, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableH4gb03 = 3, /* Hynix NME 4Gb */
T210b0SdevEmcDvfsTableM4gb03 = 4, /* Micron WT:F 4Gb */
T210b0SdevEmcDvfsTableS4gbY01 = 5, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbY01 = 6, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y8gbY01 = 7, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableS1y4gbX03 = 8, /* Samsung AA-MGCL 4Gb */
T210b0SdevEmcDvfsTableS1y8gbX03 = 9, /* Samsung AA-MGCL 8Gb */
T210b0SdevEmcDvfsTableS1y4gb01 = 10, /* (Unused) Samsung 4Gb */
T210b0SdevEmcDvfsTableM1y4gb01 = 11, /* Micron WT:E 4Gb */
T210b0SdevEmcDvfsTableH1y4gb01 = 12, /* Hynix NEE 4Gb */
T210b0SdevEmcDvfsTableS1y8gb04 = 13, /* Samsung AM-MGCJ 8Gb */
T210b0SdevEmcDvfsTableS1z4gb01 = 14, /* Samsung AB-MGCL 4Gb */
T210b0SdevEmcDvfsTableH1a4gb01 = 15, /* Hynix x267 4Gb */
T210b0SdevEmcDvfsTableM1a4gb01 = 16, /* Micron WT:B 8Gb */
MtcTableIndex_Invalid = 17,
};
struct MtcDramIndex {
DramId dramId;
MtcTableIndex index;
};
const inline MtcDramIndex mtcIndexTable[] = {
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ AULA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLXR_NEE, T210b0SdevEmcDvfsTableH1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ IOWA_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AM_MGCJ, T210b0SdevEmcDvfsTableS4gb03, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AM_MGCJ, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_4GB_HYNIX_H9HCNNNBKMMLHR_NME, T210b0SdevEmcDvfsTableH4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTE, T210b0SdevEmcDvfsTableM1y4gb01, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1z4gb01, },
{ HOAG_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AB_MGCL, T210b0SdevEmcDvfsTableS1y8gb04, },
{ HOAG_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ AULA_4GB_SAMSUNG_K4U6E3S4AA_MGCL, T210b0SdevEmcDvfsTableS1y4gbX03, },
{ IOWA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ HOAG_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_4GB_MICRON_MT53E512M32D2NP_046_WTF, T210b0SdevEmcDvfsTableM4gb03, },
{ AULA_8GB_SAMSUNG_K4UBE3D4AA_MGCL, T210b0SdevEmcDvfsTableS1y8gbX03, },
{ IOWA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ HOAG_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ AULA_4GB_HYNIX_H54G46CYRBX267, T210b0SdevEmcDvfsTableH1a4gb01, },
{ IOWA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ HOAG_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
{ AULA_4GB_MICRON_MT53E512M32D1NP_046_WTB, T210b0SdevEmcDvfsTableM1a4gb01, },
};
/*
710006abfc 40 01 1f d6 br x10
*/
/*
710006ac28 a0 03 00 90 adrp x0,0x71000de000
710006ac2c 00 80 16 91 add x0=>SdevEmcDvfsTableS4gb01,x0,#0x5a0
*/
/* Br */
/*
| Z | OP | Fixed | A | M | RN | RM
31 30 29 28 27 26 25 | 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 |11 | 10 | 9 8 7 6 5 | 4 3 2 1 0
1 1 0 1 0 1 1 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 Rn 0 0 0 0 0
Z op A M Rm
*/
/* Adrp */
/*
OP | ImmLow | | ImmHigh | RD
31 | 30 29 | 28 27 26 25 24 | 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 | 4 3 2 1 0
*/
/* ADD (immediate) */
/*
SF | OP | S | Fixed value | Sh | Imm12 | RN | RD
31 | 30 | 29 | 28 27 26 25 24 23 | 22 | 21 20 19 18 17 16 15 14 13 12 11 10 | 9 8 7 6 5 | 4 3 2 1 0
*/
constexpr u32 MtcBrAsm = 0xD61F0140;
constexpr u32 MtcMovAsm = 0x52800068;
constexpr u32 MtcAdrpAsm = 0x900003A0;
constexpr u32 MtcAddAsm = 0x91168000;
ALWAYS_INLINE bool MemMtcGetGetTablePatternFn(u32 *ptr) {
/* This builds an address that gets returned, so the register must be x0 by convention. */
return AsmCompareAddNoImm12(*ptr, MtcAddAsm);
}
constexpr u32 EmcCountCmpAsm = 0x7100851F; /* cmp w?,#0x21 (subs wzr,w?,#0x21) */
/*
str <lut>,[<rail>,#0x120] ; volt-array pointer
str w?, [<rail>,#0x154] ; num_freqs
*/
constexpr u32 EmcSocLutPtrStoreAsm = 0xF9009009; /* str x?,[x0,#0x120] (anchor) */
constexpr u32 EmcSocLutCountStoreAsm = 0xB9015408; /* str w?,[x0,#0x154] (anchor) */
constexpr u32 EmcSocFreqStoreAsm = 0xF9000D00; /* str x?,[x8,#0x18] */
constexpr u32 EmcSocVoltStoreAsm = 0xB9004900; /* str w?,[x8,#0x48] (socMinLut[i]) */
constexpr u32 EmcSocReadLoadAsm = 0xB9404929; /* ldr w?,[x9,#0x48] (socMinLut readback) */
inline bool EmcDvfsCountPatternFn(u32 *ptr) {
/* Local context: cbz w?,<skip> ; cmp w?,#0x21 ; b.cs <abort> */
return asm_compare_no_rd(*ptr, EmcCountCmpAsm) && AsmCompareBrConNoImm19(*(ptr + 1), 0x54000002) /* b.cs */
&& AsmCbzCompareOpcodeOnly(*(ptr - 1), 0x34000000); /* cbz */
}
inline bool EmcSocLutPatternFn(u32 *ptr) {
return asm_compare_no_rd(*ptr, EmcSocLutPtrStoreAsm) /* str x?,[x0,#0x120] */
&& asm_compare_no_rd(*(ptr + 1), EmcSocLutCountStoreAsm); /* str w?,[x0,#0x154] */
}
/*
mov w?,#0x20 ; the 32 cap
cmp w?,#0x20
csel w?,w?,w?,lt ; w? = min(maxCount, 32)
bl TegraGetEmcDvfsFreqTable
cmp w?,#0x20 ; (maxCount)
csel w?,<same>,<cap>,lt ; min(maxCount, 32)
bl <Get*DvfsFreqTable>
*/
constexpr u32 EmcRateCapCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateCapCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt */
inline bool EmcRateListPatternFn(u32 *ptr) {
return AsmSubsCompareNoReg(*ptr, EmcRateCapCmpAsm) /* cmp w?,#0x20 */
&& AsmCompareCselNoReg(*(ptr + 1), EmcRateCapCselAsm) /* csel w?,w?,w?,lt */
&& (AsmGetRn(*ptr) == AsmGetRn(*(ptr + 1))) /* min(reg, 0x20) */
&& AsmBlCompareOpcodeOnly(*(ptr + 2), 0x94000000); /* bl <Get*DvfsFreqTable> */
}
constexpr u32 EmcRateSessCmpAsm = 0x710082FF; /* cmp w?,#0x20 */
constexpr u32 EmcRateSessMovAsm = 0x52800400; /* movz w?,#0x20 */
constexpr u32 EmcRateSessCselAsm = 0x1A80B000; /* csel w?,w?,w?,lt (opcode + cond) */
inline bool EmcRateSessFindClamp(u32 *ptr, u32 *out_c, u32 *out_cap, u32 *out_movz_i) {
if (!AsmSubsCompareNoReg(ptr[0], EmcRateSessCmpAsm)) return false; /* cmp w<c>,#0x20 */
const u32 c = AsmGetRn(ptr[0]);
for (u32 i = 1; i <= 14; ++i) {
const u32 w = ptr[i];
if (AsmCompareCselNoReg(w, EmcRateSessCselAsm) && AsmGetRn(w) == c && asm_get_rd(w) == c) {
const u32 cap = AsmGetRm(w);
for (u32 j = 1; j < i; ++j) {
if ((ptr[j] & 0xFFFFFFE0u) == EmcRateSessMovAsm && asm_get_rd(ptr[j]) == cap) {
if (out_c) *out_c = c;
if (out_cap) *out_cap = cap;
if (out_movz_i) *out_movz_i = j;
return true;
}
}
return false;
}
}
return false;
}
inline bool EmcRateSessPatternFn(u32 *ptr) {
return EmcRateSessFindClamp(ptr, nullptr, nullptr, nullptr);
}
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
@@ -0,0 +1,136 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
THIS_MAKEFILE := $(abspath $(lastword $(MAKEFILE_LIST)))
CURRENT_DIRECTORY := $(abspath $(dir $(THIS_MAKEFILE)))
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
ifneq ($(strip $(HOC_UART_LOG)),)
export CFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
export CXXFLAGS += -DHOC_UART_LOG=$(HOC_UART_LOG)
endif
ATMOSPHERE_SYSTEM_MODULE_TARGETS := kip
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(__RECURSIVE__),1)
#---------------------------------------------------------------------------------
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c)
CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp)
SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),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) \
$(foreach dir,$(AMS_LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(ATMOSPHERE_BUILD_DIR)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(foreach dir,$(AMS_LIBDIRS),-L$(dir)/$(ATMOSPHERE_LIBRARY_DIR))
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: clean all check_lib
#---------------------------------------------------------------------------------
all: $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR) $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
@$(MAKE) __RECURSIVE__=1 OUTPUT=$(CURDIR)/$(ATMOSPHERE_OUT_DIR)/$(TARGET) \
DEPSDIR=$(CURDIR)/$(ATMOSPHERE_BUILD_DIR) \
--no-print-directory -C $(ATMOSPHERE_BUILD_DIR) \
-f $(THIS_MAKEFILE)
$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a: check_lib
@$(SILENTCMD)echo "Checked library."
ifeq ($(ATMOSPHERE_CHECKED_LIBSTRATOSPHERE),1)
check_lib:
else
check_lib:
@$(MAKE) --no-print-directory -C $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere -f $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/libstratosphere.mk
endif
$(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR):
@[ -d $@ ] || mkdir -p $@
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(ATMOSPHERE_OUT_DIR) $(ATMOSPHERE_BUILD_DIR)
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(foreach target,$(ATMOSPHERE_SYSTEM_MODULE_TARGETS),$(OUTPUT).$(target))
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES) : $(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/$(ATMOSPHERE_LIBRARY_DIR)/libstratosphere.a
%.npdm : %.npdm.json
@echo built ... $< $@
@npdmtool $< $@
@echo built ... $(notdir $@)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
+3 -3
View File
@@ -12,14 +12,14 @@ include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
TARGET := Benchmark-Toolbox
BUILD := build
SOURCES := source source/furmark source/memtester source/cpustress
SOURCES := source source/furmark source/memtester source/cpustress source/cuda95
DATA := data
ICON := icon.jpg
INCLUDES := source source/furmark source/memtester source/cpustress ../hoc-clk/common/include
INCLUDES := source source/furmark source/memtester source/cpustress source/cuda95 ../hoc-clk/common/include
APP_TITLE := Benchmark Toolbox
APP_AUTHOR := Horizon-OC
APP_VERSION := 2.0.0
APP_VERSION := 3.0.0
ROMFS := resources
BOREALIS_PATH := lib/borealis
+190 -134
View File
@@ -8,7 +8,6 @@
*/
#include <math.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -24,110 +23,178 @@
#define ALIGN_PADDING 0x100000
#define CACHE_LINE_SIZE 128
struct f_data {
void (*func)(int64_t *, int64_t *, int);
int64_t *arg1;
int64_t *arg2;
int arg3;
};
#define BW_MAX_THREADS 4
static pthread_cond_t p_ready, p_start;
static pthread_mutex_t p_lock;
static pthread_t *p_worker = NULL;
static struct f_data *worker_data = NULL;
static int p_worker_not_ready, p_workers_ready;
typedef void (*bench_kernel_fn)(int64_t *dst, int64_t *src, long size);
static void *thread_func(void *data) {
struct f_data *d = data;
pthread_mutex_lock(&p_lock);
p_worker_not_ready--;
if (!p_worker_not_ready)
pthread_cond_signal(&p_ready);
while (p_workers_ready != 1)
pthread_cond_wait(&p_start, &p_lock);
pthread_mutex_unlock(&p_lock);
(d->func)(d->arg1, d->arg2, d->arg3);
pthread_exit(NULL);
struct bw_pool;
typedef struct {
Thread thread;
Semaphore start;
struct bw_pool *pool;
bench_kernel_fn func;
int64_t *dst;
int64_t *src;
long size;
int core;
bool created;
uint64_t ticks;
} bw_worker_t;
typedef struct bw_pool {
bw_worker_t workers[BW_MAX_THREADS];
Semaphore done;
int threads;
bool ok;
} bw_pool_t;
static void bw_worker_entry(void *arg) {
bw_worker_t *w = (bw_worker_t *)arg;
bw_pool_t *p = w->pool;
/* Best-effort pin to own core. */
svcSetThreadCoreMask(CUR_THREAD_HANDLE, w->core, (u64)1 << w->core);
for (;;) {
semaphoreWait(&w->start);
bench_kernel_fn f = w->func;
if (!f)
break;
uint64_t t0 = armGetSystemTick();
f(w->dst, w->src, w->size);
w->ticks = armGetSystemTick() - t0;
semaphoreSignal(&p->done);
}
}
static void parallel_run(void) {
pthread_mutex_lock(&p_lock);
p_workers_ready = 1;
pthread_mutex_unlock(&p_lock);
pthread_cond_broadcast(&p_start);
static bool bw_pool_init(bw_pool_t *p, int threads) {
memset(p, 0, sizeof(*p));
if (threads > BW_MAX_THREADS)
threads = BW_MAX_THREADS;
p->threads = threads;
semaphoreInit(&p->done, 0);
s32 prio = 0x2C;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
for (int i = 0; i < threads; i++) {
bw_worker_t *w = &p->workers[i];
w->core = i;
w->func = NULL;
w->pool = p;
semaphoreInit(&w->start, 0);
if (R_FAILED(threadCreate(&w->thread, bw_worker_entry, w, NULL, 0x8000, prio, i)))
return false;
w->created = true;
if (R_FAILED(threadStart(&w->thread)))
return false;
}
p->ok = true;
return true;
}
static void parallel_init(int threads) {
pthread_attr_t attr;
pthread_cond_init(&p_ready, NULL);
pthread_cond_init(&p_start, NULL);
pthread_mutex_init(&p_lock, NULL);
p_worker_not_ready = threads;
p_workers_ready = 0;
pthread_attr_init(&attr);
if (!p_worker || !worker_data) {
p_worker = malloc(threads * sizeof(pthread_t));
worker_data = malloc(threads * sizeof(struct f_data));
static void bw_pool_free(bw_pool_t *p) {
for (int i = 0; i < p->threads; i++) {
bw_worker_t *w = &p->workers[i];
if (w->created) {
w->func = NULL; /* shutdown */
semaphoreSignal(&w->start);
}
}
for (int i = 0; i < p->threads; i++) {
bw_worker_t *w = &p->workers[i];
if (w->created) {
threadWaitForExit(&w->thread);
threadClose(&w->thread);
w->created = false;
}
}
p->ok = false;
}
static double bw_run_once(bw_pool_t *p, bench_kernel_fn f, int64_t *dstbuf, int64_t *srcbuf, long size) {
int threads = p->threads;
for (int i = 0; i < threads; i++) {
bw_worker_t *w = &p->workers[i];
w->func = f;
w->dst = dstbuf + (size * i) / (long)sizeof(int64_t);
w->src = srcbuf + (size * i) / (long)sizeof(int64_t);
w->size = size;
}
for (int i = 0; i < threads; i++)
pthread_create(p_worker + i, &attr, thread_func, worker_data + i);
pthread_mutex_lock(&p_lock);
while (p_worker_not_ready != 0)
pthread_cond_wait(&p_ready, &p_lock);
pthread_mutex_unlock(&p_lock);
semaphoreSignal(&p->workers[i].start);
for (int i = 0; i < threads; i++)
semaphoreWait(&p->done);
uint64_t sum = 0;
for (int i = 0; i < threads; i++)
sum += p->workers[i].ticks;
return (double)armTicksToNs(sum) / 1000000000.0 / threads;
}
static void aligned_block_copy(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
volatile int64_t *dst = dst_;
int64_t t1, t2, t3, t4;
while ((size -= 64) >= 0) {
t1 = *src++;
t2 = *src++;
t3 = *src++;
t4 = *src++;
*dst++ = t1;
*dst++ = t2;
*dst++ = t3;
*dst++ = t4;
t1 = *src++;
t2 = *src++;
t3 = *src++;
t4 = *src++;
*dst++ = t1;
*dst++ = t2;
*dst++ = t3;
*dst++ = t4;
}
}
#define BW_GPR_CLOBBERS \
"x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18"
static void aligned_block_fetch(int64_t *__restrict dst, int64_t *__restrict src_, int size) {
volatile int64_t *src = src_;
(void)dst;
while ((size -= 64) >= 0) {
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
*src++;
}
static void aligned_block_copy(int64_t *dst, int64_t *src, long size) {
__asm__ __volatile__(
"1:\n"
"ldp x3, x4, [%[s], #0x00]\n"
"ldp x5, x6, [%[s], #0x10]\n"
"ldp x7, x8, [%[s], #0x20]\n"
"ldp x9, x10, [%[s], #0x30]\n"
"ldp x11, x12, [%[s], #0x40]\n"
"ldp x13, x14, [%[s], #0x50]\n"
"ldp x15, x16, [%[s], #0x60]\n"
"ldp x17, x18, [%[s], #0x70]\n"
"add %[s], %[s], #0x80\n"
"stp x3, x4, [%[d], #0x00]\n"
"stp x5, x6, [%[d], #0x10]\n"
"stp x7, x8, [%[d], #0x20]\n"
"stp x9, x10, [%[d], #0x30]\n"
"stp x11, x12, [%[d], #0x40]\n"
"stp x13, x14, [%[d], #0x50]\n"
"stp x15, x16, [%[d], #0x60]\n"
"stp x17, x18, [%[d], #0x70]\n"
"add %[d], %[d], #0x80\n"
"subs %[n], %[n], #0x80\n"
"b.gt 1b\n"
: [s] "+r"(src), [d] "+r"(dst), [n] "+r"(size)
:
: BW_GPR_CLOBBERS, "cc", "memory");
}
static void aligned_block_fill(int64_t *__restrict dst_, int64_t *__restrict src, int size) {
volatile int64_t *dst = dst_;
int64_t data = *src;
while ((size -= 64) >= 0) {
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
*dst++ = data;
}
static void aligned_block_fetch(int64_t *dst, int64_t *src, long size) {
(void)src;
__asm__ __volatile__(
"1:\n"
"ldp x3, x4, [%[p], #0x00]\n"
"ldp x5, x6, [%[p], #0x10]\n"
"ldp x7, x8, [%[p], #0x20]\n"
"ldp x9, x10, [%[p], #0x30]\n"
"ldp x11, x12, [%[p], #0x40]\n"
"ldp x13, x14, [%[p], #0x50]\n"
"ldp x15, x16, [%[p], #0x60]\n"
"ldp x17, x18, [%[p], #0x70]\n"
"add %[p], %[p], #0x80\n"
"subs %[n], %[n], #0x80\n"
"b.gt 1b\n"
: [p] "+r"(dst), [n] "+r"(size)
:
: BW_GPR_CLOBBERS, "cc", "memory");
}
static void aligned_block_fill(int64_t *dst, int64_t *src, long size) {
(void)src;
__asm__ __volatile__(
"1:\n"
"stp x3, x4, [%[d], #0x00]\n"
"stp x5, x6, [%[d], #0x10]\n"
"stp x7, x8, [%[d], #0x20]\n"
"stp x9, x10, [%[d], #0x30]\n"
"stp x11, x12, [%[d], #0x40]\n"
"stp x13, x14, [%[d], #0x50]\n"
"stp x15, x16, [%[d], #0x60]\n"
"stp x17, x18, [%[d], #0x70]\n"
"add %[d], %[d], #0x80\n"
"subs %[n], %[n], #0x80\n"
"b.gt 1b\n"
: [d] "+r"(dst), [n] "+r"(size)
:
: BW_GPR_CLOBBERS, "cc", "memory");
}
static double gettime(void) {
@@ -136,47 +203,29 @@ static double gettime(void) {
return (double)((int64_t)tv.tv_sec * 1000000 + tv.tv_usec) / 1000000.;
}
static double bandwidth_bench_helper(int threads, int64_t *dstbuf, int64_t *srcbuf, int size, void (*f)(int64_t *, int64_t *, int)) {
static double bandwidth_bench_helper(bw_pool_t *pool, int threads, int64_t *dstbuf, int64_t *srcbuf, long size, bench_kernel_fn f) {
int i, loopcount, innerloopcount, n;
double t, t1, t2, speed, maxspeed, s, s0, s1, s2;
double t, speed, maxspeed;
if (!pool || !pool->ok)
return 0.;
s = s0 = s1 = s2 = 0.;
maxspeed = 0.;
for (n = 0; n < MAXREPEATS; n++) {
bw_run_once(pool, f, dstbuf, srcbuf, size);
loopcount = 0;
innerloopcount = 1;
t = 0.;
do {
loopcount += innerloopcount;
for (i = 0; i < innerloopcount; i++) {
parallel_init(threads);
for (int pt = 0; pt < threads; pt++) {
(worker_data + pt)->func = f;
(worker_data + pt)->arg1 = dstbuf + size * pt / sizeof(int64_t);
(worker_data + pt)->arg2 = srcbuf + size * pt / sizeof(int64_t);
(worker_data + pt)->arg3 = size;
}
t1 = gettime();
parallel_run();
for (int pt = 0; pt < threads; pt++)
pthread_join(p_worker[pt], NULL);
t2 = gettime();
t += t2 - t1;
}
for (i = 0; i < innerloopcount; i++)
t += bw_run_once(pool, f, dstbuf, srcbuf, size);
innerloopcount *= 2;
} while (t < 0.5);
} while (t < 0.25);
speed = (double)size * threads * loopcount / t / 1000000.;
s0 += 1.;
s1 += speed;
s2 += speed * speed;
if (speed > maxspeed)
maxspeed = speed;
if (s0 > 2.) {
s = sqrt((s0 * s2 - s1 * s1) / (s0 * (s0 - 1)));
if (s < maxspeed / 1000.)
break;
}
}
return maxspeed;
}
@@ -306,13 +355,16 @@ void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user
int64_t *srcbuf, *dstbuf;
void *poolbuf = alloc_nonaliased_buffers((void **)&srcbuf, size * threads, (void **)&dstbuf, size * threads, NULL, 0);
bw_pool_t bw;
bw_pool_init(&bw, threads);
STEP("CPU copy", 0.40f);
out->cpu_copy = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_copy);
out->cpu_copy = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_copy);
STEP("CPU read", 0.55f);
out->cpu_read = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fetch);
out->cpu_read = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fetch);
STEP("CPU write", 0.70f);
out->cpu_write = bandwidth_bench_helper(threads, dstbuf, srcbuf, size, aligned_block_fill);
out->cpu_write = bandwidth_bench_helper(&bw, threads, dstbuf, srcbuf, size, aligned_block_fill);
bw_pool_free(&bw);
free(poolbuf);
STEP("Latency", 0.85f);
@@ -325,9 +377,10 @@ void bench_run_full(bench_results_t *out, bench_progress_fn progress, void *user
struct bench_ctx {
int phase;
bool is_4gb;
void *pool;
void *buf;
int64_t *src;
int64_t *dst;
bw_pool_t bw;
};
bench_ctx *bench_begin(void) {
@@ -335,7 +388,8 @@ bench_ctx *bench_begin(void) {
if (!c)
return NULL;
c->is_4gb = (appletGetAppletType() == AppletType_Application);
c->pool = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
c->buf = alloc_nonaliased_buffers((void **)&c->src, SIZE * 3, (void **)&c->dst, SIZE * 3, NULL, 0);
bw_pool_init(&c->bw, 3);
return c;
}
@@ -349,24 +403,25 @@ bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *f
*frac = 0.25f;
break;
case 1:
out->cpu_copy = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_copy);
out->cpu_copy = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_copy);
*label = "CPU copy";
*frac = 0.45f;
break;
case 2:
out->cpu_read = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fetch);
out->cpu_read = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fetch);
*label = "CPU read";
*frac = 0.60f;
break;
case 3:
out->cpu_write = bandwidth_bench_helper(threads, c->dst, c->src, size, aligned_block_fill);
out->cpu_write = bandwidth_bench_helper(&c->bw, threads, c->dst, c->src, size, aligned_block_fill);
*label = "CPU write";
*frac = 0.75f;
break;
case 4:
if (c->pool) {
free(c->pool);
c->pool = NULL;
bw_pool_free(&c->bw);
if (c->buf) {
free(c->buf);
c->buf = NULL;
}
latency_bench(&out->l2_ns, &out->ram_ns);
*label = "Latency";
@@ -384,7 +439,8 @@ bool bench_step(bench_ctx *c, bench_results_t *out, const char **label, float *f
void bench_end(bench_ctx *c) {
if (!c)
return;
if (c->pool)
free(c->pool);
bw_pool_free(&c->bw);
if (c->buf)
free(c->buf);
free(c);
}
}
@@ -0,0 +1,57 @@
#version 430 core
layout(local_size_x = 16, local_size_y = 16) in;
layout(std430, binding = 0) readonly buffer DataBuffer {
double data[];
};
layout(std430, binding = 1) buffer ResultBuffer {
uint result;
};
layout(std140, binding = 0) uniform Params {
uint n_rows;
uint n_repeats;
};
const double THRESHOLD = 1e-3LF;
shared uint s_partial[256];
void main() {
uint total_cols = gl_NumWorkGroups.x * gl_WorkGroupSize.x
* gl_NumWorkGroups.y * gl_WorkGroupSize.y;
uint col = (gl_GlobalInvocationID.y * (gl_NumWorkGroups.x * gl_WorkGroupSize.x))
+ gl_GlobalInvocationID.x;
uint count = 0u;
if (n_rows >= 2u) {
for (uint k = 0u; k < n_repeats; ++k) {
double bias = double(k) * 1e-12LF;
double ref = data[col] + bias;
for (uint row = 1u; row < n_rows; ++row) {
double val = data[row * total_cols + col];
if (abs(ref - val) > THRESHOLD)
count++;
}
}
}
uint lid = gl_LocalInvocationIndex;
s_partial[lid] = count;
memoryBarrierShared();
barrier();
for (uint s = 128u; s > 0u; s >>= 1) {
if (lid < s)
s_partial[lid] += s_partial[lid + s];
memoryBarrierShared();
barrier();
}
if (lid == 0u)
atomicAdd(result, s_partial[0]);
}
@@ -0,0 +1,59 @@
#version 430 core
layout(local_size_x = 16, local_size_y = 16) in;
layout(std430, binding = 0) readonly buffer DataBuffer {
float data[];
};
layout(std430, binding = 1) buffer ResultBuffer {
uint result;
};
layout(std140, binding = 0) uniform Params {
uint n_rows;
uint n_repeats;
};
const float THRESHOLD = 0.001f;
// One partial sum to force one atomic per WGP instead of 256.
shared uint s_partial[256];
void main() {
uint total_cols = gl_NumWorkGroups.x * gl_WorkGroupSize.x
* gl_NumWorkGroups.y * gl_WorkGroupSize.y;
uint col = (gl_GlobalInvocationID.y * (gl_NumWorkGroups.x * gl_WorkGroupSize.x))
+ gl_GlobalInvocationID.x;
uint count = 0u;
if (n_rows >= 2u) {
for (uint k = 0u; k < n_repeats; ++k) {
// Fuck compiler optimizations, imma start throwing bullshit
float bias = float(k) * 1e-9;
float ref = data[col] + bias;
for (uint row = 1u; row < n_rows; ++row) {
float val = data[row * total_cols + col];
if (abs(ref - val) > THRESHOLD)
count++;
}
}
}
uint lid = gl_LocalInvocationIndex;
s_partial[lid] = count;
memoryBarrierShared();
barrier();
for (uint s = 128u; s > 0u; s >>= 1) {
if (lid < s)
s_partial[lid] += s_partial[lid + s];
memoryBarrierShared();
barrier();
}
if (lid == 0u)
atomicAdd(result, s_partial[0]);
}
@@ -0,0 +1,948 @@
#include "cuda95.h"
#include <switch.h>
#include <deko3d.h>
#include <atomic>
#include <cstdarg>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <thread>
#ifndef DK_UNIFORM_BUF_ALIGNMENT
#define DK_UNIFORM_BUF_ALIGNMENT 0x100
#endif
#define ALIGN_UP(x, a) (((x) + ((a) - 1)) & ~((uint32_t)(a) - 1))
#define WG_SIZE_X 16
#define WG_SIZE_Y 16
// groups are dispatched in a 4x4 grid, for 2048 threads total
// 2048 per SM = 4096 on TX1, so we fully saturate the GPU
#define NUM_GROUPS_X 4
#define NUM_GROUPS_Y 4
#define N_THREADS ((NUM_GROUPS_X * WG_SIZE_X) * (NUM_GROUPS_Y * WG_SIZE_Y)) // 4096
#define N_ROWS 64
// Thread buffers = N_ROWS * N_THREADS, must be power of 2
#define N_VEC ((uint64_t)N_ROWS * N_THREADS) // uvec4 elements, 2^18
#define N_ELEMS (N_VEC * 4) // uints, 2^20
static_assert((N_VEC & (N_VEC - 1)) == 0,
"N_ROWS * N_THREADS must be a power of two for the scatter mask");
// Gather size, 8192 * 16B = 128KB per SM. Fits into L2 perfectly
#define L2_WINDOW_VEC 8192u
#define DISPATCHES_PER_LIST 32
#define NUM_INFLIGHT 3
#define MAX_RECORDS 64 // fault records per batch, per slot
#define FENCE_TIMEOUT_NS (5ULL * 1000000000ULL)
#define DATA_CHECK_INTERVAL 512
#define CODEMEM_SIZE (128 * 1024)
#define CMDMEM_SIZE (512 * 1024)
#define DKSH_MAGIC UINT32_C(0x48534B44)
// TOOLBOX: console drawing removed, log kept as a ring buffer.
#define SCR_COLS 79
#define LOG_LINES 14
typedef struct {
uint32_t magic;
uint32_t header_sz;
uint32_t control_sz;
uint32_t code_sz;
uint32_t programs_off;
uint32_t num_programs;
} DkshHeaderLocal;
// Shader parameters
typedef struct {
uint32_t n_rows;
uint32_t n_repeats;
uint32_t alu_iters;
uint32_t gather_iters;
uint32_t max_records;
uint32_t mode; // 0 = capture golden, 1 = compare, 2 = ignore
uint32_t scatter_mask; // Gather window - 1, used for errors.
uint32_t chain_bias;
} Params;
// Result buffer
typedef struct {
uint32_t mismatches;
uint32_t digest;
uint32_t selfFail;
uint32_t errCount;
} GpuResult;
// Errorlog
typedef struct {
uint32_t id; // thread index
uint32_t stage; // 1 = ALU self-check, 2 = per-thread digest
uint32_t got;
uint32_t want;
} GpuRecord;
// Presets for calibration
typedef struct {
const char* name;
uint32_t alu;
uint32_t gather;
uint32_t window; // 0 = whole buffer
} Profile;
// Testing modes
// Balanced tried to hit both
// Core forces 2x chains and not as much memory access, although in practice it strains it enough.
// Dram scatters reads accross allocated size (4MB, lol, lmao even)
// L2 changes this to 256KB.
static const Profile kProfiles[] = {
{ "balanced", 64, 32, 0 },
{ "core", 512, 4, 0 },
{ "dram", 8, 256, 0 },
{ "l2", 8, 256, L2_WINDOW_VEC },
};
#define NUM_PROFILES ((int)(sizeof(kProfiles) / sizeof(kProfiles[0])))
static uint32_t scatterMaskFor(int profile)
{
uint32_t win = kProfiles[profile].window;
if (win == 0u) win = (uint32_t)N_VEC;
return win - 1u;
}
// Error storage
typedef struct {
uint64_t batches;
uint64_t detected; // batches where digest=/count
uint64_t transient; // corrected by the re-run vote
uint64_t persistent; // reproduced on re-run
uint64_t selfFail; // ALU DUP/COMP Fails
uint64_t dataBad; // corrupt inputs (how did this happen)
uint64_t warnings;
uint64_t timeouts;
} Tally;
static Tally g_tally;
static uint64_t g_tStart;
static char g_log[LOG_LINES][SCR_COLS + 1];
static uint32_t g_logCount;
// TOOLBOX: shared state
static std::thread g_thread;
static std::mutex g_lock;
static std::atomic<bool> g_stop{ false };
static std::atomic<bool> g_running{ false };
static cuda95_status_t g_pub{};
static int s_profile;
static unsigned s_repeats;
static uint32_t s_goldDigest;
static uint32_t s_goldMismatch;
static double s_dps;
static double s_gbps;
static int s_cal;
static int s_error;
static char s_state[96];
static int s_cfgProfile;
static unsigned s_cfgRepeats;
static int s_wantProfile;
static unsigned s_wantRepeats;
static bool s_wantDirty;
static uint64_t elapsedSec(void)
{
return armTicksToNs(armGetSystemTick() - g_tStart) / 1000000000ULL;
}
static void note(const char* fmt, ...)
{
char body[SCR_COLS + 1];
va_list ap;
va_start(ap, fmt);
vsnprintf(body, sizeof(body), fmt, ap);
va_end(ap);
// TOOLBOX: console branch removed
uint32_t idx = g_logCount % LOG_LINES;
int n = snprintf(g_log[idx], sizeof(g_log[idx]), "[%5llus] ",
(unsigned long long)elapsedSec());
if (n < 0) n = 0;
size_t off = (size_t)n;
if (off > sizeof(g_log[idx]) - 1) off = sizeof(g_log[idx]) - 1;
size_t room = sizeof(g_log[idx]) - 1 - off;
size_t len = strlen(body);
if (len > room) len = room;
memcpy(g_log[idx] + off, body, len);
g_log[idx][off + len] = 0;
g_logCount++;
}
// Generate seeds
static uint32_t hash32(uint32_t x)
{
x ^= x >> 16;
x *= 0x7FEB352Du;
x ^= x >> 15;
x *= 0x846CA68Bu;
x ^= x >> 16;
return x;
}
// RNG, clamped so nothing fucking explodes too hard
static uint32_t dataBits(uint64_t index)
{
uint32_t bits = hash32((uint32_t)index);
uint32_t exp = (bits >> 23) & 0xFFu;
if (exp < 0x30u) exp = 0x30u + (exp & 0x0Fu);
if (exp > 0xC0u) exp = 0xC0u - (exp & 0x0Fu);
return (bits & 0x807FFFFFu) | (exp << 23);
}
typedef struct {
DkMemBlock block;
uint32_t offset;
uint32_t size;
} BumpAlloc;
static uint32_t bumpAlloc(BumpAlloc* a, uint32_t size, uint32_t align)
{
uint32_t off = ALIGN_UP(a->offset, align);
if (off + size > a->size) {
note("out of memory (need %u, have %u)", off + size, a->size);
return UINT32_MAX;
}
a->offset = off + size;
return off;
}
static void* cpuPtr(BumpAlloc* a, uint32_t off)
{
return (uint8_t*)dkMemBlockGetCpuAddr(a->block) + off;
}
static DkGpuAddr gpuPtr(BumpAlloc* a, uint32_t off)
{
return dkMemBlockGetGpuAddr(a->block) + off;
}
// TOOLBOX: copy worker state for the UI thread.
static void publish(void)
{
cuda95_status_t s{};
s.batches = g_tally.batches;
s.detected = g_tally.detected;
s.transient = g_tally.transient;
s.persistent = g_tally.persistent;
s.selfFail = g_tally.selfFail;
s.dataBad = g_tally.dataBad;
s.warnings = g_tally.warnings;
s.timeouts = g_tally.timeouts;
s.dispPerSec = s_dps;
s.gbps = s_gbps;
s.goldDigest = s_goldDigest;
s.goldMismatch = s_goldMismatch;
s.elapsedSec = elapsedSec();
s.profile = s_profile;
s.repeats = s_repeats;
s.calibrating = s_cal;
s.running = g_running.load() ? 1 : 0;
s.error = s_error;
snprintf(s.status, sizeof(s.status), "%s", s_state);
uint32_t total = g_logCount < LOG_LINES ? g_logCount : LOG_LINES;
uint32_t keep = total < CUDA95_LOG_LINES ? total : CUDA95_LOG_LINES;
s.logLines = keep;
for (unsigned i = 0; i < keep; i++) {
uint32_t idx = (g_logCount - keep + i) % LOG_LINES;
snprintf(s.log[i], sizeof(s.log[i]), "%s", g_log[idx]);
}
std::lock_guard<std::mutex> lk(g_lock);
g_pub = s;
}
static void debugCallback(void* userData, const char* context,
DkResult result, const char* message)
{
(void)userData;
if (result == DkResult_Success) {
g_tally.warnings++;
note("deko3d warn [%s]: %s", context, message);
publish();
return;
}
// TOOLBOX: flag the error instead.
note("deko3d ERROR [%s]: %s", context, message);
s_error = 1;
snprintf(s_state, sizeof(s_state), "GPU error");
publish();
g_stop.store(true);
}
static bool loadShader(DkShader* shader, BumpAlloc* codeMem, const char* path)
{
FILE* f = fopen(path, "rb");
if (!f) { note("cannot open %s", path); return false; }
DkshHeaderLocal hdr;
if (fread(&hdr, sizeof(hdr), 1, f) != 1 || hdr.magic != DKSH_MAGIC) {
note("%s is not a DKSH file", path);
fclose(f);
return false;
}
void* control = malloc(hdr.control_sz);
if (!control) { fclose(f); return false; }
rewind(f);
if (fread(control, hdr.control_sz, 1, f) != 1) {
free(control); fclose(f); return false;
}
uint32_t codeOff = bumpAlloc(codeMem, hdr.code_sz, DK_SHADER_CODE_ALIGNMENT);
if (codeOff == UINT32_MAX) { free(control); fclose(f); return false; }
fseek(f, hdr.control_sz, SEEK_SET);
if (fread(cpuPtr(codeMem, codeOff), hdr.code_sz, 1, f) != 1) {
free(control); fclose(f); return false;
}
fclose(f);
DkShaderMaker maker;
dkShaderMakerDefaults(&maker, codeMem->block, codeOff);
maker.control = control;
dkShaderInitialize(shader, &maker);
free(control);
return dkShaderIsValid(shader);
}
// Usual Deko3D memory allocation
typedef struct {
DkQueue queue;
DkCmdBuf cmdbuf;
DkShader shader;
DkGpuAddr uboAddr;
uint32_t uboSize;
//3x slots, two are constantly worked on while a third is checked
DkGpuAddr resultAddr[NUM_INFLIGHT];
GpuResult* resultCpu[NUM_INFLIGHT];
DkGpuAddr recAddr[NUM_INFLIGHT];
GpuRecord* recCpu[NUM_INFLIGHT];
DkCmdList batch[NUM_INFLIGHT];
DkGpuAddr dataAddr;
uint32_t dataSize;
uint32_t* dataCpu;
// Golden digests
DkGpuAddr expAddr;
uint32_t expSize;
} Ctx;
static void recordBatches(Ctx* c, Params params, uint32_t dispatches)
{
dkCmdBufClear(c->cmdbuf);
for (uint32_t s = 0; s < NUM_INFLIGHT; s++) {
const DkShader* shaders[] = { &c->shader };
dkCmdBufBindShaders(c->cmdbuf, DkStageFlag_Compute, shaders, 1);
DkBufExtents ubo = { c->uboAddr, c->uboSize };
dkCmdBufBindUniformBuffers(c->cmdbuf, DkStage_Compute, 0, &ubo, 1);
dkCmdBufPushConstants(c->cmdbuf, c->uboAddr, c->uboSize,
0, sizeof(params), &params);
DkBufExtents ssbos[4];
ssbos[0].addr = c->dataAddr; ssbos[0].size = c->dataSize;
ssbos[1].addr = c->resultAddr[s]; ssbos[1].size = sizeof(GpuResult);
ssbos[2].addr = c->recAddr[s]; ssbos[2].size = MAX_RECORDS * sizeof(GpuRecord);
ssbos[3].addr = c->expAddr; ssbos[3].size = c->expSize;
dkCmdBufBindStorageBuffers(c->cmdbuf, DkStage_Compute, 0, ssbos, 4);
for (uint32_t i = 0; i < dispatches; i++)
dkCmdBufDispatchCompute(c->cmdbuf, NUM_GROUPS_X, NUM_GROUPS_Y, 1);
c->batch[s] = dkCmdBufFinishList(c->cmdbuf);
}
}
typedef enum { RUN_OK = 0, RUN_TIMEOUT, RUN_QUEUE_ERROR } RunStatus;
static RunStatus runSync(Ctx* c, uint32_t slot, GpuResult* out, uint64_t* outNs)
{
memset(c->resultCpu[slot], 0, sizeof(GpuResult));
DkFence fence;
memset(&fence, 0, sizeof(fence));
uint64_t t0 = armGetSystemTick();
dkQueueSubmitCommands(c->queue, c->batch[slot]);
// CPU politely asks for the data
dkQueueSignalFence(c->queue, &fence, true);
dkQueueFlush(c->queue);
DkResult r = dkFenceWait(&fence, (int64_t)FENCE_TIMEOUT_NS);
uint64_t t1 = armGetSystemTick();
if (outNs) *outNs = armTicksToNs(t1 - t0);
// FAHHHHHHH
if (r != DkResult_Success)
return dkQueueIsInErrorState(c->queue) ? RUN_QUEUE_ERROR : RUN_TIMEOUT;
if (dkQueueIsInErrorState(c->queue))
return RUN_QUEUE_ERROR;
*out = *c->resultCpu[slot];
return RUN_OK;
}
// Gets reports from GPU results, mainly fault records
static void reportRecords(Ctx* c, uint32_t slot, uint32_t errCount)
{
uint32_t shown = errCount < MAX_RECORDS ? errCount : MAX_RECORDS;
if (shown > 3) shown = 3;
for (uint32_t i = 0; i < shown; i++) {
GpuRecord r = c->recCpu[slot][i];
note(" rec thread %u %s got %08x want %08x", r.id,
r.stage == 1u ? "ALU" : "digest", r.got, r.want);
}
if (errCount > shown)
note(" (%u more records)", errCount - shown);
}
// Seperated because this way we can distinguish between source corruption and the GPU on a bender.
static uint32_t checkDataIntegrity(Ctx* c)
{
const uint32_t stride = 997;
uint32_t bad = 0;
for (uint64_t i = 0; i < N_ELEMS; i += stride)
if (c->dataCpu[i] != dataBits(i)) bad++;
return bad;
}
// Capture golden values to confirm them as valid.
// Runs which cannot reproduce their own digests as valid 3 times are assumed as always unstable.
// Used to refuse baking in bad data and instead re compute.
static bool calibrate(Ctx* c, Params base, uint32_t* goldDigest, uint32_t* goldMismatch)
{
GpuResult r;
Params p = base;
// Capture data
p.mode = 0;
recordBatches(c, p, 1);
if (runSync(c, 0, &r, NULL) != RUN_OK) {
note("calibration: capture run failed");
return false;
}
// Compare against what we have
p.mode = 1;
recordBatches(c, p, 1);
uint32_t d[3], m[3];
for (int i = 0; i < 3; i++) {
// TOOLBOX: abort calibration when stopped.
if (g_stop.load()) return false;
if (runSync(c, 0, &r, NULL) != RUN_OK) {
note("calibration: confirm run %d failed", i + 1);
return false;
}
d[i] = r.digest;
m[i] = r.mismatches;
if (r.selfFail || r.errCount) {
g_tally.selfFail += r.selfFail;
note("calibration: run %d reported %u selfFail, %u records",
i + 1, r.selfFail, r.errCount);
reportRecords(c, 0, r.errCount);
}
}
if (d[0] == d[1] && d[1] == d[2]) {
*goldDigest = d[0];
*goldMismatch = m[0];
return true;
}
// Weigh the results, if nothing appeared twice, then flag these values.
g_tally.detected++;
if (d[0] == d[1] || d[0] == d[2]) { *goldDigest = d[0]; *goldMismatch = m[0]; }
else if (d[1] == d[2]) { *goldDigest = d[1]; *goldMismatch = m[1]; }
else { *goldDigest = d[0]; *goldMismatch = m[0]; }
note("CALIBRATION UNSTABLE: %08x %08x %08x -- reference may be wrong",
d[0], d[1], d[2]);
return true;
}
// TOOLBOX: worker thread, replace runDemo() loop.
static void workerMain(void)
{
g_running.store(true);
appletSetAutoSleepDisabled(true);
{
std::lock_guard<std::mutex> lk(g_lock);
s_profile = s_cfgProfile;
s_repeats = s_cfgRepeats;
}
int profile = s_profile;
uint32_t repeats = s_repeats;
memset(&g_tally, 0, sizeof(g_tally));
g_logCount = 0;
g_tStart = armGetSystemTick();
s_goldDigest = 0;
s_goldMismatch = 0;
s_dps = 0.0;
s_gbps = 0.0;
s_cal = 0;
s_error = 0;
snprintf(s_state, sizeof(s_state), "starting");
Ctx c;
memset(&c, 0, sizeof(c));
note("CUDA95 %d rows x %d threads", N_ROWS, N_THREADS);
DkDeviceMaker devMaker;
dkDeviceMakerDefaults(&devMaker);
devMaker.cbDebug = debugCallback;
DkDevice device = dkDeviceCreate(&devMaker);
// Setup queues
DkQueueMaker queueMaker;
dkQueueMakerDefaults(&queueMaker, device);
queueMaker.flags = DkQueueFlags_Compute
| DkQueueFlags_MediumPrio
| DkQueueFlags_DisableZcull;
queueMaker.commandMemorySize = 512 * 1024;
queueMaker.flushThreshold = 128 * 1024;
DkQueue queue = dkQueueCreate(&queueMaker);
c.queue = queue;
// Allocate and create memory regions
DkMemBlockMaker mbMaker;
dkMemBlockMakerDefaults(&mbMaker, device, CODEMEM_SIZE);
mbMaker.flags = DkMemBlockFlags_CpuUncached
| DkMemBlockFlags_GpuCached
| DkMemBlockFlags_Code;
BumpAlloc codeMem = { dkMemBlockCreate(&mbMaker), 0, CODEMEM_SIZE };
uint32_t dataSize = (uint32_t)(N_ELEMS * sizeof(uint32_t)); // 4 MiB
uint32_t expSize = N_THREADS * sizeof(uint32_t); // 16 KiB
uint32_t recSize = MAX_RECORDS * sizeof(GpuRecord); // 1 KiB
uint32_t dataMemSize = ALIGN_UP(CMDMEM_SIZE + dataSize + expSize
+ NUM_INFLIGHT * (recSize + 0x100) + 0x4000,
DK_MEMBLOCK_ALIGNMENT);
dkMemBlockMakerDefaults(&mbMaker, device, dataMemSize);
mbMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
BumpAlloc dataMem = { dkMemBlockCreate(&mbMaker), 0, dataMemSize };
DkCmdBufMaker cbMaker;
dkCmdBufMakerDefaults(&cbMaker, device);
c.cmdbuf = dkCmdBufCreate(&cbMaker);
uint32_t cmdOff = bumpAlloc(&dataMem, CMDMEM_SIZE, DK_CMDMEM_ALIGNMENT);
dkCmdBufAddMemory(c.cmdbuf, dataMem.block, cmdOff, CMDMEM_SIZE);
c.uboSize = ALIGN_UP(sizeof(Params), DK_UNIFORM_BUF_ALIGNMENT);
uint32_t uboOff = bumpAlloc(&dataMem, c.uboSize, DK_UNIFORM_BUF_ALIGNMENT);
c.uboAddr = gpuPtr(&dataMem, uboOff);
// Allocate the result slots so we do not stall
for (uint32_t s = 0; s < NUM_INFLIGHT; s++) {
uint32_t ro = bumpAlloc(&dataMem, sizeof(GpuResult), 0x100);
c.resultAddr[s] = gpuPtr(&dataMem, ro);
c.resultCpu[s] = (GpuResult*)cpuPtr(&dataMem, ro);
uint32_t eo = bumpAlloc(&dataMem, recSize, 0x100);
c.recAddr[s] = gpuPtr(&dataMem, eo);
c.recCpu[s] = (GpuRecord*)cpuPtr(&dataMem, eo);
}
uint32_t expOff = bumpAlloc(&dataMem, expSize, 0x100);
c.expAddr = gpuPtr(&dataMem, expOff);
c.expSize = expSize;
uint32_t dataOff = bumpAlloc(&dataMem, dataSize, 0x100);
c.dataAddr = gpuPtr(&dataMem, dataOff);
c.dataSize = dataSize;
c.dataCpu = (uint32_t*)cpuPtr(&dataMem, dataOff);
// Fill memory
note("filling %u KiB of test data...", dataSize / 1024);
for (uint64_t i = 0; i < N_ELEMS; i++) {
if ((i & 0xFFFFF) == 0 && g_stop.load()) break;
c.dataCpu[i] = dataBits(i);
}
uint32_t goldDigest = 0, goldMismatch = 0;
uint32_t expDigest = 0, expMismatch = 0;
bool ready = false;
// TOOLBOX: host app owns romfs
if (!g_stop.load() && loadShader(&c.shader, &codeMem, "romfs:/shaders/stress_csh.dksh")) {
Params base = { N_ROWS, repeats,
kProfiles[profile].alu, kProfiles[profile].gather,
MAX_RECORDS, 1, scatterMaskFor(profile), 0u };
s_cal = 1;
snprintf(s_state, sizeof(s_state), "calibrating");
note("calibrating (%s, repeats %u)...", kProfiles[profile].name, repeats);
publish();
if (!g_stop.load() && calibrate(&c, base, &goldDigest, &goldMismatch)) {
expDigest = goldDigest * DISPATCHES_PER_LIST;
expMismatch = goldMismatch * DISPATCHES_PER_LIST;
s_goldDigest = goldDigest;
s_goldMismatch = goldMismatch;
note("golden digest %08x, mismatches %u", goldDigest, goldMismatch);
Params runParams = base;
runParams.mode = 1;
recordBatches(&c, runParams, DISPATCHES_PER_LIST);
ready = true;
} else if (!g_stop.load()) {
s_error = 1;
snprintf(s_state, sizeof(s_state), "calibration failed");
note("initial calibration failed, aborting");
}
s_cal = 0;
if (ready) {
// Dispatches are idential and use atomicAdd, so batches must be DISPATCHES_PER_lIST * 1 dispatch.
DkFence fences[NUM_INFLIGHT];
memset(fences, 0, sizeof(fences));
uint64_t submitted = 0;
uint64_t windowDisp = 0;
uint64_t tWindow = armGetSystemTick();
double dps = 0.0;
bool dirty = false;
bool fatal = false;
snprintf(s_state, sizeof(s_state), "running");
publish();
while (!g_stop.load() && !fatal) {
// TOOLBOX: settings from the UI
{
std::lock_guard<std::mutex> lk(g_lock);
if (s_wantDirty) {
s_wantDirty = false;
profile = s_wantProfile;
repeats = s_wantRepeats;
s_profile = profile;
s_repeats = repeats;
dirty = true;
}
}
if (dirty) {
// If we get an error, recapture golden digest for comparisons to actually work.
dkQueueWaitIdle(queue);
base.n_repeats = repeats;
base.alu_iters = kProfiles[profile].alu;
base.gather_iters = kProfiles[profile].gather;
base.scatter_mask = scatterMaskFor(profile);
base.mode = 1;
s_cal = 1;
snprintf(s_state, sizeof(s_state), "calibrating");
note("recalibrating (%s, repeats %u)...", kProfiles[profile].name, repeats);
publish();
if (g_stop.load()) break;
if (!calibrate(&c, base, &goldDigest, &goldMismatch)) {
note("recalibration failed");
s_error = 1;
snprintf(s_state, sizeof(s_state), "recalibration failed");
fatal = true;
s_cal = 0;
publish();
break;
}
expDigest = goldDigest * DISPATCHES_PER_LIST;
expMismatch = goldMismatch * DISPATCHES_PER_LIST;
s_goldDigest = goldDigest;
s_goldMismatch = goldMismatch;
note("calibrated %s repeats %u -> digest %08x",
kProfiles[profile].name, repeats, goldDigest);
Params runParams = base;
runParams.mode = 1;
recordBatches(&c, runParams, DISPATCHES_PER_LIST);
memset(fences, 0, sizeof(fences));
submitted = 0;
dirty = false;
s_cal = 0;
snprintf(s_state, sizeof(s_state), "running");
publish();
}
uint32_t slot = (uint32_t)(submitted % NUM_INFLIGHT);
if (submitted >= NUM_INFLIGHT) {
DkResult r = dkFenceWait(&fences[slot], (int64_t)FENCE_TIMEOUT_NS);
if (dkQueueIsInErrorState(queue)) {
note("FATAL: queue entered error state");
s_error = 1;
snprintf(s_state, sizeof(s_state), "GPU queue error");
fatal = true;
publish();
break;
}
if (r != DkResult_Success) {
g_tally.timeouts++;
note("FATAL: batch exceeded %llu s deadline",
(unsigned long long)(FENCE_TIMEOUT_NS / 1000000000ULL));
s_error = 1;
snprintf(s_state, sizeof(s_state), "batch timeout");
fatal = true;
publish();
break;
}
g_tally.batches++;
GpuResult res = *c.resultCpu[slot];
bool bad = (res.digest != expDigest) || (res.mismatches != expMismatch);
// Shader COMP/DUP still occurs even if digests are bad.
// Allows to distinguish what died.
if (res.selfFail) {
g_tally.selfFail += res.selfFail;
note("batch %llu: %u ALU self-check failures",
(unsigned long long)g_tally.batches, res.selfFail);
bad = true;
}
if (res.errCount) {
reportRecords(&c, slot, res.errCount);
bad = true;
}
// We outa stability
if (bad) {
if (res.digest != expDigest)
note("batch %llu: digest %08x want %08x",
(unsigned long long)g_tally.batches, res.digest, expDigest);
g_tally.detected++;
// Re run batches twice and average, mainly for transience.
dkQueueWaitIdle(queue);
GpuResult a, b;
RunStatus s1 = runSync(&c, slot, &a, NULL);
RunStatus s2 = runSync(&c, slot, &b, NULL);
if (s1 != RUN_OK || s2 != RUN_OK) {
note("FATAL: re-run failed during vote");
s_error = 1;
snprintf(s_state, sizeof(s_state), "re-run failed");
fatal = true;
publish();
break;
}
// If we rerun, replay the same list so batches are compared to what they should result in, not the golden value for single dispatches
bool aOk = (a.digest == expDigest) && !a.selfFail && !a.errCount;
bool bOk = (b.digest == expDigest) && !b.selfFail && !b.errCount;
// If we detect transience, then we sorta ball, if its persistent, fucking overvolt.
if (aOk && bOk) {
g_tally.transient++;
note(" -> transient, corrected");
} else {
g_tally.persistent++;
note(" -> PERSISTENT (re-runs %08x, %08x)", a.digest, b.digest);
}
// How did this happen
// A long time ago
uint32_t badElems = checkDataIntegrity(&c);
if (badElems) {
g_tally.dataBad += badElems;
note(" -> input buffer: %u sampled elements wrong", badElems);
}
publish();
memset(fences, 0, sizeof(fences));
submitted = 0;
continue;
}
// que
if ((g_tally.batches % DATA_CHECK_INTERVAL) == 0) {
uint32_t badElems = checkDataIntegrity(&c);
if (badElems) {
g_tally.dataBad += badElems;
note("input buffer: %u sampled elements wrong", badElems);
}
publish();
}
}
memset(c.resultCpu[slot], 0, sizeof(GpuResult));
dkQueueSubmitCommands(queue, c.batch[slot]);
dkQueueSignalFence(queue, &fences[slot], true);
dkQueueFlush(queue);
submitted++;
windowDisp += DISPATCHES_PER_LIST;
uint64_t now = armGetSystemTick();
uint64_t windowNs = armTicksToNs(now - tWindow);
if (windowNs >= 500000000ULL) {
double secs = windowNs / 1.0e9;
dps = windowDisp / secs;
double bytesPer = (double)N_THREADS * 16.0 * repeats
* ((double)N_ROWS + (double)kProfiles[profile].gather);
s_dps = dps;
s_gbps = (dps * bytesPer) / 1.0e9;
publish();
windowDisp = 0;
tWindow = now;
}
}
}
} else if (!g_stop.load()) {
s_error = 1;
snprintf(s_state, sizeof(s_state), "shader load failed");
note("could not load stress_csh.dksh, aborting");
publish();
}
if (!dkQueueIsInErrorState(queue))
dkQueueWaitIdle(queue);
dkCmdBufDestroy(c.cmdbuf);
dkMemBlockDestroy(dataMem.block);
dkMemBlockDestroy(codeMem.block);
dkQueueDestroy(queue);
dkDeviceDestroy(device);
if (!s_error && g_stop.load())
snprintf(s_state, sizeof(s_state), "stopped");
publish();
appletSetAutoSleepDisabled(false);
g_running.store(false);
publish();
}
static unsigned normRepeats(unsigned r)
{
if (r < CUDA95_REPEATS_MIN) r = CUDA95_REPEATS_MIN;
if (r > CUDA95_REPEATS_MAX) r = CUDA95_REPEATS_MAX;
unsigned p = 1;
while (p * 2u <= r) p *= 2u;
return p;
}
static int normProfile(int p)
{
if (p < 0) return 0;
if (p >= NUM_PROFILES) return NUM_PROFILES - 1;
return p;
}
extern "C" {
void cuda95_start(int profile, unsigned repeats)
{
cuda95_stop();
{
std::lock_guard<std::mutex> lk(g_lock);
s_cfgProfile = normProfile(profile);
s_cfgRepeats = normRepeats(repeats);
s_wantProfile = s_cfgProfile;
s_wantRepeats = s_cfgRepeats;
s_wantDirty = false;
memset(&g_pub, 0, sizeof(g_pub));
snprintf(g_pub.status, sizeof(g_pub.status), "starting");
}
g_stop.store(false);
g_thread = std::thread(workerMain);
}
void cuda95_stop(void)
{
g_stop.store(true);
if (g_thread.joinable())
g_thread.join();
}
int cuda95_running(void)
{
return g_running.load() ? 1 : 0;
}
void cuda95_get(cuda95_status_t *out)
{
if (!out) return;
std::lock_guard<std::mutex> lk(g_lock);
*out = g_pub;
}
void cuda95_set_profile(int profile)
{
std::lock_guard<std::mutex> lk(g_lock);
s_cfgProfile = normProfile(profile);
if (g_running.load()) {
s_wantProfile = s_cfgProfile;
s_wantDirty = true;
}
}
void cuda95_set_repeats(unsigned repeats)
{
std::lock_guard<std::mutex> lk(g_lock);
s_cfgRepeats = normRepeats(repeats);
if (g_running.load()) {
s_wantRepeats = s_cfgRepeats;
s_wantDirty = true;
}
}
const char *cuda95_profile_name(int profile)
{
profile = normProfile(profile);
return kProfiles[profile].name;
}
unsigned cuda95_profile_alu(int profile)
{
profile = normProfile(profile);
return kProfiles[profile].alu;
}
unsigned cuda95_profile_gather(int profile)
{
profile = normProfile(profile);
return kProfiles[profile].gather;
}
} // extern C
@@ -0,0 +1,70 @@
#pragma once
// CUDA-95NX backend.
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define CUDA95_NUM_PROFILES 4
#define CUDA95_REPEATS_MIN 1u
#define CUDA95_REPEATS_MAX 256u
#define CUDA95_LOG_LINES 8
#define CUDA95_LOG_COLS 96
// Profiles
// 0 = balanced
// 1 = core
// 2 = dram
// 3 = l2
typedef struct {
uint64_t batches;
uint64_t detected;
uint64_t transient;
uint64_t persistent;
uint64_t selfFail;
uint64_t dataBad;
uint64_t warnings;
uint64_t timeouts;
double dispPerSec;
double gbps;
uint32_t goldDigest;
uint32_t goldMismatch;
uint64_t elapsedSec;
int profile;
unsigned repeats;
int calibrating;
int running;
int error;
char status[96];
char log[CUDA95_LOG_LINES][CUDA95_LOG_COLS];
unsigned logLines;
} cuda95_status_t;
void cuda95_start(int profile, unsigned repeats);
void cuda95_stop(void);
int cuda95_running(void);
void cuda95_get(cuda95_status_t *out);
void cuda95_set_profile(int profile);
void cuda95_set_repeats(unsigned repeats);
const char *cuda95_profile_name(int profile);
unsigned cuda95_profile_alu(int profile);
unsigned cuda95_profile_gather(int profile);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,202 @@
#version 430 core
// CRAWLING IN MY SKINNNNNN
// THESE WOUNDS THEY WILL NOT HEALLLLLLLL
// Furmark bored the fuck out of me tbh although the black hole bench I do want to revisit but thats besides the point.
// Here we are, this compute shitpost draws more power than furmark.
// TX1 has 2SMs with 4 schedulers with 2x issue. So 256 threads per block, 8 blocks per sm, 2048 threads dispatched as a max
layout(local_size_x = 16, local_size_y = 16) in; // 256 threads = 8 warps
// uvec4 used here to use LDG, essentially forcing data to be read from the unified instruction cache, diferenciating from a global/generic load
// For us, besdies the speedup this also means that lanes pulls 512 bytes instead of 128, forcing mem/l2 more.
layout(std430, binding = 0) readonly buffer DataBuffer {
uvec4 data[];
};
layout(std430, binding = 1) buffer ResultBuffer {
uint mismatches;
uint digest;
uint selfFail;
uint errCount;
};
// ALU self checks/mismatch storage
layout(std430, binding = 2) buffer ErrorLog {
uvec4 record[];
};
layout(std430, binding = 3) buffer ExpectedBuffer {
uint expected[];
};
layout(std140, binding = 0) uniform Params {
uint n_rows;
uint n_repeats;
uint alu_iters;
uint gather_iters;
uint max_records;
uint mode; // 0 = capture golden, 1 = compare, 2 = ignore
uint scatter_mask; // limits size to stay in L2 or not
uint chain_bias; // normally 0; see aluChain2
};
const float THRESHOLD = 0.001f;
// "scratchpad", not really.
// this is configured for 8kb per block (for 64Kb), any higher drops our block count
shared uint s_scratch[2048];
// FNV-1a. Surpringly heavy because maxwell. (XMAD)
uint hmix(uint h, uint v)
{
h ^= v;
h *= 16777619u;
return h;
}
// Constant shifts, so every index below must be a mask/add
uint xs32(uint x)
{
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
void pushRecord(uint id, uint stage, uint got, uint want)
{
uint idx = atomicAdd(errCount, 1u);
if (idx < max_records)
record[idx] = uvec4(id, stage, got, want);
}
// Man fuck current limits.
// Here we issue 2 instructions per cycle, intervealing to achive this.
// chian_bias is only here to stop the compiler from optimizing it away.
// CPU side passes zero so results must be equal, otherwise we know an error occured.
// Giving anything else breaks.
uvec2 aluChain2(uint seed, uint iters)
{
uint s2 = seed ^ chain_bias;
uint h1 = seed, h2 = s2;
uint a1 = seed | 1u, a2 = s2 | 1u;
float f1 = uintBitsToFloat((seed & 0x007FFFFFu) | 0x3F800000u);
float f2 = uintBitsToFloat((s2 & 0x007FFFFFu) | 0x3F800000u);
for (uint i = 0u; i < iters; ++i) {
// int/FP work
a1 = xs32(a1) + 0x9E3779B9u;
a2 = xs32(a2) + 0x9E3779B9u;
f1 = fma(f1, f1, 0.5);
f2 = fma(f2, f2, 0.5);
// Force values back into [1,2].
f1 = uintBitsToFloat((floatBitsToUint(f1) & 0x007FFFFFu) | 0x3F800000u);
f2 = uintBitsToFloat((floatBitsToUint(f2) & 0x007FFFFFu) | 0x3F800000u);
h1 = hmix(h1 ^ a1, floatBitsToUint(f1));
h2 = hmix(h2 ^ a2, floatBitsToUint(f2));
}
return uvec2(h1, h2);
}
// I hate myself
void main()
{
uint gx = gl_NumWorkGroups.x * gl_WorkGroupSize.x;
uint tid = gl_GlobalInvocationID.y * gx + gl_GlobalInvocationID.x;
// Distance in elements between rows. Also serves as the thread count
uint stride = gx * gl_NumWorkGroups.y * gl_WorkGroupSize.y;
uint lid = gl_LocalInvocationIndex;
uint count = 0u;
uint h = 2166136261u ^ tid;
for (uint k = 0u; k < n_repeats; ++k) {
// read uvec4 data continously
uvec4 ref = data[tid];
h = hmix(hmix(hmix(hmix(h, ref.x), ref.y), ref.z), ref.w);
vec4 fref = uintBitsToFloat(ref);
uint idx = tid;
for (uint row = 1u; row < n_rows; ++row) {
// On reduced load, adss per row instead of IMAD, which because Nvidia would expand into XMAD competing for resorces.
idx += stride;
uvec4 v = data[idx];
h = hmix(hmix(hmix(hmix(h, v.x), v.y), v.z), v.w);
// FSETP + used instead of creating divergence.
bvec4 hit = greaterThan(abs(fref - uintBitsToFloat(v)), vec4(THRESHOLD));
count += uint(hit.x) + uint(hit.y) + uint(hit.z) + uint(hit.w);
}
// Gather data in a different line every lane.
// Sized under L2 max, because fuck cache
uint st = xs32(h | 1u);
for (uint g = 0u; g < gather_iters; ++g) {
st = xs32(st);
uvec4 v = data[st & scatter_mask];
h = hmix(hmix(hmix(hmix(h, v.x), v.y), v.z), v.w);
}
// 2x issue redundancy
uvec2 cc = aluChain2(h, alu_iters);
if (cc.x != cc.y) {
atomicAdd(selfFail, 1u);
pushRecord(tid, 1u, cc.x, cc.y);
}
h = cc.x;
// Write back to memory
for (uint j = 0u; j < 8u; ++j)
s_scratch[lid + j * 256u] = h + j;
memoryBarrierShared();
barrier();
uint t = 0u;
for (uint j = 0u; j < 8u; ++j)
t = hmix(t, s_scratch[((lid * 37u + 11u) + j * 251u) & 2047u]);
h = hmix(h, t);
memoryBarrierShared();
barrier();
}
// Compare golden samples
if (mode == 0u) {
expected[tid] = h;
} else if (mode == 1u && h != expected[tid]) {
pushRecord(tid, 2u, h, expected[tid]);
}
// Fused reduction, digest/count reduced to 8 barriers with one pass instead of 2.
s_scratch[lid] = h;
s_scratch[256u + lid] = count;
memoryBarrierShared();
barrier();
for (uint s = 128u; s > 0u; s >>= 1) {
if (lid < s) {
s_scratch[lid] += s_scratch[lid + s];
s_scratch[256u + lid] += s_scratch[256u + lid + s];
}
memoryBarrierShared();
barrier();
}
// Atomic (16 per WGP)
if (lid == 0u) {
atomicAdd(digest, s_scratch[0]);
atomicAdd(mismatches, s_scratch[256u]);
}
}
+125
View File
@@ -14,6 +14,7 @@ extern "C" {
#include "bench.h"
#include "cpu_stress.h"
#include "cuda95.h"
#include "gpu_bw.h"
#include "gpu_stress.h"
#include "hoc_clk.h"
@@ -436,6 +437,126 @@ class StressTab : public brls::Box {
brls::Label *statusL, *gflops, *dispatches, *mismatches;
};
class Cuda95Tab : public brls::Box {
public:
Cuda95Tab() {
this->setAxis(brls::Axis::COLUMN);
this->setGrow(1.0f);
this->setPadding(40.0f, 60.0f, 40.0f, 60.0f);
auto *profRow = new brls::Box(brls::Axis::ROW);
profRow->setMarginBottom(14.0f);
auto *pl = new brls::Label();
pl->setText("Profile");
pl->setGrow(1.0f);
profRow->addView(pl);
profVal = new brls::Label();
profRow->addView(profVal);
this->addView(profRow);
refreshOpts();
auto *hint = new brls::Label();
hint->setText("L / R: profile. A: start / stop.");
hint->setFontSize(15.0f);
hint->setTextColor(nvgRGB(150, 150, 150));
hint->setMarginBottom(18.0f);
this->addView(hint);
toggle = new brls::Button();
toggle->setText("Start");
toggle->registerClickAction([this](brls::View *) {
onToggle();
return true;
});
this->addView(toggle);
this->registerAction("Prev profile", brls::ControllerButton::BUTTON_LB, [this](brls::View *) {
cycleProfile(-1);
return true;
});
this->registerAction("Next profile", brls::ControllerButton::BUTTON_RB, [this](brls::View *) {
cycleProfile(1);
return true;
});
statusL = new brls::Label();
statusL->setText("Stopped");
statusL->setMarginTop(10.0f);
statusL->setMarginBottom(8.0f);
this->addView(statusL);
auto *h = new brls::Header();
h->setTitle("Info");
this->addView(h);
rowRate = makeRow(this, "Read rate");
rowBatches = makeRow(this, "Batches");
rowMismatch = makeRow(this, "Mismatches");
}
~Cuda95Tab() override {
if (cuda95_running())
cuda95_stop();
}
void willDisappear(bool resetState = false) override {
if (cuda95_running())
cuda95_stop();
brls::Box::willDisappear(resetState);
}
void frame(brls::FrameContext *ctx) override {
cuda95_status_t st;
cuda95_get(&st);
if (cuda95_running() || lastRunning) {
rowRate->setText(fstr("%.2f GB/s", st.gbps));
rowBatches->setText(fstru("%llu", (unsigned long long)st.batches));
unsigned long long mm = (unsigned long long)st.detected + (unsigned long long)st.selfFail
+ (unsigned long long)st.dataBad + (unsigned long long)st.timeouts;
rowMismatch->setText(fstru("%llu", mm));
if (cuda95_running())
statusL->setText(st.error ? std::string("Error: ") + st.status : std::string(st.status));
}
syncToggle(cuda95_running() != 0);
brls::Box::frame(ctx);
}
private:
void refreshOpts() {
char b[64];
std::snprintf(b, sizeof b, "%s (%u/%u)", cuda95_profile_name(profile),
cuda95_profile_alu(profile), cuda95_profile_gather(profile));
profVal->setText(b);
}
void cycleProfile(int dir) {
profile = (profile + dir + CUDA95_NUM_PROFILES) % CUDA95_NUM_PROFILES;
refreshOpts();
if (cuda95_running())
cuda95_set_profile(profile);
}
void onToggle() {
if (cuda95_running()) {
cuda95_stop();
} else {
rowRate->setText("-");
rowBatches->setText("-");
rowMismatch->setText("-");
cuda95_start(profile, 4);
}
}
void syncToggle(bool running) {
if (running == lastRunning)
return;
lastRunning = running;
toggle->setText(running ? "Stop" : "Start");
if (!running)
statusL->setText("Stopped");
}
int profile = 0;
bool lastRunning = false;
brls::Label *profVal, *statusL, *rowRate, *rowBatches, *rowMismatch;
brls::Button *toggle;
};
class FurmarkTab : public brls::Box {
public:
FurmarkTab(int which, const char *desc) : which(which) {
@@ -849,6 +970,7 @@ class CreditsTab : public brls::Box {
makeRow(this, "Memtester")->setText("Simon Kirby, Charles Cazabon, KazushiMe & CTCaer");
makeRow(this, "FurMark")->setText("StanislavPetrovV & AnxietyTimmy");
makeRow(this, "GPU Test")->setText("NaGaa95");
makeRow(this, "CUDA95")->setText("AnxietyTimmy");
makeRow(this, "Membench")->setText("Siarhei Siamashka, KazushiMe & Lineon");
makeRow(this, "Stress-ng")->setText("ColinIanKing & Lineon");
@@ -944,6 +1066,7 @@ class MainActivity : public brls::Activity {
tab->addHeader("GPU");
tab->addTab("GPU Test", [] { return new StressTab(); });
tab->addTab("CUDA95", [] { return new Cuda95Tab(); });
tab->addTab("Furmark", [] { return new FurmarkTab(0, "FurMark for Switch (48 step)"); });
tab->addTab("GPU Path Trace", [] { return new FurmarkTab(2, "GPU Path Tracer"); });
@@ -988,6 +1111,8 @@ int main(int argc, char *argv[]) {
mt_cpu_stop();
if (cpu_stress_running())
cpu_stress_stop();
if (cuda95_running())
cuda95_stop();
_exit(EXIT_SUCCESS);
}
@@ -1,10 +0,0 @@
build/
*.ovl
*.elf
*.nacp
*.nro
@@ -1,2 +0,0 @@
-CSn
/home/sould/Documents/GitHub/Horizon-OC/Source/Horizon-OC-Monitor/Horizon-OC-Monitor.elf
@@ -1,339 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
@@ -1,243 +0,0 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.json
# - config.json
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
# NACP building is skipped as well.
#---------------------------------------------------------------------------------
APP_TITLE := Horizon OC Monitor
APP_VERSION := 1.3.2+r4-hoc-r4
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
INCLUDES := include lib/Atmosphere-libs/libstratosphere/source/dmnt lib/Atmosphere-libs/libstratosphere/source ../hoc-clk/common/include/
NO_ICON := 1
#ROMFS := romfs
# This location should reflect where you place the libultrahand directory (lib can vary between projects).
include ${TOPDIR}/lib/libultrahand/ultrahand.mk
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+simd+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -Wno-address-of-packed-member -O3 -ffunction-sections -ffast-math -flto -fomit-frame-pointer \
-fuse-linker-plugin -finline-small-functions \
-fno-strict-aliasing -frename-registers -falign-functions=16 \
$(ARCH) $(DEFINES)
# For compiling Ultrahand Overlay only
IS_STATUS_MONITOR_DIRECTIVE := 1
CFLAGS += -DIS_STATUS_MONITOR_DIRECTIVE=$(IS_STATUS_MONITOR_DIRECTIVE)
# Enable appearance overriding
export MSYS2_ARG_CONV_EXCL := -DUI_OVERRIDE_PATH
UI_OVERRIDE_PATH := /config/status-monitor/
CFLAGS += -DUI_OVERRIDE_PATH="\"$(UI_OVERRIDE_PATH)\""
CFLAGS += $(INCLUDE) -D__SWITCH__ -DAPP_VERSION="\"$(APP_VERSION)\""
CXXFLAGS := $(CFLAGS) -std=c++26 -Wno-dangling-else -fno-unwind-tables -fno-asynchronous-unwind-tables
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx
CXXFLAGS += -fno-exceptions -ffunction-sections -fdata-sections -fno-rtti
LDFLAGS += -Wl,--gc-sections -Wl,--as-needed
# For Ensuring Parallel LTRANS Jobs w/ GCC, make -j6
CXXFLAGS += -flto -fuse-linker-plugin -flto=6
LDFLAGS += -flto=6
#---------------------------------------------------------------------------------
# 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 OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
@rm -rf out/
@mkdir -p out/switch/.overlays/
@cp -a $(CURDIR)/config out/
@cp $(CURDIR)/$(TARGET).ovl out/switch/.overlays/$(TARGET).ovl
#---------------------------------------------------------------------------------
clean:
@echo "Cleanning ... $(TARGET)"
@rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nro $(TARGET).nacp $(TARGET).elf
@rm -rf out/
@rm -f $(TARGET).zip
#---------------------------------------------------------------------------------
dist: all
@echo making dist ...
@rm -f $(TARGET).zip
@cd out; zip -r ../$(TARGET).zip ./*; cd ../
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).ovl
$(OUTPUT).ovl : $(OUTPUT).elf $(OUTPUT).nacp
@elf2nro $< $@ $(NROFLAGS)
@echo "built ... $(notdir $(OUTPUT).ovl)"
@printf 'ULTR' >> $@
@printf "Ultrahand signature has been added.\n"
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
@@ -1,9 +0,0 @@
Thanks to NaGa for Status Monitor Pro! <br> <br>
**Horizon-OC-Monitor is deprecated.** If you want to compile it anyway, create a `lib` folder and clone Atmosphere-libs and libultrahand into it: <br> <br>
```bash
mkdir lib
git clone https://github.com/Atmosphere-NX/Atmosphere-libs lib/Atmosphere-libs
git clone https://github.com/ppkantorski/libultrahand lib/libultrahand
```
@@ -1,127 +0,0 @@
[status-monitor]
battery_avg_iir_filter=false
battery_time_left_refreshrate=60
average_gpu_load=false
use_old_fps_average=false
[full]
refresh_rate=1
layer_width_align=left
show_real_freqs=true
show_deltas=true
show_target_freqs=true
show_fps=true
show_res=true
show_read_speed=true
use_dynamic_colors=true
disable_screenshots=false
separator_color=#888F
cat_color_1=#8FFF
cat_color_2=#8CFF
text_color=#FFFF
[mini]
refresh_rate=1
handheld_font_size=15
docked_font_size=15
spacing=8
real_freqs=true
real_volts=true
show_full_cpu=false
show_full_res=true
show_fan_percentage=true
show_soc_voltage=false
use_dynamic_colors=true
show_vddq=false
show_vdd2=true
decimal_vdd2=false
show_dtc=true
use_dtc_symbol=true
dtc_format=%m-%d-%Y%H:%M:%S
show=DTC+BAT+CPU+GPU+RAM+TMP+FPS+RES
replace_MB_with_RAM_load=true
show_RAM_load_CPU_GPU=false
invert_battery_display=true
disable_screenshots=false
sleep_exit=false
frame_offset_x=10
frame_offset_y=10
frame_padding=10
background_color=#0009
focus_background_color=#000F
separator_color=#888F
cat_color=#2DFF
text_color=#FFFF
[micro]
refresh_rate=1
layer_height_align=top
handheld_font_size=15
docked_font_size=15
text_align=center
real_freqs=true
real_volts=true
show_full_cpu=false
show_full_res=false
show_soc_voltage=true
use_dynamic_colors=true
show_vddq=false
show_vdd2=true
decimal_vdd2=false
show_dtc=true
use_dtc_symbol=true
dtc_format=%H:%M:%S
show=FPS+CPU+GPU+RAM+SOC+BAT+DTC
replace_GB_with_RAM_load=true
invert_battery_display=false
disable_screenshots=false
sleep_exit=false
background_color=#0009
separator_color=#888F
cat_color=#2DFF
text_color=#FFFF
[fps-counter]
refresh_rate=30
handheld_font_size=40
docked_font_size=40
use_integer_counter=false
disable_screenshots=false
frame_offset_x=10
frame_offset_y=10
frame_padding=10
background_color=#0009
focus_background_color=#000F
text_color=#8CFF
[fps-graph]
refresh_rate=30
show_info=false
use_dynamic_colors=true
disable_screenshots=false
frame_offset_x=10
frame_offset_y=10
frame_padding=10
background_color=#0009
focus_background_color=#000F
fps_counter_color=#888C
border_color=#2DFF
dashed_line_color=#8888
main_line_color=#FFFF
rounded_line_color=#F0FF
perfect_line_color=#0C0F
max_fps_text_color=#FFFF
min_fps_text_color=#FFFF
text_color=#FFFF
cat_color=#0F0F
[game_resolutions]
refresh_rate=10
disable_screenshots=false
frame_offset_x=10
frame_offset_y=10
frame_padding=10
background_color=#0009
focus_background_color=#000F
cat_color=#8FFF
text_color=#FFFF
@@ -1,103 +0,0 @@
# Configuration file
Configuration file is in `sdmc:/config/status-monitor/`. By default it's named `config.ini.template`. To be detected by overlay it must be renamed to `config.ini`.
---
Explanation of what each setting does.<br>
Colors are provided in RGBA4444 format, which means that each character represents different channel: red, green, blue, alpha.
> [status-monitor]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `key_combo` | Buttons combination that allows exiting Full, Mini, Micro, FPS Graph and FPS Counter modes; max 4 buttons, otherwise next buttons will be ignored. Combine different buttons with `+` | `A`, `B`, `X`, `Y`, `L`, `R`, `ZL`, `ZR`, `PLUS`, `MINUS`, `DUP`, `DDOWN`, `DLEFT`, `DRIGHT`, `SL`, `SR`, `LSTICK`, `RSTICK`, `UP`, `DOWN`, `LEFT`, `RIGHT` | `L+DDOWN+RSTICK` |
| `battery_avg_iir_filter` | Read voltage + current averages directly from fuel gauge, that uses infinite impulse response filter | `true`, `false` | `false` |
| `battery_time_left_refreshrate` | How many seconds must pass to refresh Battery Remaining Time | from `1` to `60` | `60` |
| `average_gpu_load` | Average 5 last GPU load readings. Because GPU load by design shows only load for last 1/60s, this option allows for more accurate gpu load readings at the cost of slight bump in CPU Core #3 usage (from 2 to 4 percentage points depending on CPU clock). | `true`, `false` | `false` |
| `font_cache` | Allow caching generated glyphs for better performance. Cache is cleaned up when moving between modes. | `true`, `false` | `true` |
> [full]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `refresh_rate` | How often per second this mode should be refreshed. Higher value means higher CPU Core #3 usage, that's why it is recommended to stay at 1. | From `1` to `60` | `1` |
| `layer_width_align` | On which side of the screen X axis you want this mode | `left`, `right` | `left` |
| `show_real_freqs` | This works only if you have sys-clk 2.0.0_rc4+ installed. Show real frequencies for CPU, GPU and RAM | `false`, `true` | `true` |
| `show_deltas` | This works only if you have sys-clk 2.0.0_rc4+ installed. Show difference for CPU, GPU and RAM between target frequencies and real frequencies. They are not showed if both keys `show_real_freqs` and `show_target_freqs` are set to `false` | `false`, `true` | `true` |
| `show_target_freqs` | Show target frequencies for CPU, GPU and RAM | `false`, `true` | `true` |
| `show_fps` | Show game's FPS and PFPS | `false`, `true` | `true` |
| `show_res` | Show game's 2 best candidates for internal resolution, it won't show when game is not using NVN | `false`, `true` | `true` |
| `show_read_speed` | Show game's read speed per second | `false`, `true` | `true` |
> [mini]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `refresh_rate` | How often per second this mode should be refreshed. Higher value means higher CPU Core #3 usage, that's why it is recommended to stay at 1. | From `1` to `60` | `1` |
| `layer_width_align` | On which side of the screen X axis you want this mode | `left`, `center`, `right` | `left` |
| `layer_height_align` | On which side of the screen Y axis you want this mode | `top`, `center`, `bottom` | `top` |
| `handheld_font_size` | How big should be rendered text in handheld mode | From `8` to `22` | `15` |
| `docked_font_size` | How big should be rendered text in docked mode | From `8` to `22` | `15` |
| `real_freqs` | This works only if you have sys-clk 2.0.0_rc4+ installed. It shows real frequencies for CPU, GPU and RAM instead of target frequencies | `true`, `false` | `false` |
| `show` | Which data should be rendered. Connect different stats with `+` in any order. | `CPU`, `GPU`, `RAM`, `TEMP`, `FAN`, `DRAW`, `FPS`, `RES`, `READ` | `CPU+GPU+RAM+TEMP+FAN+DRAW+FPS+RES+READ` |
| `replace_MB_with_RAM_load` | This works only if you have sys-clk 2.0.0_rc4+ installed. Instead of RAM total usage it shows RAM load. | `true`, `false` | `true` |
| `background_color` | Background color in RGBA4444 format | From `#0000` to `#FFFF` | `#1117` |
| `cat_color` | Category text color (left side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
| `text_color` | Stats text color (right side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
> [micro]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `refresh_rate` | How often per second this mode should be refreshed. Higher value means higher CPU Core #3 usage, that's why it is recommended to stay at 1. | From `1` to `60` | `1` |
| `layer_height_align` | On which side of the screen Y axis you want this mode | `top`, `bottom` | `top` |
| `handheld_font_size` | How big should be rendered text in handheld mode | From `8` to `18` | `18` |
| `docked_font_size` | How big should be rendered text in docked mode | From `8` to `18` | `18` |
| `text_align` | How shown text should be aligned | `left`, `center`, `right`| `center` |
| `real_freqs` | This works only if you have sys-clk 2.0.0_rc4+ installed. It shows real frequencies for CPU, GPU and RAM instead of target frequencies | `true`, `false` | `false` |
| `show` | Which data should be rendered. Connect different stats with `+` in any order. | `CPU`, `GPU`, `RAM`, `BRD`, `FAN`, `FPS` | `CPU+GPU+RAM+BRD+FAN+FPS` |
| `replace_GB_with_RAM_load` | This works only if you have sys-clk 2.0.0_rc4+ installed. Instead of RAM total usage it shows RAM load. | `true`, `false` | `true` |
| `background_color` | Background color in RGBA4444 format | From `#0000` to `#FFFF` | `#1117` |
| `cat_color` | Category text color (left side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FCCF` |
| `text_color` | Stats text color (right side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
> [fps-counter]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `layer_width_align` | On which side of the screen X axis you want this mode | `left`, `center`, `right` | `left` |
| `layer_height_align` | On which side of the screen Y axis you want this mode | `top`, `center`, `bottom` | `top` |
| `handheld_font_size` | How big should be rendered text in handheld mode | From `8` to `150` | `40` |
| `docked_font_size` | How big should be rendered text in docked mode | From `8` to `150` | `40` |
| `background_color` | Background color in RGBA4444 format | From `#0000` to `#FFFF` | `#1117` |
| `text_color` | Text color in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
> [fps-graph]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `layer_width_align` | On which side of the screen X axis you want this mode | `left`, `center`, `right` | `left` |
| `layer_height_align` | On which side of the screen Y axis you want this mode | `top`, `center`, `bottom` | `top` |
| `max_fps_text_color` | Text color of "60" in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
| `min_fps_text_color` | Text color of "0" in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
| `background_color` | Background color in RGBA4444 format | From `#0000` to `#FFFF` | `#1117` |
| `fps_counter_color` | Text color of average FPS printed behind graph in RGBA4444 format | From `#0000` to `#FFFF` | `#4444` |
| `border_color` | Color of rectangle in RGBA4444 format | From `#0000` to `#FFFF` | `#F77F` |
| `dashed_line_color` | Color of dashed line at the center of rectangle in RGBA4444 format | From `#0000` to `#FFFF` | `#8888` |
| `main_line_color` | Color of line representing FPS value on graph in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
| `rounded_line_color` | Color of line representing FPS value on graph if it's divisble by 10 in RGBA4444 format | From `#0000` to `#FFFF` | `#F0FF` |
| `perfect_line_color` | Color of line representing FPS value on graph if it's divisble by 30 in RGBA4444 format | From `#0000` to `#FFFF` | `#0C0F` |
> [game_resolutions]
| Key | Explanation | Possible values | Default Value |
|-----|-------------|-----------------|---------------|
| `refresh_rate` | How often per second this mode should be refreshed. Higher value means higher CPU Core #3 usage, that's why it is recommended to stay at 1. | From `1` to `60` | `10` |
| `layer_width_align` | On which side of the screen X axis you want this mode | `left`, `center`, `right` | `left` |
| `layer_height_align` | On which side of the screen Y axis you want this mode | `top`, `center`, `bottom` | `top` |
| `background_color` | Background color in RGBA4444 format | From `#0000` to `#FFFF` | `#1117` |
| `cat_color` | Category text color (left side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
| `text_color` | Stats text color (right side) in RGBA4444 format | From `#0000` to `#FFFF` | `#FFFF` |
@@ -1,252 +0,0 @@
# Modes
Horizon OC Monitor Overlay from 1.0.0 release contains five modes to choose from Main Menu.<br>
For additional functions you need to install:
- [SaltyNX](https://github.com/masagrator/SaltyNX/releases)
- [sys-clk 2.0.0_rc4+](https://github.com/retronx-team/sys-clk/releases) (using closed source forks of sys-clk can result in retrieving wrong real clockrates and ram load)
# Full
This mode you can know from older releases of Horizon OC Monitor. It contains all informations properly described and supported with high precision.
- CPU Usage
- Real Frequency: `%.1f` = Real clockrate of all CPU cores in MHz (This shows only when sys-clk 2.0.0_4c+ is installed)
- Target Frequency: `%.1f` = Target clockrate of all CPU cores in MHz
- Core #0-#3: `%.2f` = Load of CPU Cores calculated from IdleTickCount to percent value
- GPU Usage
- Real Frequency: `%.1f` = Real clockrate of GPU in MHz (This shows only when sys-clk 2.0.0_rc4+ is installed)
- Target Frequency: `%.1f` = Target clockrate of GPU in MHz
- Load: `%.1f` = GPU Load provided by PMU in percent
- RAM Usage
- Real Frequency: `%.1f` = Real clockrate of EMC in MHz (This shows only when sys-clk 2.0.0_rc4+ is installed)
- Target Frequency: `%.1f` = Target clockrate of EMC in MHz
- Load: `%.1f` (CPU `%.1f` | GPU `%.1f`) = RAM Load in % (Which part of that are CPU and GPU (with other hardware, but their impact on GPU readings is negligible))
- `%s`: `%.2f`/`%.2f` = %s memory used/%s memory available in MB (not working with FW <5.0.0)
- Board
- Battery Power Flow: `%+.2f`[h:mm] = How much power in watts is discharged from or charged to the battery [time left before shutdown]
- Temperatures: SoC: `%.1f` / PCB: `%.1f` / Skin: `%.1f` = SoC / PCB / Skin temperature in Celsius degrees (Explanation provided at the end of file)
- Fan Rotation Level: `%.1f` = Fan rotation level in percent
```Optional (shows only when SaltyNX is installed and game is running)```
| Category | Format | Explanation |
|----------|-------------------|--------------------------------------------------------------------------|
| PFPS | %u | Pushed Frames Per Second - how many frames were displayed in last second |
| FPS | %.2f | Frames Per Second - value calculated by averaging frametime |
| Resolutions | %dx%d \|\| %dx%d | Two the most promising candidates for internal game resolution `(^1)` |
| Read speed | %.2f MiB/s | Show game read speed in the last second |
- ^1 - read more informations in explanation for "Game Resolutions" menu
# Mini
Contains most of supported informations with lower precision. You can use touch screen to change its position temporarily.
| Category | Format | Explanation |
|----------|---------------------------------------------------|---------------------------------------------------------------------------|
| CPU | [%.0f,%.0f,%.0f,%.0f]@%.1f | Core #0 usage, Core #1 usage, Core #2 usage, Core #3 usage@CPU Target/Real frequency`(^1)` |
| GPU | %.1f@%.1f | Load@GPU Target/Real Frequency`(^1)` |
| RAM | %.0f/%.0f@%.1f<br>`or`<br>%.1f(%.1f \| %.1f)@%.1f | Total RAM used/Total RAM available in MB@EMC Target/Real frequency `or` RAM load (CPU \| GPU)@EMC Target frequency `(^1)` |
| TEMP | %2.1f/%2.1f/%2.1f | SoC temperature/PCB temperature/Skin temperature `(^2)` |
| FAN | %2.1f | Fan rotation level |
| DRAW | %+.2f[h:mm] | How much power in watts is discharged from or charged to the battery [Time left before shutdown] |
| RES | %dx%d \|\| %dx%d | Two the most promising candidates for internal game resolution `(^3)` |
| READ | %.2f MiB/s | Show game read speed in the last second |
- ^1 - Real Frequency + RAM Load available only with sys-clk 2.0.0_rc4+
- ^2 - Explanation provided at the end of file
- ^3 - read more informations in explanation for "Game Resolutions" menu
```Optional```
> shows only when SaltyNX is installed and game is running
>
| Category | Format | Explanation |
|----------|-------------------|--------------------------------------------------------------------------|
| FPS | %.2f | Frames Per Second |
# Micro
Contains most of supported informations with lower precision in one line.
| Category | Format | Explanation |
|----------|------------------------------------------------------------|---------------------------------------------------------------------------|
| CPU | [%.0f,%.0f,%.0f,%.0f]%s%.1f | [Core #0 usage, Core #1 usage, Core #2 usage, Core #3 usage] `(^1)` CPU Target/Real Frequency `(^3)` |
| GPU | %.1f%s%.1f | Load (^1) GPU Target/Real Frequency |
| RAM | %.1f/%.1f%s%.1f `or` %.1f%s%.1f | Total RAM used/Total RAM available in GB `(^1)` EMC Target frequency `or` RAM Load in % `(^1)` EMC Target frequency |
| BRD | %2.1f/%2.1f/%2.1f@+.1f[h:mm] | SoC temperature/PCB temperature/Skin temperature(^2)/Battery Power Flow[Time left before shutdown] |
| FAN | %2.1f | Fan rotation level |
- ^1 - by default it's `@`, but if you have sys-clk 2.0.0_rc4+ installed, this changes depending on difference between real clocks and targeted clocks. <br>
- `△` - real clocks are higher by at least 20 MHz than expected
- `▽` - real clocks are lower by at least 20 MHz than expected
- `≠` - real clocks are lower by at least 50 MHz than expected (If this shows constantly for longer than few seconds, this is a sign of throttling, usually caused by bad overclocking configuration)
- ^2 - Explanation provided at the end of file
- ^3 - Real frequencies + RAM Load available only with sys-clk 2.0.0_rc4+
```Optional```
> shows only when SaltyNX is installed and game is running
| Category | Format | Explanation |
|----------|--------------------|--------------------------------------------------------------------------|
| FPS | %.1f | Frames Per Second |
# FPS
> Counter
It shows only FPS value in 31Hz + vsync signal. <br>
If game is not launched, it will show always `n/d` aka `no data`.<br>
Mode available only with SaltyNX installed. You can use touch screen to change its position temporarily.
> Graph
It shows average FPS graph in 31Hz + vsync signal. In background of graph you can see rendered actual average FPS.<br>
If game is not launched, graph will be empty.<br>
If game is not rendering any new frame, graph is not updated.
If line is within rounding error of graph line, it's colored. Average FPS must be between -0.05 and +0.04 of graph line target FPS. So for 30 FPS it's between 29.95 - 30.04 FPS. For 43 FPS it's 42.95 - 43.04 FPS.
If it's in display refresh rate or half of it rounding error range, it's green. Any other FPS graph target is purple.
Colors can be changed in config file.
Mode available only with SaltyNX installed. You can use touch screen to change its position temporarily.
# Other
> Battery
- Battery Actual Capacity: `%d` = Battery Designed Capacity multiplied by Battery Age in mAh
- Battery Designed Capacity: `%d` = Battery capacity targeted by manufacturer in mAh
- Battery Temperature: `%.1f` = Battery temperature in Celsius
- Battery Raw Charge: `%.1f` = Raw battery charged capacity in percent
- Battery Age: `%.1f` = How much of designed capacity was charged last time battery was charged completely in percent
- Battery Voltage: `%.0f` = Battery average voltage in mV (time period: 5s, or with `battery_avg_iir_filter` enabled: 45s)
- Battery Current Flow: `%+.0f` = Battery average current flow in mA (time period: 5s, or with `battery_avg_iir_filter` enabled: 11.25s)
- Battery Power Flow: `%+.3f` = Battery average power flow in W calculated from Battery Voltage and Battery Current Flow
- Battery Remaining Time: h:mm - How much time is left before shutdown
Shows only if charger is connected:
| Category | Format | Explanation |
|---------------------------|--------------|----------------------------------------------------------------------------------|
| Charger Type | %u | Connected charger type, more in Battery.hpp "BatteryChargeInfoFieldsChargerType" |
| Charger Max Voltage | %u | Charger and external device voltage limit in mV |
| Charger Max Current | %u | Charger and external device current limit in mA |
> Miscellaneous
| Category | Format | Explanation |
|------------------------|-------------------|----------------------------------------------------------------------------------|
| DSP Usage | %u | In percent (not available on 17.0.0+) |
| NVDEC | %.2f | Target frequency in MHz (shows 0 if chip is not active) |
| NVENC | %.2f | Target frequency in MHz (shows 0 if chip is not active) |
| NVJPG | %.2f | Target frequency in MHz (shows 0 if chip is not active) |
| Network Type | %s | It shows if Switch is connected to internet, and if it's using Ethernet or Wi-Fi |
If Network Type is "Wi-Fi", you can press Y to show password. Since max password length is 64 characters, it may show in up to 3 lines.
> Game Resolutions
For this mode to show and work properly you must have SaltyNX 0.9.0+ installed. You can use touch screen to change its position temporarily.
When game runs, this menu shows what resolutions and how many times they were passed to GPU via two functions:
- __Depth__ shows info from depth texture passed to `nvnCommandBufferSetRenderTargets`
- __Viewport__ shows info from arguments passed to:
- for NVN: `nvnCommandBufferSetViewport`, `nvnCommandBufferSetViewports`, `nvnCommandBufferSetScissor` and `nvnCommandBufferSetScissors`
- for EGL: `glViewport`, `glViewportArrayv`, `glViewportIndexedf`, `glViewportIndexedfv` and all their variations
- for Vulkan: `vkCmdSetViewport` and `vkCmdSetViewportWithCount`
This menu shows first 8 resolutions passed to those functions in last frame rendering loop, sorted in descending order of calls number.<br>
Its main purpose is to catch game rendering resolution, but user must deduce which ones are correct.<br>
I have limited catched resolutions only to ones that have ratio higher than 0.6 and lower than 1.8. Such low value comes from the fact that some games are rendering at width lower than height (f.e. 480x720). There is also a limit where it won't accept resolutions which height is lower than 160 px or higher than 1440 px.<br>
Remember that resolutions you can see in this mode may be used in different ways - for example Tokyo Xanadu Ex+ max dynamic resolution in handheld will show 1280x736, but it's not that game will squeeze this into 720p screen, it's just removing those additional 16 pixels from showing on screen.
Those commands are used by all 3D games using NVN, EGL and Vulkan APIs (that's why it may not work with games using 2D engines).<br>
This mode is not 100% fullproof, so it can show that nothing is catched or it won't catch what is used for 3D rendering (if this happens for some 3D game, please report an issue).
By default refresh rate of this menu is 10 FPS. You can change that in config.ini, more in config.md<br>
Exiting is done by using the same combo buttons used in other main modes.
Games that don't mesh well with RES category in `Mini` and Resolution in `Full` modes while showing properly rendering resolutions in this menu:
- `Kirby and The Forgotten Land`: dynamic resolution has much less calls than other resolutions, it shows up correctly only when it's max dynamic resolution set. It shows up always first in Depth category of Game Resolutions menu.
- `Darksiders 2`: Dynamic resolution is randomly showing up, fighting for attention with native resolution call. In Game Resolutions menu it shows up either at 2nd or 3rd place of Viewport.
# Additional info
> How Battery Remaining Time is calculated
It is calculated from the last 3 minutes of power draw readings. In the first minute of running any mode, it is updated every 0.5s, after one minute it's updated every minute. When exiting the currently used mode, calculations are reset. When connecting or disconnecting the charger, calculations are reset. When Battery Power Draw is positive instead of negative, time is shown as [-:--] and calculations are reset.
> What is Skin temperature ($\mathbf{T}skin$)?
This is temperature calculated from SoC and PCB temperatures that is mainly used to prevent Switch from overheating. Reading it is not supported on FWs <5.0.0.
Explanation provided by CTCaer
>Temperature ($\mathbf{T}skin$) calculation.
>
>
>Console and Handheld:
>
>$\mathbf{T}soc >= 84°C$ - Immediate sleep. No questions asked.
>
>$\mathbf{T}pcb >= 84°C$ - Immediate sleep. No questions asked.
>
>
>Console only:
>
>$\mathbf{T}skin < 63°C$ - Clears both timers.
>
>$\mathbf{T}skin >= 63°C$ - Immediate sleep. No questions asked.
>
>
>Handheld only:
>
>$\mathbf{T}skin < 58°C$ - Clears both timers.
>
>$58°C <= \mathbf{T}skin < 61°C$ - Starts 60s timer and clears 10s timer.
>
>$61°C <= \mathbf{T}skin < 63°C$ - Starts 10s timer.
>
>$\mathbf{T}skin >= 63°C$ - Immediate sleep. No questions asked.
>
>
>The two timers are separate. If 61°C is reached, the 10s timer is immediatly started.
>
>If a timer ends and the temperature is not dropped, goes into sleep.
>
>
>tskin is the specific calculation below:
```
soc_adj = (temps.iir_filter_gain_soc * (temps.soc - temps.soc_adj_prev)) + temps.soc_adj_prev;
pcb_adj = (temps.iir_filter_gain_pcb * (temps.pcb - temps.pcb_adj_prev)) + temps.pcb_adj_prev;
temps.soc_adj_prev = soc_adj;
temps.pcb_adj_prev = pcb_adj;
if (soc_adj >= 38000)
{
if (temps.handheld)
soc_adj = (temps.tskin_soc_coeff_handheld[0] * soc_adj) + (1000 * temps.tskin_soc_coeff_handheld[1]);
else
soc_adj = (temps.tskin_soc_coeff_console[0] * soc_adj) + (1000 * temps.tskin_soc_coeff_console[1]);
soc_adj = (soc_adj / 10000) + 500;
}
if (pcb_adj >= 38000)
{
if (temps.handheld)
pcb_adj = (temps.tskin_pcb_coeff_handheld[0] * pcb_adj) + (1000 * temps.tskin_pcb_coeff_handheld[1]);
else
pcb_adj = (temps.tskin_pcb_coeff_console[0] * soc_adj) + (1000 * temps.tskin_pcb_coeff_console[1]);
pcb_adj = (pcb_adj / 10000) + 500;
}
skin_adj = MAX(soc_adj, pcb_adj); //tskin
```
@@ -1,81 +0,0 @@
///* Notes VoltageAvg
//
// Vavg time = 175.8ms x 2^(6+VOLT), default: VOLT = 2 (Vavg time = 45s)
//
///End of Notes
typedef enum {
NoHub = BIT(0), //If hub is disconnected
Rail = BIT(8), //At least one Joy-con is charging from rail
SPDSRC = BIT(12), //OTG
ACC = BIT(16) //Accessory
} BatteryChargeInfoFieldsFlags;
typedef enum {
NewPDO = 1, //Received new Power Data Object
NoPD = 2, //No Power Delivery source is detected
AcceptedRDO = 3 //Received and accepted Request Data Object
} BatteryChargeInfoFieldsPDControllerState; //BM92T series
typedef enum {
None = 0,
PD = 1,
TypeC_1500mA = 2,
TypeC_3000mA = 3,
DCP = 4,
CDP = 5,
SDP = 6,
Apple_500mA = 7,
Apple_1000mA = 8,
Apple_2000mA = 9
} BatteryChargeInfoFieldsChargerType;
typedef enum {
Sink = 1,
Source = 2
} BatteryChargeInfoFieldsPowerRole;
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
BatteryChargeInfoFieldsPDControllerState PDControllerState; //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)
BatteryChargeInfoFieldsPowerRole PowerRole;
BatteryChargeInfoFieldsChargerType ChargerType;
int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV
int32_t ChargerCurrentLimit; //Charger and external device current limit in mA
BatteryChargeInfoFieldsFlags Flags; //Unknown flags
char reserved[0x14]; //17.0.0+ data
} BatteryChargeInfoFields;
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
BatteryChargeInfoFieldsPDControllerState PDControllerState; //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)
char reserved[4]; //17.0.0+ data
BatteryChargeInfoFieldsPowerRole PowerRole;
BatteryChargeInfoFieldsChargerType ChargerType;
int32_t ChargerVoltageLimit; //Charger and external device voltage limit in mV
int32_t ChargerCurrentLimit; //Charger and external device current limit in mA
BatteryChargeInfoFieldsFlags Flags; //Unknown flags
char reserved2[0x10]; //17.0.0+ data
} BatteryChargeInfoFields17;
Result psmGetBatteryChargeInfoFields(Service* psmService, BatteryChargeInfoFields *out) {
return serviceDispatchOut(psmService, 17, *out);
}
@@ -1,41 +0,0 @@
typedef struct {
u8 ssid_len; ///< NifmSfWirelessSettingData::ssid_len
char ssid[0x21]; ///< NifmSfWirelessSettingData::ssid
u8 unk_x22; ///< NifmSfWirelessSettingData::unk_x21
u8 pad; ///< Padding
u32 unk_x24; ///< NifmSfWirelessSettingData::unk_x22
u32 unk_x28; ///< NifmSfWirelessSettingData::unk_x23
u8 passphrase_len; ///< Passphrase length
u8 passphrase[0x41]; ///< NifmSfWirelessSettingData::passphrase
u8 pad2[0x2]; ///< Padding
} NifmWirelessSettingData_new;
/// NetworkProfileData. Converted from/to \ref NifmSfNetworkProfileData.
typedef struct {
Uuid uuid; ///< NifmSfNetworkProfileData::uuid
char network_name[0x40]; ///< NifmSfNetworkProfileData::network_name
u32 unk_x50; ///< NifmSfNetworkProfileData::unk_x112
u32 unk_x54; ///< NifmSfNetworkProfileData::unk_x113
u8 unk_x58; ///< NifmSfNetworkProfileData::unk_x114
u8 unk_x59; ///< NifmSfNetworkProfileData::unk_x115
u8 pad[2]; ///< Padding
NifmWirelessSettingData_new wireless_setting_data; ///< \ref NifmWirelessSettingData
NifmIpSettingData ip_setting_data; ///< \ref NifmIpSettingData
} NifmNetworkProfileData_new;
Result getNvChannelClockRate(NvChannel *channel, u32 module_id, u32 *clock_rate) {
struct nvhost_clk_rate_args {
uint32_t rate;
uint32_t moduleid;
} args = {
.rate = 0,
.moduleid = module_id,
};
const u32 id = hosversionBefore(8,0,0) ? _NV_IOWR(0, 0x14, args) : _NV_IOWR(0, 0x23, args);
Result rc = nvIoctl(channel->fd, id, &args);
if (R_SUCCEEDED(rc) && clock_rate)
*clock_rate = args.rate;
return rc;
}
@@ -1,231 +0,0 @@
#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;
}
@@ -1,19 +0,0 @@
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
Result audsnoopInitialize(void);
void audsnoopExit(void);
Result audsnoopEnableDspUsageMeasurement(void);
Result audsnoopDisableDspUsageMeasurement(void);
Result audsnoopGetDspUsage(u32 *usage);
#ifdef __cplusplus
} // extern "C"
#endif
@@ -1,77 +0,0 @@
#pragma once
Result I2cReadRegHandler16(u8 reg, I2cDevice dev, u16 *out)
{
struct readReg {
u8 send;
u8 sendLength;
u8 sendData;
u8 receive;
u8 receiveLength;
};
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (res)
return res;
u16 val;
struct readReg readRegister = {
.send = 0 | (I2cTransactionOption_Start << 6),
.sendLength = sizeof(reg),
.sendData = reg,
.receive = 1 | (I2cTransactionOption_All << 6),
.receiveLength = sizeof(val),
};
res = i2csessionExecuteCommandList(&_session, &val, sizeof(val), &readRegister, sizeof(readRegister));
if (res)
{
i2csessionClose(&_session);
return res;
}
*out = val;
i2csessionClose(&_session);
return 0;
}
Result I2cReadRegHandler8(u8 reg, I2cDevice dev, u8 *out)
{
struct readReg {
u8 send;
u8 sendLength;
u8 sendData;
u8 receive;
u8 receiveLength;
};
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (res)
return res;
u8 val;
struct readReg readRegister = {
.send = 0 | (I2cTransactionOption_Start << 6),
.sendLength = sizeof(reg),
.sendData = reg,
.receive = 1 | (I2cTransactionOption_All << 6),
.receiveLength = sizeof(val),
};
res = i2csessionExecuteCommandList(&_session, &val, sizeof(val), &readRegister, sizeof(readRegister));
if (res)
{
i2csessionClose(&_session);
return res;
}
*out = val;
i2csessionClose(&_session);
return 0;
}
@@ -1,755 +0,0 @@
/**
* @file ipc.h
* @brief Inter-process communication handling
* @author plutoo
* @copyright libnx Authors
*/
#pragma once
/// 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) {
const 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) {
const 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) {
const 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) {
const 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) {
const 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; i<cmd->NumStaticIn; i++, buf+=2) {
IpcStaticSendDescriptor* desc = (IpcStaticSendDescriptor*) buf;
const 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];
const uintptr_t ptr = (uintptr_t) cmd->Buffers[i];
desc->Addr = ptr;
desc->Packed = cmd->BufferTypes[i] |
(((ptr >> 32) & 15) << 28) | ((ptr >> 36) << 2);
}
const 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; i<cmd->NumStaticOut; i++) {
const size_t off = cmd->NumStaticIn + i;
const size_t sz = (uintptr_t) cmd->StaticSizes[off];
buf_u16[i] = (sz > 0xFFFF) ? 0 : sz;
}
const 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; i<cmd->NumStaticOut; i++, buf+=2) {
IpcStaticRecvDescriptor* desc = (IpcStaticRecvDescriptor*) buf;
const size_t off = cmd->NumStaticIn + i;
const 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;
}
const size_t num_handles_copy = ((ctrl2 >> 1) & 15);
const 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; i<num_handles; i++)
{
r->Handles[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; i<num_statics; i++, buf+=2) {
IpcStaticSendDescriptor* desc = (IpcStaticSendDescriptor*) buf;
const u64 packed = (u64) desc->Packed;
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;
const size_t num_bufs_send = (ctrl0 >> 20) & 15;
const size_t num_bufs_recv = (ctrl0 >> 24) & 15;
const 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; i<num_bufs; i++, buf+=3) {
IpcBufferDescriptor* desc = (IpcBufferDescriptor*) buf;
u64 packed = (u64) desc->Packed;
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
}
///@}
@@ -1,165 +0,0 @@
/*
* Fuel gauge driver for Nintendo Switch's Maxim 17050
*
* Copyright (c) 2011 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
* Copyright (c) 2018-2020 CTCaer
*
* 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
*/
/*
* Modified by: MasaGratoR
*/
#ifndef __MAX17050_H_
#define __MAX17050_H_
//#include <utils/types.h>
#include "i2c.h"
/* Board default values */
#define MAX17050_BOARD_CGAIN 2 /* Actual: 1.99993 */
#define MAX17050_BOARD_SNS_RESISTOR_UOHM 5000 /* 0.005 Ohm */
//#define MAX17050_STATUS_BattAbsent BIT(3)
/* Consider RepCap which is less then 10 units below FullCAP full */
/*
#define MAX17050_FULL_THRESHOLD 10
#define MAX17050_CHARACTERIZATION_DATA_SIZE 48
#define MAXIM17050_I2C_ADDR 0x36
*/
#define MAX17050_WAIT_NS 1000000000
constexpr float max17050SenseResistor = MAX17050_BOARD_SNS_RESISTOR_UOHM / 1000; // in uOhm
constexpr float max17050CGain = 1.99993;
enum MAX17050_reg {
MAX17050_STATUS = 0x00,
MAX17050_VALRT_Th = 0x01,
MAX17050_TALRT_Th = 0x02,
MAX17050_SALRT_Th = 0x03,
MAX17050_AtRate = 0x04,
MAX17050_RepCap = 0x05,
MAX17050_RepSOC = 0x06,
MAX17050_Age = 0x07,
MAX17050_TEMP = 0x08,
MAX17050_VCELL = 0x09,
MAX17050_Current = 0x0A,
MAX17050_AvgCurrent = 0x0B,
MAX17050_SOC = 0x0D,
MAX17050_AvSOC = 0x0E,
MAX17050_RemCap = 0x0F,
MAX17050_FullCAP = 0x10,
MAX17050_TTE = 0x11,
MAX17050_QRTbl00 = 0x12,
MAX17050_FullSOCThr = 0x13,
MAX17050_RSLOW = 0x14,
MAX17050_AvgTA = 0x16,
MAX17050_Cycles = 0x17,
MAX17050_DesignCap = 0x18,
MAX17050_AvgVCELL = 0x19,
MAX17050_MinMaxTemp = 0x1A,
MAX17050_MinMaxVolt = 0x1B,
MAX17050_MinMaxCurr = 0x1C,
MAX17050_CONFIG = 0x1D,
MAX17050_ICHGTerm = 0x1E,
MAX17050_AvCap = 0x1F,
MAX17050_ManName = 0x20,
MAX17050_DevName = 0x21,
MAX17050_QRTbl10 = 0x22,
MAX17050_FullCAPNom = 0x23,
MAX17050_TempNom = 0x24,
MAX17050_TempLim = 0x25,
MAX17050_TempHot = 0x26,
MAX17050_AIN = 0x27,
MAX17050_LearnCFG = 0x28,
MAX17050_FilterCFG = 0x29,
MAX17050_RelaxCFG = 0x2A,
MAX17050_MiscCFG = 0x2B,
MAX17050_TGAIN = 0x2C,
MAX17050_TOFF = 0x2D,
MAX17050_CGAIN = 0x2E,
MAX17050_COFF = 0x2F,
MAX17050_QRTbl20 = 0x32,
MAX17050_SOC_empty = 0x33,
MAX17050_T_empty = 0x34,
MAX17050_FullCAP0 = 0x35,
MAX17050_LAvg_empty = 0x36,
MAX17050_FCTC = 0x37,
MAX17050_RCOMP0 = 0x38,
MAX17050_TempCo = 0x39,
MAX17050_V_empty = 0x3A,
MAX17050_K_empty0 = 0x3B,
MAX17050_TaskPeriod = 0x3C,
MAX17050_FSTAT = 0x3D,
MAX17050_TIMER = 0x3E,
MAX17050_SHDNTIMER = 0x3F,
MAX17050_QRTbl30 = 0x42,
MAX17050_dQacc = 0x45,
MAX17050_dPacc = 0x46,
MAX17050_VFSOC0 = 0x48,
Max17050_QH0 = 0x4C,
MAX17050_QH = 0x4D,
MAX17050_QL = 0x4E,
MAX17050_MinVolt = 0x50, // Custom ID. Not to be sent to i2c.
MAX17050_MaxVolt = 0x51, // Custom ID. Not to be sent to i2c.
MAX17050_VFSOC0Enable = 0x60,
MAX17050_MODELEnable1 = 0x62,
MAX17050_MODELEnable2 = 0x63,
MAX17050_MODELChrTbl = 0x80,
MAX17050_OCV = 0xEE,
MAX17050_OCVInternal = 0xFB,
MAX17050_VFSOC = 0xFF,
};
/*
int max17050_get_property(enum MAX17050_reg reg, int *value);
int max17050_fix_configuration();
u32 max17050_get_cached_batt_volt();
*/
#endif /* __MAX17050_H_ */
Result Max17050ReadReg(u8 reg, u16 *out)
{
u16 data = 0;
Result res = I2cReadRegHandler16(reg, I2cDevice_Max17050, &data);
if (R_FAILED(res))
{
return res;
}
*out = data;
return res;
}
@@ -1,41 +0,0 @@
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,
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,
} PowerDomainId;
@@ -1,22 +0,0 @@
#pragma once
#include <switch.h>
#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
@@ -1,19 +0,0 @@
#pragma once
#include <switch.h>
#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);
@@ -1,29 +0,0 @@
// rgltr_services.h
// ========
// Minimal header declarations for rgltr‐related functionality.
// Any file that wants to call rgltrOpenSession(), rgltrGetVoltage(), etc. should
// simply do `#include "infonx.h"` (NOT infonx.cpp).
#pragma once
#include <switch.h> // for Service, Result, hosversionBefore(), smGetService(), serviceClose(), etc.
#include "rgltr.h" // for RgltrSession, PowerDomainId, etc.
// Global service handle for "rgltr". Defined in infonx.cpp.
extern Service g_rgltrSrv;
// Open/close the "rgltr" service. You must call rgltrInitialize() (once) before using
// rgltrOpenSession() & friends. Call rgltrExit() when your app is shutting down.
Result rgltrInitialize(void);
void rgltrExit(void);
// Open a regulator session for the given PowerDomainId (e.g. CPU, GPU, DRAM).
// On success, (*session_out).s will contain a valid Service handle.
Result rgltrOpenSession(RgltrSession* session_out, PowerDomainId module_id);
// Query the current voltage (in microvolts, µV) from a previously opened session.
// Writes the result into *out_volt.
Result rgltrGetVoltage(RgltrSession* session, u32* out_volt);
// Close a previously opened regulator session.
void rgltrCloseSession(RgltrSession* session);
@@ -1,103 +0,0 @@
/*
* SOC/PCB Temperature driver for Nintendo Switch's TI TMP451
*
* Copyright (c) 2018 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by: MasaGratoR
*/
#ifndef __TMP451_H_
#define __TMP451_H_
//#include <utils/types.h>
#include "i2c.h"
//#define TMP451_I2C_ADDR 0x4C
#define TMP451_PCB_TEMP_REG 0x00
#define TMP451_SOC_TEMP_REG 0x01
/*
#define TMP451_CONFIG_REG 0x09
#define TMP451_CNV_RATE_REG 0x0A
*/
#define TMP451_SOC_TEMP_DEC_REG 0x10
#define TMP451_PCB_TEMP_DEC_REG 0x15
/*
#define TMP451_SOC_TMP_OFH_REG 0x11
#define TMP451_SOC_TMP_OFL_REG 0x12
*/
// If input is false, the return value is packed. MSByte is the integer in oC
// and the LSByte is the decimal point truncated to 2 decimal places.
// Otherwise it's an integer oC.
/*
u16 tmp451_get_soc_temp(bool integer);
u16 tmp451_get_pcb_temp(bool integer);
void tmp451_init();
void tmp451_end();
*/
Result Tmp451ReadReg(u8 reg, u8 *out)
{
u8 data = 0;
Result res = I2cReadRegHandler8(reg, I2cDevice_Tmp451, &data);
if (R_FAILED(res))
{
return res;
}
*out = data;
return res;
}
Result Tmp451GetSocTemp(float* temperature) {
u8 integer = 0;
u8 decimals = 0;
Result rc = Tmp451ReadReg(TMP451_SOC_TEMP_REG, &integer);
if (R_FAILED(rc))
return rc;
rc = Tmp451ReadReg(TMP451_SOC_TEMP_DEC_REG, &decimals);
if (R_FAILED(rc))
return rc;
decimals = ((u16)(decimals >> 4) * 625) / 100;
*temperature = (float)(integer) + ((float)(decimals) / 100);
return rc;
}
Result Tmp451GetPcbTemp(float* temperature) {
u8 integer = 0;
u8 decimals = 0;
Result rc = Tmp451ReadReg(TMP451_PCB_TEMP_REG, &integer);
if (R_FAILED(rc))
return rc;
rc = Tmp451ReadReg(TMP451_PCB_TEMP_DEC_REG, &decimals);
if (R_FAILED(rc))
return rc;
decimals = ((u16)(decimals >> 4) * 625) / 100;
*temperature = (float)(integer) + ((float)(decimals) / 100);
return rc;
}
#endif /* __TMP451_H_ */
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modi",
"Modes   Configure": "Modi   Konfigurieren",
"Full": "Voll",
"Mini": "Mini",
"Micro": "Mikro",
"FPS Graph": "FPS-Diagramm",
"FPS Counter": "FPS-Zähler",
"Game Resolutions": "Spielauflösungen",
"Other": "Sonstiges",
"Battery/Charger": "Akku/Ladegerät",
"Miscellaneous": "Verschiedenes",
"CPU Usage": "CPU-Auslastung",
"GPU Usage": "GPU-Auslastung",
"RAM Usage": "RAM-Auslastung",
"Target Frequency": "Ziel-Frequenz",
"Real Frequency": "Aktuelle Frequenz",
"Core 0 ": "Kern 0 ",
"Core 1 ": "Kern 1 ",
"Core 2 ": "Kern 2 ",
"Core 3 ": "Kern 3 ",
"Load": "Auslastung",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Gesamt\nAnwendung\nApplet\nSystem\nSystem (unsicher)",
"Board": "Board",
"Battery Power Flow": "Akkuleistungsfluss",
"\nTemperatures": "\nTemperaturen",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Lüfterdrehzahl",
"Game": "Spiel",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Auflösungen ",
"Read Speed ": "Lesegeschwindigkeit ",
"Press ": "Drücke ",
" to Exit": " zum Beenden",
"Depth": "Tiefe",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "Spiel läuft nicht\noder ist inkompatibel.",
"Battery Stats": "Akkustatistiken",
"Actual Capacity": "Aktuelle Kapazität",
"Designed Capacity": "Nennkapazität",
"Temperature": "Temperatur",
"Raw Charge": "Rohladung",
"Age": "Alter",
"Voltage": "Spannung",
"Current Flow": "Stromfluss",
"Power Flow": "Leistungsfluss",
"Remaining Time": "Verbleibende Zeit",
"Charger Stats": "Ladegerät-Statistiken",
"Input Current Limit": "Eingangsstrombegrenzung",
"VBUS Current Limit": "VBUS-Strombegrenzung",
"Voltage Limit": "Spannungsbegrenzung",
"Current Limit": "Strombegrenzung",
"Type": "Typ",
"Max Voltage": "Max. Spannung",
"Max Current": "Max. Strom",
"Multimedia Clock Rates": "Multimedia-Taktraten",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Netzwerk",
"Type: Wi-Fi": "Typ: Wi-Fi",
"Press Y to show password": "Drücke Y, um Passwort anzuzeigen",
"Type: Ethernet": "Typ: Ethernet",
"Type: Not connected": "Typ: Nicht verbunden",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Elemente   Nach unten   Nach oben",
"Transparent": "Transparent",
"Opaque": "Undurchsichtig",
"Alpha": "Alpha",
"Pretty": "Hübsch",
"Compact": "Kompakt",
"FileSafe": "Dateisicher",
"Day+Time": "Tag+Uhrzeit",
"Date+Time(s)": "Datum+Uhrzeit(s)",
"Date+Time AM/PM": "Datum+Uhrzeit AM/PM",
"Date+Time(s) AM/PM": "Datum+Uhrzeit(s) AM/PM",
"Date+Time EU": "Datum+Uhrzeit EU",
"Date+Time EU AM/PM": "Datum+Uhrzeit EU AM/PM",
"Date+Time(s) EU AM/PM": "Datum+Uhrzeit(s) EU AM/PM",
"Date+Time ISO": "Datum+Uhrzeit ISO",
"Time 24h": "Uhrzeit 24h",
"Time AM/PM": "Uhrzeit AM/PM",
"Time(s) 24h": "Uhrzeit(s) 24h",
"Time(s) AM/PM": "Uhrzeit(s) AM/PM",
"Date US": "Datum US",
"Date EU": "Datum EU",
"Date ISO": "Datum ISO",
"Date Short": "Datum kurz",
"DTC Format": "DTC-Format",
"Toggles": "Schalter",
"Info": "Info anzeigen",
"Disable Screenshots": "Bildschirmfotos deaktivieren",
"Real Freqs": "Aktuelle Frequenzen",
"Deltas": "Deltas",
"Target Freqs": "Ziel-Frequenzen",
"FPS": "FPS",
"RES": "AUFL",
"Read Speed": "Lesegeschwindigkeit",
"Real Frequencies": "Aktuelle Frequenzen",
"Real Voltages": "Aktuelle Spannungen",
"Full CPU": "Volle CPU",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Volle Auflösung",
"SOC Voltage": "SOC-Spannung",
"RAM Load CPU/GPU": "RAM-Last CPU/GPU",
"Use DTC Symbol": "DTC-Symbol verwenden",
"Use Dynamic Colors": "Dynamische Farben verwenden",
"Sleep Exit": "Beenden aus Ruhezustand",
"Configuration": "Konfiguration",
"Refresh Rate": "Bildwiederholrate",
"Frame Padding": "Rahmenabstand",
" Font Size": " Schriftgröße",
"Font Sizes": "Schriftgrößen",
"Handheld Font Size": "Handheld-Schriftgröße",
"Docked Font Size": "Docked-Schriftgröße",
"Black": "Schwarz",
"Dark Gray": "Dunkelgrau",
"Gray": "Grau",
"Light Gray": "Hellgrau",
"Silver": "Silber",
"White": "Weiß",
"Dark Red": "Dunkelrot",
"Red": "Rot",
"Light Red": "Hellrot",
"Pink": "Pink",
"Dark Green": "Dunkelgrün",
"Green": "Grün",
"Lime Green": "Limette",
"Light Green": "Hellgrün",
"Dark Blue": "Dunkelblau",
"Blue": "Blau",
"Light Blue": "Hellblau",
"Sky Blue": "Himmelblau",
"Dark Purple": "Dunkellila",
"Purple": "Lila",
"Light Purple": "Helllila",
"Violet": "Violett",
"Orange": "Orange",
"Yellow": "Gelb",
"Light Yellow": "Hellgelb",
"Teal": "Petrol",
"Cyan": "Cyan",
"Light Cyan": "Hellcyan",
"Magenta": "Magenta",
"Hot Pink": "Knallpink",
"Brown": "Braun",
"Light Brown": "Hellbraun",
"Colors": "Farben",
"Background Color": "Hintergrundfarbe",
"Background Alpha": "Hintergrund-Alpha",
"Focus Color": "Fokusfarbe",
"Focus Alpha": "Fokus-Alpha",
"Text Color": "Textfarbe",
"Border": "Rahmen",
"Dashed Line": "Gestrichelte Linie",
"Max FPS Text": "Max-FPS-Text",
"Min FPS Text": "Min-FPS-Text",
"Main Line": "Hauptlinie",
"Rounded Line": "Abgerundete Linie",
"Perfect Line": "Perfekte Linie",
" Color": " Farbe",
" Alpha": " Alpha",
"Category Color": "Kategoriefarbe",
"Category Color 1": "Kategoriefarbe 1",
"Category Color 2": "Kategoriefarbe 2",
"Separator Color": "Trennlinienfarbe",
"Elements": "Elemente",
"Text Alignment": "Textausrichtung",
"Vertical Position": "Vertikale Position",
"Horizontal Position": "Horizontale Position",
"Left": "Links",
"Right": "Rechts",
"Top": "Oben",
"Center": "Mitte",
"Bottom": "Unten",
"FPS Counter Color": "FPS-Zähler-Farbe",
"FPS Counter Alpha": "FPS-Zähler-Alpha",
"Border Color": "Rahmenfarbe",
"Dashed Line Color": "Gestrichelte-Linie-Farbe",
"Dashed Line Alpha": "Gestrichelte-Linie-Alpha",
"Max FPS Text Color": "Max-FPS-Textfarbe",
"Min FPS Text Color": "Min-FPS-Textfarbe",
"Main Line Color": "Hauptlinienfarbe",
"Rounded Line Color": "Abgerundete-Linienfarbe",
"Perfect Line Color": "Perfekte-Linienfarbe"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modes",
"Modes   Configure": "Modes   Configure",
"Full": "Full",
"Mini": "Mini",
"Micro": "Micro",
"FPS Graph": "FPS Graph",
"FPS Counter": "FPS Counter",
"Game Resolutions": "Game Resolutions",
"Other": "Other",
"Battery/Charger": "Battery/Charger",
"Miscellaneous": "Miscellaneous",
"CPU Usage": "CPU Usage",
"GPU Usage": "GPU Usage",
"RAM Usage": "RAM Usage",
"Target Frequency": "Target Frequency",
"Real Frequency": "Real Frequency",
"Core 0 ": "Core 0 ",
"Core 1 ": "Core 1 ",
"Core 2 ": "Core 2 ",
"Core 3 ": "Core 3 ",
"Load": "Load",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Total\nApplication\nApplet\nSystem\nSystem Unsafe",
"Board": "Board",
"Battery Power Flow": "Battery Power Flow",
"\nTemperatures": "\nTemperatures",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Fan Rotation Level",
"Game": "Game",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Resolutions ",
"Read Speed ": "Read speed ",
"Press ": "Press ",
" to Exit": " to Exit",
"Depth": "Depth",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "Game is not running\nor it's incompatible.",
"Battery Stats": "Battery Stats",
"Actual Capacity": "Actual Capacity",
"Designed Capacity": "Designed Capacity",
"Temperature": "Temperature",
"Raw Charge": "Raw Charge",
"Age": "Age",
"Voltage": "Voltage",
"Current Flow": "Current Flow",
"Power Flow": "Power Flow",
"Remaining Time": "Remaining Time",
"Charger Stats": "Charger Stats",
"Input Current Limit": "Input Current Limit",
"VBUS Current Limit": "VBUS Current Limit",
"Voltage Limit": "Voltage Limit",
"Current Limit": "Current Limit",
"Type": "Type",
"Max Voltage": "Max Voltage",
"Max Current": "Max Current",
"Multimedia Clock Rates": "Multimedia Clock Rates",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Network",
"Type: Wi-Fi": "Type: Wi-Fi",
"Press Y to show password": "Press Y to show password",
"Type: Ethernet": "Type: Ethernet",
"Type: Not connected": "Tipo: Not connected",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Elements   Move Down   Move Up",
"Transparent": "Transparent",
"Opaque": "Opaque",
"Alpha": "Alpha",
"Pretty": "Pretty",
"Compact": "Compact",
"FileSafe": "FileSafe",
"Day+Time": "Day+Time",
"Date+Time(s)": "Date+Time(s)",
"Date+Time AM/PM": "Date+Time AM/PM",
"Date+Time(s) AM/PM": "Date+Time(s) AM/PM",
"Date+Time EU": "Date+Time EU",
"Date+Time EU AM/PM": "Date+Time EU AM/PM",
"Date+Time(s) EU AM/PM": "Date+Time(s) EU AM/PM",
"Date+Time ISO": "Date+Time ISO",
"Time 24h": "Time 24h",
"Time AM/PM": "Time AM/PM",
"Time(s) 24h": "Time(s) 24h",
"Time(s) AM/PM": "Time(s) AM/PM",
"Date US": "Date US",
"Date EU": "Date EU",
"Date ISO": "Date ISO",
"Date Short": "Date Short",
"DTC Format": "DTC Format",
"Toggles": "Toggles",
"Info": "Show Info",
"Disable Screenshots": "Disable Screenshots",
"Real Freqs": "Real Freqs",
"Deltas": "Deltas",
"Target Freqs": "Target Freqs",
"FPS": "FPS",
"RES": "RES",
"Read Speed": "Read Speed",
"Real Frequencies": "Real Frequencies",
"Real Voltages": "Real Voltages",
"Full CPU": "Full CPU",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Full Resolution",
"SOC Voltage": "SOC Voltage",
"RAM Load CPU/GPU": "RAM Load CPU/GPU",
"Use DTC Symbol": "Use DTC Symbol",
"Use Dynamic Colors": "Use Dynamic Colors",
"Sleep Exit": "Sleep Exit",
"Configuration": "Configuration",
"Refresh Rate": "Refresh Rate",
"Frame Padding": "Frame Padding",
" Font Size": " Font Size",
"Font Sizes": "Font Sizes",
"Handheld Font Size": "Handheld Font Size",
"Docked Font Size": "Docked Font Size",
"Black": "Black",
"Dark Gray": "Dark Gray",
"Gray": "Gray",
"Light Gray": "Light Gray",
"Silver": "Silver",
"White": "White",
"Dark Red": "Dark Red",
"Red": "Red",
"Light Red": "Light Red",
"Pink": "Pink",
"Dark Green": "Dark Green",
"Green": "Green",
"Lime Green": "Lime Green",
"Light Green": "Light Green",
"Dark Blue": "Dark Blue",
"Blue": "Blue",
"Light Blue": "Light Blue",
"Sky Blue": "Sky Blue",
"Dark Purple": "Dark Purple",
"Purple": "Purple",
"Light Purple": "Light Purple",
"Violet": "Violet",
"Orange": "Orange",
"Yellow": "Yellow",
"Light Yellow": "Light Yellow",
"Teal": "Teal",
"Cyan": "Cyan",
"Light Cyan": "Light Cyan",
"Magenta": "Magenta",
"Hot Pink": "Hot Pink",
"Brown": "Brown",
"Light Brown": "Light Brown",
"Colors": "Colors",
"Background Color": "Background Color",
"Background Alpha": "Background Alpha",
"Focus Color": "Focus Color",
"Focus Alpha": "Focus Alpha",
"Text Color": "Text Color",
"Border": "Border",
"Dashed Line": "Dashed Line",
"Max FPS Text": "Max FPS Text",
"Min FPS Text": "Min FPS Text",
"Main Line": "Main Line",
"Rounded Line": "Rounded Line",
"Perfect Line": "Perfect Line",
" Color": " Color",
" Alpha": " Alpha",
"Category Color": "Category Color",
"Category Color 1": "Category Color 1",
"Category Color 2": "Category Color 2",
"Separator Color": "Separator Color",
"Elements": "Elements",
"Text Alignment": "Text Alignment",
"Vertical Position": "Vertical Position",
"Horizontal Position": "Horizontal Position",
"Left": "Left",
"Right": "Right",
"Top": "Top",
"Center": "Center",
"Bottom": "Bottom",
"FPS Counter Color":"FPS Counter Color",
"FPS Counter Alpha":"FPS Counter Alpha",
"Border Color":"Border Color",
"Dashed Line Color":"Dashed Line Color",
"Dashed Line Alpha":"Dashed Line Alpha",
"Max FPS Text Color":"Max FPS Text Color",
"Min FPS Text Color":"Min FPS Text Color",
"Main Line Color":"Main Line Color",
"Rounded Line Color":"Rounded Line Color",
"Perfect Line Color":"Perfect Line Color"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modos",
"Modes   Configure": "Modos   Configurar",
"Full": "Completo",
"Mini": "Mini",
"Micro": "Micro",
"FPS Graph": "Gráfico FPS",
"FPS Counter": "Contador FPS",
"Game Resolutions": "Resoluciones del juego",
"Other": "Otros",
"Battery/Charger": "Batería/Cargador",
"Miscellaneous": "Varios",
"CPU Usage": "Uso de CPU",
"GPU Usage": "Uso de GPU",
"RAM Usage": "Uso de RAM",
"Target Frequency": "Frecuencia objetivo",
"Real Frequency": "Frecuencia real",
"Core 0 ": "Núcleo 0 ",
"Core 1 ": "Núcleo 1 ",
"Core 2 ": "Núcleo 2 ",
"Core 3 ": "Núcleo 3 ",
"Load": "Carga",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Total\nAplicación\nApplet\nSistema\nSistema (inseguro)",
"Board": "Placa",
"Battery Power Flow": "Flujo de energía de batería",
"\nTemperatures": "\nTemperaturas",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Nivel de rotación del ventilador",
"Game": "Juego",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Resoluciones ",
"Read Speed ": "Velocidad de lectura ",
"Press ": "Pulsa ",
" to Exit": " para salir",
"Depth": "Profundidad",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "El juego no está en ejecución\no es incompatible.",
"Battery Stats": "Estadísticas de batería",
"Actual Capacity": "Capacidad actual",
"Designed Capacity": "Capacidad nominal",
"Temperature": "Temperatura",
"Raw Charge": "Carga bruta",
"Age": "Edad",
"Voltage": "Voltaje",
"Current Flow": "Flujo de corriente",
"Power Flow": "Flujo de potencia",
"Remaining Time": "Tiempo restante",
"Charger Stats": "Estadísticas del cargador",
"Input Current Limit": "Límite de corriente de entrada",
"VBUS Current Limit": "Límite de corriente VBUS",
"Voltage Limit": "Límite de voltaje",
"Current Limit": "Límite de corriente",
"Type": "Tipo",
"Max Voltage": "Voltaje máx.",
"Max Current": "Corriente máx.",
"Multimedia Clock Rates": "Frecuencias de reloj multimedia",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Red",
"Type: Wi-Fi": "Tipo: Wi-Fi",
"Press Y to show password": "Pulsa Y para mostrar contraseña",
"Type: Ethernet": "Tipo: Ethernet",
"Type: Not connected": "Tipo: No conectado",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Elementos   Bajar   Subir",
"Transparent": "Transparente",
"Opaque": "Opaco",
"Alpha": "Alfa",
"Pretty": "Bonito",
"Compact": "Compacto",
"FileSafe": "FileSafe",
"Day+Time": "Día+Hora",
"Date+Time(s)": "Fecha+Hora(s)",
"Date+Time AM/PM": "Fecha+Hora AM/PM",
"Date+Time(s) AM/PM": "Fecha+Hora(s) AM/PM",
"Date+Time EU": "Fecha+Hora EU",
"Date+Time EU AM/PM": "Fecha+Hora EU AM/PM",
"Date+Time(s) EU AM/PM": "Fecha+Hora(s) EU AM/PM",
"Date+Time ISO": "Fecha+Hora ISO",
"Time 24h": "Hora 24h",
"Time AM/PM": "Hora AM/PM",
"Time(s) 24h": "Hora(s) 24h",
"Time(s) AM/PM": "Hora(s) AM/PM",
"Date US": "Fecha EE.UU.",
"Date EU": "Fecha EU",
"Date ISO": "Fecha ISO",
"Date Short": "Fecha corta",
"DTC Format": "Formato DTC",
"Toggles": "Conmutadores",
"Info": "Mostrar info",
"Disable Screenshots": "Desactivar capturas",
"Real Freqs": "Frecuencias reales",
"Deltas": "Deltas",
"Target Freqs": "Frecuencias objetivo",
"FPS": "FPS",
"RES": "RES",
"Read Speed": "Velocidad de lectura",
"Real Frequencies": "Frecuencias reales",
"Real Voltages": "Voltajes reales",
"Full CPU": "CPU completo",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Resolución completa",
"SOC Voltage": "Voltaje SOC",
"RAM Load CPU/GPU": "Carga RAM CPU/GPU",
"Use DTC Symbol": "Usar símbolo DTC",
"Use Dynamic Colors": "Usar colores dinámicos",
"Sleep Exit": "Salir del reposo",
"Configuration": "Configuración",
"Refresh Rate": "Tasa de refresco",
"Frame Padding": "Relleno de marco",
" Font Size": " Tamaño de fuente",
"Font Sizes": "Tamaños de fuente",
"Handheld Font Size": "Tamaño fuente portátil",
"Docked Font Size": "Tamaño fuente acoplado",
"Black": "Negro",
"Dark Gray": "Gris oscuro",
"Gray": "Gris",
"Light Gray": "Gris claro",
"Silver": "Plata",
"White": "Blanco",
"Dark Red": "Rojo oscuro",
"Red": "Rojo",
"Light Red": "Rojo claro",
"Pink": "Rosa",
"Dark Green": "Verde oscuro",
"Green": "Verde",
"Lime Green": "Verde lima",
"Light Green": "Verde claro",
"Dark Blue": "Azul oscuro",
"Blue": "Azul",
"Light Blue": "Azul claro",
"Sky Blue": "Azul cielo",
"Dark Purple": "Púrpura oscuro",
"Purple": "Púrpura",
"Light Purple": "Púrpura claro",
"Violet": "Violeta",
"Orange": "Naranja",
"Yellow": "Amarillo",
"Light Yellow": "Amarillo claro",
"Teal": "Verde azulado",
"Cyan": "Cian",
"Light Cyan": "Cian claro",
"Magenta": "Magenta",
"Hot Pink": "Rosa intenso",
"Brown": "Marrón",
"Light Brown": "Marrón claro",
"Colors": "Colores",
"Background Color": "Color de fondo",
"Background Alpha": "Alfa de fondo",
"Focus Color": "Color de foco",
"Focus Alpha": "Alfa de foco",
"Text Color": "Color de texto",
"Border": "Borde",
"Dashed Line": "Línea discontinua",
"Max FPS Text": "Texto FPS máx.",
"Min FPS Text": "Texto FPS mín.",
"Main Line": "Línea principal",
"Rounded Line": "Línea redondeada",
"Perfect Line": "Línea perfecta",
" Color": " Color",
" Alpha": " Alfa",
"Category Color": "Color de categoría",
"Category Color 1": "Color categoría 1",
"Category Color 2": "Color categoría 2",
"Separator Color": "Color separador",
"Elements": "Elementos",
"Text Alignment": "Alineación de texto",
"Vertical Position": "Posición vertical",
"Horizontal Position": "Posición horizontal",
"Left": "Izquierda",
"Right": "Derecha",
"Top": "Arriba",
"Center": "Centro",
"Bottom": "Abajo",
"FPS Counter Color": "Color contador FPS",
"FPS Counter Alpha": "Alfa contador FPS",
"Border Color": "Color de borde",
"Dashed Line Color": "Color línea discontinua",
"Dashed Line Alpha": "Alfa línea discontinua",
"Max FPS Text Color": "Color texto FPS máx.",
"Min FPS Text Color": "Color texto FPS mín.",
"Main Line Color": "Color línea principal",
"Rounded Line Color": "Color línea redondeada",
"Perfect Line Color": "Color línea perfecta"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modes",
"Modes   Configure": "Modes   Configurer",
"Full": "Complet",
"Mini": "Mini",
"Micro": "Micro",
"FPS Graph": "Graphique FPS",
"FPS Counter": "Compteur FPS",
"Game Resolutions": "Résolutions du jeu",
"Other": "Autres",
"Battery/Charger": "Batterie/Chargeur",
"Miscellaneous": "Divers",
"CPU Usage": "Utilisation CPU",
"GPU Usage": "Utilisation GPU",
"RAM Usage": "Utilisation RAM",
"Target Frequency": "Fréquence cible",
"Real Frequency": "Fréquence réelle",
"Core 0 ": "Cœur 0 ",
"Core 1 ": "Cœur 1 ",
"Core 2 ": "Cœur 2 ",
"Core 3 ": "Cœur 3 ",
"Load": "Charge",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Total\nApplication\nApplet\nSystème\nSystème non sécurisé",
"Board": "Carte",
"Battery Power Flow": "Flux de puissance batterie",
"\nTemperatures": "\nTempératures",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Niveau de rotation ventilateur",
"Game": "Jeu",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Résolutions ",
"Read Speed ": "Vitesse de lecture ",
"Press ": "Appuyez sur ",
" to Exit": " pour quitter",
"Depth": "Profondeur",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "Le jeu ne fonctionne pas\nou est incompatible.",
"Battery Stats": "Statistiques batterie",
"Actual Capacity": "Capacité actuelle",
"Designed Capacity": "Capacité nominale",
"Temperature": "Température",
"Raw Charge": "Charge brute",
"Age": "Âge",
"Voltage": "Tension",
"Current Flow": "Flux de courant",
"Power Flow": "Flux de puissance",
"Remaining Time": "Temps restant",
"Charger Stats": "Statistiques chargeur",
"Input Current Limit": "Limite courant entrée",
"VBUS Current Limit": "Limite courant VBUS",
"Voltage Limit": "Limite tension",
"Current Limit": "Limite courant",
"Type": "Type",
"Max Voltage": "Tension max",
"Max Current": "Courant max",
"Multimedia Clock Rates": "Fréquences horloge multimédia",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Réseau",
"Type: Wi-Fi": "Type : Wi-Fi",
"Press Y to show password": "Appuyez sur Y pour afficher le mot de passe",
"Type: Ethernet": "Type : Ethernet",
"Type: Not connected": "Type : Non connecté",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Éléments   Descendre   Monter",
"Transparent": "Transparent",
"Opaque": "Opaque",
"Alpha": "Alpha",
"Pretty": "Joli",
"Compact": "Compact",
"FileSafe": "FileSafe",
"Day+Time": "Jour+Heure",
"Date+Time(s)": "Date+Heure(s)",
"Date+Time AM/PM": "Date+Heure AM/PM",
"Date+Time(s) AM/PM": "Date+Heure(s) AM/PM",
"Date+Time EU": "Date+Heure EU",
"Date+Time EU AM/PM": "Date+Heure EU AM/PM",
"Date+Time(s) EU AM/PM": "Date+Heure(s) EU AM/PM",
"Date+Time ISO": "Date+Heure ISO",
"Time 24h": "Heure 24h",
"Time AM/PM": "Heure AM/PM",
"Time(s) 24h": "Heure(s) 24h",
"Time(s) AM/PM": "Heure(s) AM/PM",
"Date US": "Date US",
"Date EU": "Date EU",
"Date ISO": "Date ISO",
"Date Short": "Date courte",
"DTC Format": "Format DTC",
"Toggles": "Basculement",
"Info": "Afficher infos",
"Disable Screenshots": "Désactiver captures d'écran",
"Real Freqs": "Fréquences réelles",
"Deltas": "Deltas",
"Target Freqs": "Fréquences cibles",
"FPS": "FPS",
"RES": "RÉS",
"Read Speed": "Vitesse de lecture",
"Real Frequencies": "Fréquences réelles",
"Real Voltages": "Tensions réelles",
"Full CPU": "CPU complet",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Résolution complète",
"SOC Voltage": "Tension SOC",
"RAM Load CPU/GPU": "Charge RAM CPU/GPU",
"Use DTC Symbol": "Utiliser symbole DTC",
"Use Dynamic Colors": "Utiliser couleurs dynamiques",
"Sleep Exit": "Sortie veille",
"Configuration": "Configuration",
"Refresh Rate": "Taux de rafraîchissement",
"Frame Padding": "Marge de cadre",
" Font Size": " Taille police",
"Font Sizes": "Tailles de police",
"Handheld Font Size": "Taille police portable",
"Docked Font Size": "Taille police dockée",
"Black": "Noir",
"Dark Gray": "Gris foncé",
"Gray": "Gris",
"Light Gray": "Gris clair",
"Silver": "Argent",
"White": "Blanc",
"Dark Red": "Rouge foncé",
"Red": "Rouge",
"Light Red": "Rouge clair",
"Pink": "Rose",
"Dark Green": "Vert foncé",
"Green": "Vert",
"Lime Green": "Vert citron",
"Light Green": "Vert clair",
"Dark Blue": "Bleu foncé",
"Blue": "Bleu",
"Light Blue": "Bleu clair",
"Sky Blue": "Bleu ciel",
"Dark Purple": "Violet foncé",
"Purple": "Violet",
"Light Purple": "Violet clair",
"Violet": "Violet",
"Orange": "Orange",
"Yellow": "Jaune",
"Light Yellow": "Jaune clair",
"Teal": "Sarcelle",
"Cyan": "Cyan",
"Light Cyan": "Cyan clair",
"Magenta": "Magenta",
"Hot Pink": "Rose vif",
"Brown": "Marron",
"Light Brown": "Marron clair",
"Colors": "Couleurs",
"Background Color": "Couleur d'arrière-plan",
"Background Alpha": "Alpha d'arrière-plan",
"Focus Color": "Couleur de focus",
"Focus Alpha": "Alpha de focus",
"Text Color": "Couleur du texte",
"Border": "Bordure",
"Dashed Line": "Ligne pointillée",
"Max FPS Text": "Texte FPS max",
"Min FPS Text": "Texte FPS min",
"Main Line": "Ligne principale",
"Rounded Line": "Ligne arrondie",
"Perfect Line": "Ligne parfaite",
" Color": " Couleur",
" Alpha": " Alpha",
"Category Color": "Couleur de catégorie",
"Category Color 1": "Couleur catégorie 1",
"Category Color 2": "Couleur catégorie 2",
"Separator Color": "Couleur de séparateur",
"Elements": "Éléments",
"Text Alignment": "Alignement du texte",
"Vertical Position": "Position verticale",
"Horizontal Position": "Position horizontale",
"Left": "Gauche",
"Right": "Droite",
"Top": "Haut",
"Center": "Centre",
"Bottom": "Bas",
"FPS Counter Color": "Couleur compteur FPS",
"FPS Counter Alpha": "Alpha compteur FPS",
"Border Color": "Couleur de bordure",
"Dashed Line Color": "Couleur ligne pointillée",
"Dashed Line Alpha": "Alpha ligne pointillée",
"Max FPS Text Color": "Couleur texte FPS max",
"Min FPS Text Color": "Couleur texte FPS min",
"Main Line Color": "Couleur ligne principale",
"Rounded Line Color": "Couleur ligne arrondie",
"Perfect Line Color": "Couleur ligne parfaite"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modalità",
"Modes   Configure": "Modalità   Configura",
"Full": "Completo",
"Mini": "Mini",
"Micro": "Micro",
"FPS Graph": "Grafico FPS",
"FPS Counter": "Contatore FPS",
"Game Resolutions": "Risoluzioni di gioco",
"Other": "Altro",
"Battery/Charger": "Batteria/Caricatore",
"Miscellaneous": "Varie",
"CPU Usage": "Utilizzo CPU",
"GPU Usage": "Utilizzo GPU",
"RAM Usage": "Utilizzo RAM",
"Target Frequency": "Frequenza target",
"Real Frequency": "Frequenza reale",
"Core 0 ": "Core 0 ",
"Core 1 ": "Core 1 ",
"Core 2 ": "Core 2 ",
"Core 3 ": "Core 3 ",
"Load": "Carico",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Totale\nApplicazione\nApplet\nSistema\nSistema non sicuro",
"Board": "Scheda",
"Battery Power Flow": "Flusso potenza batteria",
"\nTemperatures": "\nTemperature",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Livello rotazione ventola",
"Game": "Gioco",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Risoluzioni ",
"Read Speed ": "Velocità di lettura ",
"Press ": "Premi ",
" to Exit": " per uscire",
"Depth": "Profondità",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "Il gioco non è in esecuzione\no è incompatibile.",
"Battery Stats": "Statistiche batteria",
"Actual Capacity": "Capacità attuale",
"Designed Capacity": "Capacità nominale",
"Temperature": "Temperatura",
"Raw Charge": "Carica grezza",
"Age": "Età",
"Voltage": "Voltaggio",
"Current Flow": "Flusso corrente",
"Power Flow": "Flusso potenza",
"Remaining Time": "Tempo rimanente",
"Charger Stats": "Statistiche caricatore",
"Input Current Limit": "Limite corrente ingresso",
"VBUS Current Limit": "Limite corrente VBUS",
"Voltage Limit": "Limite tensione",
"Current Limit": "Limite corrente",
"Type": "Tipo",
"Max Voltage": "Tensione max",
"Max Current": "Corrente max",
"Multimedia Clock Rates": "Frequenze clock multimediali",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Rete",
"Type: Wi-Fi": "Tipo: Wi-Fi",
"Press Y to show password": "Premi Y per mostrare la password",
"Type: Ethernet": "Tipo: Ethernet",
"Type: Not connected": "Tipo: Non connesso",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Elementi   Sposta giù   Sposta su",
"Transparent": "Trasparente",
"Opaque": "Opaco",
"Alpha": "Alfa",
"Pretty": "Carino",
"Compact": "Compatto",
"FileSafe": "FileSafe",
"Day+Time": "Giorno+Ora",
"Date+Time(s)": "Data+Ora(s)",
"Date+Time AM/PM": "Data+Ora AM/PM",
"Date+Time(s) AM/PM": "Data+Ora(s) AM/PM",
"Date+Time EU": "Data+Ora EU",
"Date+Time EU AM/PM": "Data+Ora EU AM/PM",
"Date+Time(s) EU AM/PM": "Data+Ora(s) EU AM/PM",
"Date+Time ISO": "Data+Ora ISO",
"Time 24h": "Ora 24h",
"Time AM/PM": "Ora AM/PM",
"Time(s) 24h": "Ora(s) 24h",
"Time(s) AM/PM": "Ora(s) AM/PM",
"Date US": "Data US",
"Date EU": "Data EU",
"Date ISO": "Data ISO",
"Date Short": "Data breve",
"DTC Format": "Formato DTC",
"Toggles": "Interruttori",
"Info": "Mostra info",
"Disable Screenshots": "Disabilita screenshot",
"Real Freqs": "Frequenze reali",
"Deltas": "Delta",
"Target Freqs": "Frequenze target",
"FPS": "FPS",
"RES": "RIS",
"Read Speed": "Velocità di lettura",
"Real Frequencies": "Frequenze reali",
"Real Voltages": "Voltaggi reali",
"Full CPU": "CPU completo",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Risoluzione completa",
"SOC Voltage": "Voltaggio SOC",
"RAM Load CPU/GPU": "Carico RAM CPU/GPU",
"Use DTC Symbol": "Usa simbolo DTC",
"Use Dynamic Colors": "Usa colori dinamici",
"Sleep Exit": "Uscita standby",
"Configuration": "Configurazione",
"Refresh Rate": "Frequenza di aggiornamento",
"Frame Padding": "Spaziatura cornice",
" Font Size": " Dimensione font",
"Font Sizes": "Dimensioni font",
"Handheld Font Size": "Dimensione font portatile",
"Docked Font Size": "Dimensione font docked",
"Black": "Nero",
"Dark Gray": "Grigio scuro",
"Gray": "Grigio",
"Light Gray": "Grigio chiaro",
"Silver": "Argento",
"White": "Bianco",
"Dark Red": "Rosso scuro",
"Red": "Rosso",
"Light Red": "Rosso chiaro",
"Pink": "Rosa",
"Dark Green": "Verde scuro",
"Green": "Verde",
"Lime Green": "Verde lime",
"Light Green": "Verde chiaro",
"Dark Blue": "Blu scuro",
"Blue": "Blu",
"Light Blue": "Azzurro",
"Sky Blue": "Azzurro cielo",
"Dark Purple": "Viola scuro",
"Purple": "Viola",
"Light Purple": "Viola chiaro",
"Violet": "Viola",
"Orange": "Arancione",
"Yellow": "Giallo",
"Light Yellow": "Giallo chiaro",
"Teal": "Verde acqua",
"Cyan": "Ciano",
"Light Cyan": "Ciano chiaro",
"Magenta": "Magenta",
"Hot Pink": "Rosa shocking",
"Brown": "Marrone",
"Light Brown": "Marrone chiaro",
"Colors": "Colori",
"Background Color": "Colore sfondo",
"Background Alpha": "Alfa sfondo",
"Focus Color": "Colore focus",
"Focus Alpha": "Alfa focus",
"Text Color": "Colore testo",
"Border": "Bordo",
"Dashed Line": "Linea tratteggiata",
"Max FPS Text": "Testo FPS max",
"Min FPS Text": "Testo FPS min",
"Main Line": "Linea principale",
"Rounded Line": "Linea arrotondata",
"Perfect Line": "Linea perfetta",
" Color": " Colore",
" Alpha": " Alfa",
"Category Color": "Colore categoria",
"Category Color 1": "Colore categoria 1",
"Category Color 2": "Colore categoria 2",
"Separator Color": "Colore separatore",
"Elements": "Elementi",
"Text Alignment": "Allineamento testo",
"Vertical Position": "Posizione verticale",
"Horizontal Position": "Posizione orizzontale",
"Left": "Sinistra",
"Right": "Destra",
"Top": "Sopra",
"Center": "Centro",
"Bottom": "Sotto",
"FPS Counter Color": "Colore contatore FPS",
"FPS Counter Alpha": "Alfa contatore FPS",
"Border Color": "Colore bordo",
"Dashed Line Color": "Colore linea tratteggiata",
"Dashed Line Alpha": "Alfa linea tratteggiata",
"Max FPS Text Color": "Colore testo FPS max",
"Min FPS Text Color": "Colore testo FPS min",
"Main Line Color": "Colore linea principale",
"Rounded Line Color": "Colore linea arrotondata",
"Perfect Line Color": "Colore linea perfetta"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "モード",
"Modes   Configure": "モード   設定",
"Full": "フル",
"Mini": "ミニ",
"Micro": "マイクロ",
"FPS Graph": "FPSグラフ",
"FPS Counter": "FPSカウンター",
"Game Resolutions": "ゲーム解像度",
"Other": "その他",
"Battery/Charger": "バッテリー/充電器",
"Miscellaneous": "その他",
"CPU Usage": "CPU使用率",
"GPU Usage": "GPU使用率",
"RAM Usage": "RAM使用率",
"Target Frequency": "目標周波数",
"Real Frequency": "実際の周波数",
"Core 0 ": "コア 0 ",
"Core 1 ": "コア 1 ",
"Core 2 ": "コア 2 ",
"Core 3 ": "コア 3 ",
"Load": "負荷",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "合計\nアプリケーション\nアプレット\nシステム\nシステム(非安全)",
"Board": "ボード",
"Battery Power Flow": "バッテリー電力フロー",
"\nTemperatures": "\n温度",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "ファン回転レベル",
"Game": "ゲーム",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "解像度 ",
"Read Speed ": "読み込み速度 ",
"Press ": "押す ",
" to Exit": " で終了",
"Depth": "深度",
"Viewport": "ビューポート",
"Game is not running\nor it's incompatible.": "ゲームが実行されていない\nまたは非対応です。",
"Battery Stats": "バッテリー統計",
"Actual Capacity": "実際の容量",
"Designed Capacity": "設計容量",
"Temperature": "温度",
"Raw Charge": "生充電量",
"Age": "経年",
"Voltage": "電圧",
"Current Flow": "電流フロー",
"Power Flow": "電力フロー",
"Remaining Time": "残り時間",
"Charger Stats": "充電器統計",
"Input Current Limit": "入力電流制限",
"VBUS Current Limit": "VBUS電流制限",
"Voltage Limit": "電圧制限",
"Current Limit": "電流制限",
"Type": "タイプ",
"Max Voltage": "最大電圧",
"Max Current": "最大電流",
"Multimedia Clock Rates": "マルチメディアクロックレート",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "ネットワーク",
"Type: Wi-Fi": "タイプ: Wi-Fi",
"Press Y to show password": "Yを押してパスワードを表示",
"Type: Ethernet": "タイプ: Ethernet",
"Type: Not connected": "タイプ: 未接続",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "要素   下へ   上へ",
"Transparent": "透明",
"Opaque": "不透明",
"Alpha": "アルファ",
"Pretty": "プリティ",
"Compact": "コンパクト",
"FileSafe": "ファイルセーフ",
"Day+Time": "日+時間",
"Date+Time(s)": "日付+時間(s)",
"Date+Time AM/PM": "日付+時間 AM/PM",
"Date+Time(s) AM/PM": "日付+時間(s) AM/PM",
"Date+Time EU": "日付+時間 EU",
"Date+Time EU AM/PM": "日付+時間 EU AM/PM",
"Date+Time(s) EU AM/PM": "日付+時間(s) EU AM/PM",
"Date+Time ISO": "日付+時間 ISO",
"Time 24h": "時間 24h",
"Time AM/PM": "時間 AM/PM",
"Time(s) 24h": "時間(s) 24h",
"Time(s) AM/PM": "時間(s) AM/PM",
"Date US": "日付 US",
"Date EU": "日付 EU",
"Date ISO": "日付 ISO",
"Date Short": "短い日付",
"DTC Format": "DTC形式",
"Toggles": "トグル",
"Info": "情報表示",
"Disable Screenshots": "スクリーンショット無効",
"Real Freqs": "実際の周波数",
"Deltas": "デルタ",
"Target Freqs": "目標周波数",
"FPS": "FPS",
"RES": "解像度",
"Read Speed": "読み込み速度",
"Real Frequencies": "実際の周波数",
"Real Voltages": "実際の電圧",
"Full CPU": "フルCPU",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "フル解像度",
"SOC Voltage": "SOC電圧",
"RAM Load CPU/GPU": "RAM負荷 CPU/GPU",
"Use DTC Symbol": "DTCシンボル使用",
"Use Dynamic Colors": "ダイナミックカラー使用",
"Sleep Exit": "スリープ解除",
"Configuration": "設定",
"Refresh Rate": "リフレッシュレート",
"Frame Padding": "フレームパディング",
" Font Size": " フォントサイズ",
"Font Sizes": "フォントサイズ",
"Handheld Font Size": "携帯モードフォントサイズ",
"Docked Font Size": "ドックモードフォントサイズ",
"Black": "黒",
"Dark Gray": "ダークグレー",
"Gray": "グレー",
"Light Gray": "ライトグレー",
"Silver": "シルバー",
"White": "白",
"Dark Red": "ダークレッド",
"Red": "赤",
"Light Red": "ライト レッド",
"Pink": "ピンク",
"Dark Green": "ダークグリーン",
"Green": "緑",
"Lime Green": "ライムグリーン",
"Light Green": "ライトグリーン",
"Dark Blue": "ダークブルー",
"Blue": "青",
"Light Blue": "ライトブルー",
"Sky Blue": "スカイブルー",
"Dark Purple": "ダークパープル",
"Purple": "パープル",
"Light Purple": "ライトパープル",
"Violet": "バイオレット",
"Orange": "オレンジ",
"Yellow": "黄",
"Light Yellow": "ライトイエロー",
"Teal": "ティール",
"Cyan": "シアン",
"Light Cyan": "ライトシアン",
"Magenta": "マゼンタ",
"Hot Pink": "ホットピンク",
"Brown": "ブラウン",
"Light Brown": "ライトブラウン",
"Colors": "色",
"Background Color": "背景色",
"Background Alpha": "背景アルファ",
"Focus Color": "フォーカス色",
"Focus Alpha": "フォーカスアルファ",
"Text Color": "文字色",
"Border": "枠線",
"Dashed Line": "破線",
"Max FPS Text": "最大FPSテキスト",
"Min FPS Text": "最小FPSテキスト",
"Main Line": "主線",
"Rounded Line": "丸角線",
"Perfect Line": "完璧線",
" Color": " 色",
" Alpha": " アルファ",
"Category Color": "カテゴリ色",
"Category Color 1": "カテゴリ色 1",
"Category Color 2": "カテゴリ色 2",
"Separator Color": "セパレータ色",
"Elements": "要素",
"Text Alignment": "テキスト配置",
"Vertical Position": "垂直位置",
"Horizontal Position": "水平位置",
"Left": "左",
"Right": "右",
"Top": "上",
"Center": "中央",
"Bottom": "下",
"FPS Counter Color": "FPSカウンター色",
"FPS Counter Alpha": "FPSカウンターアルファ",
"Border Color": "枠線色",
"Dashed Line Color": "破線色",
"Dashed Line Alpha": "破線アルファ",
"Max FPS Text Color": "最大FPSテキスト色",
"Min FPS Text Color": "最小FPSテキスト色",
"Main Line Color": "主線色",
"Rounded Line Color": "丸角線色",
"Perfect Line Color": "完璧線色"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "모드",
"Modes   Configure": "모드   설정",
"Full": "전체",
"Mini": "미니",
"Micro": "마이크로",
"FPS Graph": "FPS 그래프",
"FPS Counter": "FPS 카운터",
"Game Resolutions": "게임 해상도",
"Other": "기타",
"Battery/Charger": "배터리/충전기",
"Miscellaneous": "기타",
"CPU Usage": "CPU 사용량",
"GPU Usage": "GPU 사용량",
"RAM Usage": "RAM 사용량",
"Target Frequency": "목표 주파수",
"Real Frequency": "실제 주파수",
"Core 0 ": "코어 0 ",
"Core 1 ": "코어 1 ",
"Core 2 ": "코어 2 ",
"Core 3 ": "코어 3 ",
"Load": "부하",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "합계\n앱\n애플릿\n시스템\n시스템(비안전)",
"Board": "보드",
"Battery Power Flow": "배터리 전력 흐름",
"\nTemperatures": "\n온도",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "팬 회전 레벨",
"Game": "게임",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "해상도 ",
"Read Speed ": "읽기 속도 ",
"Press ": "누르기 ",
" to Exit": " 종료",
"Depth": "깊이",
"Viewport": "뷰포트",
"Game is not running\nor it's incompatible.": "게임이 실행 중이 아니거나\n호환되지 않습니다.",
"Battery Stats": "배터리 통계",
"Actual Capacity": "실제 용량",
"Designed Capacity": "설계 용량",
"Temperature": "온도",
"Raw Charge": "원시 충전량",
"Age": "수명",
"Voltage": "전압",
"Current Flow": "전류 흐름",
"Power Flow": "전력 흐름",
"Remaining Time": "남은 시간",
"Charger Stats": "충전기 통계",
"Input Current Limit": "입력 전류 제한",
"VBUS Current Limit": "VBUS 전류 제한",
"Voltage Limit": "전압 제한",
"Current Limit": "전류 제한",
"Type": "유형",
"Max Voltage": "최대 전압",
"Max Current": "최대 전류",
"Multimedia Clock Rates": "멀티미디어 클럭 속도",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "네트워크",
"Type: Wi-Fi": "유형: Wi-Fi",
"Press Y to show password": "Y를 눌러 비밀번호 표시",
"Type: Ethernet": "유형: 이더넷",
"Type: Not connected": "유형: 연결 안 됨",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "요소   아래로   위로",
"Transparent": "투명",
"Opaque": "불투명",
"Alpha": "알파",
"Pretty": "예쁨",
"Compact": "컴팩트",
"FileSafe": "파일세이프",
"Day+Time": "일+시간",
"Date+Time(s)": "날짜+시간(s)",
"Date+Time AM/PM": "날짜+시간 AM/PM",
"Date+Time(s) AM/PM": "날짜+시간(s) AM/PM",
"Date+Time EU": "날짜+시간 EU",
"Date+Time EU AM/PM": "날짜+시간 EU AM/PM",
"Date+Time(s) EU AM/PM": "날짜+시간(s) EU AM/PM",
"Date+Time ISO": "날짜+시간 ISO",
"Time 24h": "시간 24시",
"Time AM/PM": "시간 AM/PM",
"Time(s) 24h": "시간(s) 24시",
"Time(s) AM/PM": "시간(s) AM/PM",
"Date US": "날짜 US",
"Date EU": "날짜 EU",
"Date ISO": "날짜 ISO",
"Date Short": "짧은 날짜",
"DTC Format": "DTC 형식",
"Toggles": "토글",
"Info": "정보 표시",
"Disable Screenshots": "스크린샷 비활성화",
"Real Freqs": "실제 주파수",
"Deltas": "델타",
"Target Freqs": "목표 주파수",
"FPS": "FPS",
"RES": "해상도",
"Read Speed": "읽기 속도",
"Real Frequencies": "실제 주파수",
"Real Voltages": "실제 전압",
"Full CPU": "전체 CPU",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "전체 해상도",
"SOC Voltage": "SOC 전압",
"RAM Load CPU/GPU": "RAM 부하 CPU/GPU",
"Use DTC Symbol": "DTC 심볼 사용",
"Use Dynamic Colors": "동적 색상 사용",
"Sleep Exit": "절전 종료",
"Configuration": "설정",
"Refresh Rate": "리프레시 레이트",
"Frame Padding": "프레임 패딩",
" Font Size": " 글꼴 크기",
"Font Sizes": "글꼴 크기",
"Handheld Font Size": "핸드헬드 글꼴 크기",
"Docked Font Size": "도킹 글꼴 크기",
"Black": "검정",
"Dark Gray": "짙은 회색",
"Gray": "회색",
"Light Gray": "밝은 회색",
"Silver": "은색",
"White": "흰색",
"Dark Red": "짙은 빨강",
"Red": "빨강",
"Light Red": "밝은 빨강",
"Pink": "분홍",
"Dark Green": "짙은 초록",
"Green": "초록",
"Lime Green": "라임 그린",
"Light Green": "밝은 초록",
"Dark Blue": "짙은 파랑",
"Blue": "파랑",
"Light Blue": "밝은 파랑",
"Sky Blue": "하늘색",
"Dark Purple": "짙은 보라",
"Purple": "보라",
"Light Purple": "밝은 보라",
"Violet": "바이올렛",
"Orange": "주황",
"Yellow": "노랑",
"Light Yellow": "밝은 노랑",
"Teal": "청록",
"Cyan": "시안",
"Light Cyan": "밝은 시안",
"Magenta": "마젠타",
"Hot Pink": "핫핑크",
"Brown": "갈색",
"Light Brown": "밝은 갈색",
"Colors": "색상",
"Background Color": "배경색",
"Background Alpha": "배경 투명도",
"Focus Color": "포커스 색상",
"Focus Alpha": "포커스 투명도",
"Text Color": "텍스트 색상",
"Border": "테두리",
"Dashed Line": "점선",
"Max FPS Text": "최대 FPS 텍스트",
"Min FPS Text": "최소 FPS 텍스트",
"Main Line": "주선",
"Rounded Line": "둥근 선",
"Perfect Line": "완벽 선",
" Color": " 색상",
" Alpha": " 투명도",
"Category Color": "카테고리 색상",
"Category Color 1": "카테고리 색상 1",
"Category Color 2": "카테고리 색상 2",
"Separator Color": "구분선 색상",
"Elements": "요소",
"Text Alignment": "텍스트 정렬",
"Vertical Position": "세로 위치",
"Horizontal Position": "가로 위치",
"Left": "왼쪽",
"Right": "오른쪽",
"Top": "위",
"Center": "가운데",
"Bottom": "아래",
"FPS Counter Color": "FPS 카운터 색상",
"FPS Counter Alpha": "FPS 카운터 투명도",
"Border Color": "테두리 색상",
"Dashed Line Color": "점선 색상",
"Dashed Line Alpha": "점선 투명도",
"Max FPS Text Color": "최대 FPS 텍스트 색상",
"Min FPS Text Color": "최소 FPS 텍스트 색상",
"Main Line Color": "주선 색상",
"Rounded Line Color": "둥근 선 색상",
"Perfect Line Color": "완벽 선 색상"
}
@@ -1,189 +0,0 @@
{
"Horizon OC Monitor": "Horizon OC Monitor",
"Modes": "Modi",
"Modes   Configure": "Modi   Configureren",
"Full": "Volledig",
"Mini": "Mini",
"Micro": "Micro",
"FPS Graph": "FPS-grafiek",
"FPS Counter": "FPS-teller",
"Game Resolutions": "Spelresoluties",
"Other": "Overig",
"Battery/Charger": "Batterij/Oplader",
"Miscellaneous": "Diversen",
"CPU Usage": "CPU-gebruik",
"GPU Usage": "GPU-gebruik",
"RAM Usage": "RAM-gebruik",
"Target Frequency": "Doelfrequentie",
"Real Frequency": "Werkelijke frequentie",
"Core 0 ": "Kern 0 ",
"Core 1 ": "Kern 1 ",
"Core 2 ": "Kern 2 ",
"Core 3 ": "Kern 3 ",
"Load": "Belasting",
"Total\nApplication\nApplet\nSystem\nSystem Unsafe": "Totaal\nApplicatie\nApplet\nSysteem\nSysteem onveilig",
"Board": "Bord",
"Battery Power Flow": "Batterijstroom",
"\nTemperatures": "\nTemperaturen",
"SoC\nPCB\nSkin": "SoC\nPCB\nSkin",
"Fan Rotation Level": "Ventilatorrotatieniveau",
"Game": "Spel",
"PFPS ": "PFPS ",
"FPS ": "FPS ",
"Resolutions ": "Resoluties ",
"Read Speed ": "Leessnelheid ",
"Press ": "Druk op ",
" to Exit": " om af te sluiten",
"Depth": "Diepte",
"Viewport": "Viewport",
"Game is not running\nor it's incompatible.": "Spel draait niet\nof is incompatibel.",
"Battery Stats": "Batterijstatistieken",
"Actual Capacity": "Werkelijke capaciteit",
"Designed Capacity": "Ontworpen capaciteit",
"Temperature": "Temperatuur",
"Raw Charge": "Ruwe lading",
"Age": "Leeftijd",
"Voltage": "Spanning",
"Current Flow": "Stroomstroom",
"Power Flow": "Stroomverbruik",
"Remaining Time": "Resterende tijd",
"Charger Stats": "Opladerstatistieken",
"Input Current Limit": "Ingangsstroomlimiet",
"VBUS Current Limit": "VBUS-stroomlimiet",
"Voltage Limit": "Spanningslimiet",
"Current Limit": "Stroomlimiet",
"Type": "Type",
"Max Voltage": "Max spanning",
"Max Current": "Max stroom",
"Multimedia Clock Rates": "Multimedia kloksnelheden",
"NVDEC": "NVDEC",
"NVENC": "NVENC",
"NVJPG": "NVJPG",
"Network": "Netwerk",
"Type: Wi-Fi": "Type: Wi-Fi",
"Press Y to show password": "Druk op Y om wachtwoord te tonen",
"Type: Ethernet": "Type: Ethernet",
"Type: Not connected": "Type: Niet verbonden",
"CPU\nGPU\nRAM\nSOC\nPCB\nSKN": "CPU\nGPU\nRAM\nSOC\nPCB\nSKN",
"Elements   Move Down   Move Up": "Elementen   Omlaag verplaatsen   Omhoog verplaatsen",
"Transparent": "Transparant",
"Opaque": "Ondoorzichtig",
"Alpha": "Alpha",
"Pretty": "Mooi",
"Compact": "Compact",
"FileSafe": "FileSafe",
"Day+Time": "Dag+Tijd",
"Date+Time(s)": "Datum+Tijd(s)",
"Date+Time AM/PM": "Datum+Tijd AM/PM",
"Date+Time(s) AM/PM": "Datum+Tijd(s) AM/PM",
"Date+Time EU": "Datum+Tijd EU",
"Date+Time EU AM/PM": "Datum+Tijd EU AM/PM",
"Date+Time(s) EU AM/PM": "Datum+Tijd(s) EU AM/PM",
"Date+Time ISO": "Datum+Tijd ISO",
"Time 24h": "Tijd 24u",
"Time AM/PM": "Tijd AM/PM",
"Time(s) 24h": "Tijd(s) 24u",
"Time(s) AM/PM": "Tijd(s) AM/PM",
"Date US": "Datum VS",
"Date EU": "Datum EU",
"Date ISO": "Datum ISO",
"Date Short": "Datum kort",
"DTC Format": "DTC-formaat",
"Toggles": "Schakelaars",
"Info": "Info tonen",
"Disable Screenshots": "Schermafbeeldingen uitschakelen",
"Real Freqs": "Werkelijke frequenties",
"Deltas": "Delta's",
"Target Freqs": "Doelfrequenties",
"FPS": "FPS",
"RES": "RES",
"Read Speed": "Leessnelheid",
"Real Frequencies": "Werkelijke frequenties",
"Real Voltages": "Werkelijke spanningen",
"Full CPU": "Volledige CPU",
"VDDQ": "VDDQ",
"VDD2": "VDD2",
"Full Resolution": "Volledige resolutie",
"SOC Voltage": "SOC-spanning",
"RAM Load CPU/GPU": "RAM-belasting CPU/GPU",
"Use DTC Symbol": "DTC-symbool gebruiken",
"Use Dynamic Colors": "Dynamische kleuren gebruiken",
"Sleep Exit": "Slaap afsluiten",
"Configuration": "Configuratie",
"Refresh Rate": "Vernieuwingsfrequentie",
"Frame Padding": "Frame opvulling",
" Font Size": " Lettergrootte",
"Font Sizes": "Lettergroottes",
"Handheld Font Size": "Handheld lettergrootte",
"Docked Font Size": "Docked lettergrootte",
"Black": "Zwart",
"Dark Gray": "Donkergrijs",
"Gray": "Grijs",
"Light Gray": "Lichtgrijs",
"Silver": "Zilver",
"White": "Wit",
"Dark Red": "Donkerrood",
"Red": "Rood",
"Light Red": "Lichtrood",
"Pink": "Roze",
"Dark Green": "Donkergroen",
"Green": "Groen",
"Lime Green": "Limoengroen",
"Light Green": "Lichtgroen",
"Dark Blue": "Donkerblauw",
"Blue": "Blauw",
"Light Blue": "Lichtblauw",
"Sky Blue": "Hemelsblauw",
"Dark Purple": "Donkerpaars",
"Purple": "Paars",
"Light Purple": "Lichtpaars",
"Violet": "Violet",
"Orange": "Oranje",
"Yellow": "Geel",
"Light Yellow": "Lichtgeel",
"Teal": "Teal",
"Cyan": "Cyaan",
"Light Cyan": "Lichtcyaan",
"Magenta": "Magenta",
"Hot Pink": "Felroze",
"Brown": "Bruin",
"Light Brown": "Lichtbruin",
"Colors": "Kleuren",
"Background Color": "Achtergrondkleur",
"Background Alpha": "Achtergrond alpha",
"Focus Color": "Focuskleur",
"Focus Alpha": "Focus alpha",
"Text Color": "Tekstkleur",
"Border": "Rand",
"Dashed Line": "Gestippelde lijn",
"Max FPS Text": "Max FPS tekst",
"Min FPS Text": "Min FPS tekst",
"Main Line": "Hoofdlijn",
"Rounded Line": "Afgeronde lijn",
"Perfect Line": "Perfecte lijn",
" Color": " Kleur",
" Alpha": " Alpha",
"Category Color": "Categoriekleur",
"Category Color 1": "Categoriekleur 1",
"Category Color 2": "Categoriekleur 2",
"Separator Color": "Scheidingskleur",
"Elements": "Elementen",
"Text Alignment": "Tekstuitlijning",
"Vertical Position": "Verticale positie",
"Horizontal Position": "Horizontale positie",
"Left": "Links",
"Right": "Rechts",
"Top": "Boven",
"Center": "Midden",
"Bottom": "Onder",
"FPS Counter Color":"FPS-tellerkleur",
"FPS Counter Alpha":"FPS-teller alpha",
"Border Color":"Randkleur",
"Dashed Line Color":"Gestippelde lijnkleur",
"Dashed Line Alpha":"Gestippelde lijn alpha",
"Max FPS Text Color":"Max FPS tekstkleur",
"Min FPS Text Color":"Min FPS tekstkleur",
"Main Line Color":"Hoofdlijnkleur",
"Rounded Line Color":"Afgeronde lijnkleur",
"Perfect Line Color":"Perfecte lijnkleur"
}
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more