/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2026 The FreeBSD Foundation * * This software was developed by Konstantin Belousov * under sponsorship from the FreeBSD Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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) { 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; 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]) { i = state->obj_count; 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);