/* NVClock 0.8 - Linux overclocker for NVIDIA cards
*
* site: http://nvclock.sourceforge.net
*
* Copyright(C) 2001-2004 Roderick Colenbrander
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "backend.h"
#include "nvclock.h"
static void insert_entry(cfg_entry **cfg, cfg_entry *entry);
/* Free the config file structure */
void destroy(cfg_entry **cfg)
{
cfg_entry *pCfg, *pPrevious;
pCfg = *cfg;
while(pCfg)
{
pPrevious = pCfg;
free(pPrevious->name);
free(pPrevious->section);
pCfg = pCfg->next;
free(pPrevious);
}
}
/* Add a new config file entry section.name=value */
void add_entry(cfg_entry **cfg, char *section, char *name, int value)
{
cfg_entry *entry;
entry = (cfg_entry*)calloc(1, sizeof(cfg_entry));
entry->name = (char*)strdup(name);
entry->section = (char*)strdup(section);
entry->value = value;
entry->next = NULL;
insert_entry(cfg, entry);
}
/* Change the value of an entry or add an entry when it doesn't exist */
void change_entry(cfg_entry **cfg, char *section, char *name, int value)
{
cfg_entry *entry = lookup_entry(cfg, section, name);
/* When an entry is found update it */
if(entry)
{
entry->value=value;
}
/* Create a new entry */
else
{
add_entry(cfg, section, name, value);
}
}
/* Insert an entry into the config file structure */
static void insert_entry(cfg_entry **cfg, cfg_entry *entry)
{
cfg_entry *pCfg = NULL;
cfg_entry *pPrev = NULL;
cfg_entry *pNext = NULL;
/* When the cfg list is still empty, add the first entry */
if(!*cfg)
{
*cfg = entry;
return;
}
/* Sort the list by section and add the entry */
for(pCfg = *cfg; pCfg != NULL; pPrev=pCfg, pCfg = pCfg->next, pNext = pCfg ? pCfg->next : NULL )
{
/* Check if the entry belongs to a section below the current section */
if(strcmp(entry->section, pCfg->section) < 0)
{
if(!pPrev)
{
*cfg = entry;
entry->next = pCfg;
}
else
{
pPrev->next = entry;
entry->next = pCfg;
}
return;
}
if(strcmp(entry->section, pCfg->section) == 0)
{
/* The entry needs to be placed before the current entry */
if(strcmp(entry->name, pCfg->name) < 0)
{
if(!pPrev)
{
*cfg = entry;
entry->next = pCfg;
}
else
{
pPrev->next = entry;
entry->next = pCfg;
}
return;
}
/* When there's a next entry, check if it belongs to the same section or
/ else add the option to the current section.
*/
if(pNext)
{
/* The sections don't match, so add the option to the current one */
if(strcmp(entry->section, pNext->section) != 0)
{
pCfg->next = entry;
entry->next = pNext;
return;
}
}
}
/* Entry should become the last one */
if(!pCfg->next)
{
pCfg->next = entry;
return;
}
continue;
}
}
/* Look up section.name */
cfg_entry* lookup_entry(cfg_entry **cfg, char *section, char *name)
{
cfg_entry *entry = *cfg;
while(entry)
{
/* If everything matches, the option is found */
if(!strcmp(entry->section, section) && !strcmp(entry->name, name))
{
return entry;
}
entry = (cfg_entry*)entry->next;
}
return NULL;
}
/* Helper function that splits lines of the form section.name=value */
static int split_line(const char *line, char **section, char **name, char **value)
{
char *a, *b;
if(!(a = strchr(line, '.')))
return 0;
/* overwrite '.' with '\0' */
a[0] = '\0';
*section = (char*)strdup(line);
a++;
if(!(b = strchr(a, '=')))
return 0;
/* overwrite '=' with '\0' */
b[0] = '\0';
*name = (char*)strdup(a);
b++;
/* overwrite '\n' with '\0' if '\n' present */
if((a = strchr(b, '\n')))
{
a[0] = '\0';
}
*value = (char*)strdup(b);
return 1;
}
/* Reads the config file from disc and stores it in cfg */
int read_config(cfg_entry **cfg, char *file)
{
char line[80];
FILE *fp;
fp = fopen(file, "r");
if(!fp)
{
return 0;
}
while(fgets(line, 80, fp) != NULL)
{
char *name, *section, *value;
cfg_entry *entry = NULL;
if((line[0] == '#'))
continue; /* Skip comments */
if((line[0] == '<'))
continue; /* There's no section on this line */
if((line[0] == '\n'))
continue; /* There's no section on this line */
if(strchr(line, '=') == NULL)
continue;
if(!split_line(line, §ion, &name, &value))
{
free(name);
free(section);
free(value);
continue;
}
/* Add the entry when it doesn't exist; we don't want double options*/
if(!(entry = lookup_entry(cfg, section, name)))
{
/* Use strtoul instead of atoi as on some nv40 cards we get issues regarding signed/unsigned */
add_entry(cfg, section, name, strtoul(value, (char**)NULL, 10));
}
free(name);
free(section);
free(value);
}
fclose(fp);
return 1;
}
/* Looks if a config file exists and then makes sure it will be read and parsed.
/ When no config file exists create it.
*/
int open_config()
{
char *file = NULL;
char *home = NULL;
struct stat tmp;
if(!(home = getenv("HOME")))
{
/* error */
}
nvclock.path = malloc(strlen(home) + strlen("/.nvclock") +1);
sprintf(nvclock.path, "%s/.nvclock", home);
file = malloc(strlen(nvclock.path) + strlen("/config") +1);
sprintf(file, "%s/config", nvclock.path);
/* Check if our ~/.nvclock directory exists if not create it */
if(stat(nvclock.path, &tmp))
{
if(mkdir(nvclock.path, 0755))
{
/* needs to be handled by the new error code .. */
/* perror("Can't create home directory .nvclock"); */
return 0;
}
}
/* If there's no config or if the config is corrupt create a new one */
if(!parse_config(file))
{
create_config(file);
}
free(file);
return 1;
}
void write_config(cfg_entry *cfg, char *file)
{
FILE *fp = fopen(file, "w+");
cfg_entry *pCfg = NULL;
pCfg = cfg;
fprintf(fp, "#This is NVClock's config file. Don't edit the hw and general section!\n");
while(pCfg != NULL)
{
fprintf(fp, "%s.%s=%u\n", pCfg->section, pCfg->name, pCfg->value);
pCfg = pCfg->next;
}
fclose(fp);
}
/* Some basic check to see if frequencies can be valid */
static int validate_clock(int arch, int freq)
{
if(arch & (NV5))
{
if((freq > 75) && (freq < 250))
return 1;
}
/* Geforce2/2MX/4MX */
else if(arch & (NV1X))
{
if((freq > 125) && (freq < 500))
return 1;
}
/* Geforce3/3Ti/4Ti/FX5200/FX5500 */
else if(arch & (NV2X))
{
if((freq > 200) && (freq < 800))
return 1;
}
/* GeforceFX */
else if(arch & (NV3X))
{
if((freq > 250) && (freq < 1100))
return 1;
}
/* Geforce6*/
else if(arch & (NV4X))
{
if((freq > 250) && (freq < 1200))
return 1;
}
return 0;
}
/* Some basic check to verify if a stored pll can be correct */
static int validate_pll(int arch, int base_freq, unsigned int pll, unsigned int pll2)
{
int freq;
if(arch & (NV5 | NV1X | NV2X))
{
freq = (int)GetClock(base_freq, pll);
if(validate_clock(arch, freq))
return 1;
}
else if(arch & (NV30 | NV35))
{
freq = (int)GetClock_nv30(base_freq, pll);
if(validate_clock(arch, freq))
return 1;
}
else if(arch & (NV31))
{
freq = (int)GetClock_nv31(base_freq, pll, pll2);
if(validate_clock(arch, freq))
return 1;
}
else if(arch & (NV4X))
{
freq = (int)GetClock_nv40(base_freq, pll, pll2);
if(validate_clock(arch, freq))
return 1;
}
return 0;
}
/* Parse the config file and do something with its contents */
int parse_config(char *file)
{
int i;
cfg_entry *general;
if(!read_config(&nvclock.cfg, file))
return 0;
if((general = (cfg_entry*)lookup_entry(&nvclock.cfg, "general", "cards")) == NULL)
{
return 0;
}
else
{
/* Check if we have the same number of videocards as before */
if(general->value != nvclock.num_cards)
return 0;
/* Walk through all detected cards */
for(i=0; i < nvclock.num_cards; i++)
{
cfg_entry *entry;
char section[4];
char filename[80];
int base_freq=0;
struct stat tmp;
struct nvbios *bios;
if(!set_card_info(i))
return 0; /* Make us fail; set_card_info already set the error */
sprintf(section, "hw%d", i);
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "card")))
{
/* The order in wich the detected cards are listed should match the order of the ones in the config file. Mask the last digit for device_id modding purposes.*/
if((nvclock.card[i].device_id & 0xfff0) != (entry->value & 0xfff0))
return 0;
}
else
{
return 0;
}
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "basefreq")))
{
base_freq = entry->value;
}
else
return 0;
/* The bios works differently on ppc and other architectures; it is also stored in a different place */
#if defined(__i386__) || defined(__ia64__) || defined(__x86_64__)
/* During this stage we also need to parse the nvidia bios.
/ This can't be done in probe_devices as it depends on the config file
/ which might not exist at that time yet
*/
sprintf(filename, "%s/bios%d.rom", nvclock.path, i);
/* Redump the bios when the file doesn't exist */
if(stat(filename, &tmp))
return 0;
/* Read the bios. Note the result can be NULL in case the
/ bios is corrupt. We don't redump the bios because the bios
/ is not dumpable on some systems. For example on various laptops
/ the bios is stored at a different place not reachable by us.
*/
bios = read_bios(filename); /* GCC 4.0.1 (what about others?) doesn't like it when we directly do nclock.card[i].bios = readbios(filename); works fine without optimizations */
nvclock.card[i].bios = bios;
#else
nvclock.card[i].bios = NULL;
#endif
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "mpll")))
nvclock.card[i].mpll = entry->value;
else
/* corrupted config file */
return 0;
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "nvpll")))
nvclock.card[i].nvpll = entry->value;
else
return 0;
/* NV31 and NV40 cards have extra pll registers */
if(nv_card->arch & (NV31 | NV4X))
{
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "mpll2")))
nvclock.card[i].mpll2 = entry->value;
else
return 0;
if((entry = (cfg_entry*)lookup_entry(&nvclock.cfg, section, "nvpll2")))
nvclock.card[i].nvpll2 = entry->value;
else
return 0;
}
/* Do some basic check on mpll/nvpll to see if they can be correct */
if(!validate_pll(nvclock.card[i].arch, base_freq, nvclock.card[i].mpll, nvclock.card[i].mpll2))
return 0;
if(!validate_pll(nvclock.card[i].arch, base_freq, nvclock.card[i].nvpll, nvclock.card[i].nvpll2))
return 0;
}
}
/* Reset the nv_card object else things might go wrong */
unset_card();
/* Return succes */
return 1;
}
/* Create a config file based on info we get from the low-level part of nvclock */
int create_config(char *file)
{
int i;
if(nvclock.cfg)
{
destroy(&nvclock.cfg);
nvclock.cfg = NULL;
}
add_entry(&nvclock.cfg, "general", "cards", nvclock.num_cards);
/* Write the config file */
for(i=0; i < nvclock.num_cards; i++)
{
char section[4];
char bios[80];
int base_freq;
/* Set the nv_card object to the card; Note this is a bit basic; function pointers can't be used */
if(!set_card_info(i))
return 0; /* Make us fail; set_card_info already set the error */
sprintf(section, "hw%d", i);
add_entry(&nvclock.cfg, section, "card", nv_card->device_id);
#if defined(__i386__) || defined(__ia64__) || defined(__x86_64__)
/* needs to be changed to dump the file in the home dir; further we need some CRC check */
sprintf(bios, "%s/bios%d.rom", nvclock.path, i);
dump_bios(bios);
nvclock.card[i].bios = read_bios(bios);
#else
nvclock.card[i].bios = NULL;
#endif
base_freq = (nv_card->PEXTDEV[0x0000/4] & 0x40) ? 14318 : 13500;
if(nv_card->arch & (NV17 | NV25 | NV3X | NV4X))
{
if (nv_card->PEXTDEV[0x0000/4] & (1<<22))
base_freq = 27000;
}
add_entry(&nvclock.cfg, section, "basefreq", base_freq);
/* TNT(1/2), Geforce(1/2/3/4) and GeforceFX 5200/5500/5800/5900 */
if(nv_card->arch & (NV5 | NV1X | NV2X | NV30 | NV35))
{
add_entry(&nvclock.cfg, section, "mpll", nv_card->PRAMDAC[0x504/4]);
add_entry(&nvclock.cfg, section, "nvpll", nv_card->PRAMDAC[0x500/4]);
nvclock.card[i].nvpll = nv_card->PRAMDAC[0x500/4];
nvclock.card[i].mpll = nv_card->PRAMDAC[0x504/4];
}
/* GeforceFX 5600/5700 */
else if(nv_card->arch & NV31)
{
add_entry(&nvclock.cfg, section, "mpll", nv_card->PRAMDAC[0x504/4]);
add_entry(&nvclock.cfg, section, "mpll2", nv_card->PRAMDAC[0x574/4]);
add_entry(&nvclock.cfg, section, "nvpll", nv_card->PRAMDAC[0x500/4]);
add_entry(&nvclock.cfg, section, "nvpll2", nv_card->PRAMDAC[0x570/4]);
nvclock.card[i].mpll = nv_card->PRAMDAC[0x504/4];
nvclock.card[i].mpll2 = nv_card->PRAMDAC[0x574/4];
nvclock.card[i].nvpll = nv_card->PRAMDAC[0x500/4];
nvclock.card[i].nvpll2 = nv_card->PRAMDAC[0x570/4];
}
/* Geforce6/7 */
else if(nv_card->arch & NV4X)
{
add_entry(&nvclock.cfg, section, "mpll", nv_card->PMC[0x4020/4]);
add_entry(&nvclock.cfg, section, "mpll2", nv_card->PMC[0x4024/4]);
add_entry(&nvclock.cfg, section, "nvpll", nv_card->PMC[0x4000/4]);
add_entry(&nvclock.cfg, section, "nvpll2", nv_card->PMC[0x4004/4]);
nvclock.card[i].mpll = nv_card->PMC[0x4020/4];
nvclock.card[i].mpll2 = nv_card->PMC[0x4024/4];
nvclock.card[i].nvpll = nv_card->PMC[0x4000/4];
nvclock.card[i].nvpll2 = nv_card->PMC[0x4004/4];
}
unset_card();
}
write_config(nvclock.cfg, file);
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1