162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
362306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
462306a36Sopenharmony_ci * for more details.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (c) 2004-2009 Silicon Graphics, Inc.  All Rights Reserved.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * Cross Partition Communication (XPC) channel support.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci *	This is the part of XPC that manages the channels and
1362306a36Sopenharmony_ci *	sends/receives messages across them to/from other partitions.
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/device.h>
1862306a36Sopenharmony_ci#include "xpc.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * Process a connect message from a remote partition.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * Note: xpc_process_connect() is expecting to be called with the
2462306a36Sopenharmony_ci * spin_lock_irqsave held and will leave it locked upon return.
2562306a36Sopenharmony_ci */
2662306a36Sopenharmony_cistatic void
2762306a36Sopenharmony_cixpc_process_connect(struct xpc_channel *ch, unsigned long *irq_flags)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	enum xp_retval ret;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	lockdep_assert_held(&ch->lock);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_OPENREQUEST) ||
3462306a36Sopenharmony_ci	    !(ch->flags & XPC_C_ROPENREQUEST)) {
3562306a36Sopenharmony_ci		/* nothing more to do for now */
3662306a36Sopenharmony_ci		return;
3762306a36Sopenharmony_ci	}
3862306a36Sopenharmony_ci	DBUG_ON(!(ch->flags & XPC_C_CONNECTING));
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_SETUP)) {
4162306a36Sopenharmony_ci		spin_unlock_irqrestore(&ch->lock, *irq_flags);
4262306a36Sopenharmony_ci		ret = xpc_arch_ops.setup_msg_structures(ch);
4362306a36Sopenharmony_ci		spin_lock_irqsave(&ch->lock, *irq_flags);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci		if (ret != xpSuccess)
4662306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, ret, irq_flags);
4762306a36Sopenharmony_ci		else
4862306a36Sopenharmony_ci			ch->flags |= XPC_C_SETUP;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci		if (ch->flags & XPC_C_DISCONNECTING)
5162306a36Sopenharmony_ci			return;
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_OPENREPLY)) {
5562306a36Sopenharmony_ci		ch->flags |= XPC_C_OPENREPLY;
5662306a36Sopenharmony_ci		xpc_arch_ops.send_chctl_openreply(ch, irq_flags);
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_ROPENREPLY))
6062306a36Sopenharmony_ci		return;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_OPENCOMPLETE)) {
6362306a36Sopenharmony_ci		ch->flags |= (XPC_C_OPENCOMPLETE | XPC_C_CONNECTED);
6462306a36Sopenharmony_ci		xpc_arch_ops.send_chctl_opencomplete(ch, irq_flags);
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_ROPENCOMPLETE))
6862306a36Sopenharmony_ci		return;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	dev_info(xpc_chan, "channel %d to partition %d connected\n",
7162306a36Sopenharmony_ci		 ch->number, ch->partid);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	ch->flags = (XPC_C_CONNECTED | XPC_C_SETUP);	/* clear all else */
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/*
7762306a36Sopenharmony_ci * spin_lock_irqsave() is expected to be held on entry.
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cistatic void
8062306a36Sopenharmony_cixpc_process_disconnect(struct xpc_channel *ch, unsigned long *irq_flags)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct xpc_partition *part = &xpc_partitions[ch->partid];
8362306a36Sopenharmony_ci	u32 channel_was_connected = (ch->flags & XPC_C_WASCONNECTED);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	lockdep_assert_held(&ch->lock);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (!(ch->flags & XPC_C_DISCONNECTING))
8862306a36Sopenharmony_ci		return;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* make sure all activity has settled down first */
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (atomic_read(&ch->kthreads_assigned) > 0 ||
9562306a36Sopenharmony_ci	    atomic_read(&ch->references) > 0) {
9662306a36Sopenharmony_ci		return;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci	DBUG_ON((ch->flags & XPC_C_CONNECTEDCALLOUT_MADE) &&
9962306a36Sopenharmony_ci		!(ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE));
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (part->act_state == XPC_P_AS_DEACTIVATING) {
10262306a36Sopenharmony_ci		/* can't proceed until the other side disengages from us */
10362306a36Sopenharmony_ci		if (xpc_arch_ops.partition_engaged(ch->partid))
10462306a36Sopenharmony_ci			return;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	} else {
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		/* as long as the other side is up do the full protocol */
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_RCLOSEREQUEST))
11162306a36Sopenharmony_ci			return;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_CLOSEREPLY)) {
11462306a36Sopenharmony_ci			ch->flags |= XPC_C_CLOSEREPLY;
11562306a36Sopenharmony_ci			xpc_arch_ops.send_chctl_closereply(ch, irq_flags);
11662306a36Sopenharmony_ci		}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_RCLOSEREPLY))
11962306a36Sopenharmony_ci			return;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	/* wake those waiting for notify completion */
12362306a36Sopenharmony_ci	if (atomic_read(&ch->n_to_notify) > 0) {
12462306a36Sopenharmony_ci		/* we do callout while holding ch->lock, callout can't block */
12562306a36Sopenharmony_ci		xpc_arch_ops.notify_senders_of_disconnect(ch);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* both sides are disconnected now */
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE) {
13162306a36Sopenharmony_ci		spin_unlock_irqrestore(&ch->lock, *irq_flags);
13262306a36Sopenharmony_ci		xpc_disconnect_callout(ch, xpDisconnected);
13362306a36Sopenharmony_ci		spin_lock_irqsave(&ch->lock, *irq_flags);
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	DBUG_ON(atomic_read(&ch->n_to_notify) != 0);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	/* it's now safe to free the channel's message queues */
13962306a36Sopenharmony_ci	xpc_arch_ops.teardown_msg_structures(ch);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	ch->func = NULL;
14262306a36Sopenharmony_ci	ch->key = NULL;
14362306a36Sopenharmony_ci	ch->entry_size = 0;
14462306a36Sopenharmony_ci	ch->local_nentries = 0;
14562306a36Sopenharmony_ci	ch->remote_nentries = 0;
14662306a36Sopenharmony_ci	ch->kthreads_assigned_limit = 0;
14762306a36Sopenharmony_ci	ch->kthreads_idle_limit = 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/*
15062306a36Sopenharmony_ci	 * Mark the channel disconnected and clear all other flags, including
15162306a36Sopenharmony_ci	 * XPC_C_SETUP (because of call to
15262306a36Sopenharmony_ci	 * xpc_arch_ops.teardown_msg_structures()) but not including
15362306a36Sopenharmony_ci	 * XPC_C_WDISCONNECT (if it was set).
15462306a36Sopenharmony_ci	 */
15562306a36Sopenharmony_ci	ch->flags = (XPC_C_DISCONNECTED | (ch->flags & XPC_C_WDISCONNECT));
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	atomic_dec(&part->nchannels_active);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	if (channel_was_connected) {
16062306a36Sopenharmony_ci		dev_info(xpc_chan, "channel %d to partition %d disconnected, "
16162306a36Sopenharmony_ci			 "reason=%d\n", ch->number, ch->partid, ch->reason);
16262306a36Sopenharmony_ci	}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (ch->flags & XPC_C_WDISCONNECT) {
16562306a36Sopenharmony_ci		/* we won't lose the CPU since we're holding ch->lock */
16662306a36Sopenharmony_ci		complete(&ch->wdisconnect_wait);
16762306a36Sopenharmony_ci	} else if (ch->delayed_chctl_flags) {
16862306a36Sopenharmony_ci		if (part->act_state != XPC_P_AS_DEACTIVATING) {
16962306a36Sopenharmony_ci			/* time to take action on any delayed chctl flags */
17062306a36Sopenharmony_ci			spin_lock(&part->chctl_lock);
17162306a36Sopenharmony_ci			part->chctl.flags[ch->number] |=
17262306a36Sopenharmony_ci			    ch->delayed_chctl_flags;
17362306a36Sopenharmony_ci			spin_unlock(&part->chctl_lock);
17462306a36Sopenharmony_ci		}
17562306a36Sopenharmony_ci		ch->delayed_chctl_flags = 0;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci/*
18062306a36Sopenharmony_ci * Process a change in the channel's remote connection state.
18162306a36Sopenharmony_ci */
18262306a36Sopenharmony_cistatic void
18362306a36Sopenharmony_cixpc_process_openclose_chctl_flags(struct xpc_partition *part, int ch_number,
18462306a36Sopenharmony_ci				  u8 chctl_flags)
18562306a36Sopenharmony_ci{
18662306a36Sopenharmony_ci	unsigned long irq_flags;
18762306a36Sopenharmony_ci	struct xpc_openclose_args *args =
18862306a36Sopenharmony_ci	    &part->remote_openclose_args[ch_number];
18962306a36Sopenharmony_ci	struct xpc_channel *ch = &part->channels[ch_number];
19062306a36Sopenharmony_ci	enum xp_retval reason;
19162306a36Sopenharmony_ci	enum xp_retval ret;
19262306a36Sopenharmony_ci	int create_kthread = 0;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	spin_lock_irqsave(&ch->lock, irq_flags);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ciagain:
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if ((ch->flags & XPC_C_DISCONNECTED) &&
19962306a36Sopenharmony_ci	    (ch->flags & XPC_C_WDISCONNECT)) {
20062306a36Sopenharmony_ci		/*
20162306a36Sopenharmony_ci		 * Delay processing chctl flags until thread waiting disconnect
20262306a36Sopenharmony_ci		 * has had a chance to see that the channel is disconnected.
20362306a36Sopenharmony_ci		 */
20462306a36Sopenharmony_ci		ch->delayed_chctl_flags |= chctl_flags;
20562306a36Sopenharmony_ci		goto out;
20662306a36Sopenharmony_ci	}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (chctl_flags & XPC_CHCTL_CLOSEREQUEST) {
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREQUEST (reason=%d) received "
21162306a36Sopenharmony_ci			"from partid=%d, channel=%d\n", args->reason,
21262306a36Sopenharmony_ci			ch->partid, ch->number);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		/*
21562306a36Sopenharmony_ci		 * If RCLOSEREQUEST is set, we're probably waiting for
21662306a36Sopenharmony_ci		 * RCLOSEREPLY. We should find it and a ROPENREQUEST packed
21762306a36Sopenharmony_ci		 * with this RCLOSEREQUEST in the chctl_flags.
21862306a36Sopenharmony_ci		 */
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci		if (ch->flags & XPC_C_RCLOSEREQUEST) {
22162306a36Sopenharmony_ci			DBUG_ON(!(ch->flags & XPC_C_DISCONNECTING));
22262306a36Sopenharmony_ci			DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
22362306a36Sopenharmony_ci			DBUG_ON(!(ch->flags & XPC_C_CLOSEREPLY));
22462306a36Sopenharmony_ci			DBUG_ON(ch->flags & XPC_C_RCLOSEREPLY);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci			DBUG_ON(!(chctl_flags & XPC_CHCTL_CLOSEREPLY));
22762306a36Sopenharmony_ci			chctl_flags &= ~XPC_CHCTL_CLOSEREPLY;
22862306a36Sopenharmony_ci			ch->flags |= XPC_C_RCLOSEREPLY;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci			/* both sides have finished disconnecting */
23162306a36Sopenharmony_ci			xpc_process_disconnect(ch, &irq_flags);
23262306a36Sopenharmony_ci			DBUG_ON(!(ch->flags & XPC_C_DISCONNECTED));
23362306a36Sopenharmony_ci			goto again;
23462306a36Sopenharmony_ci		}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci		if (ch->flags & XPC_C_DISCONNECTED) {
23762306a36Sopenharmony_ci			if (!(chctl_flags & XPC_CHCTL_OPENREQUEST)) {
23862306a36Sopenharmony_ci				if (part->chctl.flags[ch_number] &
23962306a36Sopenharmony_ci				    XPC_CHCTL_OPENREQUEST) {
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci					DBUG_ON(ch->delayed_chctl_flags != 0);
24262306a36Sopenharmony_ci					spin_lock(&part->chctl_lock);
24362306a36Sopenharmony_ci					part->chctl.flags[ch_number] |=
24462306a36Sopenharmony_ci					    XPC_CHCTL_CLOSEREQUEST;
24562306a36Sopenharmony_ci					spin_unlock(&part->chctl_lock);
24662306a36Sopenharmony_ci				}
24762306a36Sopenharmony_ci				goto out;
24862306a36Sopenharmony_ci			}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci			XPC_SET_REASON(ch, 0, 0);
25162306a36Sopenharmony_ci			ch->flags &= ~XPC_C_DISCONNECTED;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci			atomic_inc(&part->nchannels_active);
25462306a36Sopenharmony_ci			ch->flags |= (XPC_C_CONNECTING | XPC_C_ROPENREQUEST);
25562306a36Sopenharmony_ci		}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci		chctl_flags &= ~(XPC_CHCTL_OPENREQUEST | XPC_CHCTL_OPENREPLY |
25862306a36Sopenharmony_ci		    XPC_CHCTL_OPENCOMPLETE);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		/*
26162306a36Sopenharmony_ci		 * The meaningful CLOSEREQUEST connection state fields are:
26262306a36Sopenharmony_ci		 *      reason = reason connection is to be closed
26362306a36Sopenharmony_ci		 */
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci		ch->flags |= XPC_C_RCLOSEREQUEST;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_DISCONNECTING)) {
26862306a36Sopenharmony_ci			reason = args->reason;
26962306a36Sopenharmony_ci			if (reason <= xpSuccess || reason > xpUnknownReason)
27062306a36Sopenharmony_ci				reason = xpUnknownReason;
27162306a36Sopenharmony_ci			else if (reason == xpUnregistering)
27262306a36Sopenharmony_ci				reason = xpOtherUnregistering;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci			DBUG_ON(chctl_flags & XPC_CHCTL_CLOSEREPLY);
27762306a36Sopenharmony_ci			goto out;
27862306a36Sopenharmony_ci		}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci		xpc_process_disconnect(ch, &irq_flags);
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	if (chctl_flags & XPC_CHCTL_CLOSEREPLY) {
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci		dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREPLY received from partid="
28662306a36Sopenharmony_ci			"%d, channel=%d\n", ch->partid, ch->number);
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci		if (ch->flags & XPC_C_DISCONNECTED) {
28962306a36Sopenharmony_ci			DBUG_ON(part->act_state != XPC_P_AS_DEACTIVATING);
29062306a36Sopenharmony_ci			goto out;
29162306a36Sopenharmony_ci		}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST));
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_RCLOSEREQUEST)) {
29662306a36Sopenharmony_ci			if (part->chctl.flags[ch_number] &
29762306a36Sopenharmony_ci			    XPC_CHCTL_CLOSEREQUEST) {
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci				DBUG_ON(ch->delayed_chctl_flags != 0);
30062306a36Sopenharmony_ci				spin_lock(&part->chctl_lock);
30162306a36Sopenharmony_ci				part->chctl.flags[ch_number] |=
30262306a36Sopenharmony_ci				    XPC_CHCTL_CLOSEREPLY;
30362306a36Sopenharmony_ci				spin_unlock(&part->chctl_lock);
30462306a36Sopenharmony_ci			}
30562306a36Sopenharmony_ci			goto out;
30662306a36Sopenharmony_ci		}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci		ch->flags |= XPC_C_RCLOSEREPLY;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci		if (ch->flags & XPC_C_CLOSEREPLY) {
31162306a36Sopenharmony_ci			/* both sides have finished disconnecting */
31262306a36Sopenharmony_ci			xpc_process_disconnect(ch, &irq_flags);
31362306a36Sopenharmony_ci		}
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (chctl_flags & XPC_CHCTL_OPENREQUEST) {
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci		dev_dbg(xpc_chan, "XPC_CHCTL_OPENREQUEST (entry_size=%d, "
31962306a36Sopenharmony_ci			"local_nentries=%d) received from partid=%d, "
32062306a36Sopenharmony_ci			"channel=%d\n", args->entry_size, args->local_nentries,
32162306a36Sopenharmony_ci			ch->partid, ch->number);
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci		if (part->act_state == XPC_P_AS_DEACTIVATING ||
32462306a36Sopenharmony_ci		    (ch->flags & XPC_C_ROPENREQUEST)) {
32562306a36Sopenharmony_ci			goto out;
32662306a36Sopenharmony_ci		}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_WDISCONNECT)) {
32962306a36Sopenharmony_ci			ch->delayed_chctl_flags |= XPC_CHCTL_OPENREQUEST;
33062306a36Sopenharmony_ci			goto out;
33162306a36Sopenharmony_ci		}
33262306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & (XPC_C_DISCONNECTED |
33362306a36Sopenharmony_ci				       XPC_C_OPENREQUEST)));
33462306a36Sopenharmony_ci		DBUG_ON(ch->flags & (XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY |
33562306a36Sopenharmony_ci				     XPC_C_OPENREPLY | XPC_C_CONNECTED));
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci		/*
33862306a36Sopenharmony_ci		 * The meaningful OPENREQUEST connection state fields are:
33962306a36Sopenharmony_ci		 *      entry_size = size of channel's messages in bytes
34062306a36Sopenharmony_ci		 *      local_nentries = remote partition's local_nentries
34162306a36Sopenharmony_ci		 */
34262306a36Sopenharmony_ci		if (args->entry_size == 0 || args->local_nentries == 0) {
34362306a36Sopenharmony_ci			/* assume OPENREQUEST was delayed by mistake */
34462306a36Sopenharmony_ci			goto out;
34562306a36Sopenharmony_ci		}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci		ch->flags |= (XPC_C_ROPENREQUEST | XPC_C_CONNECTING);
34862306a36Sopenharmony_ci		ch->remote_nentries = args->local_nentries;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci		if (ch->flags & XPC_C_OPENREQUEST) {
35162306a36Sopenharmony_ci			if (args->entry_size != ch->entry_size) {
35262306a36Sopenharmony_ci				XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes,
35362306a36Sopenharmony_ci						       &irq_flags);
35462306a36Sopenharmony_ci				goto out;
35562306a36Sopenharmony_ci			}
35662306a36Sopenharmony_ci		} else {
35762306a36Sopenharmony_ci			ch->entry_size = args->entry_size;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci			XPC_SET_REASON(ch, 0, 0);
36062306a36Sopenharmony_ci			ch->flags &= ~XPC_C_DISCONNECTED;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci			atomic_inc(&part->nchannels_active);
36362306a36Sopenharmony_ci		}
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci		xpc_process_connect(ch, &irq_flags);
36662306a36Sopenharmony_ci	}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	if (chctl_flags & XPC_CHCTL_OPENREPLY) {
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci		dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY (local_msgqueue_pa="
37162306a36Sopenharmony_ci			"0x%lx, local_nentries=%d, remote_nentries=%d) "
37262306a36Sopenharmony_ci			"received from partid=%d, channel=%d\n",
37362306a36Sopenharmony_ci			args->local_msgqueue_pa, args->local_nentries,
37462306a36Sopenharmony_ci			args->remote_nentries, ch->partid, ch->number);
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci		if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED))
37762306a36Sopenharmony_ci			goto out;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_OPENREQUEST)) {
38062306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, xpOpenCloseError,
38162306a36Sopenharmony_ci					       &irq_flags);
38262306a36Sopenharmony_ci			goto out;
38362306a36Sopenharmony_ci		}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST));
38662306a36Sopenharmony_ci		DBUG_ON(ch->flags & XPC_C_CONNECTED);
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci		/*
38962306a36Sopenharmony_ci		 * The meaningful OPENREPLY connection state fields are:
39062306a36Sopenharmony_ci		 *      local_msgqueue_pa = physical address of remote
39162306a36Sopenharmony_ci		 *                          partition's local_msgqueue
39262306a36Sopenharmony_ci		 *      local_nentries = remote partition's local_nentries
39362306a36Sopenharmony_ci		 *      remote_nentries = remote partition's remote_nentries
39462306a36Sopenharmony_ci		 */
39562306a36Sopenharmony_ci		DBUG_ON(args->local_msgqueue_pa == 0);
39662306a36Sopenharmony_ci		DBUG_ON(args->local_nentries == 0);
39762306a36Sopenharmony_ci		DBUG_ON(args->remote_nentries == 0);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci		ret = xpc_arch_ops.save_remote_msgqueue_pa(ch,
40062306a36Sopenharmony_ci						      args->local_msgqueue_pa);
40162306a36Sopenharmony_ci		if (ret != xpSuccess) {
40262306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, ret, &irq_flags);
40362306a36Sopenharmony_ci			goto out;
40462306a36Sopenharmony_ci		}
40562306a36Sopenharmony_ci		ch->flags |= XPC_C_ROPENREPLY;
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci		if (args->local_nentries < ch->remote_nentries) {
40862306a36Sopenharmony_ci			dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new "
40962306a36Sopenharmony_ci				"remote_nentries=%d, old remote_nentries=%d, "
41062306a36Sopenharmony_ci				"partid=%d, channel=%d\n",
41162306a36Sopenharmony_ci				args->local_nentries, ch->remote_nentries,
41262306a36Sopenharmony_ci				ch->partid, ch->number);
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci			ch->remote_nentries = args->local_nentries;
41562306a36Sopenharmony_ci		}
41662306a36Sopenharmony_ci		if (args->remote_nentries < ch->local_nentries) {
41762306a36Sopenharmony_ci			dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new "
41862306a36Sopenharmony_ci				"local_nentries=%d, old local_nentries=%d, "
41962306a36Sopenharmony_ci				"partid=%d, channel=%d\n",
42062306a36Sopenharmony_ci				args->remote_nentries, ch->local_nentries,
42162306a36Sopenharmony_ci				ch->partid, ch->number);
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci			ch->local_nentries = args->remote_nentries;
42462306a36Sopenharmony_ci		}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci		xpc_process_connect(ch, &irq_flags);
42762306a36Sopenharmony_ci	}
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	if (chctl_flags & XPC_CHCTL_OPENCOMPLETE) {
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci		dev_dbg(xpc_chan, "XPC_CHCTL_OPENCOMPLETE received from "
43262306a36Sopenharmony_ci			"partid=%d, channel=%d\n", ch->partid, ch->number);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci		if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED))
43562306a36Sopenharmony_ci			goto out;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci		if (!(ch->flags & XPC_C_OPENREQUEST) ||
43862306a36Sopenharmony_ci		    !(ch->flags & XPC_C_OPENREPLY)) {
43962306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, xpOpenCloseError,
44062306a36Sopenharmony_ci					       &irq_flags);
44162306a36Sopenharmony_ci			goto out;
44262306a36Sopenharmony_ci		}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST));
44562306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & XPC_C_ROPENREPLY));
44662306a36Sopenharmony_ci		DBUG_ON(!(ch->flags & XPC_C_CONNECTED));
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci		ch->flags |= XPC_C_ROPENCOMPLETE;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci		xpc_process_connect(ch, &irq_flags);
45162306a36Sopenharmony_ci		create_kthread = 1;
45262306a36Sopenharmony_ci	}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ciout:
45562306a36Sopenharmony_ci	spin_unlock_irqrestore(&ch->lock, irq_flags);
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	if (create_kthread)
45862306a36Sopenharmony_ci		xpc_create_kthreads(ch, 1, 0);
45962306a36Sopenharmony_ci}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci/*
46262306a36Sopenharmony_ci * Attempt to establish a channel connection to a remote partition.
46362306a36Sopenharmony_ci */
46462306a36Sopenharmony_cistatic enum xp_retval
46562306a36Sopenharmony_cixpc_connect_channel(struct xpc_channel *ch)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	unsigned long irq_flags;
46862306a36Sopenharmony_ci	struct xpc_registration *registration = &xpc_registrations[ch->number];
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (mutex_trylock(&registration->mutex) == 0)
47162306a36Sopenharmony_ci		return xpRetry;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	if (!XPC_CHANNEL_REGISTERED(ch->number)) {
47462306a36Sopenharmony_ci		mutex_unlock(&registration->mutex);
47562306a36Sopenharmony_ci		return xpUnregistered;
47662306a36Sopenharmony_ci	}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	spin_lock_irqsave(&ch->lock, irq_flags);
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ci	DBUG_ON(ch->flags & XPC_C_CONNECTED);
48162306a36Sopenharmony_ci	DBUG_ON(ch->flags & XPC_C_OPENREQUEST);
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	if (ch->flags & XPC_C_DISCONNECTING) {
48462306a36Sopenharmony_ci		spin_unlock_irqrestore(&ch->lock, irq_flags);
48562306a36Sopenharmony_ci		mutex_unlock(&registration->mutex);
48662306a36Sopenharmony_ci		return ch->reason;
48762306a36Sopenharmony_ci	}
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	/* add info from the channel connect registration to the channel */
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	ch->kthreads_assigned_limit = registration->assigned_limit;
49262306a36Sopenharmony_ci	ch->kthreads_idle_limit = registration->idle_limit;
49362306a36Sopenharmony_ci	DBUG_ON(atomic_read(&ch->kthreads_assigned) != 0);
49462306a36Sopenharmony_ci	DBUG_ON(atomic_read(&ch->kthreads_idle) != 0);
49562306a36Sopenharmony_ci	DBUG_ON(atomic_read(&ch->kthreads_active) != 0);
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	ch->func = registration->func;
49862306a36Sopenharmony_ci	DBUG_ON(registration->func == NULL);
49962306a36Sopenharmony_ci	ch->key = registration->key;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	ch->local_nentries = registration->nentries;
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	if (ch->flags & XPC_C_ROPENREQUEST) {
50462306a36Sopenharmony_ci		if (registration->entry_size != ch->entry_size) {
50562306a36Sopenharmony_ci			/* the local and remote sides aren't the same */
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_ci			/*
50862306a36Sopenharmony_ci			 * Because XPC_DISCONNECT_CHANNEL() can block we're
50962306a36Sopenharmony_ci			 * forced to up the registration sema before we unlock
51062306a36Sopenharmony_ci			 * the channel lock. But that's okay here because we're
51162306a36Sopenharmony_ci			 * done with the part that required the registration
51262306a36Sopenharmony_ci			 * sema. XPC_DISCONNECT_CHANNEL() requires that the
51362306a36Sopenharmony_ci			 * channel lock be locked and will unlock and relock
51462306a36Sopenharmony_ci			 * the channel lock as needed.
51562306a36Sopenharmony_ci			 */
51662306a36Sopenharmony_ci			mutex_unlock(&registration->mutex);
51762306a36Sopenharmony_ci			XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes,
51862306a36Sopenharmony_ci					       &irq_flags);
51962306a36Sopenharmony_ci			spin_unlock_irqrestore(&ch->lock, irq_flags);
52062306a36Sopenharmony_ci			return xpUnequalMsgSizes;
52162306a36Sopenharmony_ci		}
52262306a36Sopenharmony_ci	} else {
52362306a36Sopenharmony_ci		ch->entry_size = registration->entry_size;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci		XPC_SET_REASON(ch, 0, 0);
52662306a36Sopenharmony_ci		ch->flags &= ~XPC_C_DISCONNECTED;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci		atomic_inc(&xpc_partitions[ch->partid].nchannels_active);
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	mutex_unlock(&registration->mutex);
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	/* initiate the connection */
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	ch->flags |= (XPC_C_OPENREQUEST | XPC_C_CONNECTING);
53662306a36Sopenharmony_ci	xpc_arch_ops.send_chctl_openrequest(ch, &irq_flags);
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	xpc_process_connect(ch, &irq_flags);
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	spin_unlock_irqrestore(&ch->lock, irq_flags);
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	return xpSuccess;
54362306a36Sopenharmony_ci}
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_civoid
54662306a36Sopenharmony_cixpc_process_sent_chctl_flags(struct xpc_partition *part)
54762306a36Sopenharmony_ci{
54862306a36Sopenharmony_ci	unsigned long irq_flags;
54962306a36Sopenharmony_ci	union xpc_channel_ctl_flags chctl;
55062306a36Sopenharmony_ci	struct xpc_channel *ch;
55162306a36Sopenharmony_ci	int ch_number;
55262306a36Sopenharmony_ci	u32 ch_flags;
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	chctl.all_flags = xpc_arch_ops.get_chctl_all_flags(part);
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	/*
55762306a36Sopenharmony_ci	 * Initiate channel connections for registered channels.
55862306a36Sopenharmony_ci	 *
55962306a36Sopenharmony_ci	 * For each connected channel that has pending messages activate idle
56062306a36Sopenharmony_ci	 * kthreads and/or create new kthreads as needed.
56162306a36Sopenharmony_ci	 */
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	for (ch_number = 0; ch_number < part->nchannels; ch_number++) {
56462306a36Sopenharmony_ci		ch = &part->channels[ch_number];
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci		/*
56762306a36Sopenharmony_ci		 * Process any open or close related chctl flags, and then deal
56862306a36Sopenharmony_ci		 * with connecting or disconnecting the channel as required.
56962306a36Sopenharmony_ci		 */
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci		if (chctl.flags[ch_number] & XPC_OPENCLOSE_CHCTL_FLAGS) {
57262306a36Sopenharmony_ci			xpc_process_openclose_chctl_flags(part, ch_number,
57362306a36Sopenharmony_ci							chctl.flags[ch_number]);
57462306a36Sopenharmony_ci		}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci		ch_flags = ch->flags;	/* need an atomic snapshot of flags */
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci		if (ch_flags & XPC_C_DISCONNECTING) {
57962306a36Sopenharmony_ci			spin_lock_irqsave(&ch->lock, irq_flags);
58062306a36Sopenharmony_ci			xpc_process_disconnect(ch, &irq_flags);
58162306a36Sopenharmony_ci			spin_unlock_irqrestore(&ch->lock, irq_flags);
58262306a36Sopenharmony_ci			continue;
58362306a36Sopenharmony_ci		}
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci		if (part->act_state == XPC_P_AS_DEACTIVATING)
58662306a36Sopenharmony_ci			continue;
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_ci		if (!(ch_flags & XPC_C_CONNECTED)) {
58962306a36Sopenharmony_ci			if (!(ch_flags & XPC_C_OPENREQUEST)) {
59062306a36Sopenharmony_ci				DBUG_ON(ch_flags & XPC_C_SETUP);
59162306a36Sopenharmony_ci				(void)xpc_connect_channel(ch);
59262306a36Sopenharmony_ci			}
59362306a36Sopenharmony_ci			continue;
59462306a36Sopenharmony_ci		}
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci		/*
59762306a36Sopenharmony_ci		 * Process any message related chctl flags, this may involve
59862306a36Sopenharmony_ci		 * the activation of kthreads to deliver any pending messages
59962306a36Sopenharmony_ci		 * sent from the other partition.
60062306a36Sopenharmony_ci		 */
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci		if (chctl.flags[ch_number] & XPC_MSG_CHCTL_FLAGS)
60362306a36Sopenharmony_ci			xpc_arch_ops.process_msg_chctl_flags(part, ch_number);
60462306a36Sopenharmony_ci	}
60562306a36Sopenharmony_ci}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci/*
60862306a36Sopenharmony_ci * XPC's heartbeat code calls this function to inform XPC that a partition is
60962306a36Sopenharmony_ci * going down.  XPC responds by tearing down the XPartition Communication
61062306a36Sopenharmony_ci * infrastructure used for the just downed partition.
61162306a36Sopenharmony_ci *
61262306a36Sopenharmony_ci * XPC's heartbeat code will never call this function and xpc_partition_up()
61362306a36Sopenharmony_ci * at the same time. Nor will it ever make multiple calls to either function
61462306a36Sopenharmony_ci * at the same time.
61562306a36Sopenharmony_ci */
61662306a36Sopenharmony_civoid
61762306a36Sopenharmony_cixpc_partition_going_down(struct xpc_partition *part, enum xp_retval reason)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	unsigned long irq_flags;
62062306a36Sopenharmony_ci	int ch_number;
62162306a36Sopenharmony_ci	struct xpc_channel *ch;
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	dev_dbg(xpc_chan, "deactivating partition %d, reason=%d\n",
62462306a36Sopenharmony_ci		XPC_PARTID(part), reason);
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci	if (!xpc_part_ref(part)) {
62762306a36Sopenharmony_ci		/* infrastructure for this partition isn't currently set up */
62862306a36Sopenharmony_ci		return;
62962306a36Sopenharmony_ci	}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	/* disconnect channels associated with the partition going down */
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ci	for (ch_number = 0; ch_number < part->nchannels; ch_number++) {
63462306a36Sopenharmony_ci		ch = &part->channels[ch_number];
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci		xpc_msgqueue_ref(ch);
63762306a36Sopenharmony_ci		spin_lock_irqsave(&ch->lock, irq_flags);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci		XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags);
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci		spin_unlock_irqrestore(&ch->lock, irq_flags);
64262306a36Sopenharmony_ci		xpc_msgqueue_deref(ch);
64362306a36Sopenharmony_ci	}
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	xpc_wakeup_channel_mgr(part);
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	xpc_part_deref(part);
64862306a36Sopenharmony_ci}
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci/*
65162306a36Sopenharmony_ci * Called by XP at the time of channel connection registration to cause
65262306a36Sopenharmony_ci * XPC to establish connections to all currently active partitions.
65362306a36Sopenharmony_ci */
65462306a36Sopenharmony_civoid
65562306a36Sopenharmony_cixpc_initiate_connect(int ch_number)
65662306a36Sopenharmony_ci{
65762306a36Sopenharmony_ci	short partid;
65862306a36Sopenharmony_ci	struct xpc_partition *part;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	DBUG_ON(ch_number < 0 || ch_number >= XPC_MAX_NCHANNELS);
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	for (partid = 0; partid < xp_max_npartitions; partid++) {
66362306a36Sopenharmony_ci		part = &xpc_partitions[partid];
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci		if (xpc_part_ref(part)) {
66662306a36Sopenharmony_ci			/*
66762306a36Sopenharmony_ci			 * Initiate the establishment of a connection on the
66862306a36Sopenharmony_ci			 * newly registered channel to the remote partition.
66962306a36Sopenharmony_ci			 */
67062306a36Sopenharmony_ci			xpc_wakeup_channel_mgr(part);
67162306a36Sopenharmony_ci			xpc_part_deref(part);
67262306a36Sopenharmony_ci		}
67362306a36Sopenharmony_ci	}
67462306a36Sopenharmony_ci}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_civoid
67762306a36Sopenharmony_cixpc_connected_callout(struct xpc_channel *ch)
67862306a36Sopenharmony_ci{
67962306a36Sopenharmony_ci	/* let the registerer know that a connection has been established */
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	if (ch->func != NULL) {
68262306a36Sopenharmony_ci		dev_dbg(xpc_chan, "ch->func() called, reason=xpConnected, "
68362306a36Sopenharmony_ci			"partid=%d, channel=%d\n", ch->partid, ch->number);
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci		ch->func(xpConnected, ch->partid, ch->number,
68662306a36Sopenharmony_ci			 (void *)(u64)ch->local_nentries, ch->key);
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci		dev_dbg(xpc_chan, "ch->func() returned, reason=xpConnected, "
68962306a36Sopenharmony_ci			"partid=%d, channel=%d\n", ch->partid, ch->number);
69062306a36Sopenharmony_ci	}
69162306a36Sopenharmony_ci}
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci/*
69462306a36Sopenharmony_ci * Called by XP at the time of channel connection unregistration to cause
69562306a36Sopenharmony_ci * XPC to teardown all current connections for the specified channel.
69662306a36Sopenharmony_ci *
69762306a36Sopenharmony_ci * Before returning xpc_initiate_disconnect() will wait until all connections
69862306a36Sopenharmony_ci * on the specified channel have been closed/torndown. So the caller can be
69962306a36Sopenharmony_ci * assured that they will not be receiving any more callouts from XPC to the
70062306a36Sopenharmony_ci * function they registered via xpc_connect().
70162306a36Sopenharmony_ci *
70262306a36Sopenharmony_ci * Arguments:
70362306a36Sopenharmony_ci *
70462306a36Sopenharmony_ci *	ch_number - channel # to unregister.
70562306a36Sopenharmony_ci */
70662306a36Sopenharmony_civoid
70762306a36Sopenharmony_cixpc_initiate_disconnect(int ch_number)
70862306a36Sopenharmony_ci{
70962306a36Sopenharmony_ci	unsigned long irq_flags;
71062306a36Sopenharmony_ci	short partid;
71162306a36Sopenharmony_ci	struct xpc_partition *part;
71262306a36Sopenharmony_ci	struct xpc_channel *ch;
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	DBUG_ON(ch_number < 0 || ch_number >= XPC_MAX_NCHANNELS);
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_ci	/* initiate the channel disconnect for every active partition */
71762306a36Sopenharmony_ci	for (partid = 0; partid < xp_max_npartitions; partid++) {
71862306a36Sopenharmony_ci		part = &xpc_partitions[partid];
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci		if (xpc_part_ref(part)) {
72162306a36Sopenharmony_ci			ch = &part->channels[ch_number];
72262306a36Sopenharmony_ci			xpc_msgqueue_ref(ch);
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci			spin_lock_irqsave(&ch->lock, irq_flags);
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci			if (!(ch->flags & XPC_C_DISCONNECTED)) {
72762306a36Sopenharmony_ci				ch->flags |= XPC_C_WDISCONNECT;
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci				XPC_DISCONNECT_CHANNEL(ch, xpUnregistering,
73062306a36Sopenharmony_ci						       &irq_flags);
73162306a36Sopenharmony_ci			}
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci			spin_unlock_irqrestore(&ch->lock, irq_flags);
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ci			xpc_msgqueue_deref(ch);
73662306a36Sopenharmony_ci			xpc_part_deref(part);
73762306a36Sopenharmony_ci		}
73862306a36Sopenharmony_ci	}
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	xpc_disconnect_wait(ch_number);
74162306a36Sopenharmony_ci}
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci/*
74462306a36Sopenharmony_ci * To disconnect a channel, and reflect it back to all who may be waiting.
74562306a36Sopenharmony_ci *
74662306a36Sopenharmony_ci * An OPEN is not allowed until XPC_C_DISCONNECTING is cleared by
74762306a36Sopenharmony_ci * xpc_process_disconnect(), and if set, XPC_C_WDISCONNECT is cleared by
74862306a36Sopenharmony_ci * xpc_disconnect_wait().
74962306a36Sopenharmony_ci *
75062306a36Sopenharmony_ci * THE CHANNEL IS TO BE LOCKED BY THE CALLER AND WILL REMAIN LOCKED UPON RETURN.
75162306a36Sopenharmony_ci */
75262306a36Sopenharmony_civoid
75362306a36Sopenharmony_cixpc_disconnect_channel(const int line, struct xpc_channel *ch,
75462306a36Sopenharmony_ci		       enum xp_retval reason, unsigned long *irq_flags)
75562306a36Sopenharmony_ci{
75662306a36Sopenharmony_ci	u32 channel_was_connected = (ch->flags & XPC_C_CONNECTED);
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci	lockdep_assert_held(&ch->lock);
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED))
76162306a36Sopenharmony_ci		return;
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	DBUG_ON(!(ch->flags & (XPC_C_CONNECTING | XPC_C_CONNECTED)));
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	dev_dbg(xpc_chan, "reason=%d, line=%d, partid=%d, channel=%d\n",
76662306a36Sopenharmony_ci		reason, line, ch->partid, ch->number);
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	XPC_SET_REASON(ch, reason, line);
76962306a36Sopenharmony_ci
77062306a36Sopenharmony_ci	ch->flags |= (XPC_C_CLOSEREQUEST | XPC_C_DISCONNECTING);
77162306a36Sopenharmony_ci	/* some of these may not have been set */
77262306a36Sopenharmony_ci	ch->flags &= ~(XPC_C_OPENREQUEST | XPC_C_OPENREPLY |
77362306a36Sopenharmony_ci		       XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY |
77462306a36Sopenharmony_ci		       XPC_C_CONNECTING | XPC_C_CONNECTED);
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_ci	xpc_arch_ops.send_chctl_closerequest(ch, irq_flags);
77762306a36Sopenharmony_ci
77862306a36Sopenharmony_ci	if (channel_was_connected)
77962306a36Sopenharmony_ci		ch->flags |= XPC_C_WASCONNECTED;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	spin_unlock_irqrestore(&ch->lock, *irq_flags);
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	/* wake all idle kthreads so they can exit */
78462306a36Sopenharmony_ci	if (atomic_read(&ch->kthreads_idle) > 0) {
78562306a36Sopenharmony_ci		wake_up_all(&ch->idle_wq);
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	} else if ((ch->flags & XPC_C_CONNECTEDCALLOUT_MADE) &&
78862306a36Sopenharmony_ci		   !(ch->flags & XPC_C_DISCONNECTINGCALLOUT)) {
78962306a36Sopenharmony_ci		/* start a kthread that will do the xpDisconnecting callout */
79062306a36Sopenharmony_ci		xpc_create_kthreads(ch, 1, 1);
79162306a36Sopenharmony_ci	}
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	/* wake those waiting to allocate an entry from the local msg queue */
79462306a36Sopenharmony_ci	if (atomic_read(&ch->n_on_msg_allocate_wq) > 0)
79562306a36Sopenharmony_ci		wake_up(&ch->msg_allocate_wq);
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	spin_lock_irqsave(&ch->lock, *irq_flags);
79862306a36Sopenharmony_ci}
79962306a36Sopenharmony_ci
80062306a36Sopenharmony_civoid
80162306a36Sopenharmony_cixpc_disconnect_callout(struct xpc_channel *ch, enum xp_retval reason)
80262306a36Sopenharmony_ci{
80362306a36Sopenharmony_ci	/*
80462306a36Sopenharmony_ci	 * Let the channel's registerer know that the channel is being
80562306a36Sopenharmony_ci	 * disconnected. We don't want to do this if the registerer was never
80662306a36Sopenharmony_ci	 * informed of a connection being made.
80762306a36Sopenharmony_ci	 */
80862306a36Sopenharmony_ci
80962306a36Sopenharmony_ci	if (ch->func != NULL) {
81062306a36Sopenharmony_ci		dev_dbg(xpc_chan, "ch->func() called, reason=%d, partid=%d, "
81162306a36Sopenharmony_ci			"channel=%d\n", reason, ch->partid, ch->number);
81262306a36Sopenharmony_ci
81362306a36Sopenharmony_ci		ch->func(reason, ch->partid, ch->number, NULL, ch->key);
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci		dev_dbg(xpc_chan, "ch->func() returned, reason=%d, partid=%d, "
81662306a36Sopenharmony_ci			"channel=%d\n", reason, ch->partid, ch->number);
81762306a36Sopenharmony_ci	}
81862306a36Sopenharmony_ci}
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci/*
82162306a36Sopenharmony_ci * Wait for a message entry to become available for the specified channel,
82262306a36Sopenharmony_ci * but don't wait any longer than 1 jiffy.
82362306a36Sopenharmony_ci */
82462306a36Sopenharmony_cienum xp_retval
82562306a36Sopenharmony_cixpc_allocate_msg_wait(struct xpc_channel *ch)
82662306a36Sopenharmony_ci{
82762306a36Sopenharmony_ci	enum xp_retval ret;
82862306a36Sopenharmony_ci	DEFINE_WAIT(wait);
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	if (ch->flags & XPC_C_DISCONNECTING) {
83162306a36Sopenharmony_ci		DBUG_ON(ch->reason == xpInterrupted);
83262306a36Sopenharmony_ci		return ch->reason;
83362306a36Sopenharmony_ci	}
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	atomic_inc(&ch->n_on_msg_allocate_wq);
83662306a36Sopenharmony_ci	prepare_to_wait(&ch->msg_allocate_wq, &wait, TASK_INTERRUPTIBLE);
83762306a36Sopenharmony_ci	ret = schedule_timeout(1);
83862306a36Sopenharmony_ci	finish_wait(&ch->msg_allocate_wq, &wait);
83962306a36Sopenharmony_ci	atomic_dec(&ch->n_on_msg_allocate_wq);
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci	if (ch->flags & XPC_C_DISCONNECTING) {
84262306a36Sopenharmony_ci		ret = ch->reason;
84362306a36Sopenharmony_ci		DBUG_ON(ch->reason == xpInterrupted);
84462306a36Sopenharmony_ci	} else if (ret == 0) {
84562306a36Sopenharmony_ci		ret = xpTimeout;
84662306a36Sopenharmony_ci	} else {
84762306a36Sopenharmony_ci		ret = xpInterrupted;
84862306a36Sopenharmony_ci	}
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci	return ret;
85162306a36Sopenharmony_ci}
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci/*
85462306a36Sopenharmony_ci * Send a message that contains the user's payload on the specified channel
85562306a36Sopenharmony_ci * connected to the specified partition.
85662306a36Sopenharmony_ci *
85762306a36Sopenharmony_ci * NOTE that this routine can sleep waiting for a message entry to become
85862306a36Sopenharmony_ci * available. To not sleep, pass in the XPC_NOWAIT flag.
85962306a36Sopenharmony_ci *
86062306a36Sopenharmony_ci * Once sent, this routine will not wait for the message to be received, nor
86162306a36Sopenharmony_ci * will notification be given when it does happen.
86262306a36Sopenharmony_ci *
86362306a36Sopenharmony_ci * Arguments:
86462306a36Sopenharmony_ci *
86562306a36Sopenharmony_ci *	partid - ID of partition to which the channel is connected.
86662306a36Sopenharmony_ci *	ch_number - channel # to send message on.
86762306a36Sopenharmony_ci *	flags - see xp.h for valid flags.
86862306a36Sopenharmony_ci *	payload - pointer to the payload which is to be sent.
86962306a36Sopenharmony_ci *	payload_size - size of the payload in bytes.
87062306a36Sopenharmony_ci */
87162306a36Sopenharmony_cienum xp_retval
87262306a36Sopenharmony_cixpc_initiate_send(short partid, int ch_number, u32 flags, void *payload,
87362306a36Sopenharmony_ci		  u16 payload_size)
87462306a36Sopenharmony_ci{
87562306a36Sopenharmony_ci	struct xpc_partition *part = &xpc_partitions[partid];
87662306a36Sopenharmony_ci	enum xp_retval ret = xpUnknownReason;
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_ci	dev_dbg(xpc_chan, "payload=0x%p, partid=%d, channel=%d\n", payload,
87962306a36Sopenharmony_ci		partid, ch_number);
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci	DBUG_ON(partid < 0 || partid >= xp_max_npartitions);
88262306a36Sopenharmony_ci	DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
88362306a36Sopenharmony_ci	DBUG_ON(payload == NULL);
88462306a36Sopenharmony_ci
88562306a36Sopenharmony_ci	if (xpc_part_ref(part)) {
88662306a36Sopenharmony_ci		ret = xpc_arch_ops.send_payload(&part->channels[ch_number],
88762306a36Sopenharmony_ci				  flags, payload, payload_size, 0, NULL, NULL);
88862306a36Sopenharmony_ci		xpc_part_deref(part);
88962306a36Sopenharmony_ci	}
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_ci	return ret;
89262306a36Sopenharmony_ci}
89362306a36Sopenharmony_ci
89462306a36Sopenharmony_ci/*
89562306a36Sopenharmony_ci * Send a message that contains the user's payload on the specified channel
89662306a36Sopenharmony_ci * connected to the specified partition.
89762306a36Sopenharmony_ci *
89862306a36Sopenharmony_ci * NOTE that this routine can sleep waiting for a message entry to become
89962306a36Sopenharmony_ci * available. To not sleep, pass in the XPC_NOWAIT flag.
90062306a36Sopenharmony_ci *
90162306a36Sopenharmony_ci * This routine will not wait for the message to be sent or received.
90262306a36Sopenharmony_ci *
90362306a36Sopenharmony_ci * Once the remote end of the channel has received the message, the function
90462306a36Sopenharmony_ci * passed as an argument to xpc_initiate_send_notify() will be called. This
90562306a36Sopenharmony_ci * allows the sender to free up or re-use any buffers referenced by the
90662306a36Sopenharmony_ci * message, but does NOT mean the message has been processed at the remote
90762306a36Sopenharmony_ci * end by a receiver.
90862306a36Sopenharmony_ci *
90962306a36Sopenharmony_ci * If this routine returns an error, the caller's function will NOT be called.
91062306a36Sopenharmony_ci *
91162306a36Sopenharmony_ci * Arguments:
91262306a36Sopenharmony_ci *
91362306a36Sopenharmony_ci *	partid - ID of partition to which the channel is connected.
91462306a36Sopenharmony_ci *	ch_number - channel # to send message on.
91562306a36Sopenharmony_ci *	flags - see xp.h for valid flags.
91662306a36Sopenharmony_ci *	payload - pointer to the payload which is to be sent.
91762306a36Sopenharmony_ci *	payload_size - size of the payload in bytes.
91862306a36Sopenharmony_ci *	func - function to call with asynchronous notification of message
91962306a36Sopenharmony_ci *		  receipt. THIS FUNCTION MUST BE NON-BLOCKING.
92062306a36Sopenharmony_ci *	key - user-defined key to be passed to the function when it's called.
92162306a36Sopenharmony_ci */
92262306a36Sopenharmony_cienum xp_retval
92362306a36Sopenharmony_cixpc_initiate_send_notify(short partid, int ch_number, u32 flags, void *payload,
92462306a36Sopenharmony_ci			 u16 payload_size, xpc_notify_func func, void *key)
92562306a36Sopenharmony_ci{
92662306a36Sopenharmony_ci	struct xpc_partition *part = &xpc_partitions[partid];
92762306a36Sopenharmony_ci	enum xp_retval ret = xpUnknownReason;
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	dev_dbg(xpc_chan, "payload=0x%p, partid=%d, channel=%d\n", payload,
93062306a36Sopenharmony_ci		partid, ch_number);
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	DBUG_ON(partid < 0 || partid >= xp_max_npartitions);
93362306a36Sopenharmony_ci	DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
93462306a36Sopenharmony_ci	DBUG_ON(payload == NULL);
93562306a36Sopenharmony_ci	DBUG_ON(func == NULL);
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci	if (xpc_part_ref(part)) {
93862306a36Sopenharmony_ci		ret = xpc_arch_ops.send_payload(&part->channels[ch_number],
93962306a36Sopenharmony_ci			  flags, payload, payload_size, XPC_N_CALL, func, key);
94062306a36Sopenharmony_ci		xpc_part_deref(part);
94162306a36Sopenharmony_ci	}
94262306a36Sopenharmony_ci	return ret;
94362306a36Sopenharmony_ci}
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci/*
94662306a36Sopenharmony_ci * Deliver a message's payload to its intended recipient.
94762306a36Sopenharmony_ci */
94862306a36Sopenharmony_civoid
94962306a36Sopenharmony_cixpc_deliver_payload(struct xpc_channel *ch)
95062306a36Sopenharmony_ci{
95162306a36Sopenharmony_ci	void *payload;
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	payload = xpc_arch_ops.get_deliverable_payload(ch);
95462306a36Sopenharmony_ci	if (payload != NULL) {
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci		/*
95762306a36Sopenharmony_ci		 * This ref is taken to protect the payload itself from being
95862306a36Sopenharmony_ci		 * freed before the user is finished with it, which the user
95962306a36Sopenharmony_ci		 * indicates by calling xpc_initiate_received().
96062306a36Sopenharmony_ci		 */
96162306a36Sopenharmony_ci		xpc_msgqueue_ref(ch);
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci		atomic_inc(&ch->kthreads_active);
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci		if (ch->func != NULL) {
96662306a36Sopenharmony_ci			dev_dbg(xpc_chan, "ch->func() called, payload=0x%p "
96762306a36Sopenharmony_ci				"partid=%d channel=%d\n", payload, ch->partid,
96862306a36Sopenharmony_ci				ch->number);
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci			/* deliver the message to its intended recipient */
97162306a36Sopenharmony_ci			ch->func(xpMsgReceived, ch->partid, ch->number, payload,
97262306a36Sopenharmony_ci				 ch->key);
97362306a36Sopenharmony_ci
97462306a36Sopenharmony_ci			dev_dbg(xpc_chan, "ch->func() returned, payload=0x%p "
97562306a36Sopenharmony_ci				"partid=%d channel=%d\n", payload, ch->partid,
97662306a36Sopenharmony_ci				ch->number);
97762306a36Sopenharmony_ci		}
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci		atomic_dec(&ch->kthreads_active);
98062306a36Sopenharmony_ci	}
98162306a36Sopenharmony_ci}
98262306a36Sopenharmony_ci
98362306a36Sopenharmony_ci/*
98462306a36Sopenharmony_ci * Acknowledge receipt of a delivered message's payload.
98562306a36Sopenharmony_ci *
98662306a36Sopenharmony_ci * This function, although called by users, does not call xpc_part_ref() to
98762306a36Sopenharmony_ci * ensure that the partition infrastructure is in place. It relies on the
98862306a36Sopenharmony_ci * fact that we called xpc_msgqueue_ref() in xpc_deliver_payload().
98962306a36Sopenharmony_ci *
99062306a36Sopenharmony_ci * Arguments:
99162306a36Sopenharmony_ci *
99262306a36Sopenharmony_ci *	partid - ID of partition to which the channel is connected.
99362306a36Sopenharmony_ci *	ch_number - channel # message received on.
99462306a36Sopenharmony_ci *	payload - pointer to the payload area allocated via
99562306a36Sopenharmony_ci *			xpc_initiate_send() or xpc_initiate_send_notify().
99662306a36Sopenharmony_ci */
99762306a36Sopenharmony_civoid
99862306a36Sopenharmony_cixpc_initiate_received(short partid, int ch_number, void *payload)
99962306a36Sopenharmony_ci{
100062306a36Sopenharmony_ci	struct xpc_partition *part = &xpc_partitions[partid];
100162306a36Sopenharmony_ci	struct xpc_channel *ch;
100262306a36Sopenharmony_ci
100362306a36Sopenharmony_ci	DBUG_ON(partid < 0 || partid >= xp_max_npartitions);
100462306a36Sopenharmony_ci	DBUG_ON(ch_number < 0 || ch_number >= part->nchannels);
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci	ch = &part->channels[ch_number];
100762306a36Sopenharmony_ci	xpc_arch_ops.received_payload(ch, payload);
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	/* the call to xpc_msgqueue_ref() was done by xpc_deliver_payload()  */
101062306a36Sopenharmony_ci	xpc_msgqueue_deref(ch);
101162306a36Sopenharmony_ci}
1012