/*
 * libiec61883 - Linux IEEE 1394 streaming media library.
 * Copyright (C) 2004 Kristian Hogsberg, Dan Dennedy, and Dan Maas.
 * This file written by Dan Dennedy.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "iec61883.h"
#include "iec61883-private.h"
#include "cooked.h"

#include <stdio.h>
#include <libraw1394/csr.h>
#include <netinet/in.h>


int
iec61883_cmp_calc_bandwidth (raw1394handle_t handle, nodeid_t from, int plug,
		int speed)
{
	struct iec61883_oMPR ompr;
	struct iec61883_oPCR opcr;
	int bwu = -1; // failure
		
	if (iec61883_get_oMPR (handle, from, &ompr) < 0) {
		WARN ("%s: Failed to get the oMPR plug for node %d.", __FUNCTION__, 
			(int) from & 0x3f);
	} else if (ompr.n_plugs == 0) {
		WARN ("%s: The transmitting device (%d) does not have any output plugs.", 
			__FUNCTION__, (int) from & 0x3f);
	} else if (plug < ompr.n_plugs && plug < IEC61883_PCR_MAX) {
		if (iec61883_get_oPCRX (handle, from, &opcr, plug) < 0) {
			WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
				plug, (int) from & 0x3f);
		} else {
			if (speed < 0 || speed > 2)
				speed = opcr.data_rate;
			if (opcr.overhead_id > 0)
				bwu = (opcr.overhead_id * 32) + (opcr.payload + 3) * (1 << (2 - speed)) * 4;
			else
				bwu = 512 + (opcr.payload + 3) * (1 << (2 - speed)) * 4;
		}
	}
	
	return bwu;
}

int
iec61883_cmp_create_p2p (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		nodeid_t input_node, int input_plug,	
		unsigned int channel, unsigned int speed)
{
	struct iec61883_oPCR opcr, save_opcr;
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}

	save_opcr = opcr;
	opcr.channel = ipcr.channel = channel;
	opcr.data_rate = speed;
	if (opcr.n_p2p_connections < 63)
		opcr.n_p2p_connections++;
	if (ipcr.n_p2p_connections < 63)
		ipcr.n_p2p_connections++;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		// Undo changes on the oPCR
		if (iec61883_set_oPCRX (handle, output_node, save_opcr, output_plug) < 0) {
			WARN ("%s: Failed to undo changes on the oPCR[%d] plug for node %d.",
				__FUNCTION__, output_plug, (int) output_node & 0x3f);
		}
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_create_p2p_output (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		unsigned int channel, unsigned int speed)
{
	struct iec61883_oPCR opcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}

	opcr.channel = channel;
	opcr.data_rate = speed;
	if (opcr.n_p2p_connections < 63)
		opcr.n_p2p_connections++;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_create_p2p_input (raw1394handle_t handle,
		nodeid_t input_node, int input_plug,
		unsigned int channel)
{
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}

	ipcr.channel = channel;
	if (ipcr.n_p2p_connections < 63)
		ipcr.n_p2p_connections++;
	
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_create_bcast (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		nodeid_t input_node, int input_plug,
		unsigned int channel, unsigned int speed)
{
	struct iec61883_oPCR opcr, save_opcr;
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}

	save_opcr = opcr;
	opcr.channel = ipcr.channel = channel;
	opcr.data_rate = speed;
	opcr.bcast_connection = 1;
	ipcr.bcast_connection = 1;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		// Undo changes on the oPCR
		if (iec61883_set_oPCRX (handle, output_node, save_opcr, output_plug) < 0) {
			WARN ("%s: Failed to undo changes on the oPCR[%d] plug for node %d.", 
				__FUNCTION__, output_plug, (int) output_node & 0x3f);
		}
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_create_bcast_output (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		unsigned int channel, unsigned int speed)
{
	struct iec61883_oPCR opcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}

	opcr.channel = channel;
	opcr.data_rate = speed;
	opcr.bcast_connection  = 1;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_create_bcast_input (raw1394handle_t handle,
		nodeid_t input_node, int input_plug,
		unsigned int channel)
{
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		return -1;
	}

	ipcr.channel = channel;
	ipcr.bcast_connection = 1;
	
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_overlay_p2p (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		nodeid_t input_node, int input_plug)
{
	struct iec61883_oPCR opcr, save_opcr;
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		return -1;
	}

	if (opcr.bcast_connection == 0) {
		save_opcr = opcr;
		if (opcr.n_p2p_connections < 63)
			opcr.n_p2p_connections++;
	}
	if (ipcr.bcast_connection == 0)
		if (ipcr.n_p2p_connections < 63)
			ipcr.n_p2p_connections++;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		// Undo changes on the oPCR
		if (iec61883_set_oPCRX (handle, output_node, save_opcr, output_plug) < 0) {
			WARN ("%s: Failed to undo changes on the oPCR[%d] plug for node %d.", 
				__FUNCTION__, output_plug, (int) output_node & 0x3f);
		}
		return -1;
	}
	
	return 0;
}

int
iec61883_cmp_overlay_p2p_output (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug)
{
	struct iec61883_oPCR opcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}

	if (opcr.bcast_connection == 0) {
		if (opcr.n_p2p_connections < 63)
			opcr.n_p2p_connections++;
		
		if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
			WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
				output_plug, (int) output_node & 0x3f);
			return -1;
		}
	}
	
	return 0;
}

int
iec61883_cmp_overlay_p2p_input (raw1394handle_t handle,
		nodeid_t input_node, int input_plug)
{
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}

	if (ipcr.bcast_connection == 0) {
		if (ipcr.n_p2p_connections < 63)
			ipcr.n_p2p_connections++;
		
		if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
			WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
				input_plug,	(int) input_node & 0x3f);
			return -1;
		}
	}
	
	return 0;
}

int
iec61883_cmp_overlay_bcast (raw1394handle_t handle, 
		nodeid_t output_node, int output_plug,
		nodeid_t input_node, int input_plug)
{
	struct iec61883_oPCR opcr, save_opcr;
	struct iec61883_iPCR ipcr;
	
	DEBUG ("%s", __FUNCTION__);
	
	if (iec61883_get_oPCRX (handle, output_node, &opcr, output_plug) < 0) {
		WARN ("%s: Failed to get the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_get_iPCRX (handle, input_node, &ipcr, input_plug) < 0) {
		WARN ("%s: Failed to get the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug, (int) input_node & 0x3f);
		return -1;
	}

	save_opcr = opcr;
	// We still need this function because only one of the plugs might have the
	// bcast_connection set.
	opcr.bcast_connection = 1;
	ipcr.bcast_connection = 1;
	
	if (iec61883_set_oPCRX (handle, output_node, opcr, output_plug) < 0) {
		WARN ("%s: Failed to set the oPCR[%d] plug for node %d.", __FUNCTION__,
			output_plug, (int) output_node & 0x3f);
		return -1;
	}
	if (iec61883_set_iPCRX (handle, input_node, ipcr, input_plug) < 0) {
		WARN ("%s: Failed to set the iPCR[%d] plug for node %d.", __FUNCTION__,
			input_plug,	(int) input_node & 0x3f);
		// Undo changes on the oPCR
		if (iec61883_set_oPCRX (handle, output_node, save_opcr, output_plug) < 0) {
			WARN ("%s: Failed to undo changes on the oPCR[%d] plug for node %d.", 
				__FUNCTION__, output_plug, (int) output_node & 0x3f);
		}
		return -1;
	}
	
	return 0;
}

static int
allocate_channel (raw1394handle_t handle)
{
	int c = -1;
	
	for (c = 0; c < 63; c++)
		if (raw1394_channel_modify (handle, c, RAW1394_MODIFY_ALLOC) == 0)
			break;
	
	DEBUG ("%s: %d", __FUNCTION__, c);
	
	return c;
}

/**
 * cmp_connect - establish, re-establish or overlay connection automatically
 * @handle: a libraw1394 handle
 * @output: node id of the transmitter
 * @oplug: the output plug to use. If -1, find the first online plug, and
 * upon return, contains the plug number used.
 * @input: node id of the receiver
 * @iplug: the input plug to use. If -1, find the first online plug, and
 * upon return, contains the plug number used.
 * @bandwidth: an input/output parameter. As an input this is a boolean that
 * indicates whether to perform bandwidth allocation. Supply 0 to skip bandwidth
 * allocation; any other value permits it. Upon return, actual bandwidth allocation
 * units allocated are returned, which you supply to the disconnect routine.
 * @channel: if channel is < 0, the function will establish a new connection.
 * oherwise, it will try to re-establish an existing connection.
 *
 * This is a high level function that attempts to be as smart as possible, but
 * it gives point-to-point connections higher priority over broadcast connections.
 * It can automatically handle situations where either @input and/or @output does
 * not implement plug control registers. However, if one node implements plug
 * registers it assumes the other node has some sort of manual channel selection
 * (i.e., through software or a control panel).
 *
 * Returns:
 * It returns the isochronous channel number selected or -1 if the function has
 * failed for any reason.
 **/
