#!/usr/bin/perl

# Copyright (c) 2006-2010 by Mauro Carvalho Chehab <mchehab@redhat.com>
#
# Licensed under the terms of the GNU GPL License version 2

use strict;
use File::Find;
use Proc::ProcessTable;

# Drivers currently at the staging tree
my @staging = ( "tm6000", "cx25821", "go7007" );

my %depend = ();
my %depend2 = ();
my %rmlist = ();
my @nodep;
my @modlist;
my @allmodules;
my %reqmodules;
my %loaded = ();
my $i=0;

# Device debug parameters
#		Module name		   Debug option
my %debug = (	"tuner"			=> "tuner_debug=1",
		"dvb-core"		=> "cam_debug=1",
		"dvb-ttpci"		=> "debug=247",
		"b2c2-flexcop"		=> "debug=0x01",
		"b2c2-flexcop-usb"	=> "debug=0x01",
		"b2c2-flexcop-pci"	=> "debug=0x01",
		"dvb-usb"		=> "debug=0x33",
		"dvb-usb-gp8psk"	=> "debug=0x03",
		"dvb-usb-vp7045"	=> "debug=0x03",
		"dvb-usb-dtt200u"	=> "debug=0x03",
		"dvb-usb-dibusb-common"	=> "debug=0x03",
	    );

sub findprog($)
{
	foreach(split(/:/, $ENV{PATH}),qw(/sbin /usr/sbin /usr/local/sbin)) {
		return "$_/$_[0]" if(-x "$_/$_[0]");
	}
	die "Can't find needed utility '$_[0]'";
}

sub parse_dir {
	my $file = $File::Find::name;
	my $modinfo = findprog('modinfo');

	if (!($file =~ /[.]ko$/) && !($file =~ /[.]ko[.].*$/) ) {
		return;
	}

	my $module = $file;
	$file =~ s|.*\/([^/]+)|\1|;
	$module =~ s|.*\/([^/]+)\.ko.*|\1|;

	open IN, "$modinfo $file|" or die "can't run $modinfo $file";
	while (<IN>) {
		if (m/depends:\s*(.*)/) {
			my $deps = $1;
			$deps =~ s/\n//;
			$deps =~ s/[,]/ /g;
			$deps = " $deps ";
			$depend{$module} = $deps;
			push @allmodules, $module;
			$i++;
		}
	}
	close IN;
}

sub parse_loaded {
	open IN,  "/proc/modules";
	while (<IN>) {
		m/^([\w\d_-]+)/;
		$loaded{$1}=1;
	}
	close IN;
}

sub cleandep()
{
	my $dep;

	while ( my ($k, $v) = each(%depend) ) {
		my $arg=$v;
		my $arg2=" ";
		while (!($arg =~ m/^\s*$/)) {
			if ($arg =~ m/^ ([^ ]+) /) {
				my $val=$1;
				if (exists($depend{$val})) {
					$arg2="$arg2 $val ";
				} else {
					$reqmodules{$val}=1;
				}
			}
			$arg =~ s/^ [^ ]+//;
			$arg2 =~ s/\s\s+/ /;
		}
		$depend2 { $k } = $arg2;
	}

}

sub rmdep()
{
	my $dep;

	while ($dep=pop @nodep) {
		while ( my ($k, $v) = each(%depend2) ) {
			if ($v =~ m/\s($dep)\s/) {
				$v =~ s/\s${dep}\s/ /;
				$v =~ s/\s${dep}\s/ /;
				$v =~ s/\s${dep}\s/ /;
				$depend2 {$k} = $v;
			}
		}
	}
}

sub orderdep ()
{
	my $old;
	do {
		$old=$i;
		while ( my ($key, $value) = each(%depend2) ) {
			if ($value =~ m/^\s*$/) {
				push @nodep, $key;
				push @modlist, $key;
				$i=$i-1;
				delete $depend2 {$key};
			}
		}
		rmdep();
	} until ($old==$i);
	while ( my ($key, $value) = each(%depend2) ) {
		printf "ERROR: bad dependencies - $key ($value)\n";
	}
}

sub insmod ($)
{
	my $debug=shift;
	my $modprobe = findprog('modprobe');
	my $insmod = findprog('insmod');

	while ( my ($key, $value) = each(%reqmodules) ) {
		print ("$modprobe $key\n");
		system ("$modprobe $key");
	}

	foreach my $key (@modlist) {
		if ($debug) {
			my $dbg=$debug{$key};

			print "$insmod ./$key.ko* $dbg\n";
			system "$insmod ./$key.ko* $dbg\n";
		} else {
			print "$insmod ./$key.ko*\n";
			system "$insmod ./$key.ko*\n";
		}
	}
}

my @pulse;
my $try_pulseaudio = 0;

sub check_pulseaudio()
{
	my $t = new Proc::ProcessTable;
	foreach my $p ( @{$t->table} ) {
		if ($p->cmndline =~m,\bpulseaudio\b,) {
			push @pulse, $p->uid;
			$try_pulseaudio = 1;
		}
	}

#	print "Pulseaudio is running with UUID(s): @pulse\n" if ($try_pulseaudio);
}

