blob: 2c2220a9a9299d82cdf9d92abca1482177f03538 [file] [log] [blame] [raw]
/* 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;
}