static int
cmp_connect (raw1394handle_t handle, nodeid_t output, int *oplug, 
             nodeid_t input, int *iplug, int *bandwidth, int channel)
{
	struct iec61883_oMPR ompr;
	struct iec61883_iMPR impr;
	struct iec61883_oPCR opcr;
	struct iec61883_iPCR ipcr;
	int oplug_online = -1, iplug_online = -1;
	int skip_bandwidth = (*bandwidth == 0);
	int failure = 0;
	int new_connection = (channel < 0);
	
	DEBUG ("%s", __FUNCTION__);
	
	*bandwidth = 0;
		
	// Check for plugs on output
	if (iec61883_get_oMPR (handle, output, &ompr) < 0)
		ompr.n_plugs = 0;
	
	// Check for plugs on input
	if (iec61883_get_iMPR (handle, input, &impr) < 0)
		impr.n_plugs = 0;
	
	DEBUG ("output node %d #plugs=%d, input node %d #plugs=%d", output & 0x3f,
		ompr.n_plugs, input & 0x3f, impr.n_plugs);
	
	if (ompr.n_plugs > 0 && impr.n_plugs > 0) {
		// establish or overlay point-to-point
		
		// speed to use is lesser of output and input
		unsigned int speed = 
			impr.data_rate < ompr.data_rate ? impr.data_rate : ompr.data_rate;
		
		// determine if output has plug available
		if (*oplug < 0) {
			for (*oplug = 0; *oplug < ompr.n_plugs; (*oplug)++) {
				if (iec61883_get_oPCRX (handle, output, &opcr, *oplug) == 0) {
					// get first online plug
					if (oplug_online == -1 && opcr.online)
						oplug_online = *oplug;
					if (opcr.online && opcr.n_p2p_connections == 0)
						break;
				}
			}
		} else if (iec61883_get_oPCRX (handle, output, &opcr, *oplug) < 0)
			FAIL ("Failed to get plug %d for output node", *oplug);
		
		// determine if input has plug available
		if (*iplug < 0) {
			for (*iplug = 0; *iplug < impr.n_plugs; (*iplug)++) {
				if (iec61883_get_iPCRX (handle, input, &ipcr, *iplug) == 0) {
					// get first online plug
					if (iplug_online == -1 && ipcr.online)
						iplug_online = *iplug;
					if (ipcr.online && ipcr.n_p2p_connections == 0)
						break;
				}
			}
		} else if (iec61883_get_iPCRX (handle, input, &ipcr, *iplug) < 0)
			FAIL ("Failed to get plug %d for input node", *iplug);
		
		if (*oplug < ompr.n_plugs && *iplug < impr.n_plugs) {
			
			if (opcr.bcast_connection == 1) {
				channel = opcr.channel;
				iec61883_cmp_overlay_bcast (handle, output, *oplug, input, *iplug);
			} else {
				// allocate bandwidth
				if (!skip_bandwidth) {
					*bandwidth = iec61883_cmp_calc_bandwidth (handle, output, *oplug, speed);
					if (*bandwidth < 1) {
						WARN ("Failed to calculate bandwidth.");
						failure = 1;
					} else if (raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_ALLOC) < 0) {
						WARN ("Failed to allocate bandwidth.");
						failure = 1;
					}
				}
				if (!failure) {
					if (new_connection) {
						channel = allocate_channel (handle);
					} else {
						raw1394_channel_modify (handle, channel, RAW1394_MODIFY_ALLOC);
					}
					if (iec61883_cmp_create_p2p (handle, output, *oplug, input, *iplug, 
						channel, speed) < 0) {
						// release channel and bandwidth
						failure = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
						if (!failure)
							raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_FREE);
						channel = -1;
					}
				}
			}

		} else if (*iplug < impr.n_plugs && oplug_online > -1 ) {
			// get the channel from output - can not start another transmission 
			// on an existing channel, but can receive from multiple nodes/plugs
			*oplug = oplug_online;
			if (iec61883_get_oPCRX (handle, output, &opcr, oplug_online) == 0) {
				channel = opcr.channel;
				if (opcr.bcast_connection == 1) {
					iec61883_cmp_overlay_bcast (handle, output, oplug_online, input, *iplug);
				} else {
					if (iec61883_cmp_create_p2p_input (handle, input, *iplug, channel) < 0)
						channel = -1;
					else if (iec61883_cmp_overlay_p2p_output (handle, output, oplug_online) < 0)
						channel = -1;
				}
			}
			
		} else if (oplug_online != -1 && iplug_online > -1) {
			// get channel from output
			*oplug = oplug_online;
			if (iec61883_get_oPCRX (handle, output, &opcr, oplug_online) == 0) {
				channel = opcr.channel;
				if (iec61883_cmp_overlay_p2p (handle, output, oplug_online,
					input, iplug_online) < 0)
					channel = -1;
			}
		} else {
			WARN ("All the plugs on both nodes are offline!");
			*oplug = *iplug = -1;
		}
			
	} else if (ompr.n_plugs > 0) {
		// establish or overlay half point-to-point on output
		*iplug = -1;
		
		// determine if output has plug available
		if (*oplug < 0) {
			for (*oplug = 0; *oplug < ompr.n_plugs; (*oplug)++) {
				if (iec61883_get_oPCRX (handle, output, &opcr, *oplug) == 0) {
					// get first online plug
					if (oplug_online == -1 && opcr.online)
						oplug_online = *oplug;
					if (opcr.online && opcr.n_p2p_connections == 0)
						break;
				}
			}
		} else if (iec61883_get_oPCRX (handle, output, &opcr, *oplug) < 0)
			FAIL ("Failed to get plug %d for output node", *oplug);
		
		if (*oplug < ompr.n_plugs) {
			if (opcr.bcast_connection == 1) {
				channel = opcr.channel;
			} else {
				// establish
				// XXX: the input must provide manual channel selection or we should
				// do a broadcast. Example use case: DV device is output and local
				// node is input, but software allows channel select. Failure use
				// case: local node is output but input device has no channel selection!
				// Both use cases are actually quite common. Should we provide a 
				// parameter to offer a hint in case operator knows something more
				// about the device than firewire interfaces on it suggest?
				
				// allocate bandwidth
				if (!skip_bandwidth) {
					*bandwidth = iec61883_cmp_calc_bandwidth (handle, output, *oplug, ompr.data_rate);
					if (*bandwidth < 1) {
						WARN ("Failed to calculate bandwidth.");
						failure = 1;
					} else if (raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_ALLOC) < 0) {
						WARN ("Failed to allocate bandwidth.");
						failure = 1;
					}
				}
				if (!failure) {
					if( new_connection ) {
						channel = allocate_channel (handle);
					} else {
						raw1394_channel_modify (handle, channel, RAW1394_MODIFY_ALLOC);
					}
					if (iec61883_cmp_create_p2p_output (handle, output, *oplug, 
						channel, ompr.data_rate) == 0) {
						DEBUG ("Established connection on channel %d.\n"
							  "You may need to manually set the channel on the receiving node.",
							  channel);
					} else {
						// release channel and bandwidth
						failure = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
						if (!failure)
							raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_FREE);
						channel = -1;
					}
				}
			}
			
		} else if (oplug_online > -1) {
			// overlay
			// get channel from output
			*oplug = oplug_online;
			if (iec61883_get_oPCRX (handle, output, &opcr, oplug_online) == 0) {
				channel = opcr.channel;
				if (opcr.bcast_connection != 1)
					if (iec61883_cmp_overlay_p2p_output (handle, output, oplug_online) < 0)
						channel = -1;
			}
			DEBUG ("Overlayed connection on channel %d.\n"
				  "You may need to manually set the channel on the receiving node.",
				  channel);
		} else {
			WARN ("Transmission node has no plugs online!");
			*oplug = -1;
			// failover to broadcast
			// allocate bandwidth based upon first out plug
			if (!skip_bandwidth) {
				*bandwidth = iec61883_cmp_calc_bandwidth (handle, output, 0, ompr.data_rate);
				if (*bandwidth < 1) {
					WARN ("Failed to calculate bandwidth.");
					failure = 1;
				} else if (raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_ALLOC) < 0) {
					WARN ("Failed to allocate bandwidth.");
					failure = 1;
				}
			}
			if (!failure) {
				if (raw1394_channel_modify (handle, ompr.bcast_channel, RAW1394_MODIFY_ALLOC) == 0)
					channel = ompr.bcast_channel;
			}
		}
		
	} else if (impr.n_plugs > 0) {
		// establish or overlay half point-to-point on input
		*oplug = -1;
		
		// determine if input has plug available
		if (*iplug < 0) {
			for (*iplug = 0; *iplug < impr.n_plugs; (*iplug)++) {
				if (iec61883_get_iPCRX (handle, input, &ipcr, *iplug) == 0) {
					// get first online plug
					if (iplug_online == -1 && ipcr.online)
						iplug_online = *iplug;
					if (ipcr.online && ipcr.n_p2p_connections == 0)
						break;
				}
			}
		} else if (iec61883_get_iPCRX (handle, input, &ipcr, *iplug) < 0)
			FAIL ("Failed to get plug %d for input node", *iplug);
		
		if (*iplug < impr.n_plugs) {
			if (ipcr.bcast_connection == 1) {
				channel = ipcr.channel;
			} else {
				// establish
				
				// allocate bandwidth
				// cannot accurately allocate bandwidth with no output plug
				// use an output plug on the input device as a best guess
				if (!skip_bandwidth) {
					*bandwidth = iec61883_cmp_calc_bandwidth (handle, input, *iplug, -1);
					if (*bandwidth < 1) {
						WARN ("Failed to calculate bandwidth.");
						failure = 1;
					} else if (raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_ALLOC) < 0) {
						WARN ("Failed to allocate bandwidth.");
						failure = 1;
					}
				}
				if (!failure) {
					if( new_connection ) {
						channel = allocate_channel (handle);
					} else {
						raw1394_channel_modify (handle, channel, RAW1394_MODIFY_ALLOC);
					}
					if (iec61883_cmp_create_p2p_input (handle, input, *iplug, channel) == 0) {
						DEBUG ("Established connection on channel %d.\n"
							  "You may need to manually set the channel on the transmitting node.",
							  channel);
					} else {
						// release channel and bandwidth
						failure = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
						if (!failure)
							raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_FREE);
						channel = -1;
					}
				}
			}
			
		} else if (iplug_online > -1) {
			// overlay
			// get channel from input
			*iplug = iplug_online;
			if (iec61883_get_iPCRX (handle, input, &ipcr, iplug_online) == 0) {
				channel = ipcr.channel;
				if (ipcr.bcast_connection != 1)
					if (iec61883_cmp_overlay_p2p_input (handle, input, iplug_online) < 0)
						channel = -1;
			}
			DEBUG ("Overlayed connection on channel %d.\n"
				  "You may need to manually set the channel on the transmitting node.",
				  channel);
		} else {
			WARN ("Receiving node has no plugs online!");
			// failover to broadcast
			// allocate bandwidth based upon first input plug
			*iplug = -1;

			if (!skip_bandwidth) {
				*bandwidth = iec61883_cmp_calc_bandwidth (handle, input, 0, impr.data_rate);
				if (*bandwidth < 1) {
					WARN ("Failed to calculate bandwidth.");
					failure = 1;
				} else if (raw1394_bandwidth_modify (handle, *bandwidth, RAW1394_MODIFY_ALLOC) < 0) {
					WARN ("Failed to allocate bandwidth.");
					failure = 1;
				}
			}
			if (!failure) {
				if (raw1394_channel_modify (handle, 63, RAW1394_MODIFY_ALLOC) == 0)
					channel = 63;
			}
		}
			
	} else {
		// no input or output plugs - failover broadcast on channel 63
		// not enough information to calculate bandwidth
		*oplug = *iplug = -1;
		if (raw1394_channel_modify (handle, 63, RAW1394_MODIFY_ALLOC) == 0)
			channel = 63;
		if (channel == 63)
			WARN ("No plugs exist on either node; using default broadcast channel 63.");
	}

	return channel;
}

