#!/bin/sh
#==============================================================================
# (C) Copyright IBM Corp. 2003    All Rights Reserved.
#
# chccwdev ($Revision: 1.4.2.1 $)
#
# Script to generically change attributes of a ccw device. Currently there is
# only support for kernels with sysfs support and the only attribute is online.
#
# Author(s): S. Bader <shbader@de.ibm.com>
#
# This file is part of s390-tools
#
# s390-tools 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.

# s390-tools 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 s390-tools; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#==============================================================================
function PrintUsage() {
	cat <<-EOD
		Usage: $(basename $0) [<options>] <devices>

		<options>
		 	-e|--online
		 		Tries to set the given device online.
		 	-f|--forceonline
		 		Tries to force a device online if the device
		 		driver supports this.
		  	-d|--offline
		  		Tries to set the given device offline.

		<devices>
		 	<bus ID>[-<busid>][,<busid>[-<busid>]] ...
	EOD
}

FORCE=""
while [ $# -gt 0 ]; do
	case $1 in
		-h|--help)
				PrintUsage
				exit 1
				;;
		-e|--online)
				ONLINE=1
				;;
		-f|--forceonline)
				ONLINE=1
				FORCE=force
				;;
		-d|--offline)
				ONLINE=0
				;;
		-*)
				echo "Unknown option $1"
				exit 1
				;;
		*)
				if [ "$BUSIDLIST" = "" ]; then
					BUSIDLIST="$1"
				else
					BUSIDLIST="$BUSIDLIST,$1"
				fi
				;;
	esac
	shift
done

#
# Parse the BUSIDLIST and expand the ranges and short IDs.
#
BUSIDLIST=$(
	echo "$BUSIDLIST" | awk '
	function hex2dec(hex) {
		d = 0
		for (i = 1; i <= length(hex); i++) {
			h = index("0123456789abcdef", tolower(substr(hex,i,1)))
			d = (d * 16) + (h - 1)
		}
		return d
	}
	function dec2hex(dec) {
		h = ""
		for(d = dec; d > 0; d = int(d / 16)) {
			h = substr("0123456789abcdef", (d % 16) + 1, 1) h
		}
		while (length(h) < 4)
			h = "0" h

		return h
	}
	function BusIDValid(id) {
		split(id, part, ".")
		css = int(part[1])
		if (css < 0 || css > 255)
			return 0
		dsn = int(part[2])
		if (dsn < 0 || dsn > 255)
			return 0
		if (match(part[3], /^[a-f0-9]+$/) == 0)
			return 0
		return 1
	}
	function ExpandBusID(id) {
		split(id, part, ".")
		if (3 in part) {
			css = part[1]
			dsn = part[2]
			did = part[3]
		} else if (2 in part) {
			css = 0
			dsn = part[1]
			did = part[2]
		} else {
			css = 0
			dsn = 0
			did = part[1]
		}
		while (length(did) < 4)
			did = "0" did

		busid = css "." dsn "." did
		if (! BusIDValid(busid)) {
			print busid " is not a valid bus ID." >err
			return ""
		}
		return busid
	}
	function ExpandRange(range) {
		split(range, id, "-")
		from = ExpandBusID(id[1])
		if (!BusIDValid(from)) {
			print from " is not a valid bus ID." >err
			return
		}
		to = ExpandBusID(id[2])
		if (!BusIDValid(to)) {
			print to " is not a valid bus ID." >err
			return
		}
		split(from, parts1, ".")
		split(to, parts2, ".")

		if (parts1[1] != parts2[1] || parts1[2] != parts2[2]) {
			print "Invalid range (" from "-" to ")" >err
			return
		}
		from = hex2dec(parts1[3])
		to   = hex2dec(parts2[3])
		if (from > to) {
			print "Invalid range order" >err
			return
		}
		for (i = from; i <= to; i++)
			print parts1[1] "." parts1[2] "." dec2hex(i)
	}
	BEGIN{
		err = "/dev/stderr"
	}
	{
		line = $0
		gsub(/[ \t]+/, "", line)

		split(line, range, ",")
		for (i in range) {
			if (match(range[i], /-/)) {
				ExpandRange(range[i])
			} else {
				busid = ExpandBusID(range[i])
				if (busid != "")
					print busid
			}
		}
	}'
)

if [ "$BUSIDLIST" = "" ]; then
	echo "No bus ID given!" >&2
	exit 1
fi

function IsOnline() {
	if [ ! -f "$1/online" ]; then
		if [ $2 -eq 0 ]; then
			return 0
		fi
		return 1
	fi
	if [ $(cat $1/online) -eq $2 ]; then
		return 0
	fi
	return 1
}

for BUSID in $BUSIDLIST; do
	SYSPATH=/sys/bus/ccw/devices/$BUSID
	if [ ! -r $SYSPATH ]; then
		echo "$BUSID is not a channel device"
		exit 1
	fi

	if [ "$ONLINE" != "" ]; then
		if IsOnline $SYSPATH $ONLINE; then
			if [ "$ONLINE" -eq 1 ]; then
				echo "Device already is online" >&2
			else
				echo "Device already is offline" >&2
			fi
			continue
		fi
		if [ "$ONLINE" -eq 1 ]; then
			echo "Setting device $BUSID online"
		else
			echo "Setting device $BUSID offline"
		fi
		if [ "$FORCE" != "" ]; then
			echo $FORCE >$SYSPATH/online
		else
			echo $ONLINE >$SYSPATH/online
		fi

		if ! IsOnline $SYSPATH $ONLINE; then
			sleep 1
			if ! IsOnline $SYSPATH $ONLINE; then
				echo "Failed" >&2
				exit 1
			fi
		fi
		echo "Done"
	fi
done

exit 0