sub unload_alsactl()
{
	my $t = new Proc::ProcessTable;
	foreach my $p ( @{$t->table} ) {
		if ($p->cmndline =~m,/alsactl ,) {
			if ($p->pid > 1) {
				printf "Stopping alsactl at pid %d\n", $p->pid;
				kill -9, $p->pid;
			}
		}
	}
}

sub unload_pulseaudio($)
{
	my $driver_name = shift;
	my $cur_module;

	check_pulseaudio() if (!@pulse);
	return if (!$try_pulseaudio);

	$ENV{LANG} = "C";

#	printf("Current user ID: real: %s effective %s\n", $<, $>);

	for my $uid (@pulse) {
		my @pwent = getpwuid($uid);

		$ENV{HOME} = $pwent[7];
		$ENV{USER} = $pwent[0];
		$ENV{LOGNAME} = $pwent[0];
		$< = $uid;
		$> = $uid;

		# HACK: this is needed for pacmd to find the PID on
		# some pulseaudio modes

		if (-e "/run/user/$uid/pulse/pid") {
			$ENV{PULSE_RUNTIME_PATH} = "/run/user/$uid/pulse";
		}

#		printf("pulseaudio runs with user ID %s(%d) on dir %s\n", $pwent[0], $uid, $pwent[7]);
#		printf "\$ pacmd list-sources\n";
		open IN, "pacmd list-sources |";
		while (<IN>) {
			$cur_module = $1 if (/^\s*module:\s*(\d+)/);
			if (/^\s*alsa.driver_name\s*=\s*"(.*)"/) {
				if ($1 eq $driver_name) {
					print "Disabling pulseudio for module $driver_name with: pactl unload-module $cur_module\n";
					system ("pactl unload-module $cur_module");
				}
				next;
			}

			# Special case: em28xx sometimes use a Vendor Class at
			# the same interface as the video node. Pulseaudio can't
			# get the driver name in this case
			if (/^\s*alsa.card_name\s*=\s*"Em28xx/) {
				print "pactl unload-module $cur_module\n";
				system ("pactl unload-module $cur_module");
			}
		}
		close IN;

#		printf "\$ pacmd list-sinks\n";
		open IN, "pacmd list-sinks |" or return;
		while (<IN>) {
			$cur_module = $1 if (/^\s*module:\s*(\d+)/);
			if (/^\s*alsa.driver_name\s*=\s*"(.*)"/) {
				if ($1 eq $driver_name) {
					print "pactl unload-module $1\n";
					system ("pactl unload-module $1");
				}
			}
		}
	}
	close IN;

	$> = 0;
	$< = 0;
}

sub rmmod(@)
{
	my $rmmod = findprog('rmmod');
	my @not;
	foreach (reverse @_) {
		s/-/_/g;
		if (exists ($loaded{$_})) {
			my $module = $_;
			unload_pulseaudio($module);
			print "$rmmod $module\n";
			unshift @not, $module if (system "$rmmod $module");
		}
	}
	return @not;
}

sub prepare_cmd()
{
	my $ver=qx(uname -r);
	$ver =~ s/\s+$//;
	die "Couldn't get kernel version" if (!$ver);

	foreach my $dir (@staging) {
		my $dirname = "/lib/modules/$ver/kernel/drivers/staging/$dir";
		if (-e $dirname) {
			print "Seeking media drivers at $dirname\n";
			find(\&parse_dir, $dirname);
		}
	}

	print "Seeking media drivers at /lib/modules/$ver/kernel/drivers/media/\n";
	find(\&parse_dir, "/lib/modules/$ver/kernel/drivers/media/");

	print "Seeking media drivers at /lib/modules/$ver/kernel/sound/usb/\n";
	find(\&parse_dir, "/lib/modules/$ver/kernel/sound/usb");

	printf "found $i modules\n";

	cleandep();
	orderdep();
}

# main
my $mode;

$mode=shift or $mode="unload";
if ($mode eq "load") {
		prepare_cmd;
		insmod(0);
} else {
	if ($mode eq "unload") {
		unload_alsactl();
		prepare_cmd;
		parse_loaded;
		my @notunloaded = rmmod(@modlist);
		@notunloaded = rmmod(@notunloaded) if (@notunloaded);
		if (@notunloaded) {
			print "Couldn't unload: ", join(' ', @notunloaded), "\n";
		}
	} elsif ($mode eq "reload") {
		prepare_cmd;
		parse_loaded;
		rmmod(@modlist);
		insmod(0);
	} elsif ($mode eq "debug") {
		prepare_cmd;
		parse_loaded;
		insmod(1);
	} elsif ($mode eq "check") {
		prepare_cmd;
		parse_loaded;
	} else {
		printf "Usage: $0 [load|unload|reload|debug|check]\n";
	}
}

