#!/bin/sh

# zaptel-helper: helper script/functions for Zaptel

# Wrriten by Tzafrir Cohen <tzafrir.cohen@xorcom.com>
# Copyright (C) 2006-2007, Xorcom
#
# All rights reserved.
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# Should be possible to run with -e set. This is also recommended.

# Constants:
# maximal time (in seconds) to wait for /dev/zap/dtl to appear after 
# loading zaptel
DEVZAP_TIMEOUT=${DEVZAP_TIMEOUT:-20}

# Zaptel modules we'll try when detecting zaptel hardware:
ALL_MODULES="${ALL_MODULES:-zaphfc qozap ztgsm wctdm wctdm24xxp wcfxo wcfxs pciradio tor2 torisa wct1xxp wct4xxp wcte11xp wanpipe wcusb xpp_usb}"

# Where do we write the list of modules we detected (if at all):
MODLIST_FILE_DEBIAN=${MODLIST_FILE_DEBIAN:-/etc/modules}
MODLIST_FILE_REDHAT=${MODLIST_FILE_REDHAT:-/etc/sysconfig/zaptel}

# The location of of the fxotune binary
FXOTUNE="${FXOTUNE:-/usr/sbin/fxotune}"
FXOTUNE_CONF="${FXOTUNE_CONF:-/etc/fxotune.conf}"

# this is the context FXO zaptel channels are in.
# See run_fxotune.
FXO_CONTEXT=${FXO_CONTEXT:-from-pstn}

ZTCFG="${ZTCFG:-/sbin/ztcfg}"

# TODO: this may not be appropriate for a general-purpose script.
# However you should not use a direct 'echo' to write output to the user
#, to make it simple to override.
say() {
	echo "$@"
}

error() {
	echo >&2 "$@"
}

die() {
	error "$@"
	exit 1
}


#############################################################################
#####
##### Init helper functions
#####


# Wait for udev to generate /dev/zap/ctl, if needed:
wait_for_zapctl() {
	# if device file already exists, or if zaptel has failed to load: 
	# no point waiting.
	if [ -c /dev/zap/ctl ] || ! grep -q zaptel /proc/modules ; then
	 	return
  	fi
  
  	say "Waiting for /dev/zap/ctl to be generated"
	devzap_found=0
	for i in `seq $DEVZAP_TIMEOUT`; do
		sleep 1
		if [ -c /dev/zap/ctl ]; then
		  devzap_found=1
			break
		fi
	done
	if [ "$devzap_found" != 1 ]; then
		say "Still no /dev/zap/ctl after $devzap_timeout seconds."
		error "No /dev/zap/ctl: cannot run ztcfg. Aborting."
	fi
}

# load the fxotune parameters
# FIXME: /etc/fxotune.conf is a bad location for that file . 
# /etc/zaptel/fxotune.conf?
fxotune_load() {
	if [ -x "$FXOTUNE" ] && [ -r "FXOTUNE_CONF" ]; then
		$FROTUNE -s
	fi
}
	
# If there is no zaptel timing source, load
# ztdummy. Other modules should have been loaded by
# now.
guarantee_timing_source() {
	if ! head -c 0 /dev/zap/pseudo 2>/dev/null
	then modprobe ztdummy || true # will fail if there is no module package
	fi
}

kill_zaptel_users() {
	fuser -k /dev/zap/*
}

# recursively unload a module and its dependencies, if possible.
# where's modprobe -r when you need it?
# inputs: module to unload.
# returns: the result from 
unload_module() {
	module="$1"
	line=`lsmod 2>/dev/null | grep "^$1 "`
	if [ "$line" = '' ]; then return; fi # module was not loaded

	set -- $line
	# $1: the original module, $2: size, $3: refcount, $4: deps list
	mods=`echo $4 | tr , ' '`
	for mod in $mods; do
		# run in a subshell, so it won't step over our vars:
		(unload_module $mod) 
		# TODO: the following is probably the error handling we want:
		# if [ $? != 0 ]; then return 1; fi
	done
	rmmod $module
}

# sleep a while until the xpp modules fully register
wait_for_xpp() {
	if [ -d /proc/xpp ]
	then
		# wait for the XPDs to register:
		# TODO: improve error reporting and produce a messagee here
		cat /proc/xpp/XBUS-*/waitfor_xpds 2>/dev/null >/dev/null  || true
	fi
}

