root/sys/dev/ntsync/ntsync.c
/*
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright 2026 The FreeBSD Foundation
 *
 * This software was developed by Konstantin Belousov <kib@FreeBSD.org>
 * under sponsorship from the FreeBSD Foundation.
 */

#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/kernel.h>
#include <sys/limits.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/stat.h>
#include <sys/sysent.h>
#include <sys/user.h>
#include <dev/ntsync/ntsyncvar.h>

static struct cdev *ntsync_cdev;
MALLOC_DEFINE(M_NTSYNC, "ntsync", "ntsync");

static void ntsync_free_priv(struct ntsync_priv *priv);

/*
 * Returning error from an ioctl handler prevents the generic ioctl
 * code from copying out the result.  Use direct access to ioctl(2)
 * args to get the parameters block pointer to implement Linux
 * semantic of both returning an error and updating the parameters
 * block.
 */
static int
ntsync_ioctl_copyout(struct thread *td, const void *ptr, size_t sz)
{
        void *uptr;

        if (SV_PROC_ABI(td->td_proc) != SV_ABI_FREEBSD)
                return (0);
        uptr = (void *)(uintptr_t)td->td_sa.args[2];
        return (copyout(ptr, uptr, sz));
}

static bool
ntsync_wait_any(struct ntsync_wait_state *state)
{
        struct ntsync_obj *obj;
        int i;

        MPASS(state->any);
        NTSYNC_PRIV_ASSERT(state->owner);

        for (i = 0; i < state->obj_count; i++) {
                obj = state->objs[i];
                if (obj->is_signaled(obj, state, i)) {
                        state->index = i;
                        obj->consume(obj, state, state->index);
                        return (true);
                }
        }
        return (false);
}

static bool
ntsync_wait_all_prepare(struct ntsync_wait_state *state, bool *stop)
{
        struct ntsync_obj *obj;
        int alerti, i;
        bool first;

        MPASS(state->all);
        MPASS(state->error == 0);
        MPASS(!*stop);
        NTSYNC_PRIV_ASSERT(state->owner);

        alerti = state->alert_event == NULL ? 0 : 1;
        first = true;

        for (i = 0; i < state->obj_count - alerti; i++) {
                obj = state->objs[i];
                if (!obj->prepare(obj, state, i, stop))
                        return (false);
                if (*stop) {
                        MPASS(state->error != 0);
                        return (false);
                }
                MPASS (state->error == 0);
                if (first) {
                        first = false;
                        state->index = i;
                }
        }
        return (true);
}

static void
ntsync_wait_all_commit(struct ntsync_wait_state *state)
{
        struct ntsync_obj *obj;
        int i, alerti;

        MPASS(state->all);
        NTSYNC_PRIV_ASSERT(state->owner);
        alerti = state->alert_event == NULL ? 0 : 1;

        for (i = 0; i < state->obj_count - alerti; i++) {
                obj = state->objs[i];
                obj->commit(obj, state, i);
        }
}

static void
ntsync_wait_link_waiters(struct ntsync_wait_state *state)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_waiter *waiter;
        int i;

        NTSYNC_PRIV_ASSERT(state->owner);

        for (i = 0; i < state->obj_count; i++) {
                obj = state->objs[i];
                waiter = &state->waiters[i];
                waiter->state = state;
                TAILQ_INSERT_TAIL(&obj->waiters, waiter, link);
        }
}

static void
ntsync_wait_unlink_waiters(struct ntsync_wait_state *state)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_waiter *waiter;
        int i;

        NTSYNC_PRIV_ASSERT(state->owner);

        for (i = 0; i < state->obj_count; i++) {
                obj = state->objs[i];
                waiter = &state->waiters[i];
                TAILQ_REMOVE(&obj->waiters, waiter, link);
        }
}

static void
ntsync_wait_post_commit(struct ntsync_wait_state *state)
{
        struct ntsync_obj *obj;
        int alerti, i;

        NTSYNC_PRIV_ASSERT(state->owner);

        alerti = state->alert_event == NULL ? 0 : 1;
        for (i = 0; i < state->obj_count - alerti; i++) {
                obj = state->objs[i];
                obj->post_commit(obj, state, i);
        }
}

static void
ntsync_wait_check_ready(struct ntsync_wait_state *state)
{
        struct ntsync_obj *ae;
        int index;
        bool stop;

        NTSYNC_PRIV_ASSERT(state->owner);

        if (state->ready)
                return;

        if (state->all) {
                stop = false;
                if (ntsync_wait_all_prepare(state, &stop)) {
                        MPASS(!stop);
                        ntsync_wait_all_commit(state);
                        state->ready = true;
                        ntsync_wait_post_commit(state);
                } else if (stop) {
                        /* skip */
                } else if (state->alert_event != NULL) {
                        ae = &state->alert_event->obj;
                        index = state->obj_count - 1;
                        if (ae->is_signaled(ae, state, index)) {
                                state->index = index;
                                ae->consume(ae, state, index);
                                ae->post_commit(ae, state, index);
                                state->ready = true;
                        }
                }
        } else {        /* state->any */
                if (ntsync_wait_any(state))
                        state->ready = true;
        }
}

