#include <sys/types.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/file.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/bitmap.h>
#include <sys/rctl.h>
#include <sys/atomic.h>
#include <sys/poll_impl.h>
#include <sys/port_impl.h>
uint_t port_max_list = PORT_MAX_LIST;
port_control_t port_control;
void
port_block(port_queue_t *portq)
{
ASSERT(MUTEX_HELD(&portq->portq_mutex));
while (portq->portq_flags & PORTQ_BLOCKED)
cv_wait(&portq->portq_block_cv, &portq->portq_mutex);
portq->portq_flags |= PORTQ_BLOCKED;
}
void
port_unblock(port_queue_t *portq)
{
ASSERT(MUTEX_HELD(&portq->portq_mutex));
portq->portq_flags &= ~PORTQ_BLOCKED;
cv_signal(&portq->portq_block_cv);
}
int
port_pollwkup(port_t *pp)
{
int events = 0;
port_queue_t *portq;
portq = &pp->port_queue;
mutex_enter(&portq->portq_mutex);
if (portq->portq_flags & PORTQ_POLLIN &&
!(portq->portq_flags & PORTQ_POLLWK_PEND)) {
portq->portq_flags &= ~PORTQ_POLLIN;
portq->portq_flags |= PORTQ_POLLWK_PEND;
events = POLLIN;
}
mutex_exit(&portq->portq_mutex);
return (events);
}
void
port_pollwkdone(port_t *pp)
{
port_queue_t *portq;
portq = &pp->port_queue;
ASSERT(portq->portq_flags & PORTQ_POLLWK_PEND);
mutex_enter(&portq->portq_mutex);
portq->portq_flags &= ~PORTQ_POLLWK_PEND;
cv_signal(&pp->port_cv);
mutex_exit(&portq->portq_mutex);
}
void
port_send_event(port_kevent_t *pkevp)
{
port_queue_t *portq;
portq = &pkevp->portkev_port->port_queue;
mutex_enter(&portq->portq_mutex);
if (pkevp->portkev_flags & PORT_KEV_DONEQ) {
if (pkevp->portkev_source == PORT_SOURCE_FD) {
mutex_exit(&pkevp->portkev_lock);
}
mutex_exit(&portq->portq_mutex);
return;
}
list_insert_tail(&portq->portq_list, pkevp);
portq->portq_nent++;
portq->portq_flags &= ~PORTQ_WAIT_EVENTS;
pkevp->portkev_flags |= PORT_KEV_DONEQ;
if (pkevp->portkev_source == PORT_SOURCE_FD) {
mutex_exit(&pkevp->portkev_lock);
}
if (portq->portq_flags & PORTQ_CLOSE) {
if (pkevp->portkev_port->port_curr <= portq->portq_nent)
cv_signal(&portq->portq_closecv);
}
if (portq->portq_getn == 0) {
if (portq->portq_thread &&
(portq->portq_nent >= portq->portq_nget))
cv_signal(&portq->portq_thread->portget_cv);
}
if (pkevp->portkev_source != PORT_SOURCE_FD &&
portq->portq_flags & PORTQ_POLLIN) {
port_t *pp;
portq->portq_flags &= ~PORTQ_POLLIN;
pp = pkevp->portkev_port;
mutex_exit(&portq->portq_mutex);
pollwakeup(&pp->port_pollhd, POLLIN);
} else {
mutex_exit(&portq->portq_mutex);
}
}
int
port_alloc_event(int port, int flags, int source, port_kevent_t **pkevpp)
{
port_t *pp;
file_t *fp;
port_kevent_t *pkevp;
if ((fp = getf(port)) == NULL)
return (EBADF);
if (fp->f_vnode->v_type != VPORT) {
releasef(port);
return (EBADFD);
}
pkevp = kmem_cache_alloc(port_control.pc_cache, KM_NOSLEEP);
if (pkevp == NULL) {
releasef(port);
return (ENOMEM);
}
pp = VTOEP(fp->f_vnode);
mutex_enter(&pp->port_queue.portq_mutex);
if (pp->port_curr >= pp->port_max_events) {
mutex_exit(&pp->port_queue.portq_mutex);
kmem_cache_free(port_control.pc_cache, pkevp);
releasef(port);
return (EAGAIN);
}
pp->port_curr++;
mutex_exit(&pp->port_queue.portq_mutex);
bzero(pkevp, sizeof (port_kevent_t));
mutex_init(&pkevp->portkev_lock, NULL, MUTEX_DEFAULT, NULL);
pkevp->portkev_source = source;
pkevp->portkev_flags = flags;
pkevp->portkev_pid = curproc->p_pid;
pkevp->portkev_port = pp;
*pkevpp = pkevp;
releasef(port);
return (0);
}
int
port_dup_event(port_kevent_t *pkevp, port_kevent_t **pkevdupp, int flags)
{
int error;
error = port_alloc_event_local(pkevp->portkev_port,
pkevp->portkev_source, flags, pkevdupp);
if (error == 0)
(*pkevdupp)->portkev_pid = pkevp->portkev_pid;
return (error);
}
int
port_alloc_event_local(port_t *pp, int source, int flags,
port_kevent_t **pkevpp)
{
port_kevent_t *pkevp;
pkevp = kmem_cache_alloc(port_control.pc_cache, KM_NOSLEEP);
if (pkevp == NULL)
return (ENOMEM);
mutex_enter(&pp->port_queue.portq_mutex);
if (pp->port_curr >= pp->port_max_events) {
mutex_exit(&pp->port_queue.portq_mutex);
kmem_cache_free(port_control.pc_cache, pkevp);
return (EAGAIN);
}
pp->port_curr++;
mutex_exit(&pp->port_queue.portq_mutex);
bzero(pkevp, sizeof (port_kevent_t));
mutex_init(&pkevp->portkev_lock, NULL, MUTEX_DEFAULT, NULL);
pkevp->portkev_flags = flags;
pkevp->portkev_port = pp;
pkevp->portkev_source = source;
pkevp->portkev_pid = curproc->p_pid;
*pkevpp = pkevp;
return (0);
}
int
port_alloc_event_block(port_t *pp, int source, int flags,
port_kevent_t **pkevpp)
{
port_kevent_t *pkevp =
kmem_cache_alloc(port_control.pc_cache, KM_SLEEP);
mutex_enter(&pp->port_queue.portq_mutex);
while (pp->port_curr >= pp->port_max_events) {
if (!cv_wait_sig(&pp->port_cv, &pp->port_queue.portq_mutex)) {
mutex_exit(&pp->port_queue.portq_mutex);
kmem_cache_free(port_control.pc_cache, pkevp);
return (EINTR);
}
}
pp->port_curr++;
mutex_exit(&pp->port_queue.portq_mutex);
bzero(pkevp, sizeof (port_kevent_t));
mutex_init(&pkevp->portkev_lock, NULL, MUTEX_DEFAULT, NULL);
pkevp->portkev_flags = flags;
pkevp->portkev_port = pp;
pkevp->portkev_source = source;
pkevp->portkev_pid = curproc->p_pid;
*pkevpp = pkevp;
return (0);
}
static void
port_remove_event_doneq(port_kevent_t *pkevp, port_queue_t *portq)
{
ASSERT(MUTEX_HELD(&portq->portq_mutex));
list_remove(&portq->portq_list, pkevp);
portq->portq_nent--;
pkevp->portkev_flags &= ~PORT_KEV_DONEQ;
}
int
port_remove_done_event(port_kevent_t *pkevp)
{
port_queue_t *portq;
int removed = 0;
portq = &pkevp->portkev_port->port_queue;
mutex_enter(&portq->portq_mutex);
port_block(portq);
if (pkevp->portkev_flags & PORT_KEV_DONEQ) {
if (portq->portq_getn) {
port_push_eventq(portq);
}
port_remove_event_doneq(pkevp, portq);
removed = 1;
}
port_unblock(portq);
mutex_exit(&portq->portq_mutex);
return (removed);
}
void
port_free_event(port_kevent_t *pkevp)
{
port_queue_t *portq;
port_t *pp;
pp = pkevp->portkev_port;
if (pp == NULL)
return;
if (pkevp->portkev_flags & PORT_ALLOC_PRIVATE) {
port_free_event_local(pkevp, 0);
return;
}
portq = &pp->port_queue;
mutex_enter(&portq->portq_mutex);
port_block(portq);
if (pkevp->portkev_flags & PORT_KEV_DONEQ) {
pkevp->portkev_flags |= PORT_KEV_FREE;
pkevp->portkev_callback = NULL;
port_unblock(portq);
mutex_exit(&portq->portq_mutex);
return;
}
port_unblock(portq);
if (pkevp->portkev_flags & PORT_KEV_CACHED) {
mutex_exit(&portq->portq_mutex);
return;
}
if (--pp->port_curr < pp->port_max_events)
cv_signal(&pp->port_cv);
if (portq->portq_flags & PORTQ_CLOSE) {
if (pp->port_curr <= portq->portq_nent)
cv_signal(&portq->portq_closecv);
}
mutex_exit(&portq->portq_mutex);
port_free_event_local(pkevp, 1);
}
void
port_free_event_local(port_kevent_t *pkevp, int counter)
{
port_t *pp = pkevp->portkev_port;
port_queue_t *portq = &pp->port_queue;
int wakeup;
pkevp->portkev_callback = NULL;
pkevp->portkev_flags = 0;
pkevp->portkev_port = NULL;
mutex_destroy(&pkevp->portkev_lock);
kmem_cache_free(port_control.pc_cache, pkevp);
mutex_enter(&portq->portq_mutex);
if (counter == 0) {
if (--pp->port_curr < pp->port_max_events)
cv_signal(&pp->port_cv);
}
wakeup = (portq->portq_flags & PORTQ_POLLOUT);
portq->portq_flags &= ~PORTQ_POLLOUT;
mutex_exit(&portq->portq_mutex);
if (wakeup)
pollwakeup(&pp->port_pollhd, POLLOUT);
}
void
port_init_event(port_kevent_t *pkevp, uintptr_t object, void *user,
int (*port_callback)(void *, int *, pid_t, int, void *),
void *sysarg)
{
pkevp->portkev_object = object;
pkevp->portkev_user = user;
pkevp->portkev_callback = port_callback;
pkevp->portkev_arg = sysarg;
}
void
port_pcache_remove_fd(port_fdcache_t *pcp, portfd_t *pfd)
{
polldat_t *lpdp;
polldat_t *cpdp;
portfd_t **bucket;
polldat_t *pdp = PFTOD(pfd);
ASSERT(MUTEX_HELD(&pcp->pc_lock));
bucket = PORT_FD_BUCKET(pcp, pdp->pd_fd);
cpdp = PFTOD(*bucket);
if (pdp == cpdp) {
*bucket = PDTOF(pdp->pd_hashnext);
if (--pcp->pc_fdcount == 0) {
cv_signal(&pcp->pc_lclosecv);
}
kmem_free(pfd, sizeof (portfd_t));
return;
}
while (cpdp != NULL) {
lpdp = cpdp;
cpdp = cpdp->pd_hashnext;
if (cpdp == pdp) {
lpdp->pd_hashnext = pdp->pd_hashnext;
if (--pcp->pc_fdcount == 0) {
cv_signal(&pcp->pc_lclosecv);
}
break;
}
}
ASSERT(cpdp != NULL);
kmem_free(pfd, sizeof (portfd_t));
}
void
port_push_eventq(port_queue_t *portq)
{
list_move_tail(&portq->portq_list, &portq->portq_get_list);
portq->portq_nent += portq->portq_tnent;
portq->portq_tnent = 0;
}
int
port_remove_fd_object(portfd_t *pfd, port_t *pp, port_fdcache_t *pcp)
{
port_queue_t *portq;
polldat_t *pdp = PFTOD(pfd);
port_kevent_t *pkevp;
int error;
int removed = 0;
ASSERT(MUTEX_HELD(&pcp->pc_lock));
polldat_disassociate(pdp);
pkevp = pdp->pd_portev;
portq = &pp->port_queue;
mutex_enter(&portq->portq_mutex);
port_block(portq);
if (pkevp->portkev_flags & PORT_KEV_DONEQ) {
if (portq->portq_getn && portq->portq_tnent) {
port_push_eventq(portq);
}
port_remove_event_doneq(pkevp, portq);
removed = 1;
}
port_unblock(portq);
mutex_exit(&portq->portq_mutex);
if (pkevp->portkev_callback) {
(void) (*pkevp->portkev_callback)(pkevp->portkev_arg,
&error, pkevp->portkev_pid, PORT_CALLBACK_DISSOCIATE,
pkevp);
}
port_free_event_local(pkevp, 0);
port_pcache_remove_fd(pcp, pfd);
return (removed);
}
void
port_close_pfd(portfd_t *pfd)
{
port_t *pp;
port_fdcache_t *pcp;
ASSERT(curproc->p_pid == PFTOD(pfd)->pd_portev->portkev_pid);
pp = PFTOD(pfd)->pd_portev->portkev_port;
pcp = pp->port_queue.portq_pcp;
mutex_enter(&pcp->pc_lock);
(void) port_remove_fd_object(pfd, pp, pcp);
mutex_exit(&pcp->pc_lock);
}
int
port_associate_ksource(int port, int source, port_source_t **portsrc,
void (*port_src_close)(void *, int, pid_t, int), void *arg,
int (*port_src_associate)(port_kevent_t *, int, int, uintptr_t, void *))
{
port_t *pp;
file_t *fp;
port_source_t **ps;
port_source_t *pse;
if ((fp = getf(port)) == NULL)
return (EBADF);
if (fp->f_vnode->v_type != VPORT) {
releasef(port);
return (EBADFD);
}
pp = VTOEP(fp->f_vnode);
mutex_enter(&pp->port_queue.portq_source_mutex);
ps = &pp->port_queue.portq_scache[PORT_SHASH(source)];
for (pse = *ps; pse != NULL; pse = pse->portsrc_next) {
if (pse->portsrc_source == source)
break;
}
if (pse == NULL) {
pse = kmem_zalloc(sizeof (port_source_t), KM_NOSLEEP);
if (pse == NULL) {
mutex_exit(&pp->port_queue.portq_source_mutex);
releasef(port);
return (ENOMEM);
}
pse->portsrc_source = source;
pse->portsrc_close = port_src_close;
pse->portsrc_closearg = arg;
pse->portsrc_cnt = 1;
if (*ps)
pse->portsrc_next = (*ps)->portsrc_next;
*ps = pse;
} else {
pse->portsrc_cnt++;
}
mutex_exit(&pp->port_queue.portq_source_mutex);
releasef(port);
if (portsrc)
*portsrc = pse;
return (0);
}
int
port_dissociate_ksource(int port, int source, port_source_t *ps)
{
port_t *pp;
file_t *fp;
port_source_t **psh;
if (ps == NULL)
return (EINVAL);
if ((fp = getf(port)) == NULL)
return (EBADF);
if (fp->f_vnode->v_type != VPORT) {
releasef(port);
return (EBADFD);
}
pp = VTOEP(fp->f_vnode);
mutex_enter(&pp->port_queue.portq_source_mutex);
if (--ps->portsrc_cnt == 0) {
if (ps->portsrc_prev == NULL) {
psh = &pp->port_queue.portq_scache[PORT_SHASH(source)];
*psh = ps->portsrc_next;
if (ps->portsrc_next)
ps->portsrc_next->portsrc_prev = NULL;
} else {
ps->portsrc_prev->portsrc_next = ps->portsrc_next;
if (ps->portsrc_next)
ps->portsrc_next->portsrc_prev =
ps->portsrc_prev;
}
kmem_free(ps, sizeof (port_source_t));
}
mutex_exit(&pp->port_queue.portq_source_mutex);
releasef(port);
return (0);
}
void
free_fopdata(vnode_t *vp)
{
portfop_vp_t *pvp;
pvp = vp->v_fopdata;
ASSERT(pvp->pvp_femp == NULL);
mutex_destroy(&pvp->pvp_mutex);
list_destroy(&pvp->pvp_pfoplist);
kmem_free(pvp, sizeof (*pvp));
vp->v_fopdata = NULL;
}