zap_reg_xpp() {
	if [ ! -d /proc/xpp ]; then return; fi
	
	# Get a list of connected Astribank devices, sorted by the name of 
	# the USB connector. That order is rather arbitrary, but will not 
	# change without changes to the cabling.
	xbusses=`sort -k 2 /proc/xpp/xbuses | awk -F: '/STATUS=connected/ {print $1}'`

	# get a list of XPDs that were not yet registered as zaptel spans.
	# this will be the case if you set the parameter zap_autoreg=0 to
	# the module xpp
	# Append /dev/null to provide a valid file name in case of an empty pattern.
	xbusses_pattern=`echo $xbusses| sed -e 's|XBUS-[0-9]*|/proc/xpp/&/XPD-*/zt_registration|g'`' /dev/null'
	xpds_to_register=`grep -l 0 $xbusses_pattern 2>/dev/null` || true
	for file in $xpds_to_register; do
		echo 1 >$file
	done
}

# Set the sync source of the Astribank to the right value
fix_asterisbank_sync() {
	# do nothing if module not present
	if [ ! -d /proc/xpp ]; then return; fi
	
	#if ! grep -q '^HOST' /proc/xpp/sync 2>/dev/null; then return; fi

	case "$XPP_SYNC" in
	n*|N*) return;;
	host|HOST) sync_value="HOST";;
	[0-9]*)sync_value="$XPP_SYNC";;
	*) 
		# find the number of the first bus, and sync from it:
		fxo_pat=`awk -F: '/STATUS=connected/{print $1}' /proc/xpp/xbuses | sed -e 's|.*|/proc/xpp/&/*/fxo_info|'`
		# find the first FXO unit, and set it as the sync master
		bus=`ls -1 $fxo_pat 2> /dev/null | head -n1 | cut -d- -f2 | cut -d/ -f1` 

		# do nothing if there is no bus:
		case "$bus" in [0-9]*):;; *) return;; esac
		sync_value="$bus 0"
		;;
	esac
	# the built-in echo of bash fails to print a proper error on failure
	if ! /bin/echo "$sync_value" >/proc/xpp/sync
	then 
		error "Updating XPP sync source failed (used XPP_SYNC='$XPP_SYNC')"
	fi
}

run_adj_clock() {
	if [ "$XPP_RUN_ADJ_CLOCK" = '' ]; then return; fi

	# daemonize adj_clock:
	(adj_clock </dev/null >/dev/null 2>&1 &)&
}

init_astribank() {
	wait_for_xpp
	zap_reg_xpp
	fix_asterisbank_sync
	run_adj_clock
}

xpp_do_blink() {
	val="$1"
	shift
	for xbus in $*
	do
		for xpd in /proc/xpp/XBUS-"$xbus"/XPD-*
		do
			echo "$val" > "$xpd/blink"
		done
	done
}

xpp_blink() {
	xbuses=`grep STATUS=connected /proc/xpp/xbuses | sed -e 's/^XBUS-//' -e 's/:.*$//'`
	num=`echo $1 | tr -c -d 0-9`
	case "$num" in
	[0-9]*)
		shift
		xpp_do_blink 1 $xbuses
		sleep 2
		xpp_do_blink 0 $xbuses
		;;
	*)
		shift
		echo 1>&2 Enumerating $xbuses
		xpp_do_blink 0 $xbuses
		for i in $xbuses
		do
			echo "BLINKING: $i"
			xpp_do_blink 1 "$i"
			sleep 2
			xpp_do_blink 0 "$i"
		done
		;;
	esac
}

# The current Debian start function.
# The function is not responsible for loading the zaptel modules:
# they will be loaded beforehand. 
debian_start() {
	wait_for_xpp
	zap_reg_xpp
	fix_asterisbank_sync
	wait_for_zapctl
	
        if [ -r /etc/fxotune.conf ] && [ -x $FXOTUNE ]; then
          $FXOTUNE -s
        fi

	# configure existing modules:
	$ZTCFG
}


