/*
* 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-2008 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/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/cache.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/completion.h>
#include <asm/sn/sn_sal.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_allocate_msgqueues(ch);
spin_lock_irqsave(&ch->lock, *irq_flags);
if (ret != xpSuccess)
XPC_DISCONNECT_CHANNEL(ch, ret, irq_flags);
ch->flags |= XPC_C_SETUP;
if (ch->flags & (XPC_C_CONNECTED | XPC_C_DISCONNECTING))
return;
DBUG_ON(ch->local_msgqueue == NULL);
DBUG_ON(ch->remote_msgqueue == NULL);
}
if (!(ch->flags & XPC_C_OPENREPLY)) {
ch->flags |= XPC_C_OPENREPLY;
xpc_send_chctl_openreply(ch, irq_flags);
}
if (!(ch->flags & XPC_C_ROPENREPLY))
return;
DBUG_ON(ch->remote_msgqueue_pa == 0);
ch->flags = (XPC_C_CONNECTED | XPC_C_SETUP); /* clear all else */
dev_info(xpc_chan, "channel %d to partition %d connected\n",
ch->number, ch->partid);
spin_unlock_irqrestore(&ch->lock, *irq_flags);
xpc_create_kthreads(ch, 1, 0);
spin_lock_irqsave(&ch->lock, *irq_flags);
}
/*
* 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_DEACTIVATING) {
/* can't proceed until the other side disengages from us */
if (xpc_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_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_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);
}
/* it's now safe to free the channel's message queues */
xpc_free_msgqueues(ch);
/*
* Mark the channel disconnected and clear all other flags, including
* XPC_C_SETUP (because of call to xpc_free_msgqueues()) but not
* including XPC_C_WDISCONNECT (if it was set).
*/
ch->flags = (XPC_C_DISCONNECTED | (ch->flags & XPC_C_WDISCONNECT));
atomic_dec(&part->nchannels_active);
if (channel_was_connected) {
dev_info(xpc_chan, "channel %d to partition %d disconnected, "
"reason=%d\n", ch->number, ch->