| /* Copyright 2015-2022 Rivoreo |
| |
| Permission is hereby granted, free of charge, to any person obtaining |
| a copy of this software and associated documentation files (the |
| "Software"), to deal in the Software without restriction, including |
| without limitation the rights to use, copy, modify, merge, publish, |
| distribute, sublicense, and/or sell copies of the Software, and to |
| permit persons to whom the Software is furnished to do so, subject to |
| the following conditions: |
| |
| The above copyright notice and this permission notice shall be |
| included in all copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE |
| FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF |
| CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| typedef uint32_t NvU32; |
| |
| typedef struct { |
| NvU32 version; |
| NvU32 ClockType:2; |
| NvU32 reserved:22; |
| NvU32 reserved1:8; |
| struct { |
| NvU32 bIsPresent:1; |
| NvU32 reserved:31; |
| NvU32 frequency; |
| } domain[32]; |
| } NV_GPU_CLOCK_FREQUENCIES_V2; |
| |
| typedef struct { |
| int value; |
| struct { |
| int mindelta; |
| int maxdelta; |
| } valueRange; |
| } NV_GPU_PERF_PSTATES20_PARAM_DELTA; |
| |
| typedef struct { |
| NvU32 domainId; |
| NvU32 typeId; |
| NvU32 bIsEditable:1; |
| NvU32 reserved:31; |
| NV_GPU_PERF_PSTATES20_PARAM_DELTA freqDelta_kHz; |
| union { |
| struct { |
| NvU32 freq_kHz; |
| } single; |
| struct { |
| NvU32 minFreq_kHz; |
| NvU32 maxFreq_kHz; |
| NvU32 domainId; |
| NvU32 minVoltage_uV; |
| NvU32 maxVoltage_uV; |
| } range; |
| } data; |
| } NV_GPU_PSTATE20_CLOCK_ENTRY_V1; |
| |
| typedef struct { |
| NvU32 domainId; |
| NvU32 bIsEditable:1; |
| NvU32 reserved:31; |
| NvU32 volt_uV; |
| int voltDelta_uV; |
| } NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1; |
| |
| typedef struct { |
| NvU32 version; |
| NvU32 bIsEditable:1; |
| NvU32 reserved:31; |
| NvU32 numPstates; |
| NvU32 numClocks; |
| NvU32 numBaseVoltages; |
| struct { |
| NvU32 pstateId; |
| NvU32 bIsEditable:1; |
| NvU32 reserved:31; |
| NV_GPU_PSTATE20_CLOCK_ENTRY_V1 clocks[8]; |
| NV_GPU_PSTATE20_BASE_VOLTAGE_ENTRY_V1 baseVoltages[4]; |
| } pstates[16]; |
| } NV_GPU_PERF_PSTATES20_INFO_V1; |
| |
| extern int (*nvapi_QueryInterface(uint32_t))(); |
| |
| static void print_usage(const char *name) { |
| fprintf(stderr, "Usage:\n" |
| " %s { -a | -u <unit> } [-c <core-freq-offset>] [-m <vram-freq-offset>]\n" |
| " %s -l\n" |
| "Frequency offset values are in MHz.\n", |
| name, name); |
| } |
| |
| static int *parse_and_create_number(const char *s) { |
| char *end_p; |
| int *n = malloc(sizeof(int)); |
| if(!n) { |
| fputs("Out of memory when parsing options\n", stderr); |
| return NULL; |
| } |
| *n = strtol(s, &end_p, 10); |
| if(*end_p) { |
| free(n); |
| fprintf(stderr, "Invalid number '%s'\n", s); |
| return NULL; |
| } |
| return n; |
| } |
| |
| static int (*nvapi_init)(void); |
| static int (*nvapi_end)(void); |
| static int (*nvapi_get_error_message)(int, char *); |
| static int (*nvapi_enum_physical_gpus)(void **, unsigned int *); |
| static int (*nvapi_get_system_type)(void *, unsigned int *); |
| static int (*nvapi_get_full_name)(void *, char *); |
| static int (*nvapi_get_physical_frame_buffer_size)(void *, unsigned int *); |
| static int (*nvapi_get_ram_type)(void *, uint32_t *); |
| static int (*nvapi_get_vbios_version)(void *, char *); |
| static int (*nvapi_get_all_clock_frequencies)(void *, NV_GPU_PERF_PSTATES20_INFO_V1 *); |
| static int (*nvapi_get_pstates20)(void *, NV_GPU_PERF_PSTATES20_INFO_V1 *); |
| static int (*nvapi_set_pstates20)(void *, NV_GPU_PERF_PSTATES20_INFO_V1 *); |
| |
| static void import_nvapi() { |
| nvapi_init = nvapi_QueryInterface(0x0150E828); |
| nvapi_end = nvapi_QueryInterface(0x6FF81213); |
| nvapi_get_error_message = nvapi_QueryInterface(0x6C2D048C); |
| nvapi_enum_physical_gpus = nvapi_QueryInterface(0xE5AC921F); |
| nvapi_get_system_type = nvapi_QueryInterface(0xBAAABFCC); |
| nvapi_get_full_name = nvapi_QueryInterface(0xCEEE8E9F); |
| nvapi_get_physical_frame_buffer_size = nvapi_QueryInterface(0x46FBEB03); |
| nvapi_get_ram_type = nvapi_QueryInterface(0x57F7CAAC); |
| nvapi_get_vbios_version = nvapi_QueryInterface(0xA561FD7D); |
| nvapi_get_all_clock_frequencies = nvapi_QueryInterface(0xDCB616C3); |
| nvapi_get_pstates20 = nvapi_QueryInterface(0x6FF81213); |
| nvapi_set_pstates20 = nvapi_QueryInterface(0x0F4DAE6B); |
| } |
| |
| static void nvapi_perror(int e, const char *prefix) { |
| static char msg[256]; |
| nvapi_get_error_message(e, msg); |
| fprintf(stderr, "%s: %s\n", prefix, msg); |
| } |
| |
| static void print_gpu_information(void *gpu) { |
| char name[64]; |
| unsigned int sys_type; |
| unsigned int ram_size; |
| uint32_t ram_type; |
| char vbios_version[64]; |
| NV_GPU_PERF_PSTATES20_INFO_V1 pstates_info = { |
| .version = 0x10200 + sizeof(NV_GPU_PERF_PSTATES20_INFO_V1) |
| }; |
| int e = nvapi_get_full_name(gpu, name); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_full_name"); |
| return; |
| } |
| e = nvapi_get_system_type(gpu, &sys_type); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_system_type"); |
| return; |
| } |
| e = nvapi_get_physical_frame_buffer_size(gpu, &ram_size); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_physical_frame_buffer_size"); |
| return; |
| } |
| e = nvapi_get_ram_type(gpu, &ram_type); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_ram_type"); |
| return; |
| } |
| e = nvapi_get_vbios_version(gpu, vbios_version); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_vbios_version"); |
| return; |
| } |
| e = nvapi_get_pstates20(gpu, &pstates_info); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_pstates20"); |
| return; |
| } |
| printf("%s, type %u\nVideo RAM size %u MiB, type 0x%x\nVideo BIOS version %s\n" |
| "Core frequency %u MHz, offset %d\nRAM frequency %u MHz, offset %d\n\n", |
| name, sys_type, ram_size / 1024, (unsigned int)ram_type, vbios_version, |
| (unsigned int)pstates_info.pstates[0].clocks[0].data.single.freq_kHz / 1000, |
| pstates_info.pstates[0].clocks[0].freqDelta_kHz.value / 1000, |
| (unsigned int)pstates_info.pstates[0].clocks[1].data.single.freq_kHz / 1000, |
| pstates_info.pstates[0].clocks[1].freqDelta_kHz.value / 1000); |
| } |
| |
| static int set_core_freq_offset(void *gpu, int value) { |
| NV_GPU_PERF_PSTATES20_INFO_V1 pstates_info = { |
| .version = 0x10200 + sizeof(NV_GPU_PERF_PSTATES20_INFO_V1), |
| .numPstates = 1, |
| .numClocks = 1, |
| .pstates[0] = { |
| .clocks[0] = { |
| .domainId = 0, |
| .freqDelta_kHz = { .value = value * 1000 } |
| } |
| } |
| }; |
| int e = nvapi_set_pstates20(gpu, &pstates_info); |
| if(e) { |
| nvapi_perror(e, "nvapi_set_pstates20"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int set_vram_freq_offset(void *gpu, int value) { |
| NV_GPU_PERF_PSTATES20_INFO_V1 pstates_info = { |
| .version = 0x10200 + sizeof(NV_GPU_PERF_PSTATES20_INFO_V1), |
| .numPstates = 1, |
| .numClocks = 1, |
| .pstates[0] = { |
| .clocks[0] = { |
| .domainId = 4, |
| .freqDelta_kHz = { .value = value * 1000 } |
| } |
| } |
| }; |
| unsigned int ram_type; |
| int e = nvapi_get_ram_type(gpu, &ram_type); |
| if(e) { |
| nvapi_perror(e, "nvapi_get_ram_type"); |
| return -1; |
| } |
| if(ram_type > 7) pstates_info.pstates[0].clocks[0].freqDelta_kHz.value *= 2; |
| e = nvapi_set_pstates20(gpu, &pstates_info); |
| if(e) { |
| nvapi_perror(e, "nvapi_set_pstates20"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int main(int argc, char **argv) { |
| int list_gpu = 0; |
| int unit = -2; |
| int *core_freq_offset = NULL; |
| int *ram_freq_offset = NULL; |
| while(1) { |
| int c = getopt(argc, argv, "au:c:m:lh"); |
| if(c == -1) break; |
| switch(c) { |
| case 'a': |
| unit = -1; |
| break; |
| case 'u': |
| unit = atoi(optarg); |
| if(unit < 0) { |
| fprintf(stderr, "%s: Invalid unit number '%s'\n", |
| argv[0], optarg); |
| return -1; |
| } |
| break; |
| case 'c': |
| core_freq_offset = parse_and_create_number(optarg); |
| if(!core_freq_offset) return 1; |
| break; |
| case 'm': |
| ram_freq_offset = parse_and_create_number(optarg); |
| if(!ram_freq_offset) return 1; |
| break; |
| case 'l': |
| list_gpu = 1; |
| break; |
| case 'h': |
| print_usage(argv[0]); |
| return 0; |
| case '?': |
| return -1; |
| } |
| } |
| if(unit == -2 && !list_gpu) { |
| print_usage(argv[0]); |
| return -1; |
| } |
| |
| import_nvapi(); |
| |
| void *gpus[64]; |
| unsigned int gpu_count; |
| int e = nvapi_init(); |
| if(e) { |
| nvapi_perror(e, "nvapi_init"); |
| return 1; |
| } |
| e = nvapi_enum_physical_gpus(gpus, &gpu_count); |
| if(e) { |
| nvapi_perror(e, "nvapi_enum_physical_gpus"); |
| return 1; |
| } |
| unsigned int i = 0; |
| if(list_gpu) { |
| char name[64]; |
| while(i < gpu_count) { |
| e = nvapi_get_full_name(gpus[i], name); |
| if(e) nvapi_perror(e, "nvapi_get_full_name"); |
| else printf("%d %s\n", i, name); |
| i++; |
| } |
| return 0; |
| } |
| if(!core_freq_offset && !ram_freq_offset) { |
| if(unit < 0) while(i < gpu_count) { |
| printf("%d:\n", i); |
| print_gpu_information(gpus[i]); |
| i++; |
| } else if(unit < gpu_count) { |
| print_gpu_information(gpus[unit]); |
| } else { |
| fprintf(stderr, "%s: Unit number %u out of range\n", argv[0], unit); |
| return 1; |
| } |
| return 0; |
| } |
| if(core_freq_offset && *core_freq_offset > 1000) { |
| fprintf(stderr, "%s: Core frequency offset is too high\n", argv[0]); |
| return 1; |
| } |
| if(ram_freq_offset && *ram_freq_offset > 1000) { |
| fprintf(stderr, "%s: Video RAM frequency offset is too high\n", argv[0]); |
| return 1; |
| } |
| int r = 0; |
| if(unit < 0) while(i < gpu_count) { |
| if(core_freq_offset && set_core_freq_offset(gpus[i], *core_freq_offset) < 0) r = 1; |
| if(ram_freq_offset && set_vram_freq_offset(gpus[i], *ram_freq_offset) < 0) r = 1; |
| i++; |
| } else if(unit < gpu_count) { |
| if(core_freq_offset && set_core_freq_offset(gpus[unit], *core_freq_offset) < 0) r = 1; |
| if(ram_freq_offset && set_vram_freq_offset(gpus[unit], *ram_freq_offset) < 0) r = 1; |
| } else { |
| fprintf(stderr, "%s: Unit number %u out of range\n", argv[0], unit); |
| return 1; |
| } |
| nvapi_end(); |
| return r; |
| } |