Files
V2TMUMemTester/main.c
2026-03-09 16:43:04 +01:00

521 lines
14 KiB
C

/* V2MemTest - A CLI Tool to test & fix Voodoo² TMU System
* Copyright (C) 2026 ChaCha
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
#define _BSD_SOURCE 1
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include "cvg.h"
#include <glide.h>
#include "sst1init.h"
#include "fxpci.h"
#include "V2MemTest.h"
#include "FaultSources.h"
#include "Utils.h"
#include "Draw.h"
#include "Test_Common.h"
#include "Test_Address.h"
#include "Test_Data.h"
#include "Test_Data_Huge.h"
const char* szErrorMsg[] =
{
"no error.", // E_ERROR__NO_ERROR
"unknown error.", // E_ERROR__UNKNOWN_ERROR
"unknown argument found in command line.", // E_ERROR__UNKNOWN_ARGUMENT
"wrong argument value.", // E_ERROR__BAD_ARGUMENT_VALUE
"error initializing Voodoo² board.", // E_ERROR__SST1_INIT,
"error getting Voodoo² board informations.", // E_ERROR__SST1_GET_INFO,
"not enough FBI RAM to process TMU tests.", // E_ERROR__NOT_ENOUGH_FBI_RAM,
"TMU not found.", // E_ERROR__TMU_NOT_FOUND,
"no TMU selected.", // E_ERROR__NO_TMU_SELECTED,
};
const char szTitle[] =
"v2-tmu-memtester-%d.%d.%d - A CLI Tool to test & fix Voodoo² TMU System\n"
"Copyright (C) 2026 ChaCha\n";
const char szLicence[] =
"This program is free software : you can redistribute it and/or modify it under\n"
"the terms of the GNU General Public License as published by the Free Software\n"
"Foundation either version 3 of the License, or (at your option) any later version.\n"
"This program is distributed in the hope that it will be useful, but WITHOUT ANY\n"
"WARRANTY !\n";
const char szKool[] =
"Oh nooo.... Have fun fixing your Holy Voodoo² !\n"
"Note: Do not forget to sleep though :) !\n";
const char szKool2[] =
"Did it just worked ?!\n";
const char szHelp[] =
"Usage: ./v2-tmu-memtester [options]\n"
"\n"
"General options:\n"
" -h, --help Show this help and exit.\n"
" --version Print version (MAJOR.MINOR.PATCH) and exit.\n"
" -n, --num <N> Number of loops (default: 1).\n"
" -l, --log <file> Log file path.\n"
" --silent Silent mode (suppress normal output).\n"
/*" -t, --tsv <file> TSV output file path.\n"*/
"\n"
"Verbosity:\n"
" -v Increase log level by one step (repeatable).\n"
" Levels: 0=ERR, 1=WARN, 2=INFO, 3=DEBUG, 4=TRACE\n"
" Default: 2 (INFO). Example: -v -> DEBUG.\n"
"\n"
"Target selection:\n"
" --tmu0 Enable only tests for TMU0.\n"
" --tmu1 Enable only tests for TMU1.\n"
"\n"
"Test selection [ all if none set ] :\n"
" --address Run address tests.\n"
" --data Run data tests.\n"
" --data-huge Run large/extended data tests.\n"
"\n"
"TMU RAM limit override:\n"
" --tmu0-ram <MB> Force TMU0 RAM size: -1=auto, 1..4=MB.\n"
" --tmu1-ram <MB> Force TMU1 RAM size: -1=auto, 1..4=MB.\n"
"\n"
"Examples:\n"
/*" ./v2-tmu-memtester --tmu0 --address -v --tsv out.tsv\n"*/
" ./v2-tmu-memtester --tmu0 --address -v\n"
" ./v2-tmu-memtester --tmu0-ram 2 --tmu1-ram -1 -n 10\n"
" ./v2-tmu-memtester --tmu1 --data-huge\n";
def_sOptions sOptions = {
.eLogLevel = E_LOGLEVEL__INFO,
.bSilent = false,
.szLogFileName = {0},
.szTSVFile = {0},
.lNumLoops = 1,
.bTestTMU0 = false,
.eTMU0RamLimit = E_TMU_RAMSIZE__AUTO,
.bTestTMU1 = false,
.eTMU1RamLimit = E_TMU_RAMSIZE__AUTO,
.bTestTMUAddress = false,
.bTestTMUData = false,
.bTestTMUDataHuge = false,
};
static struct option long_args[] = {
/* {<NAME>, <HAS_ARG>, <FLAG>, <VAL> */
{"help", no_argument, NULL, 'h'},
{"num", required_argument, NULL, 'n'},
{"log", required_argument, NULL, 'l'},
{"silent", no_argument, &sOptions.bSilent, true},
/*{"tsv", required_argument, NULL, 't'},*/
{"tmu0", no_argument, &sOptions.bTestTMU0, true},
{"tmu1", no_argument, &sOptions.bTestTMU1, true},
{"address", no_argument, &sOptions.bTestTMUAddress, true},
{"data", no_argument, &sOptions.bTestTMUData, true},
{"data-huge", no_argument, &sOptions.bTestTMUDataHuge, true},
{"tmu0-ram", required_argument, NULL, 3},
{"tmu1-ram", required_argument, NULL, 1},
{"version", no_argument, NULL, 2},
};
def_sFaultSourceScoreRec
ar_dFaultScores[NB_FAULT_SOURCE];
def_sFaultSourceScoreRec
ar_dFaultScores_sorted[NB_FAULT_SOURCE];
#define _DEF_TEST_TIME_PER_MB_S 5
int main(int argc, char **argv)
{
int Status = 0;
unsigned long long ullNbErrorAll = 0;
const unsigned char boardNum = 0;
int option_index = 0;
int opt;
bool bQuit = false;
FxU32* sst = NULL;
while((opt=getopt_long(argc,argv,"hvn:t:l:",long_args,&option_index)) != -1)
{
switch(opt)
{
/* other long options ? */
case 0:
break;
/* Version */
case 2:
printf("%d.%d.%d", V2MEMTEST__VERSION__MAJOR,
V2MEMTEST__VERSION__MINOR,
V2MEMTEST__VERSION__PATCH);
bQuit = true;
break;
/* TMU0 long options */
case 3:
/* TMU1 long options */
case 1:
{
def_eTMURamLimit newRAMLimit;
switch(strtol(optarg, NULL, 10))
{
case -1: newRAMLimit = E_TMU_RAMSIZE__AUTO; break;
case 1: newRAMLimit = E_TMU_RAMSIZE__1MB; break;
case 2: newRAMLimit = E_TMU_RAMSIZE__2MB; break;
case 3: newRAMLimit = E_TMU_RAMSIZE__3MB; break;
case 4: newRAMLimit = E_TMU_RAMSIZE__4MB; break;
default:
Status = E_ERROR__BAD_ARGUMENT_VALUE;
ErrorCheck_gotoCleanUp();
break;
}
if(opt==3) sOptions.eTMU0RamLimit = newRAMLimit;
else sOptions.eTMU1RamLimit = newRAMLimit;
}
break;
case '?':
puts(szHelp);
Status = E_ERROR__UNKNOWN_ARGUMENT;
ErrorCheck_gotoCleanUp();
break;
case 'l':
strncpy(sOptions.szLogFileName, optarg, 2048);
break;
/*
case 't':
strncpy(sOptions.szTSVFile, optarg, 2048);
break;
*/
case 'h':
puts(szHelp);
bQuit = true;
break;
case 'n':
sOptions.lNumLoops = strtol(optarg, NULL, 10);
if( sOptions.lNumLoops < 0 )
{
Status = E_ERROR__BAD_ARGUMENT_VALUE;
ErrorCheck_gotoCleanUp();
}
break;
case 'v':
if(sOptions.eLogLevel < E_LOGLEVEL__TRACE)
sOptions.eLogLevel++;
break;
default:
Status = E_ERROR__UNKNOWN_ERROR;
ErrorCheck_gotoCleanUp();
break;
}
}
if(bQuit)
return 0;
if(!sOptions.bTestTMU0 && !sOptions.bTestTMU1)
{
sOptions.bTestTMU0 = true;
sOptions.bTestTMU1 = true;
}
if( !sOptions.bTestTMUAddress &&
!sOptions.bTestTMUData &&
!sOptions.bTestTMUDataHuge)
{
sOptions.bTestTMUAddress = true;
sOptions.bTestTMUData = true;
sOptions.bTestTMUDataHuge = true;
}
sst1DeviceInfoStruct devInfo;
memset(&devInfo,0,sizeof(sst1DeviceInfoStruct));
SstRegs *sstregs = NULL;
logI(szTitle, V2MEMTEST__VERSION__MAJOR,
V2MEMTEST__VERSION__MINOR,
V2MEMTEST__VERSION__PATCH);
logI("\n");
logI(szLicence);
logI("\n");
srandom(time(NULL));
FaultSource_reset(ar_dFaultScores);
for(long j=0; j < sOptions.lNumLoops; j++)
{
logI("# processing loop %ld of %ld\n",j+1,sOptions.lNumLoops);
if ((sst = sst1InitMapBoard(boardNum)) == NULL)
{
logE("no Voodoo boards found\n");
Status = E_ERROR__SST1_INIT;
ErrorCheck_gotoCleanUp();
}
sst1InitRegisters(sst);
sstregs = (SstRegs *) sst;
if (sst1InitGetDeviceInfo(sst, &devInfo) == FXFALSE)
{
logE("couldn't get info for Voodoo # %d\n", boardNum);
Status = E_ERROR__SST1_GET_INFO;
ErrorCheck_gotoCleanUp();
}
/* Enabling video output so the Board will
* be configured automatically (FIFO, GRXCLK...)*/
sst1InitVideo(sst,GR_RESOLUTION_800x600,GR_REFRESH_60Hz,0);
logI("FBI detected memory:\t%ld MB\n", (unsigned long)devInfo.fbiMemSize);
if(devInfo.fbiMemSize < 2)
{
logE("couldn't test Voodoo2 TMUs without minimum 2MB of FBI memory\n");
Status = E_ERROR__NOT_ENOUGH_FBI_RAM;
ErrorCheck_gotoCleanUp();
}
for (int tmu = 0; tmu < devInfo.numberTmus; tmu++)
logI("TMU%d detected memory:\t%ld MB\n", tmu, (unsigned long)devInfo.tmuMemSize[tmu]);
if((sOptions.eTMU0RamLimit >= 0) && (devInfo.numberTmus>=1))
devInfo.tmuMemSize[0] = sOptions.eTMU0RamLimit;
if((sOptions.eTMU1RamLimit >= 0) && (devInfo.numberTmus==2))
devInfo.tmuMemSize[1] = sOptions.eTMU1RamLimit;
for (int tmu = 0; tmu < devInfo.numberTmus; tmu++)
logI("TMU%d tested memory:\t%ld MB\n", tmu, (unsigned long)devInfo.tmuMemSize[tmu]);
putchar('\n');
if(sOptions.bTestTMU0 && devInfo.numberTmus<1)
{
logE("TMU0 not Found\n");
Status = E_ERROR__TMU_NOT_FOUND;
ErrorCheck_gotoCleanUp();
}
if(sOptions.bTestTMU1 && devInfo.numberTmus<2)
{
logE("TMU1 not Found\n");
Status = E_ERROR__TMU_NOT_FOUND;
ErrorCheck_gotoCleanUp();
}
const bool bTestTMU0 = (devInfo.numberTmus > 0)
&& (devInfo.tmuMemSize[0] > 0)
&& sOptions.bTestTMU0;
const bool bTestTMU1 = (devInfo.numberTmus > 1)
&& (devInfo.tmuMemSize[1] > 0)
&& sOptions.bTestTMU1;
if(!bTestTMU0 && !bTestTMU1)
{
logE("no TMU selected.\n");
Status = E_ERROR__NO_TMU_SELECTED;
ErrorCheck_gotoCleanUp();
}
if(bTestTMU1 && !bTestTMU0)
{
logW("/!\\ make sure you did test TMU0 BEFORE TMU1.\n\n");
}
ISET(sstregs->lfbMode, SST_LFB_RGBALANES_ARGB | SST_LFB_READFRONTBUFFER);
ISET(sstregs->fbzMode, SST_DRAWBUFFER_FRONT | SST_RGBWRMASK);
ISET(sstregs->fbzColorPath, SST_RGBSEL_TREXOUT | SST_CC_PASS | SST_ENTEXTUREMAP);
ISET(sstregs->textureMode, SST_RGB565 | SST_TC_REPLACE | SST_TCA_REPLACE);
ISET(sstregs->tLOD, 0);
for (int tmu = 0; tmu < devInfo.numberTmus; tmu++)
{
if((tmu == 0) && !bTestTMU0) continue;
if((tmu == 1) && !bTestTMU1) continue;
logI("## testing Board %d, TMU %d, %ldMB \n",boardNum,tmu,(unsigned long)devInfo.tmuMemSize[tmu]);
if(sOptions.bTestTMUAddress)
{
unsigned long long err=0;
logI("### pre-heating\n");
HeatMemAndTMU(&devInfo,sst,sstregs,tmu,0x000000);
logI("### address & control lines test - cumulated\n");
clearScreen(sstregs,0x00000000,256,256);
clock_t test_begin = clock();
do
{
err = RenderTestAddress(&devInfo,
sst,
sstregs,
tmu,
devInfo.tmuMemSize[tmu],
ar_dFaultScores);
ullNbErrorAll += err;
logI( err ? "E" : ".");
fflush(stdout);
}
while(!err
&& (((double)(clock() - test_begin)/CLOCKS_PER_SEC)
< (devInfo.tmuMemSize[tmu]*_DEF_TEST_TIME_PER_MB_S)));
logI("\n");
if(err)
{
logW("error detected on address line !\n");
logW("skiping next tests.\n\n");
continue;
}
}
if(sOptions.bTestTMUData)
{
unsigned long long err=0;
logI("### pre-heating\n");
HeatMemAndTMU(&devInfo,sst,sstregs,tmu,0x000000);
logI("### data test - single bit move\n");
clearScreen(sstregs,0x00000000,256,256);
clock_t test_begin = clock();
do
{
err = test_TMU_datalines(&devInfo,
sst,
sstregs,
tmu ,
0, /* bit shift mode */
devInfo.tmuMemSize[tmu],
ar_dFaultScores);
ullNbErrorAll += err;
logI( err ? "E" : ".");
fflush(stdout);
}
while(!err &&
(((double)(clock() - test_begin)/CLOCKS_PER_SEC)
< (devInfo.tmuMemSize[tmu]*_DEF_TEST_TIME_PER_MB_S)));
logI("\n");
if(err)
{
logW("error detected on data line (single bit move) !\n");
logW("skiping next tests.\n\n");
continue;
}
logI("### pre-heating\n");
HeatMemAndTMU(&devInfo,sst,sstregs,tmu,0x000000);
logI("### data test - random patterns\n");
clearScreen(sstregs,0x00000000,256,256);
test_begin = clock();
do
{
err = test_TMU_datalines(&devInfo,
sst,
sstregs,
tmu ,
1, /* random mode */
devInfo.tmuMemSize[tmu],
ar_dFaultScores);
ullNbErrorAll += err;
logI( err ? "E" : ".");
fflush(stdout);
}
while(!err &&
(((double)(clock() - test_begin)/CLOCKS_PER_SEC)
< (devInfo.tmuMemSize[tmu]*_DEF_TEST_TIME_PER_MB_S)));
logI("\n");
if(err)
{
logW("error detected on data line (random patterns) !\n");
logW("skiping next tests.\n\n");
continue;
}
}
if(sOptions.bTestTMUDataHuge)
{
unsigned long long err=0;
logI("### pre-heating\n");
HeatMemAndTMU(&devInfo,sst,sstregs,tmu,0x000000);
logI("### data test - huge data set\n");
clearScreen(sstregs,0x00000000,256,256);
clock_t test_begin = clock();
do
{
err = test_TMU_datalines_Huge( &devInfo,
sst,
sstregs,
tmu ,
devInfo.tmuMemSize[tmu],
ar_dFaultScores);
ullNbErrorAll += err;
logI( err ? "E" : ".");
fflush(stdout);
}
while(!err &&
(((double)(clock() - test_begin)/CLOCKS_PER_SEC)
< (devInfo.tmuMemSize[tmu]*_DEF_TEST_TIME_PER_MB_S)));
logI("\n");
if(err)
{
logW("error detected on data line (huge data set) !\n");
logW("skiping next tests.\n\n");
continue;
}
}
logI("\n");
}
}
FaultSource_getSorted(ar_dFaultScores_sorted,ar_dFaultScores);
FaultSource_display(ar_dFaultScores_sorted);
logI("test completed, ullNbErrorAll = %lld\n",ullNbErrorAll);
logI("\n");
if(ullNbErrorAll)
logI(szKool);
else
logI(szKool2);
logI("\n");
CleanUp:
if(sst)
sst1InitShutdown(sst);
return Status;
}