/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/stropts.h>
#include <sys/socketvar.h>
#include <sys/ksocket.h>
#include <io/ksocket/ksocket_impl.h>
#include <fs/sockfs/sockcommon.h>
#include <fs/sockfs/sodirect.h>

/*
 * There can only be a single thread waiting for data (enforced by
 * so_lock_read()), whereas for write there might be multiple threads
 * waiting for transmit buffers. So therefore we use cv_broadcast for
 * write and cv_signal for read.
 */
#define	SO_WAKEUP_READER(so) {				\
	if ((so)->so_rcv_wakeup) {			\
		(so)->so_rcv_wakeup = B_FALSE;		\
		cv_signal(&(so)->so_rcv_cv);		\
	}						\
}

#define	SO_WAKEUP_WRITER(so) {			\
	if ((so)->so_snd_wakeup) {		\
		(so)->so_snd_wakeup = B_FALSE;	\
		cv_broadcast(&(so)->so_snd_cv);	\
	}					\
}

static int i_so_notify_last_rx(struct sonode *, int *, int *);
static int i_so_notify_last_tx(struct sonode *, int *, int *);

/*
 * The notification functions must be called with so_lock held,
 * and they will all *drop* so_lock before returning.
 */

/*
 * Wake up anyone waiting for the connection to be established.
 */
