kern,sim: implement FUTEX_WAKE_OP

This patch implements FUTEX_WAKE_OP operation in the futex syscall.
Below is its description:

int futex(int *uaddr, int futex_op, int val,
          const struct timespec *timeout,
          int *uaddr2, int val3);

This operation was added to support some user-space use cases where more
than one futex must be handled at the same time.  The most notable
example is the implementation of pthread_cond_signal(3), which requires
operations on two futexes, the one used to implement the mutex and the
one used in the implementation of the wait queue associated with the
condition variable.  FUTEX_WAKE_OP allows such cases to be implemented
without leading to high rates of contention and context switching.

Reference: http://man7.org/linux/man-pages/man2/futex.2.html

Change-Id: I215f3c2a7bdc6374e5dfe06ee721c76933a10f2d
Reviewed-on: https://gem5-review.googlesource.com/c/9630
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-by: Brandon Potter <Brandon.Potter@amd.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/kern/linux/linux.hh b/src/kern/linux/linux.hh
index 4ed3902..9325301 100644
--- a/src/kern/linux/linux.hh
+++ b/src/kern/linux/linux.hh
@@ -243,12 +243,28 @@
     static const unsigned TGT_FUTEX_WAKE                = 1;
     static const unsigned TGT_FUTEX_REQUEUE             = 3;
     static const unsigned TGT_FUTEX_CMP_REQUEUE         = 4;
+    static const unsigned TGT_FUTEX_WAKE_OP             = 5;
     static const unsigned TGT_FUTEX_WAIT_BITSET         = 9;
     static const unsigned TGT_FUTEX_WAKE_BITSET         = 10;
     static const unsigned TGT_EAGAIN                    = 11;
     static const unsigned TGT_EWOULDBLOCK               = TGT_EAGAIN;
     static const unsigned TGT_FUTEX_PRIVATE_FLAG        = 128;
     static const unsigned TGT_FUTEX_CLOCK_REALTIME_FLAG = 256;
+    // op field of futex_wake_op operation
+    static const unsigned TGT_FUTEX_OP_SET  = 0; // uaddr2 = oparg;
+    static const unsigned TGT_FUTEX_OP_ADD  = 1; // uaddr2 += oparg;
+    static const unsigned TGT_FUTEX_OP_OR   = 2; // uaddr2 |= oparg;
+    static const unsigned TGT_FUTEX_OP_ANDN = 3; // uaddr2 &= ~oparg;
+    static const unsigned TGT_FUTEX_OP_XOR  = 4; // uaddr2 ^= oparg;
+    // Use (1 << oparg) as operand
+    static const unsigned TGT_FUTEX_OP_ARG_SHIFT = 8;
+    // cmp field of futex_wake_op operation
+    static const unsigned TGT_FUTEX_OP_CMP_EQ = 0;
+    static const unsigned TGT_FUTEX_OP_CMP_NE = 1;
+    static const unsigned TGT_FUTEX_OP_CMP_LT = 2;
+    static const unsigned TGT_FUTEX_OP_CMP_LE = 3;
+    static const unsigned TGT_FUTEX_OP_CMP_GT = 4;
+    static const unsigned TGT_FUTEX_OP_CMP_GE = 5;
 
     // for *at syscalls
     static const int TGT_AT_FDCWD   = -100;
diff --git a/src/sim/syscall_emul.hh b/src/sim/syscall_emul.hh
index 0b7585c..0378bd7 100644
--- a/src/sim/syscall_emul.hh
+++ b/src/sim/syscall_emul.hh
@@ -455,6 +455,77 @@
         if (OS::TGT_FUTEX_CMP_REQUEUE && val3 != mem_val)
             return -OS::TGT_EWOULDBLOCK;
         return futex_map.requeue(uaddr, process->tgid(), val, timeout, uaddr2);
