summaryrefslogtreecommitdiffstats
path: root/arch/s390/kernel/semaphore.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/s390/kernel/semaphore.c')
-rw-r--r--arch/s390/kernel/semaphore.c108
1 files changed, 108 insertions, 0 deletions
diff --git a/arch/s390/kernel/semaphore.c b/arch/s390/kernel/semaphore.c
new file mode 100644
index 000000000000..8dfb690c159f
--- /dev/null
+++ b/arch/s390/kernel/semaphore.c
@@ -0,0 +1,108 @@
+/*
+ * linux/arch/s390/kernel/semaphore.c
+ *
+ * S390 version
+ * Copyright (C) 1998-2000 IBM Corporation
+ * Author(s): Martin Schwidefsky
+ *
+ * Derived from "linux/arch/i386/kernel/semaphore.c
+ * Copyright (C) 1999, Linus Torvalds
+ *
+ */
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <asm/semaphore.h>
+
+/*
+ * Atomically update sem->count. Equivalent to:
+ * old_val = sem->count.counter;
+ * new_val = ((old_val >= 0) ? old_val : 0) + incr;
+ * sem->count.counter = new_val;
+ * return old_val;
+ */
+static inline int __sem_update_count(struct semaphore *sem, int incr)
+{
+ int old_val, new_val;
+
+ __asm__ __volatile__(" l %0,0(%3)\n"
+ "0: ltr %1,%0\n"
+ " jhe 1f\n"
+ " lhi %1,0\n"
+ "1: ar %1,%4\n"
+ " cs %0,%1,0(%3)\n"
+ " jl 0b\n"
+ : "=&d" (old_val), "=&d" (new_val),
+ "=m" (sem->count)
+ : "a" (&sem->count), "d" (incr), "m" (sem->count)
+ : "cc" );
+ return old_val;
+}
+
+/*
+ * The inline function up() incremented count but the result
+ * was <= 0. This indicates that some process is waiting on
+ * the semaphore. The semaphore is free and we'll wake the
+ * first sleeping process, so we set count to 1 unless some
+ * other cpu has called up in the meantime in which case
+ * we just increment count by 1.
+ */
+void __up(struct semaphore *sem)
+{
+ __sem_update_count(sem, 1);
+ wake_up(&sem->wait);
+}
+
+/*
+ * The inline function down() decremented count and the result
+ * was < 0. The wait loop will atomically test and update the
+ * semaphore counter following the rules:
+ * count > 0: decrement count, wake up queue and exit.
+ * count <= 0: set count to -1, go to sleep.
+ */
+void __sched __down(struct semaphore * sem)
+{
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, tsk);
+
+ __set_task_state(tsk, TASK_UNINTERRUPTIBLE);
+ add_wait_queue_exclusive(&sem->wait, &wait);
+ while (__sem_update_count(sem, -1) <= 0) {
+ schedule();
+ set_task_state(tsk, TASK_UNINTERRUPTIBLE);
+ }
+ remove_wait_queue(&sem->wait, &wait);
+ __set_task_state(tsk, TASK_RUNNING);
+ wake_up(&sem->wait);
+}
+
+/*
+ * Same as __down() with an additional test for signals.
+ * If a signal is pending the count is updated as follows:
+ * count > 0: wake up queue and exit.
+ * count <= 0: set count to 0, wake up queue and exit.
+ */
+int __sched __down_interruptible(struct semaphore * sem)
+{
+ int retval = 0;
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, tsk);
+
+ __set_task_state(tsk, TASK_INTERRUPTIBLE);
+ add_wait_queue_exclusive(&sem->wait, &wait);
+ while (__sem_update_count(sem, -1) <= 0) {
+ if (signal_pending(current)) {
+ __sem_update_count(sem, 0);
+ retval = -EINTR;
+ break;
+ }
+ schedule();
+ set_task_state(tsk, TASK_INTERRUPTIBLE);
+ }
+ remove_wait_queue(&sem->wait, &wait);
+ __set_task_state(tsk, TASK_RUNNING);
+ wake_up(&sem->wait);
+ return retval;
+}
+