/*
 * Perform the wait.  Errors returned through state->error still
 * result in the copyout of the ntsync_wait_args after the wait, while
 * errors returned as the function result do not.
 */
static int
ntsync_wait_locked(struct ntsync_wait_state *state, struct thread *td)
{
        int error;

        NTSYNC_PRIV_ASSERT(state->owner);

        for (;;) {
                ntsync_wait_check_ready(state);
                if (state->ready)
                        break;
                error = msleep_sbt(state, &state->owner->lock,
                    PCATCH, "ntsync", state->sb, state->prec,
                    C_ABSOLUTE /* | C_HARDCLOCK XXXKIB */);

                /*
                 * Check state->ready before checking error from
                 * msleep().  If there was a wake up that set the
                 * readiness before us receiving a signal or timeout,
                 * the objects states are modified to reflect wakeup.
                 * Due to this, ready should result in normal return.
                 */
                if (state->ready) {
                        error = 0;
                        break;
                }

                if (error != 0) {
                        if (error == EAGAIN)
                                error = ETIMEDOUT;
                        break;
                }
        }
        return (error);
}

static int
ntsync_wait(struct ntsync_wait_state *state, struct thread *td)
{
        int error;

        NTSYNC_PRIV_LOCK(state->owner);
        ntsync_wait_link_waiters(state);
        error = ntsync_wait_locked(state, td);
        ntsync_wait_unlink_waiters(state);
        NTSYNC_PRIV_UNLOCK(state->owner);
        return (error);
}

static void
ntsync_wakeup_waiters(struct ntsync_obj *obj)
{
        struct ntsync_obj_waiter *w;

        NTSYNC_PRIV_ASSERT(obj->owner);

        TAILQ_FOREACH(w, &obj->waiters, link) {
                ntsync_wait_check_ready(w->state);
                if (w->state->ready)
                        wakeup(w->state);
        }
}

static int
ntsync_create_obj(struct ntsync_obj *obj, struct fileops *fops,
    struct ntsync_priv *priv, struct thread *td)
{
        struct file *fp;
        int error, fd;

        error = falloc_noinstall(td, &fp);
        if (error != 0)
                return (error);

        /*
         * The priv fd cannot be closed during object creation since
         * it is fget-ed around ioctl.
         */
        obj->owner = priv;

        TAILQ_INIT(&obj->waiters);
        NTSYNC_PRIV_LOCK(priv);
        MPASS(!priv->closed);
        if (priv->objs_cnt == UINT_MAX) {
                NTSYNC_PRIV_UNLOCK(priv);
                fdrop(fp, td);
                return (EMFILE);
        }
        priv->objs_cnt++;
        NTSYNC_PRIV_UNLOCK(priv);

        finit(fp, FREAD | FWRITE, DTYPE_NTSYNC, obj, fops);
        error = finstall(td, fp, &fd, 0, NULL);
        if (error != 0) {
                finit(fp, FREAD | FWRITE, DTYPE_NONE, NULL, &badfileops);
                NTSYNC_PRIV_LOCK(priv);
                MPASS(priv->objs_cnt > 0);
                priv->objs_cnt--;
                NTSYNC_PRIV_UNLOCK(priv);
        } else {
                td->td_retval[0] = fd;
        }
        fdrop(fp, td);
        return (error);
}

static void
ntsync_close_obj(struct ntsync_obj *obj, struct thread *td)
{
        struct ntsync_priv *priv;

        priv = obj->owner;
        NTSYNC_PRIV_LOCK(priv);
        MPASS(priv->objs_cnt > 0);
        MPASS(TAILQ_EMPTY(&obj->waiters));
        priv->objs_cnt--;
        NTSYNC_PRIV_UNLOCK(priv);
        ntsync_free_priv(priv);
}

static bool
ntsync_sem_is_signaled(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_sem *sem;

        MPASS(obj->type == NTSYNC_OBJ_SEM);
        NTSYNC_PRIV_ASSERT(obj->owner);
        sem = OBJ_TO_SEM(obj);
        return (sem->a.count != 0);
}

static void
ntsync_sem_consume(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_sem *sem;

        MPASS(obj->type == NTSYNC_OBJ_SEM);
        NTSYNC_PRIV_ASSERT(obj->owner);
        sem = OBJ_TO_SEM(obj);
        MPASS(sem->a.count != 0);
        sem->a.count--;
}

static bool
ntsync_sem_prepare(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index, bool *stop)
{
        struct ntsync_obj_sem *sem;

        MPASS(obj->type == NTSYNC_OBJ_SEM);
        NTSYNC_PRIV_ASSERT(obj->owner);
        sem = OBJ_TO_SEM(obj);
        if (sem->a.count == 0)
                return (false);
        sem->a1 = sem->a;
        sem->a1.count--;
        return (true);
}

