/* This file is part of cputemp2maxfreq. Copyright (C) 2023-2024 pa4wdh cputemp2maxfreq 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. cputemp2maxfreq 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 cputemp2maxfreq; see the file COPYING. If not, see . */ #include #include #include #include #include #include #include "sysfs.h" #include "cpufreq.h" #include "debug.h" #include "cputemp2maxfreq.h" #include "failsafe.h" #include "version.h" #include "argparse.h" #include "logger.h" #include "cputemp.h" #include "cpulist.h" // Set default config struct s_config config={ .name="", // Name of this program, set by argparse .governor="conservative", // Governor .target_temp=70000, // Target temperature .temp_input="auto", // Temperature input .freq_step=100000, // Frequency step .fallback_freq=2000000, // Fallback frequency .interval=10, // Interval .logger_name="stdout", // Logger name .logger=&logger_stdout, // Logger function .log_data=0, // Don't log measurement data .csvlog="", // CSV logfile .csvoverwrite=0, // Don't overwrite CSV logfile .csvfile=NULL, // File handler for CSV file .use_unixtime=0, // Don't use unix timestamps in log outputs .transition_latency=-1, // Transition latency, default to autodetect .keepstate=0, // Do not keep state on exit (Set lowest frequency) .cpu=-1, // Physical CPU to change }; struct s_cpudata cpudata={ .min_freq=0, // CPU's minimum frequency .max_freq=0, // CPU's maximum frequency .cur_freq=0, // CPU's current frequency .cur_temp=0, // CPU's current temperature .scale_max=0, // Governor's maximum scaling frequency .transition_latency=0, // CPU's transition latency .cpulist=NULL, // Pointer to the cpulist, set by cpulist_find_cpus .cpulist_len=0, // Number of CPU's in cpulist, set by cpulist_find_cpus }; void signal_handler(int signum) { config.logger("Received signal %d, exiting.",signum); failsafe(0); } char *transition_latency_remarks[] = { "Autodetected", "Set via commandline", "This seems low, consider setting it via -d" }; char *autodetect_sensors[] = { "Package id 0", "Tctl", "k10temp", NULL }; int main(int argc,char **argv) { int count; long int diff; long int newfreq; struct s_sensor sensor; char *transition_latency_remark; char sensor_string[20]; struct sigaction sigact={0}; argparse(argc,argv); config.logger("%s version %s, buildtime %s %s",config.name,version(),__DATE__,__TIME__); // Log configuration config.logger("Configuration:"); config.logger("Governor: %s",config.governor); config.logger("Target temperature: %ld",config.target_temp); config.logger("Temp input: %s",config.temp_input); config.logger("Frequency step: %ld",config.freq_step); config.logger("Fallback frquency: %ld",config.fallback_freq); config.logger("Interval: %d",config.interval); config.logger("Logger: %s (%p)",config.logger_name,config.logger); config.logger("Log measurement data: %d",config.log_data); if (config.csvlog[0]==0) { config.logger("CSV Log file: Disabled"); } else { config.logger("CSV Log file: %s",config.csvlog); config.logger("Overwrite CSV log: %d",config.csvoverwrite); } config.logger("Use unix timestamps: %d",config.use_unixtime); if (config.transition_latency<0) { config.logger("Transition latency: Autodetect"); } else { config.logger("Transition latency: %d",config.transition_latency); } if (config.keepstate==1) { config.logger("State on exit: Keep last state on normal exit"); } else { config.logger("State on exit: Always set CPU to lowest frequency"); } if (config.cpu<0) { config.logger("Physical CPU to change: all"); } else { config.logger("Physical CPU to change: %d",config.cpu); } if ((config.target_tempVALID_TEMP_MAX)) { config.logger("Invalid temperature, range is %d-%d",VALID_TEMP_MIN,VALID_TEMP_MAX); exit(1); } if ((config.freq_stepVALID_STEP_MAX)) { config.logger("Invalid frequency step, range is %d-%d",VALID_STEP_MIN,VALID_STEP_MAX); exit(1); } if ((config.intervalVALID_INTERVAL_MAX)) { config.logger("Invalid poll interval, range is %d-%d",VALID_INTERVAL_MIN,VALID_INTERVAL_MAX); exit(1); } // Autodetect temperature input if needed if (config.temp_input[0]!='/') { if (strcmp(config.temp_input,"auto")==0) { config.logger("Starting temperature sensor autodetection"); sensor.valid=0; if (config.cpu>=0) { sprintf(sensor_string,"Package id %d",config.cpu); DEBUG1_MAIN("Searching for sensor \"%s\" because of -P option\n",sensor_string); cputemp_find_sensor(sensor_string,&sensor); } if (sensor.valid==0) { for(count=0;autodetect_sensors[count]!=NULL;count++) { DEBUG1_MAIN("Searching for sensor \"%s\"\n",autodetect_sensors[count]); cputemp_find_sensor(autodetect_sensors[count],&sensor); if (sensor.valid==1) break; } } } else { DEBUG1_MAIN("Starting temperature sensor detection with name \"%s\"\n",config.temp_input); cputemp_find_sensor(config.temp_input,&sensor); } if (sensor.valid==1) { DEBUG1_MAIN("Found sensor \"%s\"\n",sensor.name); DEBUG1_MAIN("Sensor file: %s\n",sensor.filename); strncpy(config.temp_input,sensor.filename,sizeof(config.temp_input)); config.logger("Detection returned temperature input: %s",config.temp_input); config.logger("Temperature input name: %s",sensor.name); } else { config.logger("Detection failed to return a valid sensor, set it using -i"); exit(1); } } // Find CPU's cpulist_find_cpus(); if (cpudata.cpulist_len==0) { if (config.cpu>=0) { config.logger("No CPU's found for physical CPU %d, exiting",config.cpu); } else { config.logger("Failed to find any CPU, is sysfs mounted?"); } exit(1); } // Get and validate CPU data cpudata.min_freq=cpufreq_get_long_int("cpuinfo_min_freq"); cpudata.max_freq=cpufreq_get_long_int("cpuinfo_max_freq"); cpudata.scale_max=cpufreq_get_long_int("scaling_max_freq"); if (config.transition_latency<0) { transition_latency_remark=transition_latency_remarks[0]; cpudata.transition_latency=cpufreq_get_long_int("cpuinfo_transition_latency"); if (cpudata.transition_latency<1000) transition_latency_remark=transition_latency_remarks[2]; } else { transition_latency_remark=transition_latency_remarks[1]; cpudata.transition_latency=config.transition_latency; } config.logger("CPU data:"); config.logger("Number of CPU's: %d",cpudata.cpulist_len); config.logger("Minimum frequency: %ld",cpudata.min_freq); config.logger("Maximum frequency: %ld",cpudata.max_freq); config.logger("Scaling maximum frequency: %ld",cpudata.scale_max); config.logger("Transition Latency: %ld (%s)",cpudata.transition_latency,transition_latency_remark); if ((cpudata.min_freqVALID_FREQ_MAX) || (cpudata.max_freqVALID_FREQ_MAX) || (cpudata.scale_maxVALID_FREQ_MAX) || (cpudata.transition_latencyVALID_TRANS_MAX)) { // If we have to fail now, there's not much we can do because we have no data config.logger("Invalid CPU data, exiting"); cpulist_free(); exit(1); } // Modify fallback frequency if needed if (config.fallback_freqcpudata.max_freq) { config.logger("Fallback frequency is higher than CPU's maximum frequency, setting to %ld",cpudata.max_freq); config.fallback_freq=cpudata.max_freq; } // Set the governor if (strcmp(config.governor,"keep")!=0) { if (cpufreq_set_str("scaling_governor",config.governor,0)<0) { config.logger("Failed to set governor, error %d (%s)",errno,strerror(errno)); // We failed to set the governor, call the failsafe failsafe(1); } } // Initialise CSV logging if (config.csvlog[0]!=0) csvlog_init(); // Set signal handlers sigact.sa_handler=&signal_handler; sigemptyset(&sigact.sa_mask); sigaddset(&sigact.sa_mask,SIGTERM); sigaddset(&sigact.sa_mask,SIGINT); sigaddset(&sigact.sa_mask,SIGQUIT); sigaction(SIGTERM,&sigact,NULL); sigaction(SIGINT,&sigact,NULL); sigaction(SIGQUIT,&sigact,NULL); while(1) { // Get new measurements cpudata.cur_freq=cpufreq_get_long_int("scaling_cur_freq"); if ((cpudata.cur_freqVALID_FREQ_MAX)) { config.logger("Invalid current frequency reported by CPU, exiting"); failsafe(1); } cpudata.cur_temp=sysfs_read_long_int(config.temp_input); if ((cpudata.cur_tempVALID_TEMP_MAX)) { config.logger("Invalid current temperature reported by CPU, exiting"); failsafe(1); } cpudata.scale_max=cpufreq_get_long_int("scaling_max_freq"); if ((cpudata.scale_maxVALID_FREQ_MAX)) { config.logger("Invalid scale_max_freq reported, exiting"); failsafe(1); } DEBUG1_MAIN("Data: %ld %ld %ld %ld %ld\n",cpudata.cur_temp,config.target_temp,cpudata.max_freq,cpudata.scale_max,cpudata.cur_freq); if (config.log_data>0) { config.logger("CPU Temperature: %ld, CPU Frequency: %ld",cpudata.cur_temp/1000,cpudata.cur_freq); } if (config.csvfile!=NULL) csvlog_write(); diff=config.target_temp-cpudata.cur_temp; // Check if we should increase if ((diff>=1000) && (cpudata.scale_maxcpudata.max_freq) newfreq=cpudata.max_freq; DEBUG1_MAIN("Increase to %ld\n",newfreq); config.logger("Increase scaling_max_freq to %ld",newfreq); // Set new value and validate if (cpufreq_set_long_int("scaling_max_freq",newfreq,cpudata.transition_latency)<0) { config.logger("Failed to set scaling_max_freq, exiting"); failsafe(1); } } // Check if we should decrease if ((diff<=-1000) && (cpudata.scale_max>cpudata.min_freq)) { newfreq=cpudata.scale_max+(config.freq_step*(diff/1000)); if (newfreq