xref: /freebsd/sys/compat/linuxkpi/common/src/linux_lock.c (revision 949440623b74e9c0f3b47987cff1d2890a8abe88)
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  * $FreeBSD$
279760ac0aSHans Petter Selasky  */
289760ac0aSHans Petter Selasky 
299760ac0aSHans Petter Selasky #include <sys/queue.h>
309760ac0aSHans Petter Selasky 
31*94944062SHans Petter Selasky #include <linux/sched.h>
329760ac0aSHans Petter Selasky #include <linux/ww_mutex.h>
339760ac0aSHans Petter Selasky 
349760ac0aSHans Petter Selasky struct ww_mutex_thread {
359760ac0aSHans Petter Selasky 	TAILQ_ENTRY(ww_mutex_thread) entry;
369760ac0aSHans Petter Selasky 	struct thread *thread;
379760ac0aSHans Petter Selasky 	struct ww_mutex *lock;
389760ac0aSHans Petter Selasky };
399760ac0aSHans Petter Selasky 
409760ac0aSHans Petter Selasky static TAILQ_HEAD(, ww_mutex_thread) ww_mutex_head;
419760ac0aSHans Petter Selasky static struct mtx ww_mutex_global;
429760ac0aSHans Petter Selasky 
439760ac0aSHans Petter Selasky static void
449760ac0aSHans Petter Selasky linux_ww_init(void *arg)
459760ac0aSHans Petter Selasky {
469760ac0aSHans Petter Selasky 	TAILQ_INIT(&ww_mutex_head);
479760ac0aSHans Petter Selasky 	mtx_init(&ww_mutex_global, "lkpi-ww-mtx", NULL, MTX_DEF);
489760ac0aSHans Petter Selasky }
499760ac0aSHans Petter Selasky 
509760ac0aSHans Petter Selasky SYSINIT(ww_init, SI_SUB_LOCK, SI_ORDER_SECOND, linux_ww_init, NULL);
519760ac0aSHans Petter Selasky 
529760ac0aSHans Petter Selasky static void
539760ac0aSHans Petter Selasky linux_ww_uninit(void *arg)
549760ac0aSHans Petter Selasky {
559760ac0aSHans Petter Selasky 	mtx_destroy(&ww_mutex_global);
569760ac0aSHans Petter Selasky }
579760ac0aSHans Petter Selasky 
589760ac0aSHans Petter Selasky SYSUNINIT(ww_uninit, SI_SUB_LOCK, SI_ORDER_SECOND, linux_ww_uninit, NULL);
599760ac0aSHans Petter Selasky 
609760ac0aSHans Petter Selasky static inline void
619760ac0aSHans Petter Selasky linux_ww_lock(void)
629760ac0aSHans Petter Selasky {
639760ac0aSHans Petter Selasky 	mtx_lock(&ww_mutex_global);
649760ac0aSHans Petter Selasky }
659760ac0aSHans Petter Selasky 
669760ac0aSHans Petter Selasky static inline void
679760ac0aSHans Petter Selasky linux_ww_unlock(void)
689760ac0aSHans Petter Selasky {
699760ac0aSHans Petter Selasky 	mtx_unlock(&ww_mutex_global);
709760ac0aSHans Petter Selasky }
719760ac0aSHans Petter Selasky 
729760ac0aSHans Petter Selasky /* lock a mutex with deadlock avoidance */
739760ac0aSHans Petter Selasky int
749760ac0aSHans Petter Selasky linux_ww_mutex_lock_sub(struct ww_mutex *lock, int catch_signal)
759760ac0aSHans Petter Selasky {
76*94944062SHans Petter Selasky 	struct task_struct *task;
779760ac0aSHans Petter Selasky 	struct ww_mutex_thread entry;
789760ac0aSHans Petter Selasky 	struct ww_mutex_thread *other;
799760ac0aSHans Petter Selasky 	int retval = 0;
809760ac0aSHans Petter Selasky 
81*94944062SHans Petter Selasky 	task = current;
82*94944062SHans Petter Selasky 
839760ac0aSHans Petter Selasky 	linux_ww_lock();
849760ac0aSHans Petter Selasky 	if (unlikely(sx_try_xlock(&lock->base.sx) == 0)) {
859760ac0aSHans Petter Selasky 		entry.thread = curthread;
869760ac0aSHans Petter Selasky 		entry.lock = lock;
879760ac0aSHans Petter Selasky 		TAILQ_INSERT_TAIL(&ww_mutex_head, &entry, entry);
889760ac0aSHans Petter Selasky 
899760ac0aSHans Petter Selasky 		do {
909760ac0aSHans Petter Selasky 			struct thread *owner = (struct thread *)
919760ac0aSHans Petter Selasky 			    SX_OWNER(lock->base.sx.sx_lock);
929760ac0aSHans Petter Selasky 
939760ac0aSHans Petter Selasky 			/* scan for deadlock */
949760ac0aSHans Petter Selasky 			TAILQ_FOREACH(other, &ww_mutex_head, entry) {
959760ac0aSHans Petter Selasky 				/* skip own thread */
969760ac0aSHans Petter Selasky 				if (other == &entry)
979760ac0aSHans Petter Selasky 					continue;
989760ac0aSHans Petter Selasky 				/*
999760ac0aSHans Petter Selasky 				 * If another thread is owning our
1009760ac0aSHans Petter Selasky 				 * lock and is at the same time trying
1019760ac0aSHans Petter Selasky 				 * to acquire a lock this thread owns,
1029760ac0aSHans Petter Selasky 				 * that means deadlock.
1039760ac0aSHans Petter Selasky 				 */
1049760ac0aSHans Petter Selasky 				if (other->thread == owner &&
1059760ac0aSHans Petter Selasky 				    (struct thread *)SX_OWNER(
1069760ac0aSHans Petter Selasky 				    other->lock->base.sx.sx_lock) == curthread) {
1079760ac0aSHans Petter Selasky 					retval = -EDEADLK;
1089760ac0aSHans Petter Selasky 					goto done;
1099760ac0aSHans Petter Selasky 				}
1109760ac0aSHans Petter Selasky 			}
1119760ac0aSHans Petter Selasky 			if (catch_signal) {
112*94944062SHans Petter Selasky 				retval = -cv_wait_sig(&lock->condvar, &ww_mutex_global);
113*94944062SHans Petter Selasky 				if (retval != 0) {
114*94944062SHans Petter Selasky 					linux_schedule_save_interrupt_value(task, retval);
1159760ac0aSHans Petter Selasky 					retval = -EINTR;
1169760ac0aSHans Petter Selasky 					goto done;
1179760ac0aSHans Petter Selasky 				}
1189760ac0aSHans Petter Selasky 			} else {
1199760ac0aSHans Petter Selasky 				cv_wait(&lock->condvar, &ww_mutex_global);
1209760ac0aSHans Petter Selasky 			}
1219760ac0aSHans Petter Selasky 		} while (sx_try_xlock(&lock->base.sx) == 0);
1229760ac0aSHans Petter Selasky done:
1239760ac0aSHans Petter Selasky 		TAILQ_REMOVE(&ww_mutex_head, &entry, entry);
1249760ac0aSHans Petter Selasky 
1259760ac0aSHans Petter Selasky 		/* if the lock is free, wakeup next lock waiter, if any */
1269760ac0aSHans Petter Selasky 		if ((struct thread *)SX_OWNER(lock->base.sx.sx_lock) == NULL)
1279760ac0aSHans Petter Selasky 			cv_signal(&lock->condvar);
1289760ac0aSHans Petter Selasky 	}
1299760ac0aSHans Petter Selasky 	linux_ww_unlock();
1309760ac0aSHans Petter Selasky 	return (retval);
1319760ac0aSHans Petter Selasky }
1329760ac0aSHans Petter Selasky 
1339760ac0aSHans Petter Selasky void
1349760ac0aSHans Petter Selasky linux_ww_mutex_unlock_sub(struct ww_mutex *lock)
1359760ac0aSHans Petter Selasky {
1369760ac0aSHans Petter Selasky 	/* protect ww_mutex ownership change */
1379760ac0aSHans Petter Selasky 	linux_ww_lock();
1389760ac0aSHans Petter Selasky 	sx_xunlock(&lock->base.sx);
1399760ac0aSHans Petter Selasky 	/* wakeup a lock waiter, if any */
1409760ac0aSHans Petter Selasky 	cv_signal(&lock->condvar);
1419760ac0aSHans Petter Selasky 	linux_ww_unlock();
1429760ac0aSHans Petter Selasky }
143*94944062SHans Petter Selasky 
144*94944062SHans Petter Selasky int
145*94944062SHans Petter Selasky linux_mutex_lock_interruptible(mutex_t *m)
146*94944062SHans Petter Selasky {
147*94944062SHans Petter Selasky 	int error;
148*94944062SHans Petter Selasky 
149*94944062SHans Petter Selasky 	error = -sx_xlock_sig(&m->sx);
150*94944062SHans Petter Selasky 	if (error != 0) {
151*94944062SHans Petter Selasky 		linux_schedule_save_interrupt_value(current, error);
152*94944062SHans Petter Selasky 		error = -EINTR;
153*94944062SHans Petter Selasky 	}
154*94944062SHans Petter Selasky 	return (error);
155*94944062SHans Petter Selasky }
156*94944062SHans Petter Selasky 
157*94944062SHans Petter Selasky int
158*94944062SHans Petter Selasky linux_down_write_killable(struct rw_semaphore *rw)
159*94944062SHans Petter Selasky {
160*94944062SHans Petter Selasky 	int error;
161*94944062SHans Petter Selasky 
162*94944062SHans Petter Selasky 	error = -sx_xlock_sig(&rw->sx);
163*94944062SHans Petter Selasky 	if (error != 0) {
164*94944062SHans Petter Selasky 		linux_schedule_save_interrupt_value(current, error);
165*94944062SHans Petter Selasky 		error = -EINTR;
166*94944062SHans Petter Selasky 	}
167*94944062SHans Petter Selasky 	return (error);
168*94944062SHans Petter Selasky }
169