static void
ntsync_sem_commit(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_sem *sem;

        MPASS(obj->type == NTSYNC_OBJ_SEM);
        NTSYNC_PRIV_ASSERT(obj->owner);
        sem = OBJ_TO_SEM(obj);
        sem->a = sem->a1;
}

static void
ntsync_sem_post_commit(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
}

static int
ntsync_sem_close(struct file *fp, struct thread *td)
{
        struct ntsync_obj_sem *sem;

        sem = fp->f_data;
        ntsync_close_obj(&sem->obj, td);
        free(sem, M_NTSYNC);
        return (0);
}

int
ntsync_sem_release(struct thread *td, struct file *fp, uint32_t *val)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_sem *sem;
        struct ntsync_priv *priv;
        uint32_t prev;
        int error;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_SEM)
                return (EINVAL);
        sem = OBJ_TO_SEM(obj);
        priv = obj->owner;
        error = 0;

        NTSYNC_PRIV_LOCK(priv);
        if (sem->a.count + *val < sem->a.count ||
            sem->a.count + *val > sem->a.max) {
                error = EOVERFLOW;
        } else {
                prev = sem->a.count;
                sem->a.count += *val;
                if (sem->a.count != 0)
                        ntsync_wakeup_waiters(obj);
                *val = prev;
        }
        NTSYNC_PRIV_UNLOCK(priv);
        return (error);
}

int
ntsync_sem_read(struct thread *td, struct file *fp, struct ntsync_sem_args *a)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_sem *sem;
        struct ntsync_priv *priv;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_SEM)
                return (EINVAL);
        sem = OBJ_TO_SEM(obj);
        priv = obj->owner;
        NTSYNC_PRIV_LOCK(priv);
        *a = sem->a;
        NTSYNC_PRIV_UNLOCK(priv);
        return (0);
}

static int
ntsync_sem_ioctl(struct file *fp, u_long com, void *data,
    struct ucred *active_cred, struct thread *td)
{
        int error;

        switch (com) {
        case NTSYNC_IOC_SEM_RELEASE:
                error = ntsync_sem_release(td, fp, data);
                break;
        case NTSYNC_IOC_SEM_READ:
                error = ntsync_sem_read(td, fp, data);
                break;
        default:
                error = ENOTTY;
                break;
        }
        return (error);
}

static int
ntsync_sem_stat(struct file *fp, struct stat *sbp, struct ucred *cred)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_sem *sem;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_SEM);
        sem = OBJ_TO_SEM(obj);

        memset(sbp, 0, sizeof(*sbp));
        sbp->st_mode = S_IFREG /* XXXKIB */ | S_IRUSR | S_IWUSR;
        NTSYNC_PRIV_LOCK(obj->owner);
        sbp->st_size = sem->a.max;
        sbp->st_nlink = sem->a.count;
        NTSYNC_PRIV_UNLOCK(obj->owner);
        return (0);
}

static int
ntsync_sem_fill_kinfo(struct file *fp, struct kinfo_file *kif,
    struct filedesc *fdp)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_sem *sem;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_SEM);
        sem = OBJ_TO_SEM(obj);

        kif->kf_type = KF_TYPE_NTSYNC;
        kif->kf_un.kf_ntsync.kf_ntsync_type = KF_NTSYNC_TYPE_SEM;
        kif->kf_un.kf_ntsync.kf_ntsync_dev = (uintptr_t)obj->owner;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_sem.count = sem->a.count;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_sem.max = sem->a.max;
        return (0);
}

struct fileops ntsync_sem_fops = {
        .fo_read = invfo_rdwr,
        .fo_write = invfo_rdwr,
        .fo_truncate = invfo_truncate,
        .fo_ioctl = ntsync_sem_ioctl,
        .fo_poll = invfo_poll,
        .fo_kqfilter = invfo_kqfilter,
        .fo_stat = ntsync_sem_stat,
        .fo_close = ntsync_sem_close,
        .fo_chmod = invfo_chmod,
        .fo_chown = invfo_chown,
        .fo_sendfile = invfo_sendfile,
        .fo_fill_kinfo = ntsync_sem_fill_kinfo,
        .fo_flags = DFLAG_PASSABLE,
};

static int
ntsync_create_sem(struct ntsync_sem_args *args, struct ntsync_priv *priv,
    struct thread *td)
{
        struct ntsync_obj_sem *sem;
        int error;

        if (args->count > args->max)
                return (EINVAL);

        sem = malloc(sizeof(*sem), M_NTSYNC, M_WAITOK | M_ZERO);
        sem->obj.type = NTSYNC_OBJ_SEM;
        sem->obj.is_signaled = ntsync_sem_is_signaled;
        sem->obj.consume = ntsync_sem_consume;
        sem->obj.prepare = ntsync_sem_prepare;
        sem->obj.commit = ntsync_sem_commit;
        sem->obj.post_commit = ntsync_sem_post_commit;
        sem->a = *args;

        error = ntsync_create_obj(&sem->obj, &ntsync_sem_fops, priv, td);
        if (error != 0)
                free(sem, M_NTSYNC);

