/* * XPC's heartbeat code calls this function to inform XPC that a partition is * going down. XPC responds by tearing down the XPartition Communication * infrastructure used for the just downed partition. * * XPC's heartbeat code will never call this function and xpc_partition_up() * at the same time. Nor will it ever make multiple calls to either function * at the same time. */ void xpc_partition_going_down(struct xpc_partition *part, enum xp_retval reason) { unsigned long irq_flags; int ch_number; struct xpc_channel *ch; dev_dbg(xpc_chan, "deactivating partition %d, reason=%d\n", XPC_PARTID(part), reason); if (!xpc_part_ref(part)) { /* infrastructure for this partition isn't currently set up */ return; } /* disconnect channels associated with the partition going down */ for (ch_number = 0; ch_number < part->nchannels; ch_number++) { ch = &part->channels[ch_number]; xpc_msgqueue_ref(ch); spin_lock_irqsave(&ch->lock, irq_flags); XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); xpc_msgqueue_deref(ch); } xpc_wakeup_channel_mgr(part); xpc_part_deref(part); }
void xpc_partition_going_down(struct xpc_partition *part, enum xp_retval reason) { unsigned long irq_flags; int ch_number; struct xpc_channel *ch; dev_dbg(xpc_chan, "deactivating partition %d, reason=%d\n", XPC_PARTID(part), reason); if (!xpc_part_ref(part)) { return; } for (ch_number = 0; ch_number < part->nchannels; ch_number++) { ch = &part->channels[ch_number]; xpc_msgqueue_ref(ch); spin_lock_irqsave(&ch->lock, irq_flags); XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); xpc_msgqueue_deref(ch); } xpc_wakeup_channel_mgr(part); xpc_part_deref(part); }
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)) { 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); }
/* * 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_setup_msg_structures(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; } 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; 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); }
/* * Called by XP at the time of channel connection unregistration to cause * XPC to teardown all current connections for the specified channel. * * Before returning xpc_initiate_disconnect() will wait until all connections * on the specified channel have been closed/torndown. So the caller can be * assured that they will not be receiving any more callouts from XPC to the * function they registered via xpc_connect(). * * Arguments: * * ch_number - channel # to unregister. */ void xpc_initiate_disconnect(int ch_number) { unsigned long irq_flags; short partid; struct xpc_partition *part; struct xpc_channel *ch; DBUG_ON(ch_number < 0 || ch_number >= XPC_MAX_NCHANNELS); /* initiate the channel disconnect for every active partition */ for (partid = 0; partid < xp_max_npartitions; partid++) { part = &xpc_partitions[partid]; if (xpc_part_ref(part)) { ch = &part->channels[ch_number]; xpc_msgqueue_ref(ch); spin_lock_irqsave(&ch->lock, irq_flags); if (!(ch->flags & XPC_C_DISCONNECTED)) { ch->flags |= XPC_C_WDISCONNECT; XPC_DISCONNECT_CHANNEL(ch, xpUnregistering, &irq_flags); } spin_unlock_irqrestore(&ch->lock, irq_flags); xpc_msgqueue_deref(ch); xpc_part_deref(part); } } xpc_disconnect_wait(ch_number); }
/* * Attempt to establish a channel connection to a remote partition. */ static enum xp_retval xpc_connect_channel(struct xpc_channel *ch) { unsigned long irq_flags; struct xpc_registration *registration = &xpc_registrations[ch->number]; if (mutex_trylock(®istration->mutex) == 0) return xpRetry; if (!XPC_CHANNEL_REGISTERED(ch->number)) { mutex_unlock(®istration->mutex); return xpUnregistered; } spin_lock_irqsave(&ch->lock, irq_flags); DBUG_ON(ch->flags & XPC_C_CONNECTED); DBUG_ON(ch->flags & XPC_C_OPENREQUEST); if (ch->flags & XPC_C_DISCONNECTING) { spin_unlock_irqrestore(&ch->lock, irq_flags); mutex_unlock(®istration->mutex); return ch->reason; } /* add info from the channel connect registration to the channel */ ch->kthreads_assigned_limit = registration->assigned_limit; ch->kthreads_idle_limit = registration->idle_limit; DBUG_ON(atomic_read(&ch->kthreads_assigned) != 0); DBUG_ON(atomic_read(&ch->kthreads_idle) != 0); DBUG_ON(atomic_read(&ch->kthreads_active) != 0); ch->func = registration->func; DBUG_ON(registration->func == NULL); ch->key = registration->key; ch->local_nentries = registration->nentries; if (ch->flags & XPC_C_ROPENREQUEST) { if (registration->entry_size != ch->entry_size) { /* the local and remote sides aren't the same */ /* * Because XPC_DISCONNECT_CHANNEL() can block we're * forced to up the registration sema before we unlock * the channel lock. But that's okay here because we're * done with the part that required the registration * sema. XPC_DISCONNECT_CHANNEL() requires that the * channel lock be locked and will unlock and relock * the channel lock as needed. */ mutex_unlock(®istration->mutex); XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return xpUnequalMsgSizes; } } else { ch->entry_size = registration->entry_size; XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&xpc_partitions[ch->partid].nchannels_active); } mutex_unlock(®istration->mutex); /* initiate the connection */ ch->flags |= (XPC_C_OPENREQUEST | XPC_C_CONNECTING); xpc_send_chctl_openrequest(ch, &irq_flags); xpc_process_connect(ch, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return xpSuccess; }
/* * Process a change in the channel's remote connection state. */ static void xpc_process_openclose_chctl_flags(struct xpc_partition *part, int ch_number, u8 chctl_flags) { unsigned long irq_flags; struct xpc_openclose_args *args = &part->remote_openclose_args[ch_number]; struct xpc_channel *ch = &part->channels[ch_number]; enum xp_retval reason; spin_lock_irqsave(&ch->lock, irq_flags); again: if ((ch->flags & XPC_C_DISCONNECTED) && (ch->flags & XPC_C_WDISCONNECT)) { /* * Delay processing chctl flags until thread waiting disconnect * has had a chance to see that the channel is disconnected. */ ch->delayed_chctl_flags |= chctl_flags; spin_unlock_irqrestore(&ch->lock, irq_flags); return; } if (chctl_flags & XPC_CHCTL_CLOSEREQUEST) { dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREQUEST (reason=%d) received " "from partid=%d, channel=%d\n", args->reason, ch->partid, ch->number); /* * If RCLOSEREQUEST is set, we're probably waiting for * RCLOSEREPLY. We should find it and a ROPENREQUEST packed * with this RCLOSEREQUEST in the chctl_flags. */ if (ch->flags & XPC_C_RCLOSEREQUEST) { DBUG_ON(!(ch->flags & XPC_C_DISCONNECTING)); DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST)); DBUG_ON(!(ch->flags & XPC_C_CLOSEREPLY)); DBUG_ON(ch->flags & XPC_C_RCLOSEREPLY); DBUG_ON(!(chctl_flags & XPC_CHCTL_CLOSEREPLY)); chctl_flags &= ~XPC_CHCTL_CLOSEREPLY; ch->flags |= XPC_C_RCLOSEREPLY; /* both sides have finished disconnecting */ xpc_process_disconnect(ch, &irq_flags); DBUG_ON(!(ch->flags & XPC_C_DISCONNECTED)); goto again; } if (ch->flags & XPC_C_DISCONNECTED) { if (!(chctl_flags & XPC_CHCTL_OPENREQUEST)) { if (part->chctl.flags[ch_number] & XPC_CHCTL_OPENREQUEST) { DBUG_ON(ch->delayed_chctl_flags != 0); spin_lock(&part->chctl_lock); part->chctl.flags[ch_number] |= XPC_CHCTL_CLOSEREQUEST; spin_unlock(&part->chctl_lock); } spin_unlock_irqrestore(&ch->lock, irq_flags); return; } XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&part->nchannels_active); ch->flags |= (XPC_C_CONNECTING | XPC_C_ROPENREQUEST); } chctl_flags &= ~(XPC_CHCTL_OPENREQUEST | XPC_CHCTL_OPENREPLY); /* * The meaningful CLOSEREQUEST connection state fields are: * reason = reason connection is to be closed */ ch->flags |= XPC_C_RCLOSEREQUEST; if (!(ch->flags & XPC_C_DISCONNECTING)) { reason = args->reason; if (reason <= xpSuccess || reason > xpUnknownReason) reason = xpUnknownReason; else if (reason == xpUnregistering) reason = xpOtherUnregistering; XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags); DBUG_ON(chctl_flags & XPC_CHCTL_CLOSEREPLY); spin_unlock_irqrestore(&ch->lock, irq_flags); return; } xpc_process_disconnect(ch, &irq_flags); } if (chctl_flags & XPC_CHCTL_CLOSEREPLY) { dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREPLY received from partid=" "%d, channel=%d\n", ch->partid, ch->number); if (ch->flags & XPC_C_DISCONNECTED) { DBUG_ON(part->act_state != XPC_P_AS_DEACTIVATING); spin_unlock_irqrestore(&ch->lock, irq_flags); return; } DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST)); if (!(ch->flags & XPC_C_RCLOSEREQUEST)) { if (part->chctl.flags[ch_number] & XPC_CHCTL_CLOSEREQUEST) { DBUG_ON(ch->delayed_chctl_flags != 0); spin_lock(&part->chctl_lock); part->chctl.flags[ch_number] |= XPC_CHCTL_CLOSEREPLY; spin_unlock(&part->chctl_lock); } spin_unlock_irqrestore(&ch->lock, irq_flags); return; } ch->flags |= XPC_C_RCLOSEREPLY; if (ch->flags & XPC_C_CLOSEREPLY) { /* both sides have finished disconnecting */ xpc_process_disconnect(ch, &irq_flags); } } if (chctl_flags & XPC_CHCTL_OPENREQUEST) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREQUEST (entry_size=%d, " "local_nentries=%d) received from partid=%d, " "channel=%d\n", args->entry_size, args->local_nentries, ch->partid, ch->number); if (part->act_state == XPC_P_AS_DEACTIVATING || (ch->flags & XPC_C_ROPENREQUEST)) { spin_unlock_irqrestore(&ch->lock, irq_flags); return; } if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_WDISCONNECT)) { ch->delayed_chctl_flags |= XPC_CHCTL_OPENREQUEST; spin_unlock_irqrestore(&ch->lock, irq_flags); return; } DBUG_ON(!(ch->flags & (XPC_C_DISCONNECTED | XPC_C_OPENREQUEST))); DBUG_ON(ch->flags & (XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY | XPC_C_OPENREPLY | XPC_C_CONNECTED)); /* * The meaningful OPENREQUEST connection state fields are: * entry_size = size of channel's messages in bytes * local_nentries = remote partition's local_nentries */ if (args->entry_size == 0 || args->local_nentries == 0) { /* assume OPENREQUEST was delayed by mistake */ spin_unlock_irqrestore(&ch->lock, irq_flags); return; } ch->flags |= (XPC_C_ROPENREQUEST | XPC_C_CONNECTING); ch->remote_nentries = args->local_nentries; if (ch->flags & XPC_C_OPENREQUEST) { if (args->entry_size != ch->entry_size) { XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return; } } else { ch->entry_size = args->entry_size; XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&part->nchannels_active); } xpc_process_connect(ch, &irq_flags); } if (chctl_flags & XPC_CHCTL_OPENREPLY) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY (local_msgqueue_pa=" "0x%lx, local_nentries=%d, remote_nentries=%d) " "received from partid=%d, channel=%d\n", args->local_msgqueue_pa, args->local_nentries, args->remote_nentries, ch->partid, ch->number); if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED)) { spin_unlock_irqrestore(&ch->lock, irq_flags); return; } if (!(ch->flags & XPC_C_OPENREQUEST)) { XPC_DISCONNECT_CHANNEL(ch, xpOpenCloseError, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return; } DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST)); DBUG_ON(ch->flags & XPC_C_CONNECTED); /* * The meaningful OPENREPLY connection state fields are: * local_msgqueue_pa = physical address of remote * partition's local_msgqueue * local_nentries = remote partition's local_nentries * remote_nentries = remote partition's remote_nentries */ DBUG_ON(args->local_msgqueue_pa == 0); DBUG_ON(args->local_nentries == 0); DBUG_ON(args->remote_nentries == 0); ch->flags |= XPC_C_ROPENREPLY; xpc_save_remote_msgqueue_pa(ch, args->local_msgqueue_pa); if (args->local_nentries < ch->remote_nentries) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new " "remote_nentries=%d, old remote_nentries=%d, " "partid=%d, channel=%d\n", args->local_nentries, ch->remote_nentries, ch->partid, ch->number); ch->remote_nentries = args->local_nentries; } if (args->remote_nentries < ch->local_nentries) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new " "local_nentries=%d, old local_nentries=%d, " "partid=%d, channel=%d\n", args->remote_nentries, ch->local_nentries, ch->partid, ch->number); ch->local_nentries = args->remote_nentries; } xpc_process_connect(ch, &irq_flags); } spin_unlock_irqrestore(&ch->lock, irq_flags); }
/* * For each partition that XPC has established communications with, there is * a minimum of one kernel thread assigned to perform any operation that * may potentially sleep or block (basically the callouts to the asynchronous * functions registered via xpc_connect()). * * Additional kthreads are created and destroyed by XPC as the workload * demands. * * A kthread is assigned to one of the active channels that exists for a given * partition. */ void xpc_create_kthreads(struct xpc_channel *ch, int needed, int ignore_disconnecting) { unsigned long irq_flags; u64 args = XPC_PACK_ARGS(ch->partid, ch->number); struct xpc_partition *part = &xpc_partitions[ch->partid]; struct task_struct *kthread; while (needed-- > 0) { /* * The following is done on behalf of the newly created * kthread. That kthread is responsible for doing the * counterpart to the following before it exits. */ if (ignore_disconnecting) { if (!atomic_inc_not_zero(&ch->kthreads_assigned)) { /* kthreads assigned had gone to zero */ BUG_ON(!(ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE)); break; } } else if (ch->flags & XPC_C_DISCONNECTING) { break; } else if (atomic_inc_return(&ch->kthreads_assigned) == 1 && atomic_inc_return(&part->nchannels_engaged) == 1) { xpc_indicate_partition_engaged(part); } (void)xpc_part_ref(part); xpc_msgqueue_ref(ch); kthread = kthread_run(xpc_kthread_start, (void *)args, "xpc%02dc%d", ch->partid, ch->number); if (IS_ERR(kthread)) { /* the fork failed */ /* * NOTE: if (ignore_disconnecting && * !(ch->flags & XPC_C_DISCONNECTINGCALLOUT)) is true, * then we'll deadlock if all other kthreads assigned * to this channel are blocked in the channel's * registerer, because the only thing that will unblock * them is the xpDisconnecting callout that this * failed kthread_run() would have made. */ if (atomic_dec_return(&ch->kthreads_assigned) == 0 && atomic_dec_return(&part->nchannels_engaged) == 0) { xpc_indicate_partition_disengaged(part); } xpc_msgqueue_deref(ch); xpc_part_deref(part); if (atomic_read(&ch->kthreads_assigned) < ch->kthreads_idle_limit) { /* * Flag this as an error only if we have an * insufficient #of kthreads for the channel * to function. */ spin_lock_irqsave(&ch->lock, irq_flags); XPC_DISCONNECT_CHANNEL(ch, xpLackOfResources, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); } break; } } }
void xpc_create_kthreads(struct xpc_channel *ch, int needed, int ignore_disconnecting) { unsigned long irq_flags; u64 args = XPC_PACK_ARGS(ch->partid, ch->number); struct xpc_partition *part = &xpc_partitions[ch->partid]; struct task_struct *kthread; void (*indicate_partition_disengaged) (struct xpc_partition *) = xpc_arch_ops.indicate_partition_disengaged; while (needed-- > 0) { /* */ if (ignore_disconnecting) { if (!atomic_inc_not_zero(&ch->kthreads_assigned)) { /* */ BUG_ON(!(ch->flags & XPC_C_DISCONNECTINGCALLOUT_MADE)); break; } } else if (ch->flags & XPC_C_DISCONNECTING) { break; } else if (atomic_inc_return(&ch->kthreads_assigned) == 1 && atomic_inc_return(&part->nchannels_engaged) == 1) { xpc_arch_ops.indicate_partition_engaged(part); } (void)xpc_part_ref(part); xpc_msgqueue_ref(ch); kthread = kthread_run(xpc_kthread_start, (void *)args, "xpc%02dc%d", ch->partid, ch->number); if (IS_ERR(kthread)) { /* */ /* */ if (atomic_dec_return(&ch->kthreads_assigned) == 0 && atomic_dec_return(&part->nchannels_engaged) == 0) { indicate_partition_disengaged(part); } xpc_msgqueue_deref(ch); xpc_part_deref(part); if (atomic_read(&ch->kthreads_assigned) < ch->kthreads_idle_limit) { /* */ spin_lock_irqsave(&ch->lock, irq_flags); XPC_DISCONNECT_CHANNEL(ch, xpLackOfResources, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); } break; } } }
static enum xp_retval xpc_connect_channel(struct xpc_channel *ch) { unsigned long irq_flags; struct xpc_registration *registration = &xpc_registrations[ch->number]; if (mutex_trylock(®istration->mutex) == 0) return xpRetry; if (!XPC_CHANNEL_REGISTERED(ch->number)) { mutex_unlock(®istration->mutex); return xpUnregistered; } spin_lock_irqsave(&ch->lock, irq_flags); DBUG_ON(ch->flags & XPC_C_CONNECTED); DBUG_ON(ch->flags & XPC_C_OPENREQUEST); if (ch->flags & XPC_C_DISCONNECTING) { spin_unlock_irqrestore(&ch->lock, irq_flags); mutex_unlock(®istration->mutex); return ch->reason; } ch->kthreads_assigned_limit = registration->assigned_limit; ch->kthreads_idle_limit = registration->idle_limit; DBUG_ON(atomic_read(&ch->kthreads_assigned) != 0); DBUG_ON(atomic_read(&ch->kthreads_idle) != 0); DBUG_ON(atomic_read(&ch->kthreads_active) != 0); ch->func = registration->func; DBUG_ON(registration->func == NULL); ch->key = registration->key; ch->local_nentries = registration->nentries; if (ch->flags & XPC_C_ROPENREQUEST) { if (registration->entry_size != ch->entry_size) { mutex_unlock(®istration->mutex); XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return xpUnequalMsgSizes; } } else { ch->entry_size = registration->entry_size; XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&xpc_partitions[ch->partid].nchannels_active); } mutex_unlock(®istration->mutex); ch->flags |= (XPC_C_OPENREQUEST | XPC_C_CONNECTING); xpc_arch_ops.send_chctl_openrequest(ch, &irq_flags); xpc_process_connect(ch, &irq_flags); spin_unlock_irqrestore(&ch->lock, irq_flags); return xpSuccess; }
static void xpc_process_openclose_chctl_flags(struct xpc_partition *part, int ch_number, u8 chctl_flags) { unsigned long irq_flags; struct xpc_openclose_args *args = &part->remote_openclose_args[ch_number]; struct xpc_channel *ch = &part->channels[ch_number]; enum xp_retval reason; enum xp_retval ret; int create_kthread = 0; spin_lock_irqsave(&ch->lock, irq_flags); again: if ((ch->flags & XPC_C_DISCONNECTED) && (ch->flags & XPC_C_WDISCONNECT)) { ch->delayed_chctl_flags |= chctl_flags; goto out; } if (chctl_flags & XPC_CHCTL_CLOSEREQUEST) { dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREQUEST (reason=%d) received " "from partid=%d, channel=%d\n", args->reason, ch->partid, ch->number); if (ch->flags & XPC_C_RCLOSEREQUEST) { DBUG_ON(!(ch->flags & XPC_C_DISCONNECTING)); DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST)); DBUG_ON(!(ch->flags & XPC_C_CLOSEREPLY)); DBUG_ON(ch->flags & XPC_C_RCLOSEREPLY); DBUG_ON(!(chctl_flags & XPC_CHCTL_CLOSEREPLY)); chctl_flags &= ~XPC_CHCTL_CLOSEREPLY; ch->flags |= XPC_C_RCLOSEREPLY; xpc_process_disconnect(ch, &irq_flags); DBUG_ON(!(ch->flags & XPC_C_DISCONNECTED)); goto again; } if (ch->flags & XPC_C_DISCONNECTED) { if (!(chctl_flags & XPC_CHCTL_OPENREQUEST)) { if (part->chctl.flags[ch_number] & XPC_CHCTL_OPENREQUEST) { DBUG_ON(ch->delayed_chctl_flags != 0); spin_lock(&part->chctl_lock); part->chctl.flags[ch_number] |= XPC_CHCTL_CLOSEREQUEST; spin_unlock(&part->chctl_lock); } goto out; } XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&part->nchannels_active); ch->flags |= (XPC_C_CONNECTING | XPC_C_ROPENREQUEST); } chctl_flags &= ~(XPC_CHCTL_OPENREQUEST | XPC_CHCTL_OPENREPLY | XPC_CHCTL_OPENCOMPLETE); ch->flags |= XPC_C_RCLOSEREQUEST; if (!(ch->flags & XPC_C_DISCONNECTING)) { reason = args->reason; if (reason <= xpSuccess || reason > xpUnknownReason) reason = xpUnknownReason; else if (reason == xpUnregistering) reason = xpOtherUnregistering; XPC_DISCONNECT_CHANNEL(ch, reason, &irq_flags); DBUG_ON(chctl_flags & XPC_CHCTL_CLOSEREPLY); goto out; } xpc_process_disconnect(ch, &irq_flags); } if (chctl_flags & XPC_CHCTL_CLOSEREPLY) { dev_dbg(xpc_chan, "XPC_CHCTL_CLOSEREPLY received from partid=" "%d, channel=%d\n", ch->partid, ch->number); if (ch->flags & XPC_C_DISCONNECTED) { DBUG_ON(part->act_state != XPC_P_AS_DEACTIVATING); goto out; } DBUG_ON(!(ch->flags & XPC_C_CLOSEREQUEST)); if (!(ch->flags & XPC_C_RCLOSEREQUEST)) { if (part->chctl.flags[ch_number] & XPC_CHCTL_CLOSEREQUEST) { DBUG_ON(ch->delayed_chctl_flags != 0); spin_lock(&part->chctl_lock); part->chctl.flags[ch_number] |= XPC_CHCTL_CLOSEREPLY; spin_unlock(&part->chctl_lock); } goto out; } ch->flags |= XPC_C_RCLOSEREPLY; if (ch->flags & XPC_C_CLOSEREPLY) { xpc_process_disconnect(ch, &irq_flags); } } if (chctl_flags & XPC_CHCTL_OPENREQUEST) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREQUEST (entry_size=%d, " "local_nentries=%d) received from partid=%d, " "channel=%d\n", args->entry_size, args->local_nentries, ch->partid, ch->number); if (part->act_state == XPC_P_AS_DEACTIVATING || (ch->flags & XPC_C_ROPENREQUEST)) { goto out; } if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_WDISCONNECT)) { ch->delayed_chctl_flags |= XPC_CHCTL_OPENREQUEST; goto out; } DBUG_ON(!(ch->flags & (XPC_C_DISCONNECTED | XPC_C_OPENREQUEST))); DBUG_ON(ch->flags & (XPC_C_ROPENREQUEST | XPC_C_ROPENREPLY | XPC_C_OPENREPLY | XPC_C_CONNECTED)); if (args->entry_size == 0 || args->local_nentries == 0) { goto out; } ch->flags |= (XPC_C_ROPENREQUEST | XPC_C_CONNECTING); ch->remote_nentries = args->local_nentries; if (ch->flags & XPC_C_OPENREQUEST) { if (args->entry_size != ch->entry_size) { XPC_DISCONNECT_CHANNEL(ch, xpUnequalMsgSizes, &irq_flags); goto out; } } else { ch->entry_size = args->entry_size; XPC_SET_REASON(ch, 0, 0); ch->flags &= ~XPC_C_DISCONNECTED; atomic_inc(&part->nchannels_active); } xpc_process_connect(ch, &irq_flags); } if (chctl_flags & XPC_CHCTL_OPENREPLY) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY (local_msgqueue_pa=" "0x%lx, local_nentries=%d, remote_nentries=%d) " "received from partid=%d, channel=%d\n", args->local_msgqueue_pa, args->local_nentries, args->remote_nentries, ch->partid, ch->number); if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED)) goto out; if (!(ch->flags & XPC_C_OPENREQUEST)) { XPC_DISCONNECT_CHANNEL(ch, xpOpenCloseError, &irq_flags); goto out; } DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST)); DBUG_ON(ch->flags & XPC_C_CONNECTED); DBUG_ON(args->local_msgqueue_pa == 0); DBUG_ON(args->local_nentries == 0); DBUG_ON(args->remote_nentries == 0); ret = xpc_arch_ops.save_remote_msgqueue_pa(ch, args->local_msgqueue_pa); if (ret != xpSuccess) { XPC_DISCONNECT_CHANNEL(ch, ret, &irq_flags); goto out; } ch->flags |= XPC_C_ROPENREPLY; if (args->local_nentries < ch->remote_nentries) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new " "remote_nentries=%d, old remote_nentries=%d, " "partid=%d, channel=%d\n", args->local_nentries, ch->remote_nentries, ch->partid, ch->number); ch->remote_nentries = args->local_nentries; } if (args->remote_nentries < ch->local_nentries) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENREPLY: new " "local_nentries=%d, old local_nentries=%d, " "partid=%d, channel=%d\n", args->remote_nentries, ch->local_nentries, ch->partid, ch->number); ch->local_nentries = args->remote_nentries; } xpc_process_connect(ch, &irq_flags); } if (chctl_flags & XPC_CHCTL_OPENCOMPLETE) { dev_dbg(xpc_chan, "XPC_CHCTL_OPENCOMPLETE received from " "partid=%d, channel=%d\n", ch->partid, ch->number); if (ch->flags & (XPC_C_DISCONNECTING | XPC_C_DISCONNECTED)) goto out; if (!(ch->flags & XPC_C_OPENREQUEST) || !(ch->flags & XPC_C_OPENREPLY)) { XPC_DISCONNECT_CHANNEL(ch, xpOpenCloseError, &irq_flags); goto out; } DBUG_ON(!(ch->flags & XPC_C_ROPENREQUEST)); DBUG_ON(!(ch->flags & XPC_C_ROPENREPLY)); DBUG_ON(!(ch->flags & XPC_C_CONNECTED)); ch->flags |= XPC_C_ROPENCOMPLETE; xpc_process_connect(ch, &irq_flags); create_kthread = 1; } out: spin_unlock_irqrestore(&ch->lock, irq_flags); if (create_kthread) xpc_create_kthreads(ch, 1, 0); }