#!/usr/bin/env perl
#
#       cpuscaling 0.1.1
# 
#  Copyright (c) 2006 Sergio Perticone <gall0ws@tiscali.it>.
#  All rights reserved.
#
#  Redistribution and use of this script, with or without modification,
#  are permitted provided that this copyright notice and the following
#  disclaimer are retained.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #
#
# for limited, but trusted, users you can use sudo(8), it works fine.
#
# add in your /etc/sudoers something like this:
#  -------------------------------------------------------
#   %group  ALL=(ALL)   NOPASSWD: /usr/sbin/cpuscaling 
#  -------------------------------------------------------
# so:
#     myhost% sudo /usr/sbin/cpuscaling [ options ]
#
#     see sudoers(5) for more info.
#
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #

use warnings;
use Getopt::Long;

## set some global variables:
$SYSDIR = "/sys/devices/system/cpu/cpu0/cpufreq";
$AVAILABLE_GOVERNORS = "$SYSDIR/scaling_available_governors";
$GOVERNOR = "$SYSDIR/scaling_governor";
$FREQMAX_FILE = "$SYSDIR/scaling_max_freq";
$FREQMIN_FILE = "$SYSDIR/scaling_min_freq";
$FREQCUR = "$SYSDIR/scaling_cur_freq";
$FREQS = "$SYSDIR/scaling_available_frequencies";


sub usage
{
    print <<EoF;

 cpuscaling <governor>
 cpuscaling [ -a, --frequence-max <freq> ] [ -b, --frequence-min <freq> ]
 cpuscaling [ -f, --freqs ] [ -s, --state ]

     conservative	Tune governor to conservative
     ondemand		Tune governor to ondemand
     performance	Tune governor to performance
     powersave		Tune governor to powersave
     userspace		Tune governor to userspace

     -a, --frequence-max <freq>	Set max frequence
     -b, --frequence-min <freq>	Set min frequence
     -f, --freqs		Print frequences supported by your CPU
     -s, --state		Show current state

     -h, --help		Print this help and exit

Report bugs to <gall0ws\@tiscali.it>.

EoF

    exit 0;
}


sub only_root
{
    print "only superuser can do it.\n";
    exit 2;
}


sub something_wrong
{
    print "Failed. Please check your kernel setting.\n";
    exit 1;
}


sub no_freq
{
    print "Please choose a frequence.\n";
    exit 1;
}


sub show_state
{
    open(GOVERNOR_FILE, "< $GOVERNOR") || something_wrong();
    $_ = <GOVERNOR_FILE>;
    chop;
    close(GOVERNOR_FILE);
    print "governor:\t $_\n";
    
    open(FREQCUR_FILE, "< $FREQCUR") || something_wrong();
    $_ = <FREQCUR_FILE>;
    chop;
    close(FREQCUR_FILE);
    print "current :\t $_ Hz\n";

    exit 0 if ("$GOVERNOR" eq "performance");

    open(FREQMIN_FILE, "< $FREQMIN_FILE") || something_wrong();
    $_ = <FREQMIN_FILE>;
    chop;
    close(FREQMIN_FILE);
    print "freq min:\t $_ Hz\n";

    open(FREQMAX_FILE, "< $FREQMAX_FILE") || something_wrong();
    $_ = <FREQMAX_FILE>;
    chop;
    close(FREQMAX_FILE);
    print "freq max:\t $_ Hz\n";

    exit 0;
}


sub show_freqs
{
    open(FREQS, "< $FREQS") || something_wrong();
    $_ = <FREQS>;
    close(FREQS);
    print;

    exit 0;
}

 
sub change_freq
{
    only_root() if("$<" != "0"); # $< is UID

    my ($new_frequence, $file) = @_;

    open(FREQ, "> $file") || something_wrong();
    print FREQ "$new_frequence";
    close(FREQ);

    show_state();
}



sub available
{
    my ($governor) = @_;

    open(AV_GOVS, "< $AVAILABLE_GOVERNORS");
      $_ = <AV_GOVS>;
      chop;
    close(AV_GOVS);

    my @govs = split(' ', $_);
    my $i = 0;

    foreach (@govs) {
	return 1 if ( "$govs[$i]" eq "$governor" );
	$i++;
    }
    
    return 0; ## governor is n/a
}


sub set_governor
{
    only_root() if("$<" != "0");

    my ($new_governor) = @_;

    ## if governor seems to be not available then we try to load module:
    if ( ! available($new_governor) ) {
	`/sbin/modprobe $new_governor` || something_wrong();
    }

    open(GOVS_FILE, "> $GOVERNOR");
    print GOVS_FILE "$new_governor";
    close(GOVS_FILE);

    show_state();
}

sub wtf
{
    print "Try \`cpuscaling --help' for more information.\n";
    exit 1;
}

GetOptions(
	   "freqs"           => \$freqs,
	   "f"               => \$freqs,
	   "frequence-max=i" => \$freq_max,
	   "a=i"             => \$freq_max,
	   "frequence-min=i" => \$freq_min,
	   "b=i"             => \$freq_min,
	   "state"           => \$show_state,
	   "s"               => \$show_state,
	   "help"            => \$usage,
	   "h"               => \$usage,
	   ) || wtf();

usage() if (defined $usage);

show_freqs() if (defined $freqs);
show_state() if (defined $show_state);

change_freq($freq_max, $FREQMAX_FILE) if (defined $freq_max);
change_freq($freq_min, $FREQMIN_FILE) if (defined $freq_min);

if ( defined @ARGV ) {
    set_governor(@ARGV);
}
else {
    print "cpuscaling: too few arguments\n";
    wtf();
}

# EOF