        return (error);
}

static bool
ntsync_mutex_can_lock(struct ntsync_obj_mutex *mutex, uint32_t nwa_owner)
{
        return (mutex->a.owner == 0 ||
            (mutex->a.owner == nwa_owner && mutex->a.count < UINT32_MAX) ||
            mutex->abandoned);
}

static bool
ntsync_mutex_is_signaled(struct ntsync_obj *obj,
    struct ntsync_wait_state *state, int index)
{
        struct ntsync_obj_mutex *mutex;

        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        NTSYNC_PRIV_ASSERT(obj->owner);
        mutex = OBJ_TO_MUTEX(obj);
        return (ntsync_mutex_can_lock(mutex, state->nwa->owner));
}

static void
ntsync_mutex_consume(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_mutex *mutex;

        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        NTSYNC_PRIV_ASSERT(obj->owner);
        mutex = OBJ_TO_MUTEX(obj);
        MPASS(ntsync_mutex_can_lock(mutex, state->nwa->owner));
        if (state->nwa->owner == 0) {
                state->error = EINVAL;
                return;
        }
        if (mutex->a.owner == 0 || mutex->abandoned)
                mutex->a.count = 1;
        else
                mutex->a.count++;
        mutex->a.owner = state->nwa->owner;
        if (mutex->abandoned && state->error == 0)
                state->error = EOWNERDEAD;
        mutex->abandoned = false;
}

static bool
ntsync_mutex_prepare(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index, bool *stop)
{
        struct ntsync_obj_mutex *mutex;

        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        NTSYNC_PRIV_ASSERT(obj->owner);
        mutex = OBJ_TO_MUTEX(obj);
        if (!ntsync_mutex_can_lock(mutex, state->nwa->owner))
                return (false);
        if (state->nwa->owner == 0) {
                state->error = EINVAL;
                *stop = true;
                return (false);
        }
        mutex->a1 = mutex->a;
        if (mutex->a.owner == 0 || mutex->abandoned)
                mutex->a1.count = 1;
        else
                mutex->a1.count++;
        mutex->a1.owner = state->nwa->owner;
        return (true);
}

static void
ntsync_mutex_commit(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_mutex *mutex;

        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        NTSYNC_PRIV_ASSERT(obj->owner);
        mutex = OBJ_TO_MUTEX(obj);
        mutex->a = mutex->a1;
        if (mutex->abandoned)
                state->error = EOWNERDEAD;
        mutex->abandoned = false;
}

static void
ntsync_mutex_post_commit(struct ntsync_obj *obj,
    struct ntsync_wait_state *state, int index)
{
}

static int
ntsync_mutex_close(struct file *fp, struct thread *td)
{
        struct ntsync_obj_mutex *mutex;

        mutex = fp->f_data;
        ntsync_close_obj(&mutex->obj, td);
        free(mutex, M_NTSYNC);
        return (0);
}

int
ntsync_mutex_unlock(struct thread *td, struct file *fp,
    struct ntsync_mutex_args *a)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_mutex *mutex;
        struct ntsync_priv *priv;
        uint32_t prev;
        int error;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_MUTEX)
                return (EINVAL);
        mutex = OBJ_TO_MUTEX(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        if (a->owner == 0) {
                error = EINVAL;
        } else if (a->owner != mutex->a.owner) {
                error = EPERM;
        } else {
                error = 0;
                prev = mutex->a.count;
                MPASS(mutex->a.count > 0);
                mutex->a.count--;
                a->count = prev;
                if (mutex->a.count == 0) {
                        mutex->a.owner = 0;
                        ntsync_wakeup_waiters(obj);
                }
        }
        NTSYNC_PRIV_UNLOCK(priv);
        return (error);
}

int
ntsync_mutex_kill(struct thread *td, struct file *fp, uint32_t val)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_mutex *mutex;
        struct ntsync_priv *priv;
        int error;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_MUTEX)
                return (EINVAL);
        mutex = OBJ_TO_MUTEX(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        if (val == 0) {
                error = EINVAL;
        } else if (mutex->a.owner != val) {
                error = EPERM;
        } else {
                error = 0;
                mutex->a.owner = 0;
                mutex->a.count = 0;
                mutex->abandoned = true;
                ntsync_wakeup_waiters(obj);
        }
        NTSYNC_PRIV_UNLOCK(priv);
        return (error);
}

int
ntsync_mutex_read(struct thread *td, struct file *fp,
    struct ntsync_mutex_args *a, bool *doco)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_mutex *mutex;
        struct ntsync_priv *priv;
        int error;

        *doco = false;
        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_MUTEX)
                return (EINVAL);
        mutex = OBJ_TO_MUTEX(obj);
        priv = obj->owner;
        error = 0;

        NTSYNC_PRIV_LOCK(priv);
        *a = mutex->a;
        if (mutex->abandoned)
                error = EOWNERDEAD;
        NTSYNC_PRIV_UNLOCK(priv);
        *doco = true;
        return (error);
}