# run_fxotune: destroy all FXO channels and run fxotune.
# This allows running fxotune without completly shutting down Asterisk.
#
# A simplistic assumption: every zaptel channel in the context from-pstn 
# is a FXO ones.
# or rather: all tunable FXO channels are in the context from-pstn are
# not defined by zaptel.
run_fxotune() {
  zap_fxo_chans=`asterisk -rx "zap show channels" | awk "/$FXO_CONTEXT/{print \$1}"`
  xpp_fxo_chans=`cat /proc/zaptel/* | awk '/XPP_FXO/{print $1}'`
  for chan in $xpp_fxo_chans $zap_fxo_chans; do
    asterisk -rx "zap destroy channel $chan"
  done
  $FXOTUNE -i
  asterisk -rx "zap restart"
}


# recursively unload a module and its dependencies, if possible.
# where's modprobe -r when you need it?
# inputs: module to unload.
unload_module() {
	set +e
	module="$1"
	line=`lsmod 2>/dev/null | grep "^$module "`
	if [ "$line" = '' ]; then return; fi # module was not loaded

	set -- $line
	# $1: the original module, $2: size, $3: refcount, $4: deps list
	mods=`echo $4 | tr , ' '`
	# xpd_fxs actually sort of depends on xpp:
	case "$module" in xpd_*) mods="xpp_usb $mods";; esac
	for mod in $mods; do
		# run in a subshell, so it won't step over our vars:
		(unload_module $mod) 
	done
	rmmod $module || true
	set -e
}

unload() {
	unload_module zaptel
}

# sleep a while until the xpp modules fully register
wait_for_xpp() {
	if [ -d /proc/xpp ] && \
	   [ "`cat /sys/module/xpp/parameters/zap_autoreg`" = 'Y' ]
	then
		# wait for the XPDs to register:
		# TODO: improve error reporting and produce a messagee here
		cat /proc/xpp/XBUS-*/waitfor_xpds 2>/dev/null >/dev/null  || true
	fi
}

#############################################################################
#####
##### Hardware detection functions
#####

load_modules() {
	say "Test Loading modules:"
	for i in $ALL_MODULES
	do
		lines_before=`count_proc_zap_lines`
		args="${i}_args"
		eval "args=\$$args"
		# a module is worth listing if it:
		# a. loaded successfully, and
		# b. added channels lines under /proc/zaptel/*
		if /sbin/modprobe $i $args 2> /dev/null 
		then
		  check=0
		  case "$i" in
			xpp_usb) check=`grep 'STATUS=connected' 2>/dev/null /proc/xpp/xbuses | wc -l` ;;
			# FIXME: zttranscode will always load, and will never 
			# add a span. Maybe try to read from /dev/zap/transcode .
			zttranscode) : ;; 
			*) if [ $lines_before -lt `count_proc_zap_lines` ]; then check=1; fi ;;
			esac
			if [ "$check" != 0 ]
			then
			  probed_modules="$probed_modules $i"
			  say "	ok	$i	$args"
		  else
			  say "	- 	$i	$args"
				rmmod $i
		  fi
		else
			say "	- 	$i	$args"
		fi
	done
}

update_module_list_debian() {
	say "Updating Debian modules list $MODLIST_FILE_DEBIAN."
	del_args=`for i in $ALL_MODULES ztdummy
	do
		echo "$i" | sed s:.\*:-e\ '/^&/d':
	done`
	add_args=`for i in $*
	do
		echo "$i" | sed s:.\*:-e\ '\$a&':
	done`
	
	sed -i.bak $del_args "$MODLIST_FILE_DEBIAN"
	for i in $*
	do
		echo "$i"
	done >> "$MODLIST_FILE_DEBIAN"
}

update_module_list_redhat() {
	say "Updating modules list in zaptel init config $MODLIST_FILE_REDHAT."
	sed -i.bak -e '/^MODULES=/d' "$MODLIST_FILE_REDHAT"
	echo "MODULES=\"$*\"" >> "$MODLIST_FILE_REDHAT"
}

update_module_list() {
	if   [ -f "$MODLIST_FILE_DEBIAN" ]; then
		update_module_list_debian "$@"
	elif [ -f "$MODLIST_FILE_REDHAT" ]; then
		update_module_list_redhat "$@"
	else
		die "Can't find a modules list to update. Tried: $MODLIST_FILE_DEBIAN, $MODLIST_FILE_REDHAT. Aborting"
	fi
}






# unless we wanted to use this as a set of functions, run 
# the given function with its parameters:
if [ "$ZAPHELPER_ONLY_INCLUDE" = '' ]; then
  "$@"
fi