+    } else if (OS::TGT_FUTEX_WAKE_OP == op) {
+        /*
+         * The FUTEX_WAKE_OP operation is equivalent to executing the
+         * following code atomically and totally ordered with respect to
+         * other futex operations on any of the two supplied futex words:
+         *
+         *   int oldval = *(int *) addr2;
+         *   *(int *) addr2 = oldval op oparg;
+         *   futex(addr1, FUTEX_WAKE, val, 0, 0, 0);
+         *   if (oldval cmp cmparg)
+         *        futex(addr2, FUTEX_WAKE, val2, 0, 0, 0);
+         *
+         * (op, oparg, cmp, cmparg are encoded in val3)
+         *
+         * +---+---+-----------+-----------+
+         * |op |cmp|   oparg   |  cmparg   |
+         * +---+---+-----------+-----------+
+         *   4   4       12          12    <== # of bits
+         *
+         * reference: http://man7.org/linux/man-pages/man2/futex.2.html
+         *
+         */
+        // get value from simulated-space
+        BufferArg buf(uaddr2, sizeof(int));
+        buf.copyIn(tc->getMemProxy());
+        int oldval = *(int*)buf.bufferPtr();
+        int newval = oldval;
+        // extract op, oparg, cmp, cmparg from val3
+        int wake_cmparg =  val3 & 0xfff;
+        int wake_oparg  = (val3 & 0xfff000)   >> 12;
+        int wake_cmp    = (val3 & 0xf000000)  >> 24;
+        int wake_op     = (val3 & 0xf0000000) >> 28;
+        if ((wake_op & OS::TGT_FUTEX_OP_ARG_SHIFT) >> 3 == 1)
+            wake_oparg = (1 << wake_oparg);
+        wake_op &= ~OS::TGT_FUTEX_OP_ARG_SHIFT;
+        // perform operation on the value of the second futex
+        if (wake_op == OS::TGT_FUTEX_OP_SET)
+            newval = wake_oparg;
+        else if (wake_op == OS::TGT_FUTEX_OP_ADD)
+            newval += wake_oparg;
+        else if (wake_op == OS::TGT_FUTEX_OP_OR)
+            newval |= wake_oparg;
+        else if (wake_op == OS::TGT_FUTEX_OP_ANDN)
+            newval &= ~wake_oparg;
+        else if (wake_op == OS::TGT_FUTEX_OP_XOR)
+            newval ^= wake_oparg;
+        // copy updated value back to simulated-space
+        *(int*)buf.bufferPtr() = newval;
+        buf.copyOut(tc->getMemProxy());
+        // perform the first wake-up
+        int woken1 = futex_map.wakeup(uaddr, process->tgid(), val);
+        int woken2 = 0;
+        // calculate the condition of the second wake-up
+        bool is_wake2 = false;
+        if (wake_cmp == OS::TGT_FUTEX_OP_CMP_EQ)
+            is_wake2 = oldval == wake_cmparg;
+        else if (wake_cmp == OS::TGT_FUTEX_OP_CMP_NE)
+            is_wake2 = oldval != wake_cmparg;
+        else if (wake_cmp == OS::TGT_FUTEX_OP_CMP_LT)
+            is_wake2 = oldval < wake_cmparg;
+        else if (wake_cmp == OS::TGT_FUTEX_OP_CMP_LE)
+            is_wake2 = oldval <= wake_cmparg;
+        else if (wake_cmp == OS::TGT_FUTEX_OP_CMP_GT)
+            is_wake2 = oldval > wake_cmparg;
+        else if (wake_cmp == OS::TGT_FUTEX_OP_CMP_GE)
+            is_wake2 = oldval >= wake_cmparg;
+        // perform the second wake-up
+        if (is_wake2)
+            woken2 = futex_map.wakeup(uaddr2, process->tgid(), timeout);
+
+        return woken1 + woken2;
     }
     warn("futex: op %d not implemented; ignoring.", op);
     return -ENOSYS;