• Bug#1105129: Please backport ntsync driver from Linux-6.14 to trixie (4

    From Piotr Morgwai Kotarbinski@1:229/2 to All on Sun May 11 22:50:01 2025
    [continued from previous message]

    + * Therefore we first check whether all_hint is zero, and, if it is,
    + * we skip trying to wake "all" waiters.
    + *
    + * Since wait requests must originate from user-space threads, we're
    + * limited here by PID_MAX_LIMIT, so there's no risk of overflow.
    + */
    + atomic_t all_hint;
    +};
    +
    +struct ntsync_q_entry {
    + struct list_head node;
    + struct ntsync_q *q;
    + struct ntsync_obj *obj;
    + __u32 index;
    +};
    +
    +struct ntsync_q {
    + struct task_struct *task;
    + __u32 owner;
    +
    + /*
    + * Protected via atomic_try_cmpxchg(). Only the thread that wins the
    + * compare-and-swap may actually change object states and wake this
    + * task.
    + */
    + atomic_t signaled;
    +
    + bool all;
    + bool ownerdead;
    + __u32 count;
    + struct ntsync_q_entry entries[];
    };

    struct ntsync_device {
    + /*
    + * Wait-all operations must atomically grab all objects, and be totally + * ordered with respect to each other and wait-any operations.
    + * If one thread is trying to acquire several objects, another thread
    + * cannot touch the object at the same time.
    + *
    + * This device-wide lock is used to serialize wait-for-all
    + * operations, and operations on an object that is involved in a
    + * wait-for-all.
    + */
    + struct mutex wait_all_lock;
    +
    struct file *file;
    };

    /*
    + * Single objects are locked using obj->lock.
    + *
    + * Multiple objects are 'locked' while holding dev->wait_all_lock.
    + * In this case however, individual objects are not locked by holding
    + * obj->lock, but by setting obj->dev_locked.
    + *
    + * This means that in order to lock a single object, the sequence is slightly + * more complicated than usual. Specifically it needs to check obj->dev_locked + * after acquiring obj->lock, if set, it needs to drop the lock and acquire
    + * dev->wait_all_lock in order to serialize against the multi-object operation.
    + */
    +
    +static void dev_lock_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
    +{
    + lockdep_assert_held(&dev->wait_all_lock);
    + lockdep_assert(obj->dev == dev);
    + spin_lock(&obj->lock);
    + /*
    + * By setting obj->dev_locked inside obj->lock, it is ensured that
    + * anyone holding obj->lock must see the value.
    + */
    + obj->dev_locked = 1;
    + spin_unlock(&obj->lock);
    +}
    +
    +static void dev_unlock_obj(struct ntsync_device *dev, struct ntsync_obj *obj) +{
    + lockdep_assert_held(&dev->wait_all_lock);
    + lockdep_assert(obj->dev == dev);
    + spin_lock(&obj->lock);
    + obj->dev_locked = 0;
    + spin_unlock(&obj->lock);
    +}
    +
    +static void obj_lock(struct ntsync_obj *obj)
    +{
    + struct ntsync_device *dev = obj->dev;
    +
    + for (;;) {
    + spin_lock(&obj->lock);
    + if (likely(!obj->dev_locked))
    + break;
    +
    + spin_unlock(&obj->lock);
    + mutex_lock(&dev->wait_all_lock);
    + spin_lock(&obj->lock);
    + /*
    + * obj->dev_locked should be set and released under the same
    + * wait_all_lock section, since we now own this lock, it should + * be clear.
    + */
    + lockdep_assert(!obj->dev_locked);
    + spin_unlock(&obj->lock);
    + mutex_unlock(&dev->wait_all_lock);
    + }
    +}
    +
    +static void obj_unlock(struct ntsync_obj *obj)
    +{
    + spin_unlock(&obj->lock);
    +}
    +
    +static bool ntsync_lock_obj(struct ntsync_device *dev, struct ntsync_obj *obj) +{
    + bool all;
    +
    + obj_lock(obj);
    + all = atomic_read(&obj->all_hint);
    + if (unlikely(all)) {
    + obj_unlock(obj);
    + mutex_lock(&dev->wait_all_lock);
    + dev_lock_obj(dev, obj);
    + }
    +
    + return all;
    +}
    +
    +static void ntsync_unlock_obj(struct ntsync_device *dev, struct ntsync_obj *obj, bool all)
    +{
    + if (all) {
    + dev_unlock_obj(dev, obj);
    + mutex_unlock(&dev->wait_all_lock);
    + } else {
    + obj_unlock(obj);
    + }
    +}
    +
    +#define ntsync_assert_held(obj) \
    + lockdep_assert((lockdep_is_held(&(obj)->lock) != LOCK_STATE_NOT_HELD) || \
    + ((lockdep_is_held(&(obj)->dev->wait_all_lock) != LOCK_STATE_NOT_HELD) && \
    + (obj)->dev_locked))
    +
    +static bool is_signaled(struct ntsync_obj *obj, __u32 owner)
    +{
    + ntsync_assert_held(obj);
    +
    + switch (obj->type) {
    + case NTSYNC_TYPE_SEM:
    + return !!obj->u.sem.count;
    + case NTSYNC_TYPE_MUTEX:
    + if (obj->u.mutex.owner && obj->u.mutex.owner != owner)
    + return false;
    + return obj->u.mutex.count < UINT_MAX;
    + case NTSYNC_TYPE_EVENT:
    + return obj->u.event.signaled;
    + }
    +
    + WARN(1, "bad object type %#x\n", obj->type);
    + return false;
    +}
    +
    +/*
    + * "locked_obj" is an optional pointer to an object which is already locked and
    + * should not be locked again. This is necessary so that changing an object's + * state and waking it can be a single atomic operation.
    + */
    +static void try_wake_all(struct ntsync_device *dev, struct ntsync_q *q,
    + struct ntsync_obj *locked_obj)
    +{
    + __u32 count = q->count;
    + bool can_wake = true;
    + int signaled = -1;
    + __u32 i;
    +
    + lockdep_assert_held(&dev->wait_all_lock);
    + if (locked_obj)
    + lockdep_assert(locked_obj->dev_locked);
    +
    + for (i = 0; i < count; i++) {
    + if (q->entries[i].obj != locked_obj)
    + dev_lock_obj(dev, q->entries[i].obj);
    + }
    +
    + for (i = 0; i < count; i++) {
    + if (!is_signaled(q->entries[i].obj, q->owner)) {
    + can_wake = false;
    + break;
    + }
    + }
    +
    + if (can_wake && atomic_try_cmpxchg(&q->signaled, &signaled, 0)) {
    + for (i = 0; i < count; i++) {
    + struct ntsync_obj *obj = q->entries[i].obj;
    +
    + switch (obj->type) {
    + case NTSYNC_TYPE_SEM:
    + obj->u.sem.count--;
    + break;
    + case NTSYNC_TYPE_MUTEX:
    + if (obj->u.mutex.ownerdead)
    + q->ownerdead = true;
    + obj->u.mutex.ownerdead = false;
    + obj->u.mutex.count++;
    + obj->u.mutex.owner = q->owner;
    + break;
    + case NTSYNC_TYPE_EVENT:
    + if (!obj->u.event.manual)
    + obj->u.event.signaled = false;
    + break;
    + }
    + }
    + wake_up_process(q->task);
    + }
    +
    + for (i = 0; i < count; i++) {
    + if (q->entries[i].obj != locked_obj)
    + dev_unlock_obj(dev, q->entries[i].obj);
    + }
    +}
    +
    +static void try_wake_all_obj(struct ntsync_device *dev, struct ntsync_obj *obj)
    +{
    + struct ntsync_q_entry *entry;
    +
    + lockdep_assert_held(&dev->wait_all_lock);
    + lockdep_assert(obj->dev_locked);
    +
    + list_for_each_entry(entry, &obj->all_waiters, node)
    + try_wake_all(dev, entry->q, obj);
    +}
    +
    +static void try_wake_any_sem(struct ntsync_obj *sem)
    +{
    + struct ntsync_q_entry *entry;
    +
    + ntsync_assert_held(sem);
    + lockdep_assert(sem->type == NTSYNC_TYPE_SEM);
    +
    + list_for_each_entry(entry, &sem->any_waiters, node) {
    + struct ntsync_q *q = entry->q;
    + int signaled = -1;
    +
    + if (!sem->u.sem.count)
    + break;
    +
    + if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) {
    + sem->u.sem.count--;
    + wake_up_process(q->task);
    + }
    + }
    +}
    +
    +static void try_wake_any_mutex(struct ntsync_obj *mutex)
    +{
    + struct ntsync_q_entry *entry;
    +
    + ntsync_assert_held(mutex);
    + lockdep_assert(mutex->type == NTSYNC_TYPE_MUTEX);
    +
    + list_for_each_entry(entry, &mutex->any_waiters, node) {
    + struct ntsync_q *q = entry->q;
    + int signaled = -1;
    +
    + if (mutex->u.mutex.count == UINT_MAX)
    + break;
    + if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner)
    + continue;
    +
    + if (atomic_try_cmpxchg(&q->signaled, &signaled, entry->index)) {
    + if (mutex->u.mutex.ownerdead)
    + q->ownerdead = true;
    + mutex->u.mutex.ownerdead = false;
    + mutex->u.mutex.count++;
    + mutex->u.mutex.owner = q->owner;
    + wake_up_process(q->task);
    + }
    + }
    +}
    +
    +static void try_wake_any_event(struct ntsync_obj *event)
    +{
    + struct ntsync_q_entry *entry;
    +
    + ntsync_assert_held(event);
    + lockdep_assert(event->type == NTSYNC_TYPE_EVENT);
    +
    + list_for_each_entry(entry, &event->any_waiters, node) {

    [continued in next message]

    --- SoupGate-Win32 v1.05
    * Origin: you cannot sedate... all the things you hate (1:229/2)