void
so_notify_connected(struct sonode *so)
{
	ASSERT(MUTEX_HELD(&so->so_lock));

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, connected, 0);
		mutex_exit(&so->so_lock);
	} else {
		socket_sendsig(so, SOCKETSIG_WRITE);
		mutex_exit(&so->so_lock);
		pollwakeup(&so->so_poll_list, POLLOUT);
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * The socket is disconnecting, so no more data can be sent. Wake up
 * anyone that is waiting to send data.
 */
void
so_notify_disconnecting(struct sonode *so)
{
	int pollev = 0;
	int sigev = 0;

	ASSERT(MUTEX_HELD(&so->so_lock));

	if (IS_KERNEL_SOCKET(so)) {
		SO_WAKEUP_WRITER(so);
		KSOCKET_CALLBACK(so, cantsendmore, 0);
		mutex_exit(&so->so_lock);
	} else if (i_so_notify_last_tx(so, &pollev, &sigev)) {
		socket_sendsig(so, sigev);
		mutex_exit(&so->so_lock);
		pollwakeup(&so->so_poll_list, pollev);
	} else {
		mutex_exit(&so->so_lock);
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * The socket is disconnected, so not more data can be sent or received.
 * Wake up anyone that is waiting to send or receive data.
 */
void
so_notify_disconnected(struct sonode *so, int error)
{
	int pollev = 0;
	int sigev = 0;

	ASSERT(MUTEX_HELD(&so->so_lock));

	(void) i_so_notify_last_tx(so, &pollev, &sigev);
	(void) i_so_notify_last_rx(so, &pollev, &sigev);

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, disconnected, error);
		mutex_exit(&so->so_lock);
	} else {
		if (sigev != 0)
			socket_sendsig(so, sigev);
		mutex_exit(&so->so_lock);
		if (pollev != 0)
			pollwakeup(&so->so_poll_list, pollev);
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * The socket is writeable. Wake up anyone waiting to send data.
 */
void
so_notify_writable(struct sonode *so)
{
	ASSERT(MUTEX_HELD(&so->so_lock));

	SO_WAKEUP_WRITER(so);

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, cansend, 0);
		mutex_exit(&so->so_lock);
	} else {
		socket_sendsig(so, SOCKETSIG_WRITE);
		mutex_exit(&so->so_lock);
		pollwakeup(&so->so_poll_list, POLLOUT);
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * Data is available, so wake up anyone waiting for data.
 */
void
so_notify_data(struct sonode *so, size_t qlen)
{
	ASSERT(MUTEX_HELD(&so->so_lock));

	SO_WAKEUP_READER(so);

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, newdata, qlen);
		mutex_exit(&so->so_lock);
	} else {
		socket_sendsig(so, SOCKETSIG_READ);
		if (so->so_pollev & (SO_POLLEV_IN|SO_POLLEV_ALWAYS)) {
			so->so_pollev &= ~SO_POLLEV_IN;
			mutex_exit(&so->so_lock);
			pollwakeup(&so->so_poll_list, POLLIN|POLLRDNORM);
		} else {
			mutex_exit(&so->so_lock);
		}
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * Transient error. Wake up anyone waiting to send or receive data.
 */
void
so_notify_error(struct sonode *so)
{
	ASSERT(MUTEX_HELD(&so->so_lock));

	SO_WAKEUP_WRITER(so);
	SO_WAKEUP_READER(so);

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, error, 0);
		mutex_exit(&so->so_lock);
	} else {
		socket_sendsig(so, SOCKETSIG_WRITE|SOCKETSIG_READ);
		so->so_pollev &= ~SO_POLLEV_IN;
		mutex_exit(&so->so_lock);
		pollwakeup(&so->so_poll_list, POLLOUT|POLLIN|POLLRDNORM);
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * Out-of-band data is incoming, notify any interested parties.
 */
void
so_notify_oobsig(struct sonode *so)
{
	socket_sendsig(so, SOCKETSIG_URG);
	mutex_exit(&so->so_lock);
	pollwakeup(&so->so_poll_list, POLLRDBAND);
}

/*
 * Received out-of-band data. If the OOB data is delivered inline, then
 * in addition of regular OOB notification, anyone waiting for normal
 * data is also notified.
 */
void
so_notify_oobdata(struct sonode *so, boolean_t oob_inline)
{
	ASSERT(MUTEX_HELD(&so->so_lock));
	if (so->so_direct != NULL)
		SOD_UIOAFINI(so->so_direct);

	if (IS_KERNEL_SOCKET(so)) {
		if (oob_inline)
			SO_WAKEUP_READER(so);
		KSOCKET_CALLBACK(so, oobdata, 0);
		mutex_exit(&so->so_lock);
	} else {
		if (oob_inline) {
			socket_sendsig(so, SOCKETSIG_READ);
			so->so_pollev &= ~SO_POLLEV_IN;
			mutex_exit(&so->so_lock);
			pollwakeup(&so->so_poll_list,
			    POLLRDBAND|POLLIN|POLLRDNORM);

			SO_WAKEUP_READER(so);
		} else {
			mutex_exit(&so->so_lock);
			pollwakeup(&so->so_poll_list, POLLRDBAND);
		}
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * End-of-file has been reach, so peer will send no new data. Wake up
 * anyone that is waiting for data.
 */
void
so_notify_eof(struct sonode *so)
{
	int pollev = 0;
	int sigev = 0;

	ASSERT(MUTEX_HELD(&so->so_lock));

	(void) i_so_notify_last_rx(so, &pollev, &sigev);

	if (IS_KERNEL_SOCKET(so)) {
		SO_WAKEUP_READER(so);
		KSOCKET_CALLBACK(so, cantrecvmore, 0);
		mutex_exit(&so->so_lock);
	} else {
		if (sigev != 0)
			socket_sendsig(so, sigev);
		mutex_exit(&so->so_lock);
		if (pollev != 0)
			pollwakeup(&so->so_poll_list, pollev);

	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * Wake up anyone waiting for a new connection.
 */
void
so_notify_newconn(struct sonode *so)
{
	ASSERT(MUTEX_HELD(&so->so_lock));

	if (IS_KERNEL_SOCKET(so)) {
		KSOCKET_CALLBACK(so, newconn, so->so_rcv_queued);
		mutex_exit(&so->so_lock);
	} else {
		socket_sendsig(so, SOCKETSIG_READ);
		if (so->so_pollev & (SO_POLLEV_IN|SO_POLLEV_ALWAYS)) {
			so->so_pollev &= ~SO_POLLEV_IN;
			mutex_exit(&so->so_lock);
			pollwakeup(&so->so_poll_list, POLLIN|POLLRDNORM);
		} else {
			mutex_exit(&so->so_lock);
		}
	}

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * User initated shutdown/close, wake anyone that is trying to do
 * an operation that is no longer possible.
 */
void
so_notify_shutdown(struct sonode *so)
{
	int pollev = 0;
	int sigev = 0;

	ASSERT(MUTEX_HELD(&so->so_lock));
	ASSERT(so->so_state & (SS_CANTSENDMORE|SS_CANTRCVMORE));

	if (so->so_state & SS_CANTSENDMORE)
		(void) i_so_notify_last_tx(so, &pollev, &sigev);
	if (so->so_state & SS_CANTRCVMORE)
		(void) i_so_notify_last_rx(so, &pollev, &sigev);

	if (sigev != 0)
		socket_sendsig(so, sigev);
	mutex_exit(&so->so_lock);
	if (pollev != 0)
		pollwakeup(&so->so_poll_list, pollev);

	ASSERT(MUTEX_NOT_HELD(&so->so_lock));
}

/*
 * No more data will be coming in, and this will be the last notification
 * made.
 */
static int
i_so_notify_last_rx(struct sonode *so, int *pollev, int *sigev)
{
	if (!(so->so_state & SS_SENTLASTREADSIG)) {
		SOCKET_TIMER_CANCEL(so);
		SO_WAKEUP_READER(so);
		so->so_state |= SS_SENTLASTREADSIG;
		so->so_pollev &= ~SO_POLLEV_IN;

		*pollev |= POLLIN|POLLRDNORM;
		*sigev |= SOCKETSIG_READ;

		return (1);
	} else {
		return (0);
	}
}

/*
 * The socket is un-writeable. Make one last notification.
 */
static int
i_so_notify_last_tx(struct sonode *so, int *pollev, int *sigev)
{
	if (!(so->so_state & SS_SENTLASTWRITESIG)) {
		SO_WAKEUP_WRITER(so);
		so->so_state |= SS_SENTLASTWRITESIG;

		*pollev |= POLLOUT;
		*sigev |= SOCKETSIG_WRITE;

		return (1);
	} else {
		return (0);
	}
}