/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 Source Code 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. Doom 3 Source Code 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 Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include #include #include #include #include #include #ifdef ID_MCHECK #include #endif #include "sys/platform.h" #include "framework/Licensee.h" #include "framework/FileSystem.h" #include "sys/posix/posix_public.h" #include "sys/sys_local.h" #include "sys/linux/local.h" static idStr basepath; static idStr savepath; /* =========== Sys_InitScanTable =========== */ void Sys_InitScanTable( void ) { common->DPrintf( "TODO: Sys_InitScanTable\n" ); } /* ================= Sys_AsyncThread ================= */ THREAD_RETURN_TYPE Sys_AsyncThread( void * ) { int now; int next; int want_sleep; // multi tick compensate for poor schedulers (Linux 2.4) int ticked, to_ticked; now = Sys_Milliseconds(); ticked = now >> 4; while (1) { // sleep now = Sys_Milliseconds(); next = ( now & 0xFFFFFFF0 ) + 0x10; want_sleep = ( next-now-1 ) * 1000; if ( want_sleep > 0 ) { usleep( want_sleep ); // sleep 1ms less than true target } // compensate if we slept too long now = Sys_Milliseconds(); to_ticked = now >> 4; // show ticking statistics - every 100 ticks, print a summary #if 0 #define STAT_BUF 100 static int stats[STAT_BUF]; static int counter = 0; // how many ticks to play stats[counter] = to_ticked - ticked; counter++; if (counter == STAT_BUF) { Sys_DebugPrintf("\n"); for( int i = 0; i < STAT_BUF; i++) { if ( ! (i & 0xf) ) { Sys_DebugPrintf("\n"); } Sys_DebugPrintf( "%d ", stats[i] ); } Sys_DebugPrintf("\n"); counter = 0; } #endif while ( ticked < to_ticked ) { common->Async(); ticked++; Sys_TriggerEvent( TRIGGER_EVENT_ONE ); } // thread exit pthread_testcancel(); } return (THREAD_RETURN_TYPE) 0; } /* ============== Sys_DefaultSavePath ============== */ const char *Sys_DefaultSavePath(void) { #if defined( ID_DEMO_BUILD ) sprintf( savepath, "%s/.doom3-demo", getenv( "HOME" ) ); #else sprintf( savepath, "%s/.doom3", getenv( "HOME" ) ); #endif return savepath.c_str(); } /* ============== Sys_EXEPath ============== */ const char *Sys_EXEPath( void ) { static char buf[ 1024 ]; idStr linkpath; int len; buf[ 0 ] = '\0'; sprintf( linkpath, "/proc/%d/exe", getpid() ); len = readlink( linkpath.c_str(), buf, sizeof( buf ) ); if ( len == -1 ) { Sys_Printf("couldn't stat exe path link %s\n", linkpath.c_str()); buf[ 0 ] = '\0'; } return buf; } /* ================ Sys_DefaultBasePath Get the default base path - binary image path - current directory - hardcoded Try to be intelligent: if there is no BASE_GAMEDIR, try the next path ================ */ const char *Sys_DefaultBasePath(void) { struct stat st; idStr testbase; basepath = Sys_EXEPath(); if ( basepath.Length() ) { basepath.StripFilename(); testbase = basepath; testbase += "/"; testbase += BASE_GAMEDIR; if ( stat( testbase.c_str(), &st ) != -1 && S_ISDIR( st.st_mode ) ) { return basepath.c_str(); } else { common->Printf( "no '%s' directory in exe path %s, skipping\n", BASE_GAMEDIR, basepath.c_str() ); } } if ( basepath != Posix_Cwd() ) { basepath = Posix_Cwd(); testbase = basepath; testbase += "/"; testbase += BASE_GAMEDIR; if ( stat( testbase.c_str(), &st ) != -1 && S_ISDIR( st.st_mode ) ) { return basepath.c_str(); } else { common->Printf("no '%s' directory in cwd path %s, skipping\n", BASE_GAMEDIR, basepath.c_str()); } } common->Printf( "WARNING: using hardcoded default base path\n" ); return LINUX_DEFAULT_PATH; } /* =============== Sys_GetConsoleKey =============== */ unsigned char Sys_GetConsoleKey( bool shifted ) { return shifted ? '~' : '`'; } /* =============== Sys_Shutdown =============== */ void Sys_Shutdown( void ) { basepath.Clear(); savepath.Clear(); Posix_Shutdown(); } /* =============== Sys_GetProcessorId =============== */ static char cpustring[13] = "generic\0"; #if defined(__x86_64__) || defined(__i386__) #if __x86_64__ # define REG_b "rbx" # define REG_S "rsi" #elif __i386__ # define REG_b "ebx" # define REG_S "esi" #endif #define cpuid(index,eax,ebx,ecx,edx) \ __asm__ volatile \ ( "mov %%" REG_b ", %%" REG_S "\n\t" \ "cpuid\n\t" \ "xchg %%" REG_b ", %%" REG_S \ : "=a" (eax), "=S" (ebx), \ "=c" (ecx), "=d" (edx) \ : "0" (index)); int Sys_GetProcessorId( void ) { int eax, ebx, ecx, edx; int max_std_level, max_ext_level, std_caps=0, ext_caps=0; union { int i[3]; char c[12]; } vendor; int i = CPUID_GENERIC; cpuid(0, max_std_level, ebx, ecx, edx); vendor.i[0] = ebx; vendor.i[1] = edx; vendor.i[2] = ecx; strncpy(cpustring, vendor.c, 12); cpustring[12] = 0; Sys_Printf("Detected '%s' CPU with", cpustring); if (max_std_level >= 1) { cpuid(1, eax, ebx, ecx, std_caps); #ifdef __MMX__ if (std_caps & (1<<23)) { Sys_Printf(" MMX"); i |= CPUID_MMX; } #endif #ifdef __SSE__ if (std_caps & (1<<25)) { Sys_Printf(" SSE"); i |= CPUID_SSE; } #endif #ifdef __SSE2__ if (std_caps & (1<<26)) { Sys_Printf(" SSE2"); i |= CPUID_SSE2; } #endif #ifdef __SSE3__ if (ecx & 1) { Sys_Printf(" SSE3"); i |= CPUID_SSE3; } #endif } cpuid(0x80000000, max_ext_level, ebx, ecx, edx); if (max_ext_level >= 0x80000001) { cpuid(0x80000001, eax, ebx, ecx, ext_caps); #ifdef __3dNOW__ if (ext_caps & (1U<<31)) { Sys_Printf(" 3DNOW"); i |= CPUID_3DNOW; } #endif #ifdef __MMX__ if (ext_caps & (1<<23)) { if (!(i & CPUID_MMX)) Sys_Printf(" MMX"); i |= CPUID_MMX; } #endif } Sys_Printf("\n"); return i; } #else int Sys_GetProcessorId( void ) { return CPUID_GENERIC; } #endif /* =============== Sys_GetProcessorString =============== */ const char *Sys_GetProcessorString( void ) { return cpustring; } /* =============== Sys_FPU_EnableExceptions =============== */ void Sys_FPU_EnableExceptions( int exceptions ) { } /* =============== Sys_FPE_handler =============== */ void Sys_FPE_handler( int signum, siginfo_t *info, void *context ) { assert( signum == SIGFPE ); Sys_Printf( "FPE\n" ); } /* =============== Sys_GetClockticks =============== */ double Sys_GetClockTicks( void ) { #if defined( __i386__ ) unsigned long lo, hi; __asm__ __volatile__ ( "push %%ebx\n" \ "xor %%eax,%%eax\n" \ "cpuid\n" \ "rdtsc\n" \ "mov %%eax,%0\n" \ "mov %%edx,%1\n" \ "pop %%ebx\n" : "=r" (lo), "=r" (hi) ); return (double) lo + (double) 0xFFFFFFFF * hi; #else return 0.0; #endif } /* =============== MeasureClockTicks =============== */ double MeasureClockTicks( void ) { double t0, t1; t0 = Sys_GetClockTicks( ); Sys_Sleep( 1000 ); t1 = Sys_GetClockTicks( ); return t1 - t0; } /* =============== Sys_ClockTicksPerSecond =============== */ double Sys_ClockTicksPerSecond(void) { static bool init = false; static double ret; int fd, len, pos, end; char buf[ 4096 ]; if ( init ) { return ret; } fd = open( "/proc/cpuinfo", O_RDONLY ); if ( fd == -1 ) { common->Printf( "couldn't read /proc/cpuinfo\n" ); ret = MeasureClockTicks(); init = true; common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 ); return ret; } len = read( fd, buf, 4096 ); close( fd ); pos = 0; while ( pos < len ) { if ( !idStr::Cmpn( buf + pos, "cpu MHz", 7 ) ) { pos = strchr( buf + pos, ':' ) - buf + 2; end = strchr( buf + pos, '\n' ) - buf; if ( pos < len && end < len ) { buf[end] = '\0'; ret = atof( buf + pos ); } else { common->Printf( "failed parsing /proc/cpuinfo\n" ); ret = MeasureClockTicks(); init = true; common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 ); return ret; } common->Printf( "/proc/cpuinfo CPU frequency: %g MHz\n", ret ); ret *= 1000000; init = true; return ret; } pos = strchr( buf + pos, '\n' ) - buf + 1; } common->Printf( "failed parsing /proc/cpuinfo\n" ); ret = MeasureClockTicks(); init = true; common->Printf( "measured CPU frequency: %g MHz\n", ret / 1000000.0 ); return ret; } /* ================ Sys_GetSystemRam returns in megabytes ================ */ int Sys_GetSystemRam( void ) { long count, page_size; int mb; count = sysconf( _SC_PHYS_PAGES ); if ( count == -1 ) { common->Printf( "GetSystemRam: sysconf _SC_PHYS_PAGES failed\n" ); return 512; } page_size = sysconf( _SC_PAGE_SIZE ); if ( page_size == -1 ) { common->Printf( "GetSystemRam: sysconf _SC_PAGE_SIZE failed\n" ); return 512; } mb= (int)( (double)count * (double)page_size / ( 1024 * 1024 ) ); // round to the nearest 16Mb mb = ( mb + 8 ) & ~15; return mb; } /* ================== Sys_DoStartProcess if we don't fork, this function never returns the no-fork lets you keep the terminal when you're about to spawn an installer if the command contains spaces, system() is used. Otherwise the more straightforward execl ( system() blows though ) ================== */ void Sys_DoStartProcess( const char *exeName, bool dofork ) { bool use_system = false; if ( strchr( exeName, ' ' ) ) { use_system = true; } else { // set exec rights when it's about a single file to execute struct stat buf; if ( stat( exeName, &buf ) == -1 ) { printf( "stat %s failed: %s\n", exeName, strerror( errno ) ); } else { if ( chmod( exeName, buf.st_mode | S_IXUSR ) == -1 ) { printf( "cmod +x %s failed: %s\n", exeName, strerror( errno ) ); } } } if ( dofork ) { switch ( fork() ) { case -1: printf( "fork failed: %s\n", strerror( errno ) ); break; case 0: if ( use_system ) { printf( "system %s\n", exeName ); if (system( exeName ) == -1) printf( "system failed: %s\n", strerror( errno ) ); _exit( 0 ); } else { printf( "execl %s\n", exeName ); execl( exeName, exeName, NULL ); printf( "execl failed: %s\n", strerror( errno ) ); _exit( -1 ); } break; default: break; } } else { if ( use_system ) { printf( "system %s\n", exeName ); if (system( exeName ) == -1) printf( "system failed: %s\n", strerror( errno ) ); else sleep( 1 ); // on some systems I've seen that starting the new process and exiting this one should not be too close } else { printf( "execl %s\n", exeName ); execl( exeName, exeName, NULL ); printf( "execl failed: %s\n", strerror( errno ) ); } // terminate _exit( 0 ); } } /* ================= Sys_OpenURL ================= */ void idSysLocal::OpenURL( const char *url, bool quit ) { const char *script_path; idFile *script_file; char cmdline[ 1024 ]; static bool quit_spamguard = false; if ( quit_spamguard ) { common->DPrintf( "Sys_OpenURL: already in a doexit sequence, ignoring %s\n", url ); return; } common->Printf( "Open URL: %s\n", url ); // opening an URL on *nix can mean a lot of things .. // just spawn a script instead of deciding for the user :-) // look in the savepath first, then in the basepath script_path = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_savepath" ), "", "openurl.sh" ); script_file = fileSystem->OpenExplicitFileRead( script_path ); if ( !script_file ) { script_path = fileSystem->BuildOSPath( cvarSystem->GetCVarString( "fs_basepath" ), "", "openurl.sh" ); script_file = fileSystem->OpenExplicitFileRead( script_path ); } if ( !script_file ) { common->Printf( "Can't find URL script 'openurl.sh' in either savepath or basepath\n" ); common->Printf( "OpenURL '%s' failed\n", url ); return; } fileSystem->CloseFile( script_file ); // if we are going to quit, only accept a single URL before quitting and spawning the script if ( quit ) { quit_spamguard = true; } common->Printf( "URL script: %s\n", script_path ); // StartProcess is going to execute a system() call with that - hence the & idStr::snPrintf( cmdline, 1024, "%s '%s' &", script_path, url ); sys->StartProcess( cmdline, quit ); } /* ================== Sys_DoPreferences ================== */ void Sys_DoPreferences( void ) { } /* ================ Sys_FPU_SetDAZ ================ */ void Sys_FPU_SetDAZ( bool enable ) { /* DWORD dwData; _asm { movzx ecx, byte ptr enable and ecx, 1 shl ecx, 6 STMXCSR dword ptr dwData mov eax, dwData and eax, ~(1<<6) // clear DAX bit or eax, ecx // set the DAZ bit mov dwData, eax LDMXCSR dword ptr dwData } */ } /* ================ Sys_FPU_SetFTZ ================ */ void Sys_FPU_SetFTZ( bool enable ) { /* DWORD dwData; _asm { movzx ecx, byte ptr enable and ecx, 1 shl ecx, 15 STMXCSR dword ptr dwData mov eax, dwData and eax, ~(1<<15) // clear FTZ bit or eax, ecx // set the FTZ bit mov dwData, eax LDMXCSR dword ptr dwData } */ } /* =============== mem consistency stuff =============== */ #ifdef ID_MCHECK const char *mcheckstrings[] = { "MCHECK_DISABLED", "MCHECK_OK", "MCHECK_FREE", // block freed twice "MCHECK_HEAD", // memory before the block was clobbered "MCHECK_TAIL" // memory after the block was clobbered }; void abrt_func( mcheck_status status ) { Sys_Printf( "memory consistency failure: %s\n", mcheckstrings[ status + 1 ] ); Posix_SetExit( EXIT_FAILURE ); common->Quit(); } #endif /* =============== main =============== */ int main(int argc, const char **argv) { #ifdef ID_MCHECK // must have -lmcheck linkage mcheck( abrt_func ); Sys_Printf( "memory consistency checking enabled\n" ); #endif Posix_EarlyInit( ); if ( argc > 1 ) { common->Init( argc-1, &argv[1], NULL ); } else { common->Init( 0, NULL, NULL ); } Posix_LateInit( ); while (1) { common->Frame(); } }