Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F157554794
D57038.id178165.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
49 KB
Referenced Files
None
Subscribers
None
D57038.id178165.diff
View Options
diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist
--- a/etc/mtree/BSD.include.dist
+++ b/etc/mtree/BSD.include.dist
@@ -235,6 +235,8 @@
mpilib
..
..
+ ntsync
+ ..
nvme
..
nvmf
diff --git a/include/Makefile b/include/Makefile
--- a/include/Makefile
+++ b/include/Makefile
@@ -51,7 +51,7 @@
LSUBDIRS= dev/acpica dev/agp dev/ciss dev/filemon dev/firewire \
dev/hwpmc dev/hyperv \
- dev/ic dev/iicbus dev/io dev/mfi dev/mmc \
+ dev/ic dev/iicbus dev/io dev/mfi dev/mmc dev/ntsync \
dev/ofw dev/pbio dev/pci ${_dev_powermac_nvram} dev/ppbus dev/pwm \
dev/smbus dev/speaker dev/tcp_log dev/veriexec dev/vkbd dev/wg \
fs/devfs fs/fdescfs fs/msdosfs fs/nfs fs/nullfs \
diff --git a/sys/dev/ntsync/linux_ntsync.h b/sys/dev/ntsync/linux_ntsync.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/ntsync/linux_ntsync.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Kernel support for NT synchronization primitive emulation
+ *
+ * Copyright (C) 2021-2022 Elizabeth Figura <zfigura@codeweavers.com>
+ */
+
+#ifndef __LINUX_NTSYNC_H
+#define __LINUX_NTSYNC_H
+
+#include <sys/types.h>
+
+typedef uint32_t __u32;
+typedef uint64_t __u64;
+
+struct linux_ntsync_sem_args {
+ __u32 count;
+ __u32 max;
+};
+
+struct linux_ntsync_mutex_args {
+ __u32 owner;
+ __u32 count;
+};
+
+struct linux_ntsync_event_args {
+ __u32 manual;
+ __u32 signaled;
+};
+
+#define LINUX_NTSYNC_WAIT_REALTIME 0x1
+
+struct linux_ntsync_wait_args {
+ __u64 timeout;
+ __u64 objs;
+ __u32 count;
+ __u32 index;
+ __u32 flags;
+ __u32 owner;
+ __u32 alert;
+ __u32 pad;
+};
+
+#define LNTSYNC_IOC_CREATE_SEM 0x40084e80
+#define LNTSYNC_IOC_WAIT_ANY 0xc0284e82
+#define LNTSYNC_IOC_WAIT_ALL 0xc0284e83
+#define LNTSYNC_IOC_CREATE_MUTEX 0x40084e84
+#define LNTSYNC_IOC_CREATE_EVENT 0x40084e87
+#define LNTSYNC_IOC_SEM_RELEASE 0xc0044e81
+#define LNTSYNC_IOC_MUTEX_UNLOCK 0xc0084e85
+#define LNTSYNC_IOC_MUTEX_KILL 0x40044e86
+#define LNTSYNC_IOC_EVENT_SET 0x80044e88
+#define LNTSYNC_IOC_EVENT_RESET 0x80044e89
+#define LNTSYNC_IOC_EVENT_PULSE 0x80044e8a
+#define LNTSYNC_IOC_SEM_READ 0x80084e8b
+#define LNTSYNC_IOC_MUTEX_READ 0x80084e8c
+#define LNTSYNC_IOC_EVENT_READ 0x80084e8d
+
+#define LNTSYNC_IOCTL_MIN 0x4e80
+#define LNTSYNC_IOCTL_MAX 0x4eff
+
+#endif
diff --git a/sys/dev/ntsync/linux_ntsync.c b/sys/dev/ntsync/linux_ntsync.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/ntsync/linux_ntsync.c
@@ -0,0 +1,301 @@
+/*
+ * 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/file.h>
+#include <sys/filedesc.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/vnode.h>
+#include <dev/ntsync/ntsyncvar.h>
+
+#include <machine/../linux/linux.h>
+#include <machine/../linux/linux_proto.h>
+#include <compat/linux/linux_common.h>
+#include <compat/linux/linux_ioctl.h>
+#include <dev/ntsync/linux_ntsync.h>
+
+MODULE_DEPEND(linux_ntsync, linux, 1, 1, 1);
+MODULE_DEPEND(linux_ntsync, ntsync, 1, 1, 1);
+
+static linux_ioctl_function_t linux_ntsync_ioctl;
+static struct linux_ioctl_handler linux_ntsync_handler = {linux_ntsync_ioctl,
+ LNTSYNC_IOCTL_MIN, LNTSYNC_IOCTL_MAX};
+
+static int
+linux_ntsync_modevent(module_t mod __unused, int type, void *data __unused)
+{
+ int error;
+
+ error = 0;
+ switch(type) {
+ case MOD_LOAD:
+ error = linux_ioctl_register_handler(&linux_ntsync_handler);
+ if (error != 0) {
+ printf("linux_ntsync: cannot register ioctl handler, "
+ "error %d\n", error);
+ } else if (bootverbose)
+ printf("linux_ntsync\n");
+ break;
+
+ case MOD_UNLOAD:
+ linux_ioctl_unregister_handler(&linux_ntsync_handler);
+ break;
+
+ case MOD_SHUTDOWN:
+ break;
+
+ default:
+ error = EOPNOTSUPP;
+ }
+
+ return (error);
+}
+
+DEV_MODULE(linux_ntsync, linux_ntsync_modevent, NULL);
+MODULE_VERSION(linux_ntsync, 1);
+
+/* XXXKIB no translation of structs */
+static void
+ntsync_lsa_to_sa(struct ntsync_sem_args *sa,
+ const struct linux_ntsync_sem_args *lsa)
+{
+ memcpy(sa, lsa, sizeof(*sa));
+}
+
+static void
+ntsync_sa_to_lsa(struct linux_ntsync_sem_args *lsa,
+ const struct ntsync_sem_args *sa)
+{
+ memcpy(lsa, sa, sizeof(*lsa));
+}
+
+static void
+ntsync_lma_to_ma(struct ntsync_mutex_args *ma,
+ const struct linux_ntsync_mutex_args *lma)
+{
+ memcpy(ma, lma, sizeof(*ma));
+}
+
+static void
+ntsync_ma_to_lma(struct linux_ntsync_mutex_args *ma,
+ const struct ntsync_mutex_args *lma)
+{
+ memcpy(ma, lma, sizeof(*ma));
+}
+
+static void
+ntsync_lea_to_ea(struct ntsync_event_args *ea,
+ const struct linux_ntsync_event_args *lea)
+{
+ memcpy(ea, lea, sizeof(*ea));
+}
+
+static void
+ntsync_ea_to_lea(struct linux_ntsync_event_args *lea,
+ const struct ntsync_event_args *ea)
+{
+ memcpy(lea, ea, sizeof(*lea));
+}
+
+static void
+ntsync_lwa_to_wa(struct ntsync_wait_args *wa,
+ const struct linux_ntsync_wait_args *lwa)
+{
+ memcpy(wa, lwa, sizeof(*wa));
+}
+
+static void
+ntsync_wa_to_lwa(struct linux_ntsync_wait_args *lwa,
+ const struct ntsync_wait_args *wa)
+{
+ memcpy(lwa, wa, sizeof(*lwa));
+}
+
+static int
+linux_ntsync_cdev_ioctl(struct thread *td, u_long cmd, void *data,
+ struct file *fp)
+{
+ struct cdev *dev;
+ struct cdevsw *dsw;
+ struct vnode *vp;
+ struct file *fpop;
+ int error, ref;
+
+ /* XXXKIB FREAD | FWRITE */
+ if (fp->f_type != DTYPE_VNODE)
+ return (error = ENOIOCTL);
+
+ vp = fp->f_vnode;
+ if (vp->v_type != VCHR)
+ return (ENOIOCTL);
+ dev = vp->v_rdev;
+ dsw = dev_refthread(dev, &ref);
+ if (dsw == NULL)
+ return (ENXIO);
+ if (dsw != &ntsync_cdevsw) {
+ error = ENOIOCTL;
+ } else {
+ fpop = td->td_fpop;
+ td->td_fpop = fp;
+ error = dsw->d_ioctl(dev, cmd, data, 0, td);
+ td->td_fpop = fpop;
+ }
+ dev_relthread(dev, ref);
+ return (error);
+}
+
+static int
+linux_ntsync_ioctl(struct thread *td, struct linux_ioctl_args *args)
+{
+ struct file *fp;
+ void *data;
+ struct linux_ntsync_sem_args lsa;
+ struct linux_ntsync_mutex_args lma;
+ struct linux_ntsync_event_args lea;
+ struct linux_ntsync_wait_args lwa;
+ struct ntsync_sem_args sa;
+ struct ntsync_mutex_args ma;
+ struct ntsync_event_args ea;
+ struct ntsync_wait_args wa;
+ uint32_t val;
+ int error, error1, lcmd;
+
+ lcmd = args->cmd;
+ data = (void *)args->arg;
+
+ error = fget_cap(td, args->fd, &cap_no_rights, NULL, &fp, NULL);
+ if (error != 0)
+ goto out;
+
+ switch (lcmd) {
+ case LNTSYNC_IOC_CREATE_SEM:
+ error = copyin(data, &lsa, sizeof(lsa));
+ ntsync_lsa_to_sa(&sa, &lsa);
+ if (error == 0) {
+ error = linux_ntsync_cdev_ioctl(td,
+ NTSYNC_IOC_CREATE_SEM, &sa, fp);
+ }
+ break;
+ case LNTSYNC_IOC_CREATE_MUTEX:
+ error = copyin(data, &lma, sizeof(lma));
+ ntsync_lma_to_ma(&ma, &lma);
+ if (error == 0) {
+ error = linux_ntsync_cdev_ioctl(td,
+ NTSYNC_IOC_CREATE_MUTEX, &ma, fp);
+ }
+ break;
+ case LNTSYNC_IOC_CREATE_EVENT:
+ error = copyin(data, &lea, sizeof(lea));
+ ntsync_lea_to_ea(&ea, &lea);
+ if (error == 0) {
+ error = linux_ntsync_cdev_ioctl(td,
+ NTSYNC_IOC_CREATE_EVENT, &ea, fp);
+ }
+ break;
+ case LNTSYNC_IOC_WAIT_ANY:
+ error = copyin(data, &lwa, sizeof(lwa));
+ ntsync_lwa_to_wa(&wa, &lwa);
+ if (error == 0) {
+ error = linux_ntsync_cdev_ioctl(td,
+ NTSYNC_IOC_WAIT_ANY, &wa, fp);
+ if (error == 0 || error == EOWNERDEAD) {
+ ntsync_wa_to_lwa(&lwa, &wa);
+ error1 = copyout(&lwa, data, sizeof(lwa));
+ if (error == 0)
+ error = error1;
+ }
+ }
+ break;
+ case LNTSYNC_IOC_WAIT_ALL:
+ error = copyin(data, &lwa, sizeof(lwa));
+ ntsync_lwa_to_wa(&wa, &lwa);
+ if (error == 0) {
+ error = linux_ntsync_cdev_ioctl(td,
+ NTSYNC_IOC_WAIT_ALL, &wa, fp);
+ if (error == 0 || error == EOWNERDEAD) {
+ ntsync_wa_to_lwa(&lwa, &wa);
+ error1 = copyout(&lwa, data, sizeof(lwa));
+ if (error == 0)
+ error = error1;
+ }
+ }
+ break;
+ case LNTSYNC_IOC_SEM_RELEASE:
+ error = copyin(data, &val, sizeof(val));
+ if (error == 0) {
+ error = ntsync_sem_release(td, fp, &val);
+ if (error == 0)
+ error = copyout(&val, data, sizeof(val));
+ }
+ break;
+ case LNTSYNC_IOC_SEM_READ:
+ error = ntsync_sem_read(td, fp, &sa);
+ if (error == 0) {
+ ntsync_sa_to_lsa(&lsa, &sa);
+ error = copyout(&lsa, data, sizeof(lsa));
+ }
+ break;
+ case LNTSYNC_IOC_MUTEX_UNLOCK:
+ error = copyin(data, &lma, sizeof(lma));
+ ntsync_lma_to_ma(&ma, &lma);
+ if (error == 0) {
+ error = ntsync_mutex_unlock(td, fp, &ma);
+ if (error == 0) {
+ ntsync_ma_to_lma(&lma, &ma);
+ error = copyout(&lma, data, sizeof(lma));
+ }
+ }
+ break;
+ case LNTSYNC_IOC_MUTEX_KILL:
+ error = copyin(data, &val, sizeof(val));
+ if (error == 0)
+ error = ntsync_mutex_kill(td, fp, val);
+ break;
+ case LNTSYNC_IOC_MUTEX_READ:
+ error = ntsync_mutex_read(td, fp, &ma);
+ if (error == 0 || error == EOWNERDEAD) {
+ ntsync_ma_to_lma(&lma, &ma);
+ error1 = copyout(&lma, data, sizeof(lma));
+ if (error == 0)
+ error = error1;
+ }
+ break;
+ case LNTSYNC_IOC_EVENT_SET:
+ error = ntsync_event_set(td, fp, &val);
+ if (error == 0)
+ error = copyout(&val, data, sizeof(val));
+ break;
+ case LNTSYNC_IOC_EVENT_RESET:
+ error = ntsync_event_reset(td, fp, &val);
+ if (error == 0)
+ error = copyout(&val, data, sizeof(val));
+ break;
+ case LNTSYNC_IOC_EVENT_PULSE:
+ error = ntsync_event_pulse(td, fp, &val);
+ if (error == 0)
+ error = copyout(&val, data, sizeof(val));
+ break;
+ case LNTSYNC_IOC_EVENT_READ:
+ error = ntsync_event_read(td, fp, &ea);
+ if (error == 0) {
+ ntsync_ea_to_lea(&lea, &ea);
+ error = copyout(&lea, data, sizeof(lea));
+ }
+ break;
+ default:
+ error = ENOTTY;
+ break;
+ }
+ fdrop(fp, td);
+out:
+ return (error);
+}
diff --git a/sys/dev/ntsync/ntsync.h b/sys/dev/ntsync/ntsync.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/ntsync/ntsync.h
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#ifndef __DEV_NTSYNC_H__
+#define __DEV_NTSYNC_H__
+
+#include <sys/types.h>
+#include <sys/ioccom.h>
+
+struct ntsync_sem_args {
+ uint32_t count;
+ uint32_t max;
+};
+
+struct ntsync_mutex_args {
+ uint32_t owner;
+ uint32_t count;
+};
+
+struct ntsync_event_args {
+ uint32_t manual;
+ uint32_t signaled;
+};
+
+struct ntsync_wait_args {
+ uint64_t timeout;
+ uint64_t objs;
+ uint32_t count;
+ uint32_t index;
+ uint32_t flags;
+ uint32_t owner;
+ uint32_t alert;
+ uint32_t pad;
+};
+
+#define NTSYNC_WAIT_REALTIME 0x00000001
+
+#define NTSYNC_MAX_WAIT_COUNT 64
+
+/*
+ * 'sp' means that the ioctl is special, it might return both error
+ * and copy out parameters. See ntsync_ioctl_copyout().
+ */
+
+#define NTSYNC_IOC_CREATE_SEM _IOW('n', 1, struct ntsync_sem_args)
+#define NTSYNC_IOC_CREATE_MUTEX _IOW('n', 2, struct ntsync_mutex_args)
+#define NTSYNC_IOC_CREATE_EVENT _IOW('n', 3, struct ntsync_event_args)
+#define NTSYNC_IOC_SEM_RELEASE _IOWR('n', 4, uint32_t)
+#define NTSYNC_IOC_MUTEX_UNLOCK _IOWR('n', 5, struct ntsync_mutex_args)
+#define NTSYNC_IOC_EVENT_SET _IOR('n', 6, uint32_t)
+#define NTSYNC_IOC_EVENT_RESET _IOR('n', 7, uint32_t)
+#define NTSYNC_IOC_EVENT_PULSE _IOR('n', 8, uint32_t)
+#define NTSYNC_IOC_SEM_READ _IOR('n', 9, struct ntsync_sem_args)
+#define NTSYNC_IOC_MUTEX_READ _IO('n', 10) /* sp */
+#define NTSYNC_IOC_EVENT_READ _IOR('n', 11, struct ntsync_event_args)
+#define NTSYNC_IOC_MUTEX_KILL _IOW('n', 12, uint32_t)
+#define NTSYNC_IOC_WAIT_ANY _IOW('n', 13, struct ntsync_wait_args) /* sp */
+#define NTSYNC_IOC_WAIT_ALL _IOW('n', 14, struct ntsync_wait_args) /* sp */
+
+#endif
diff --git a/sys/dev/ntsync/ntsync.c b/sys/dev/ntsync/ntsync.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/ntsync/ntsync.c
@@ -0,0 +1,1413 @@
+/*
+ * 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, 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_refed(td, fp, &fd, 0, NULL);
+ if (error != 0) {
+ NTSYNC_PRIV_LOCK(priv);
+ MPASS(priv->objs_cnt > 0);
+ priv->objs_cnt--;
+ NTSYNC_PRIV_UNLOCK(priv);
+ fdrop(fp, td);
+ } else {
+ td->td_retval[0] = fd;
+ }
+ 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)
+{
+ 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;
+ error = 0;
+
+ NTSYNC_PRIV_LOCK(priv);
+ *a = mutex->a;
+ if (mutex->abandoned)
+ error = EOWNERDEAD;
+ NTSYNC_PRIV_UNLOCK(priv);
+ 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;
+
+ 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);
+ 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;
+ struct bintime btb;
+ 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)
+ return (error);
+
+ 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]) {
+ error = EINVAL;
+ goto error_out;
+ }
+ }
+ }
+ }
+
+ if (nwa->timeout == UINT64_MAX) {
+ state->sb = 0;
+ } else {
+ state->sb = nstosbt(nwa->timeout);
+ if ((nwa->flags & NTSYNC_WAIT_REALTIME) != 0) {
+ getboottimebin(&btb);
+ state->sb += bttosbt(btb);
+ }
+ }
+
+ *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);
+ 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);
diff --git a/sys/dev/ntsync/ntsyncvar.h b/sys/dev/ntsync/ntsyncvar.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/ntsync/ntsyncvar.h
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+#ifndef __DEV_NTSYNCVAR_H__
+#define __DEV_NTSYNCVAR_H__
+
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/time.h>
+#include <sys/queue.h>
+#include <dev/ntsync/ntsync.h>
+
+enum ntsync_obj_type {
+ NTSYNC_OBJ_SEM,
+ NTSYNC_OBJ_MUTEX,
+ NTSYNC_OBJ_EVENT,
+};
+
+struct ntsync_wait_state;
+
+struct ntsync_obj_waiter {
+ struct ntsync_wait_state *state;
+ TAILQ_ENTRY(ntsync_obj_waiter) link;
+};
+
+struct ntsync_obj {
+ enum ntsync_obj_type type;
+ struct ntsync_priv *owner;
+ TAILQ_HEAD(, ntsync_obj_waiter) waiters;
+ /* any */
+ bool (*is_signaled)(struct ntsync_obj *,
+ struct ntsync_wait_state *state, int index);
+ void (*consume)(struct ntsync_obj *, struct ntsync_wait_state *,
+ int index);
+ /* all */
+ bool (*prepare)(struct ntsync_obj *, struct ntsync_wait_state *state,
+ int index, bool *stop);
+ void (*commit)(struct ntsync_obj *, struct ntsync_wait_state *state,
+ int index);
+ void (*post_commit)(struct ntsync_obj *,
+ struct ntsync_wait_state *state, int index);
+};
+
+struct ntsync_obj_sem {
+ struct ntsync_obj obj;
+ struct ntsync_sem_args a;
+ struct ntsync_sem_args a1;
+};
+#define OBJ_TO_SEM(obj) __containerof(obj, struct ntsync_obj_sem, obj)
+
+struct ntsync_obj_mutex {
+ struct ntsync_obj obj;
+ struct ntsync_mutex_args a;
+ struct ntsync_mutex_args a1;
+ bool abandoned;
+};
+#define OBJ_TO_MUTEX(obj) __containerof(obj, struct ntsync_obj_mutex, obj)
+
+struct ntsync_obj_event {
+ struct ntsync_obj obj;
+ struct ntsync_event_args a;
+ struct ntsync_event_args a1;
+ bool pulse;
+};
+#define OBJ_TO_EVENT(obj) __containerof(obj, struct ntsync_obj_event, obj)
+
+struct ntsync_wait_state {
+ struct ntsync_wait_args *nwa;
+ struct ntsync_priv *owner;
+ struct ntsync_obj_waiter waiters[NTSYNC_MAX_WAIT_COUNT + 1];
+ int fds[NTSYNC_MAX_WAIT_COUNT];
+ struct file *fps[NTSYNC_MAX_WAIT_COUNT];
+ struct file *fp_alert;
+ int obj_count;
+ struct ntsync_obj *objs[NTSYNC_MAX_WAIT_COUNT + 1];
+ struct ntsync_obj_event *alert_event;
+ sbintime_t sb;
+ int error;
+ int index;
+ bool any;
+ bool all;
+ bool ready;
+};
+
+struct ntsync_priv {
+ struct mtx lock;
+ unsigned objs_cnt;
+ bool closed;
+};
+
+#define NTSYNC_PRIV_LOCK(priv) mtx_lock(&priv->lock)
+#define NTSYNC_PRIV_UNLOCK(priv) mtx_unlock(&priv->lock)
+#define NTSYNC_PRIV_ASSERT(priv) mtx_assert(&priv->lock, MA_OWNED)
+
+extern struct cdevsw ntsync_cdevsw;
+
+struct file;
+struct thread;
+int ntsync_sem_release(struct thread *td, struct file *fp, uint32_t *val);
+int ntsync_sem_read(struct thread *td, struct file *fp,
+ struct ntsync_sem_args *a);
+int ntsync_mutex_unlock(struct thread *td, struct file *fp,
+ struct ntsync_mutex_args *a);
+int ntsync_mutex_kill(struct thread *td, struct file *fp, uint32_t val);
+int ntsync_mutex_read(struct thread *td, struct file *fp,
+ struct ntsync_mutex_args *a);
+int ntsync_event_set(struct thread *td, struct file *fp, uint32_t *val);
+int ntsync_event_reset(struct thread *td, struct file *fp, uint32_t *val);
+int ntsync_event_pulse(struct thread *td, struct file *fp, uint32_t *val);
+int ntsync_event_read(struct thread *td, struct file *fp,
+ struct ntsync_event_args *a);
+
+#endif
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -296,6 +296,7 @@
nlsysevent \
nge \
nmdm \
+ ntsync \
nullfs \
${_ntb} \
nvd \
@@ -542,6 +543,7 @@
${MACHINE_CPUARCH} == "i386"
SUBDIR+= linprocfs
SUBDIR+= linsysfs
+SUBDIR+= linux_ntsync
.endif
.if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "i386"
SUBDIR+= linux
diff --git a/sys/modules/linux_ntsync/Makefile b/sys/modules/linux_ntsync/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/linux_ntsync/Makefile
@@ -0,0 +1,6 @@
+.PATH: ${SRCTOP}/sys/dev/ntsync
+
+KMOD= linux_ntsync
+SRCS= linux_ntsync.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/modules/ntsync/Makefile b/sys/modules/ntsync/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/ntsync/Makefile
@@ -0,0 +1,6 @@
+.PATH: ${SRCTOP}/sys/dev/ntsync
+
+KMOD= ntsync
+SRCS= ntsync.c
+
+.include <bsd.kmod.mk>
diff --git a/sys/sys/file.h b/sys/sys/file.h
--- a/sys/sys/file.h
+++ b/sys/sys/file.h
@@ -73,6 +73,7 @@
#define DTYPE_TIMERFD 14 /* timerfd */
#define DTYPE_INOTIFY 15 /* inotify descriptor */
#define DTYPE_JAILDESC 16 /* jail descriptor */
+#define DTYPE_NTSYNC 17 /* /dev/ntsync */
#ifdef _KERNEL
diff --git a/sys/sys/user.h b/sys/sys/user.h
--- a/sys/sys/user.h
+++ b/sys/sys/user.h
@@ -269,6 +269,7 @@
#define KF_TYPE_TIMERFD 14
#define KF_TYPE_INOTIFY 15
#define KF_TYPE_JAILDESC 16
+#define KF_TYPE_NTSYNC 17
#define KF_TYPE_UNKNOWN 255
#define KF_VTYPE_VNON 0
@@ -289,6 +290,11 @@
#define KF_FD_TYPE_TEXT -5 /* Text vnode */
#define KF_FD_TYPE_CTTY -6 /* Controlling terminal */
+#define KF_NTSYNC_TYPE_DEV 1 /* Not reported, reserved */
+#define KF_NTSYNC_TYPE_SEM 2
+#define KF_NTSYNC_TYPE_MUTEX 3
+#define KF_NTSYNC_TYPE_EVENT 4
+
#define KF_FLAG_READ 0x00000001
#define KF_FLAG_WRITE 0x00000002
#define KF_FLAG_APPEND 0x00000004
@@ -467,6 +473,24 @@
uint64_t kf_inotify_npending;
uint64_t kf_inotify_nbpending;
} kf_inotify;
+ struct {
+ uint32_t kf_ntsync_type;
+ uint64_t kf_ntsync_dev;
+ union {
+ struct {
+ uint32_t count;
+ uint32_t max;
+ } kf_ntsync_sem;
+ struct{
+ uint32_t owner;
+ uint32_t count;
+ } kf_ntsync_mutex;
+ struct {
+ uint32_t signaled;
+ uint32_t manual;
+ } kf_ntsync_event;
+ } kf_ntsync_un;
+ } kf_ntsync;
} kf_un;
};
uint16_t kf_status; /* Status flags. */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 23, 6:48 PM (10 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
33452013
Default Alt Text
D57038.id178165.diff (49 KB)
Attached To
Mode
D57038: ntsync(9)
Attached
Detach File
Event Timeline
Log In to Comment