/* * 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 #include #include #include #include #include #include #include #include /* * 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); } }