git: 43702cfc0ccb - stable/15 - ntsync(4)

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Sat, 13 Jun 2026 01:03:39 UTC
The branch stable/15 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=43702cfc0ccbd9b357243aedcf18e19d94a64ef9

commit 43702cfc0ccbd9b357243aedcf18e19d94a64ef9
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-05-11 09:49:13 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-06-13 00:58:31 +0000

    ntsync(4)
    
    (cherry picked from commit 03ca6dbdb80da79408f135d823fbd9a00fd4f25b)
---
 sys/dev/ntsync/ntsync.c     | 1379 +++++++++++++++++++++++++++++++++++++++++++
 sys/dev/ntsync/ntsync.h     |   66 +++
 sys/dev/ntsync/ntsyncvar.h  |  119 ++++
 sys/modules/Makefile        |    1 +
 sys/modules/ntsync/Makefile |    6 +
 sys/sys/file.h              |    1 +
 6 files changed, 1572 insertions(+)

diff --git a/sys/dev/ntsync/ntsync.c b/sys/dev/ntsync/ntsync.c
new file mode 100644
index 000000000000..8b0984d12c75
--- /dev/null
+++ b/sys/dev/ntsync/ntsync.c
@@ -0,0 +1,1379 @@
+/*
+ * 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 <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, 0,
+		    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) {
+		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)
+{
+	// XXXKIB
+	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)
+{
+	// XXXKIB
+	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)
*** 653 LINES SKIPPED ***