int
iec61883_cmp_reconnect (raw1394handle_t handle, nodeid_t output, int *oplug,
		nodeid_t input, int *iplug, int *bandwidth, int channel)
{
	/* Passing an existing channel means it is a reconnection. */
	return cmp_connect (handle, output, oplug, input, iplug, bandwidth, channel);
}

int
iec61883_cmp_connect (raw1394handle_t handle, nodeid_t output, int *oplug, 
		nodeid_t input, int *iplug, int *bandwidth)
{
	/* Passing "-1" as the channel means it is a new connection. */
	return cmp_connect (handle, output, oplug, input, iplug, bandwidth, -1);
}

int
iec61883_cmp_disconnect (raw1394handle_t handle, nodeid_t output, int oplug,
		nodeid_t input, int iplug, unsigned int channel, unsigned int bandwidth)
{
	struct iec61883_oMPR ompr;
	struct iec61883_iMPR impr;
	struct iec61883_oPCR opcr;
	struct iec61883_iPCR ipcr;
	int result = 0;
	
	DEBUG ("%s: oplug %d iplug %d channel %u bw %u", __FUNCTION__,
		oplug, iplug, channel, bandwidth);
	
	// Check for plugs on output
	if (iec61883_get_oMPR (handle, output, &ompr) < 0)
		ompr.n_plugs = 0;
	
	// Check for plugs on input
	if (iec61883_get_iMPR (handle, input, &impr) < 0)
		impr.n_plugs = 0;
	
	if (ompr.n_plugs > 0 && impr.n_plugs > 0) {
		// establish or overlay point-to-point
		
		// determine if output has plug available
		if (oplug < 0) {
			for (oplug = 0; oplug < ompr.n_plugs; oplug++) {
				if (iec61883_get_oPCRX (handle, output, &opcr, oplug) == 0) {
					if (opcr.online && opcr.channel == channel)
						break;
				}
			}
		} else if (iec61883_get_oPCRX (handle, output, &opcr, oplug) < 0)
			FAIL ("Failed to get plug %d for output node", oplug);
		
		// determine if input has plug available
		if (iplug < 0) {
			for (iplug = 0; iplug < impr.n_plugs; iplug++) {
				if (iec61883_get_iPCRX (handle, input, &ipcr, iplug) == 0) {
					if (ipcr.online && ipcr.channel == channel)
						break;
				}
			}
		} else if (iec61883_get_iPCRX (handle, input, &ipcr, iplug) < 0)
			FAIL ("Failed to get plug %d for input node", iplug);
		
		if (oplug != ompr.n_plugs) {
			if (opcr.n_p2p_connections > 0) {
				opcr.n_p2p_connections--;
				if (opcr.n_p2p_connections == 0)
					opcr.channel = ompr.bcast_channel;
				result = iec61883_set_oPCRX (handle, output, opcr, oplug);
				if (result == 0) {
					if (opcr.n_p2p_connections == 0) {
						// release channel and bandwidth
						result = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
						if (result == 0)
							result = raw1394_bandwidth_modify (handle, bandwidth, RAW1394_MODIFY_FREE);
					}
				}
			}
		}
		if (iplug != impr.n_plugs) {
			if (ipcr.n_p2p_connections > 0) {
				ipcr.n_p2p_connections--;
				// receiver connection count does not affect iso resource management
				result = iec61883_set_iPCRX (handle, input, ipcr, iplug);
			} else if (ipcr.bcast_connection == 1) {
				ipcr.bcast_connection = 0;
				ipcr.channel = ompr.bcast_channel;
				result = iec61883_set_iPCRX (handle, input, ipcr, iplug);
			}
		}
		if (oplug == ompr.n_plugs && iplug == impr.n_plugs)
			result = -1;
			
	} else if (ompr.n_plugs > 0) {
		// establish or overlay half point-to-point on output
		
		// determine if output has plug available
		if (oplug < 0) {
			for (oplug = 0; oplug < ompr.n_plugs; oplug++) {
				if (iec61883_get_oPCRX (handle, output, &opcr, oplug) == 0) {
					if (opcr.online && opcr.channel == channel)
						break;
				}
			}
		} else if (iec61883_get_oPCRX (handle, output, &opcr, oplug) < 0)
			FAIL ("Failed to get plug %d for output node", oplug);
		
		if (oplug != ompr.n_plugs) {
			if (opcr.n_p2p_connections > 0) {
				opcr.n_p2p_connections--;
				if (opcr.n_p2p_connections == 0)
					opcr.channel = ompr.bcast_channel;
				result = iec61883_set_oPCRX (handle, output, opcr, oplug);
				if (result == 0 && opcr.n_p2p_connections == 0) {
					// release channel and bandwidth
					result = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
					if (result == 0)
						result = raw1394_bandwidth_modify (handle, bandwidth, RAW1394_MODIFY_FREE);
				}
			}
		} else {
			// release channel and bandwidth
			result = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
			if (result == 0)
				result = raw1394_bandwidth_modify (handle, bandwidth, RAW1394_MODIFY_FREE);
		}
		
	} else if (impr.n_plugs > 0) {
		// establish or overlay half point-to-point on input
		
		// determine if input has plug available
		if (iplug < 0) {
			for (iplug = 0; iplug < impr.n_plugs; iplug++) {
				if (iec61883_get_iPCRX (handle, input, &ipcr, iplug) == 0) {
					if (ipcr.online && ipcr.channel == channel)
						break;
				}
			}
		} else if (iec61883_get_iPCRX (handle, input, &ipcr, iplug) < 0)
			FAIL ("Failed to get plug %d for input node", iplug);
		
		if (iplug != impr.n_plugs) {
			if (ipcr.n_p2p_connections > 0) {
				ipcr.n_p2p_connections--;
				if (ipcr.n_p2p_connections == 0)
					ipcr.channel = 63;
				// Normally, changes on receiver connection count does not 
				// affect iso resource management. However, in this special
				// half-way management mode, we need to in order to allow
				// multiple capture sessions.
				result = iec61883_set_iPCRX (handle, input, ipcr, iplug);
				if (result == 0 && ipcr.n_p2p_connections == 0) {
					// release channel and bandwidth
					result = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
					if (result == 0)
						result = raw1394_bandwidth_modify (handle, bandwidth, RAW1394_MODIFY_FREE);
				}
			}
		} else {
			// release channel and bandwidth
			result = raw1394_channel_modify (handle, channel, RAW1394_MODIFY_FREE);
			if (result == 0)
				result = raw1394_bandwidth_modify (handle, bandwidth, RAW1394_MODIFY_FREE);
		}
			
	} else {
		// no input or output plugs - failover broadcast on channel 63
		// just release channel
		result = raw1394_channel_modify (handle, 63, RAW1394_MODIFY_FREE);
	}
	
	return result;
}

