19760ac0aSHans Petter Selasky /*-
29760ac0aSHans Petter Selasky * Copyright (c) 2017 Mellanox Technologies, Ltd.
39760ac0aSHans Petter Selasky * All rights reserved.
49760ac0aSHans Petter Selasky *
59760ac0aSHans Petter Selasky * Redistribution and use in source and binary forms, with or without
69760ac0aSHans Petter Selasky * modification, are permitted provided that the following conditions
79760ac0aSHans Petter Selasky * are met:
89760ac0aSHans Petter Selasky * 1. Redistributions of source code must retain the above copyright
99760ac0aSHans Petter Selasky * notice unmodified, this list of conditions, and the following
109760ac0aSHans Petter Selasky * disclaimer.
119760ac0aSHans Petter Selasky * 2. Redistributions in binary form must reproduce the above copyright
129760ac0aSHans Petter Selasky * notice, this list of conditions and the following disclaimer in the
139760ac0aSHans Petter Selasky * documentation and/or other materials provided with the distribution.
149760ac0aSHans Petter Selasky *
159760ac0aSHans Petter Selasky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
169760ac0aSHans Petter Selasky * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
179760ac0aSHans Petter Selasky * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
189760ac0aSHans Petter Selasky * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
199760ac0aSHans Petter Selasky * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
209760ac0aSHans Petter Selasky * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
219760ac0aSHans Petter Selasky * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
229760ac0aSHans Petter Selasky * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
239760ac0aSHans Petter Selasky * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
249760ac0aSHans Petter Selasky * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
259760ac0aSHans Petter Selasky */
269760ac0aSHans Petter Selasky
279760ac0aSHans Petter Selasky #include <sys/queue.h>
289760ac0aSHans Petter Selasky
2994944062SHans Petter Selasky #include <linux/sched.h>
309760ac0aSHans Petter Selasky #include <linux/ww_mutex.h>
319760ac0aSHans Petter Selasky
329760ac0aSHans Petter Selasky struct ww_mutex_thread {
339760ac0aSHans Petter Selasky TAILQ_ENTRY(ww_mutex_thread) entry;
349760ac0aSHans Petter Selasky struct thread *thread;
359760ac0aSHans Petter Selasky struct ww_mutex *lock;
369760ac0aSHans Petter Selasky };
379760ac0aSHans Petter Selasky
389760ac0aSHans Petter Selasky static TAILQ_HEAD(, ww_mutex_thread) ww_mutex_head;
399760ac0aSHans Petter Selasky static struct mtx ww_mutex_global;
409760ac0aSHans Petter Selasky
419760ac0aSHans Petter Selasky static void
linux_ww_init(void * arg)429760ac0aSHans Petter Selasky linux_ww_init(void *arg)
439760ac0aSHans Petter Selasky {
449760ac0aSHans Petter Selasky TAILQ_INIT(&ww_mutex_head);
459760ac0aSHans Petter Selasky mtx_init(&ww_mutex_global, "lkpi-ww-mtx", NULL, MTX_DEF);
469760ac0aSHans Petter Selasky }
479760ac0aSHans Petter Selasky
489760ac0aSHans Petter Selasky SYSINIT(ww_init, SI_SUB_LOCK, SI_ORDER_SECOND, linux_ww_init, NULL);
499760ac0aSHans Petter Selasky
509760ac0aSHans Petter Selasky static void
linux_ww_uninit(void * arg)519760ac0aSHans Petter Selasky linux_ww_uninit(void *arg)
529760ac0aSHans Petter Selasky {
539760ac0aSHans Petter Selasky mtx_destroy(&ww_mutex_global);
549760ac0aSHans Petter Selasky }
559760ac0aSHans Petter Selasky
569760ac0aSHans Petter Selasky SYSUNINIT(ww_uninit, SI_SUB_LOCK, SI_ORDER_SECOND, linux_ww_uninit, NULL);
579760ac0aSHans Petter Selasky
589760ac0aSHans Petter Selasky static inline void
linux_ww_lock(void)599760ac0aSHans Petter Selasky linux_ww_lock(void)
609760ac0aSHans Petter Selasky {
619760ac0aSHans Petter Selasky mtx_lock(&ww_mutex_global);
629760ac0aSHans Petter Selasky }
639760ac0aSHans Petter Selasky
649760ac0aSHans Petter Selasky static inline void
linux_ww_unlock(void)659760ac0aSHans Petter Selasky linux_ww_unlock(void)
669760ac0aSHans Petter Selasky {
679760ac0aSHans Petter Selasky mtx_unlock(&ww_mutex_global);
689760ac0aSHans Petter Selasky }
699760ac0aSHans Petter Selasky
709760ac0aSHans Petter Selasky /* lock a mutex with deadlock avoidance */
719760ac0aSHans Petter Selasky int
linux_ww_mutex_lock_sub(struct ww_mutex * lock,struct ww_acquire_ctx * ctx,int catch_signal)724c2dddd8SHans Petter Selasky linux_ww_mutex_lock_sub(struct ww_mutex *lock,
734c2dddd8SHans Petter Selasky struct ww_acquire_ctx *ctx, int catch_signal)
749760ac0aSHans Petter Selasky {
7594944062SHans Petter Selasky struct task_struct *task;
769760ac0aSHans Petter Selasky struct ww_mutex_thread entry;
779760ac0aSHans Petter Selasky struct ww_mutex_thread *other;
789760ac0aSHans Petter Selasky int retval = 0;
799760ac0aSHans Petter Selasky
8094944062SHans Petter Selasky task = current;
8194944062SHans Petter Selasky
829760ac0aSHans Petter Selasky linux_ww_lock();
839760ac0aSHans Petter Selasky if (unlikely(sx_try_xlock(&lock->base.sx) == 0)) {
849760ac0aSHans Petter Selasky entry.thread = curthread;
859760ac0aSHans Petter Selasky entry.lock = lock;
869760ac0aSHans Petter Selasky TAILQ_INSERT_TAIL(&ww_mutex_head, &entry, entry);
879760ac0aSHans Petter Selasky
889760ac0aSHans Petter Selasky do {
899760ac0aSHans Petter Selasky struct thread *owner = (struct thread *)
909760ac0aSHans Petter Selasky SX_OWNER(lock->base.sx.sx_lock);
919760ac0aSHans Petter Selasky
929760ac0aSHans Petter Selasky /* scan for deadlock */
939760ac0aSHans Petter Selasky TAILQ_FOREACH(other, &ww_mutex_head, entry) {
949760ac0aSHans Petter Selasky /* skip own thread */
959760ac0aSHans Petter Selasky if (other == &entry)
969760ac0aSHans Petter Selasky continue;
979760ac0aSHans Petter Selasky /*
989760ac0aSHans Petter Selasky * If another thread is owning our
999760ac0aSHans Petter Selasky * lock and is at the same time trying
1009760ac0aSHans Petter Selasky * to acquire a lock this thread owns,
1019760ac0aSHans Petter Selasky * that means deadlock.
1029760ac0aSHans Petter Selasky */
1039760ac0aSHans Petter Selasky if (other->thread == owner &&
1049760ac0aSHans Petter Selasky (struct thread *)SX_OWNER(
1059760ac0aSHans Petter Selasky other->lock->base.sx.sx_lock) == curthread) {
1069760ac0aSHans Petter Selasky retval = -EDEADLK;
1079760ac0aSHans Petter Selasky goto done;
1089760ac0aSHans Petter Selasky }
1099760ac0aSHans Petter Selasky }
1109760ac0aSHans Petter Selasky if (catch_signal) {
11194944062SHans Petter Selasky retval = -cv_wait_sig(&lock->condvar, &ww_mutex_global);
11294944062SHans Petter Selasky if (retval != 0) {
11394944062SHans Petter Selasky linux_schedule_save_interrupt_value(task, retval);
1149760ac0aSHans Petter Selasky retval = -EINTR;
1159760ac0aSHans Petter Selasky goto done;
1169760ac0aSHans Petter Selasky }
1179760ac0aSHans Petter Selasky } else {
1189760ac0aSHans Petter Selasky cv_wait(&lock->condvar, &ww_mutex_global);
1199760ac0aSHans Petter Selasky }
1209760ac0aSHans Petter Selasky } while (sx_try_xlock(&lock->base.sx) == 0);
1219760ac0aSHans Petter Selasky done:
1229760ac0aSHans Petter Selasky TAILQ_REMOVE(&ww_mutex_head, &entry, entry);
1239760ac0aSHans Petter Selasky
1249760ac0aSHans Petter Selasky /* if the lock is free, wakeup next lock waiter, if any */
1259760ac0aSHans Petter Selasky if ((struct thread *)SX_OWNER(lock->base.sx.sx_lock) == NULL)
1269760ac0aSHans Petter Selasky cv_signal(&lock->condvar);
1279760ac0aSHans Petter Selasky }
1284c2dddd8SHans Petter Selasky
1294c2dddd8SHans Petter Selasky if (retval == 0)
1304c2dddd8SHans Petter Selasky lock->ctx = ctx;
1319760ac0aSHans Petter Selasky linux_ww_unlock();
1329760ac0aSHans Petter Selasky return (retval);
1339760ac0aSHans Petter Selasky }
1349760ac0aSHans Petter Selasky
1359760ac0aSHans Petter Selasky void
linux_ww_mutex_unlock_sub(struct ww_mutex * lock)1369760ac0aSHans Petter Selasky linux_ww_mutex_unlock_sub(struct ww_mutex *lock)
1379760ac0aSHans Petter Selasky {
1389760ac0aSHans Petter Selasky /* protect ww_mutex ownership change */
1399760ac0aSHans Petter Selasky linux_ww_lock();
1404c2dddd8SHans Petter Selasky lock->ctx = NULL;
1419760ac0aSHans Petter Selasky sx_xunlock(&lock->base.sx);
1429760ac0aSHans Petter Selasky /* wakeup a lock waiter, if any */
1439760ac0aSHans Petter Selasky cv_signal(&lock->condvar);
1449760ac0aSHans Petter Selasky linux_ww_unlock();
1459760ac0aSHans Petter Selasky }
14694944062SHans Petter Selasky
14794944062SHans Petter Selasky int
linux_mutex_lock_interruptible(mutex_t * m)14894944062SHans Petter Selasky linux_mutex_lock_interruptible(mutex_t *m)
14994944062SHans Petter Selasky {
15094944062SHans Petter Selasky int error;
15194944062SHans Petter Selasky
15294944062SHans Petter Selasky error = -sx_xlock_sig(&m->sx);
15394944062SHans Petter Selasky if (error != 0) {
15494944062SHans Petter Selasky linux_schedule_save_interrupt_value(current, error);
15594944062SHans Petter Selasky error = -EINTR;
15694944062SHans Petter Selasky }
15794944062SHans Petter Selasky return (error);
15894944062SHans Petter Selasky }
15994944062SHans Petter Selasky
16094944062SHans Petter Selasky int
linux_down_read_killable(struct rw_semaphore * rw)161*f0b0f28fSJake Freeland linux_down_read_killable(struct rw_semaphore *rw)
162*f0b0f28fSJake Freeland {
163*f0b0f28fSJake Freeland int error;
164*f0b0f28fSJake Freeland
165*f0b0f28fSJake Freeland error = -sx_slock_sig(&rw->sx);
166*f0b0f28fSJake Freeland if (error != 0) {
167*f0b0f28fSJake Freeland linux_schedule_save_interrupt_value(current, error);
168*f0b0f28fSJake Freeland error = -EINTR;
169*f0b0f28fSJake Freeland }
170*f0b0f28fSJake Freeland return (error);
171*f0b0f28fSJake Freeland }
172*f0b0f28fSJake Freeland
173*f0b0f28fSJake Freeland int
linux_down_write_killable(struct rw_semaphore * rw)17494944062SHans Petter Selasky linux_down_write_killable(struct rw_semaphore *rw)
17594944062SHans Petter Selasky {
17694944062SHans Petter Selasky int error;
17794944062SHans Petter Selasky
17894944062SHans Petter Selasky error = -sx_xlock_sig(&rw->sx);
17994944062SHans Petter Selasky if (error != 0) {
18094944062SHans Petter Selasky linux_schedule_save_interrupt_value(current, error);
18194944062SHans Petter Selasky error = -EINTR;
18294944062SHans Petter Selasky }
18394944062SHans Petter Selasky return (error);
18494944062SHans Petter Selasky }
185