static int
ntsync_mutex_ioctl(struct file *fp, u_long com, void *data,
    struct ucred *active_cred, struct thread *td)
{
        struct ntsync_mutex_args aa;
        int error, error1;
        bool doco;

        doco = false;
        switch (com) {
        case NTSYNC_IOC_MUTEX_UNLOCK:
                error = ntsync_mutex_unlock(td, fp, data);
                break;
        case NTSYNC_IOC_MUTEX_KILL:
                error = ntsync_mutex_kill(td, fp, *(uint32_t *)data);
                break;
        case NTSYNC_IOC_MUTEX_READ:
                error = ntsync_mutex_read(td, fp, &aa, &doco);
                if (doco) {
                        error1 = ntsync_ioctl_copyout(td, &aa, sizeof(aa));
                        if (error1 != 0)
                                error = error1;
                }
                break;
        default:
                error = ENOTTY;
                break;
        }
        return (error);
}

static int
ntsync_mutex_stat(struct file *fp, struct stat *sbp, struct ucred *cred)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_mutex *mutex;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        mutex = OBJ_TO_MUTEX(obj);

        memset(sbp, 0, sizeof(*sbp));
        sbp->st_mode = S_IFREG /* XXXKIB */ | S_IRUSR | S_IWUSR;
        NTSYNC_PRIV_LOCK(obj->owner);
        sbp->st_size = mutex->a.owner;
        sbp->st_nlink = mutex->a.count;
        NTSYNC_PRIV_UNLOCK(obj->owner);
        return (0);
}

static int
ntsync_mutex_fill_kinfo(struct file *fp, struct kinfo_file *kif,
    struct filedesc *fdp)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_mutex *mutex;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_MUTEX);
        mutex = OBJ_TO_MUTEX(obj);

        kif->kf_type = KF_TYPE_NTSYNC;
        kif->kf_un.kf_ntsync.kf_ntsync_type = KF_NTSYNC_TYPE_MUTEX;
        kif->kf_un.kf_ntsync.kf_ntsync_dev = (uintptr_t)obj->owner;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_mutex.owner =
            mutex->a.owner;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_mutex.count =
            mutex->a.count;
        return (0);
}

struct fileops ntsync_mutex_fops = {
        .fo_read = invfo_rdwr,
        .fo_write = invfo_rdwr,
        .fo_truncate = invfo_truncate,
        .fo_ioctl = ntsync_mutex_ioctl,
        .fo_poll = invfo_poll,
        .fo_kqfilter = invfo_kqfilter,
        .fo_stat = ntsync_mutex_stat,
        .fo_close = ntsync_mutex_close,
        .fo_chmod = invfo_chmod,
        .fo_chown = invfo_chown,
        .fo_sendfile = invfo_sendfile,
        .fo_fill_kinfo = ntsync_mutex_fill_kinfo,
        .fo_flags = DFLAG_PASSABLE,
};

static int
ntsync_create_mutex(struct ntsync_mutex_args *args, struct ntsync_priv *priv,
    struct thread *td)
{
        struct ntsync_obj_mutex *mutex;
        int error;

        if ((args->owner != 0 && args->count == 0) ||
            (args->owner == 0 && args->count != 0))
                return (EINVAL);

        mutex = malloc(sizeof(*mutex), M_NTSYNC, M_WAITOK | M_ZERO);
        mutex->obj.type = NTSYNC_OBJ_MUTEX;
        mutex->obj.is_signaled = ntsync_mutex_is_signaled;
        mutex->obj.consume = ntsync_mutex_consume;
        mutex->obj.prepare = ntsync_mutex_prepare;
        mutex->obj.commit = ntsync_mutex_commit;
        mutex->obj.post_commit = ntsync_mutex_post_commit;
        mutex->a = *args;
        mutex->abandoned = false;

        error = ntsync_create_obj(&mutex->obj, &ntsync_mutex_fops, priv, td);
        if (error != 0)
                free(mutex, M_NTSYNC);

        return (error);
}

static bool
ntsync_event_is_signaled(struct ntsync_obj *obj,
    struct ntsync_wait_state *state, int index)
{
        struct ntsync_obj_event *event;

        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        NTSYNC_PRIV_ASSERT(obj->owner);
        event = OBJ_TO_EVENT(obj);
        return (event->a.signaled != 0);
}

static void
ntsync_event_consume(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_event *event;

        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        NTSYNC_PRIV_ASSERT(obj->owner);
        MPASS(ntsync_event_is_signaled(obj, state, index));

        event = OBJ_TO_EVENT(obj);
        if (event->a.manual == 0)
                event->a.signaled = 0;
}

static bool
ntsync_event_prepare(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index, bool *stop)
{
        struct ntsync_obj_event *event;

        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        NTSYNC_PRIV_ASSERT(obj->owner);
        event = OBJ_TO_EVENT(obj);
        if (!ntsync_event_is_signaled(obj, state, index))
                return (false);
        event->a1 = event->a;
        return (true);
}