int
iec61883_cmp_normalize_output (raw1394handle_t handle, nodeid_t node)
{
	struct iec61883_oMPR ompr;
	struct iec61883_oPCR opcr;
	int oplug;
	int result = 0;

	DEBUG ("iec61883_cmp_normalize_output: node %d\n", (int) node & 0x3f);

	// Check for plugs on output
	result = iec61883_get_oMPR (handle, node, &ompr);
	if (result < 0)
		return result;
	
	// locate an ouput plug that has a connection
	for (oplug = 0; oplug < ompr.n_plugs; oplug++) {
		if (iec61883_get_oPCRX (handle, node, &opcr, oplug) == 0) {
			if (opcr.online && (opcr.n_p2p_connections > 0 || 
				                opcr.bcast_connection == 1)) {

				// Make sure the plug's channel is allocated with IRM
				quadlet_t buffer;
				nodeaddr_t addr = CSR_REGISTER_BASE;
				unsigned int c = opcr.channel;
				quadlet_t compare, swap = 0;
				quadlet_t new;
				
				if (c > 31 && c < 64) {
					addr += CSR_CHANNELS_AVAILABLE_LO;
					c -= 32;
				} else if (c < 64)
					addr += CSR_CHANNELS_AVAILABLE_HI;
				else
					FAIL ("Invalid channel");
				c = 31 - c;

				result = iec61883_cooked_read (handle, raw1394_get_irm_id (handle), addr, 
					sizeof (quadlet_t), &buffer);
				if (result < 0)
					FAIL ("Failed to get channels available.");
				
				buffer = ntohl (buffer);
				DEBUG ("channels available before: 0x%08x", buffer);

				if ((buffer & (1 << c)) != 0) {
					swap = htonl (buffer & ~(1 << c));
					compare = htonl (buffer);

					result = raw1394_lock (handle, raw1394_get_irm_id (handle), addr,
							   EXTCODE_COMPARE_SWAP, swap, compare, &new);
					if ( (result < 0) || (new != compare) ) {
						FAIL ("Failed to modify channel %d", opcr.channel);
					}
					DEBUG ("channels available after: 0x%08x", ntohl (swap));
				}
			}
		}
	}
		
	return result;
}
