/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* Copyright (c) 2004-2009 Silicon Graphics, Inc. All Rights Reserved.
*/
/*
* Cross Partition Communication (XPC) channel support.
*
* This is the part of XPC that manages the channels and
* sends/receives messages across them to/from other partitions.
*
*/
#include <linux/device.h>
#include "xpc.h"
/*
* Process a connect message from a remote partition.
*
* Note: xpc_process_connect() is expecting to be called with the
* spin_lock_irqsave held and will leave it locked upon return.
*/
static void
xpc_process_connect(struct xpc_channel *ch, unsigned long *irq_flags)
{
enum xp_retval ret;
DBUG_ON(!spin_is_locked(&ch->lock));
if (!(ch->flags & XPC_C_OPENREQUEST) ||
!(ch->flags & XPC_C_ROPENREQUEST)) {
/* nothing more to do for now */
return;
}
DBUG_ON(!(ch->flags & XPC_C_CONNECTING));
if (!(ch->flags & XPC_C_SETUP)) {
spin_unlock_irqrestore(&ch->lock, *irq_flags);
ret = xpc_arch_ops.setup_msg_structures(ch);
spin_lock_irqsave(&ch->lock, *irq_flags);
if (ret != xpSuccess)
XPC_DISCONNECT_CHANNEL(ch, ret, irq_flags);
else
ch->flags |= XPC_C_SETUP;
if (ch->flags & XPC_C_DISCONNECTING)
return;
}
if (!(ch->flags & XPC_C_OPENREPLY)) {
ch->flags |= XPC_C_OPENREPLY;
xpc_arch_ops.send_chctl_openreply(ch, irq_flags);
}
if (!(ch->flags & XPC_C_ROPENREPLY))
return;
if (!(ch->flags & XPC_C_OPENCOMPLETE)) {
ch->flags |= (XPC_C_OPENCOMPLETE | XPC_C_CONNECTED);
xpc_arch_ops.send_chctl_opencomplete(ch, irq_flags);
}
if (!(ch->flags & XPC_C_ROPENCOMPLETE))
return;
dev_info(xpc_chan, "channel %d to partition %d connected\n",
ch->number, ch->partid);
ch->flags = (XPC_C_CONNECTED | XPC_C_SETUP); /* clear all else */
}
/*
* spin_lock_irqsave() is expected to be held on entry.
*/
static void
xpc_process_disconnect(struct xpc_channel *ch, unsigned long *irq_flags)
{
struct xpc_partition *part = &xpc_partitions[ch->partid];
u32 channel_was_connected = (ch->flags & XPC_C_WASCONNECTED);
DBUG_ON(!spin_is_locked(&ch->lock));
if (!(ch->flags & XPC_C_DISCONNECTING))
return;
DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
/* make sure all activity has settled down first */
if (atomic_read(&ch->kthreads_assigned) > 0 ||
atomic_read(&ch->references) > 0) {
return;
}
DBUG_ON((ch->flags & XPC_C_CONNECTEDCALLOUT_MADE) &&
!(ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE));
if (part->act_state == XPC_P_AS_DEACTIVATING) {
/* can't proceed until the other side disengages from us */
if (xpc_arch_ops.partition_engaged(ch->partid))
return;
} else {
/* as long as the other side is up do the full protocol */
if (!(ch->flags & XPC_C_RCLOSEREQUEST))
return;
if (!(ch->flags & XPC_C_CLOSEREPLY)) {
ch->flags |= XPC_C_CLOSEREPLY;
xpc_arch_ops.send_chctl_closereply(ch, irq_flags);
}
if (!(ch->flags & XPC_C_RCLOSEREPLY))
return;
}
/* wake those waiting for notify completion */
if (atomic_read(&ch->n_to_notify) > 0) {
/* we do callout while holding ch->lock, callout can't block */
xpc_arch_ops.notify_senders_of_disconnect(ch);
}
/* both sides are disconnected now */
if (ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE) {
spin_unlock_irqrestore(&ch->lock, *irq_flags);
xpc_disconnect_callout(ch, xpDisconnected);
spin_lock_irqsave(&ch->lock, *irq_flags);
}
DBUG_ON(atomic_read(&ch->n_to_notify) != 0);
/* it's now safe to free the channel's message queues */
xpc_arch_ops.teardown_msg_structures(ch);
ch->func = NULL;
ch->key = NULL;
ch->entry_size = 0;
ch->local_nentries = 0;
ch->remote_nentries = 0;
ch->kthreads_assigned_limit = 0