static void
ntsync_event_commit(struct ntsync_obj *obj, struct ntsync_wait_state *state,
    int index)
{
        struct ntsync_obj_event *event;

        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        NTSYNC_PRIV_ASSERT(obj->owner);
        event = OBJ_TO_EVENT(obj);
        event->a = event->a1;
        if (event->pulse && event->a.manual == 0) {
                event->a.signaled = 0;
                event->pulse = false;
        }
}

static void
ntsync_event_post_commit(struct ntsync_obj *obj,
    struct ntsync_wait_state *state, int index)
{
        struct ntsync_obj_event *event;

        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        NTSYNC_PRIV_ASSERT(obj->owner);
        event = OBJ_TO_EVENT(obj);
        if (event->a.manual == 0)
                event->a.signaled = 0;
}

static int
ntsync_event_close(struct file *fp, struct thread *td)
{
        struct ntsync_obj_event *event;

        event = fp->f_data;
        ntsync_close_obj(&event->obj, td);
        free(event, M_NTSYNC);
        return (0);
}

int
ntsync_event_set(struct thread *td, struct file *fp, uint32_t *val)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;
        struct ntsync_priv *priv;
        uint32_t prev;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_EVENT)
                return (EINVAL);
        event = OBJ_TO_EVENT(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        prev = event->a.signaled;
        event->a.signaled = 1;
        ntsync_wakeup_waiters(obj);
        NTSYNC_PRIV_UNLOCK(priv);

        *val = prev;
        return (0);
}

int
ntsync_event_reset(struct thread *td, struct file *fp, uint32_t *val)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;
        struct ntsync_priv *priv;
        uint32_t prev;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_EVENT)
                return (EINVAL);
        event = OBJ_TO_EVENT(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        prev = event->a.signaled;
        event->a.signaled = 0;
        NTSYNC_PRIV_UNLOCK(priv);

        *val = prev;
        return (0);
}

int
ntsync_event_pulse(struct thread *td, struct file *fp, uint32_t *val)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;
        struct ntsync_priv *priv;
        uint32_t prev;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_EVENT)
                return (EINVAL);
        event = OBJ_TO_EVENT(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        prev = event->a.signaled;
        event->a.signaled = 1;
        event->pulse = true;
        ntsync_wakeup_waiters(obj);
        event->a.signaled = 0;
        event->pulse = false;
        NTSYNC_PRIV_UNLOCK(priv);

        *val = prev;
        return (0);
}

int
ntsync_event_read(struct thread *td, struct file *fp,
    struct ntsync_event_args *a)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;
        struct ntsync_priv *priv;

        obj = fp->f_data;
        if (obj->type != NTSYNC_OBJ_EVENT)
                return (EINVAL);
        event = OBJ_TO_EVENT(obj);
        priv = obj->owner;

        NTSYNC_PRIV_LOCK(priv);
        *a = event->a;
        NTSYNC_PRIV_UNLOCK(priv);

        return (0);
}

static int
ntsync_event_ioctl(struct file *fp, u_long com, void *data,
    struct ucred *active_cred, struct thread *td)
{
        int error;

        switch (com) {
        case NTSYNC_IOC_EVENT_SET:
                error = ntsync_event_set(td, fp, data);
                break;
        case NTSYNC_IOC_EVENT_RESET:
                error = ntsync_event_reset(td, fp, data);
                break;
        case NTSYNC_IOC_EVENT_PULSE:
                error = ntsync_event_pulse(td, fp, data);
                break;
        case NTSYNC_IOC_EVENT_READ:
                error = ntsync_event_read(td, fp, data);
                break;
        default:
                error = ENOTTY;
                break;
        }
        return (error);
}

static int
ntsync_event_stat(struct file *fp, struct stat *sbp, struct ucred *cred)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        event = OBJ_TO_EVENT(obj);

        memset(sbp, 0, sizeof(*sbp));
        sbp->st_mode = S_IFREG /* XXXKIB */ | S_IRUSR | S_IWUSR;
        NTSYNC_PRIV_LOCK(obj->owner);
        sbp->st_size = event->a.signaled;
        sbp->st_nlink = event->a.manual;
        NTSYNC_PRIV_UNLOCK(obj->owner);
        return (0);
}

static int
ntsync_event_fill_kinfo(struct file *fp, struct kinfo_file *kif,
    struct filedesc *fdp)
{
        struct ntsync_obj *obj;
        struct ntsync_obj_event *event;

        MPASS(fp->f_type == DTYPE_NTSYNC);
        obj = fp->f_data;
        MPASS(obj->type == NTSYNC_OBJ_EVENT);
        event = OBJ_TO_EVENT(obj);

        kif->kf_type = KF_TYPE_NTSYNC;
        kif->kf_un.kf_ntsync.kf_ntsync_type = KF_NTSYNC_TYPE_EVENT;
        kif->kf_un.kf_ntsync.kf_ntsync_dev = (uintptr_t)obj->owner;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_event.signaled =
                event->a.signaled;
        kif->kf_un.kf_ntsync.kf_ntsync_un.kf_ntsync_event.manual =
                event->a.manual;
        return (0);
}

