/* 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 #include #include #include #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; }