struct fileops ntsync_event_fops = {
        .fo_read = invfo_rdwr,
        .fo_write = invfo_rdwr,
        .fo_truncate = invfo_truncate,
        .fo_ioctl = ntsync_event_ioctl,
        .fo_poll = invfo_poll,
        .fo_kqfilter = invfo_kqfilter,
        .fo_stat = ntsync_event_stat,
        .fo_close = ntsync_event_close,
        .fo_chmod = invfo_chmod,
        .fo_chown = invfo_chown,
        .fo_sendfile = invfo_sendfile,
        .fo_fill_kinfo = ntsync_event_fill_kinfo,
        .fo_flags = DFLAG_PASSABLE,
};

static int
ntsync_create_event(struct ntsync_event_args *args, struct ntsync_priv *priv,
    struct thread *td)
{
        struct ntsync_obj_event *event;
        int error;

        event = malloc(sizeof(*event), M_NTSYNC, M_WAITOK | M_ZERO);
        event->obj.type = NTSYNC_OBJ_EVENT;
        event->obj.is_signaled = ntsync_event_is_signaled;
        event->obj.consume = ntsync_event_consume;
        event->obj.prepare = ntsync_event_prepare;
        event->obj.commit = ntsync_event_commit;
        event->obj.post_commit = ntsync_event_post_commit;
        event->a = *args;

        error = ntsync_create_obj(&event->obj, &ntsync_event_fops, priv, td);
        if (error != 0)
                free(event, M_NTSYNC);

        return (error);
}

static void
ntsync_free_priv(struct ntsync_priv *priv)
{
        bool do_free;

        NTSYNC_PRIV_LOCK(priv);
        do_free = priv->closed && priv->objs_cnt == 0;
        NTSYNC_PRIV_UNLOCK(priv);
        if (do_free) {
                mtx_destroy(&priv->lock);
                free(priv, M_NTSYNC);
        }
}

static void
ntsync_priv_dtr(void *data)
{
        ntsync_free_priv(data);
}

static int
ntsync_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
        struct ntsync_priv *priv;

        priv = malloc(sizeof(*priv), M_NTSYNC, M_WAITOK);
        priv->closed = false;
        priv->objs_cnt = 0;
        mtx_init(&priv->lock, "ntsync", "ntsync", MTX_DEF | MTX_NEW);
        devfs_set_cdevpriv(priv, ntsync_priv_dtr);
        return (0);
}

static int
ntsync_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
        struct ntsync_priv *priv;
        void *a;
        int error;

        error = devfs_get_cdevpriv(&a);
        if (error == 0) {
                priv = a;
                NTSYNC_PRIV_LOCK(priv);
                priv->closed = true;
                NTSYNC_PRIV_UNLOCK(priv);
        }
        devfs_clear_cdevpriv();
        return (0);
}

static int
ntsync_wait_state_get(struct ntsync_wait_args *nwa, u_long cmd,
    struct ntsync_priv *owner, struct ntsync_wait_state **statep,
    struct thread *td)
{
        struct ntsync_wait_state *state;
        struct ntsync_obj *obj;
        int error, i, j;

        if (nwa->count > NTSYNC_MAX_WAIT_COUNT)
                return (EINVAL);
        if ((nwa->flags & ~NTSYNC_WAIT_REALTIME) != 0)
                return (EINVAL);

        state = malloc(sizeof(*state), M_NTSYNC, M_WAITOK | M_ZERO);
        state->nwa = nwa;
        state->owner = owner;
        state->all = cmd == NTSYNC_IOC_WAIT_ALL;
        state->any = !state->all;
        error = copyin((void *)(uintptr_t)nwa->objs, &state->fds[0],
            nwa->count * sizeof(state->fds[0]));
        if (error != 0)
                goto error_ret;

        i = 0;
        if (nwa->alert != 0) {
                error = fget_cap(td, nwa->alert, &cap_no_rights, NULL,
                    &state->fp_alert, NULL);
                if (error != 0) {
                        state->fp_alert = NULL;
                        goto error_out;
                }
                if (state->fp_alert->f_type != DTYPE_NTSYNC) {
                        error = EINVAL;
                        goto error_out;
                }
                obj = state->fp_alert->f_data;
                if (obj->type != NTSYNC_OBJ_EVENT || obj->owner != owner) {
                        error = EINVAL;
                        goto error_out;
                }
                state->alert_event = OBJ_TO_EVENT(obj);
        }

        for (; i < nwa->count; i++) {
                error = fget_cap(td, state->fds[i], &cap_no_rights, NULL,
                    &state->fps[i], NULL);
                if (error != 0) {
                        state->fps[i] = NULL;
                        goto error_out;
                }
                if (state->fps[i]->f_type != DTYPE_NTSYNC ||
                    (obj = state->fps[i]->f_data)->owner != owner) {
                        i++;
                        error = EINVAL;
                        goto error_out;
                }
        }

        state->obj_count = nwa->count;
        for (i = 0; i < nwa->count; i++)
                state->objs[i] = state->fps[i]->f_data;
        if (state->alert_event != NULL) {
                state->objs[i] = &state->alert_event->obj;
                state->obj_count++;
        }

        if (state->all) {
                /* Check no dups */
                for (i = 0; i < state->obj_count; i++) {
                        obj = state->objs[i];
                        for (j = i + 1; j < state->obj_count; j++) {
                                if (obj == state->objs[j]) {
                                        i = state->obj_count;
                                        error = EINVAL;
                                        goto error_out;
                                }
                        }
                }
        }

        state->prec = 0;
        if (nwa->timeout == UINT64_MAX) {
                state->sb = 0;
        } else {
                state->sb = nstosbt(nwa->timeout);
                if ((nwa->flags & NTSYNC_WAIT_REALTIME) != 0) {
                        struct bintime btb;

                        bintime(&btb);
                        state->sb -= bttosbt(btb);
                } else {
                        struct timespec ts;

                        nanouptime(&ts);
                        state->sb -= tstosbt(ts);
                }
                state->sb += sbinuptime();
        }

        *statep = state;
        return (0);

error_out:
        for (j = 0; j < i; j++)
                fdrop(state->fps[j], td);
        if (state->fp_alert != NULL)
                fdrop(state->fp_alert, td);
error_ret:
        free(state, M_NTSYNC);
        return (error);
}

static void
ntsync_wait_state_put(struct ntsync_wait_state *state, struct thread *td)
{
        int i;

        for (i = 0; i < state->nwa->count; i++)
                fdrop(state->fps[i], td);
        if (state->fp_alert != NULL)
                fdrop(state->fp_alert, td);
        free(state, M_NTSYNC);
}

static int
ntsync_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
    struct thread *td)
{
        struct ntsync_priv *owner;
        struct ntsync_wait_args *nwa;
        struct ntsync_wait_state *state;
        void *a;
        int error;

        error = devfs_get_cdevpriv(&a);
        if (error != 0)
                return (error);
        owner = a;

        switch (cmd) {
        case NTSYNC_IOC_CREATE_SEM:
                error = ntsync_create_sem((struct ntsync_sem_args *)data,
                    owner, td);
                break;
        case NTSYNC_IOC_CREATE_MUTEX:
                error = ntsync_create_mutex((struct ntsync_mutex_args *)data,
                    owner, td);
                break;
        case NTSYNC_IOC_CREATE_EVENT:
                error = ntsync_create_event((struct ntsync_event_args *)data,
                    owner, td);
                break;
        case NTSYNC_IOC_WAIT_ANY:
                nwa = (struct ntsync_wait_args *)data;
                error = ntsync_wait_state_get(nwa, cmd, owner, &state, td);
                if (error != 0)
                        break;
                error = ntsync_wait(state, td);
                if (error == 0) {
                        nwa->index = state->index;
                        error = ntsync_ioctl_copyout(td, nwa, sizeof(*nwa));
                        if (error == 0)
                                error = state->error;
                }
                ntsync_wait_state_put(state, td);
                break;
        case NTSYNC_IOC_WAIT_ALL:
                nwa = (struct ntsync_wait_args *)data;
                error = ntsync_wait_state_get(nwa, cmd, owner, &state, td);
                if (error != 0)
                        break;
                error = ntsync_wait(state, td);
                if (error == 0) {
                        nwa->index = state->index;
                        error = ntsync_ioctl_copyout(td, nwa, sizeof(*nwa));
                        if (error == 0)
                                error = state->error;
                }
                ntsync_wait_state_put(state, td);
                break;

        default:
                error = ENOTTY;
                break;
        }
        return (error);
}

struct cdevsw ntsync_cdevsw = {
        .d_version =    D_VERSION,
        .d_flags =      0,
        .d_open =       ntsync_open,
        .d_close =      ntsync_close,
        .d_ioctl =      ntsync_ioctl,
        .d_name =       "ntsync",
};

static int
ntsync_modevent(module_t mod __unused, int type, void *data __unused)
{
        struct make_dev_args mda;
        int error;

        error = 0;
        switch (type) {
        case MOD_LOAD:
                make_dev_args_init(&mda);
                mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
                mda.mda_devsw = &ntsync_cdevsw;
                mda.mda_uid = UID_ROOT;
                mda.mda_gid = GID_GAMES;
                mda.mda_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
                    S_IROTH | S_IWOTH;

                error = make_dev_s(&mda, &ntsync_cdev, "ntsync");
                if (error != 0) {
                        printf("cannot create ntsync dev err %d\n", error);
                        break;
                }
                if (bootverbose)
                        printf("ntsync\n");
                break;

        case MOD_UNLOAD:
                destroy_dev(ntsync_cdev);
                break;

        case MOD_SHUTDOWN:
                break;

        default:
                error = EOPNOTSUPP;
        }

        return (error);
}

DEV_MODULE(ntsync, ntsync_modevent, NULL);
MODULE_VERSION(ntsync, 1);