Introduce RSEQ_FLAG_REGISTER with the same behavior as the flag value
"0". The main advantage of introducing this flag as a non-zero (1 << 1)
value is that it can be combined with other flags to register and check
for features with a single system call. Considering that this system
call needs to be performed for each new thread in glibc, minimize the
amount of overhead required.

This is needed for introducing a new RSEQ_FLAG_RELIABLE_CPU_ID flag in a
later change.

Signed-off-by: Mathieu Desnoyers <[email protected]>
Cc: Peter Zijlstra (Intel) <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Florian Weimer <[email protected]>
Cc: "Paul E. McKenney" <[email protected]>
Cc: Boqun Feng <[email protected]>
Cc: "H . Peter Anvin" <[email protected]>
Cc: Paul Turner <[email protected]>
Cc: Dmitry Vyukov <[email protected]>
Cc: Neel Natu <[email protected]>
Cc: [email protected]
---
 include/uapi/linux/rseq.h | 10 ++++-
 kernel/rseq.c             | 77 +++++++++++++++++++++------------------
 2 files changed, 51 insertions(+), 36 deletions(-)

diff --git a/include/uapi/linux/rseq.h b/include/uapi/linux/rseq.h
index 9a402fdb60e9..3b5fba25461a 100644
--- a/include/uapi/linux/rseq.h
+++ b/include/uapi/linux/rseq.h
@@ -18,8 +18,16 @@ enum rseq_cpu_id_state {
        RSEQ_CPU_ID_REGISTRATION_FAILED         = -2,
 };
 
+/*
+ * RSEQ_FLAG_UNREGISTER:       Unregister rseq ABI for caller thread.
+ * RSEQ_FLAG_REGISTER:         Register rseq ABI for caller thread.
+ *
+ * Flag value 0 has the same behavior as RSEQ_FLAG_REGISTER, but cannot be
+ * combined with other flags. This behavior is kept for backward compatibility.
+ */
 enum rseq_flags {
-       RSEQ_FLAG_UNREGISTER = (1 << 0),
+       RSEQ_FLAG_UNREGISTER                    = (1 << 0),
+       RSEQ_FLAG_REGISTER                      = (1 << 1),
 };
 
 enum rseq_cs_flags_bit {
diff --git a/kernel/rseq.c b/kernel/rseq.c
index a4f86a9d6937..47ce221cd6f9 100644
--- a/kernel/rseq.c
+++ b/kernel/rseq.c
@@ -309,9 +309,16 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, 
rseq_len,
 {
        int ret;
 
-       if (flags & RSEQ_FLAG_UNREGISTER) {
-               if (flags & ~RSEQ_FLAG_UNREGISTER)
-                       return -EINVAL;
+       /*
+        * Flag value 0 has the same behavior as RSEQ_FLAG_REGISTER, but cannot
+        * be combined with other flags. This behavior is kept for backward
+        * compatibility.
+        */
+       if (!flags)
+               flags = RSEQ_FLAG_REGISTER;
+
+       switch (flags) {
+       case RSEQ_FLAG_UNREGISTER:
                /* Unregister rseq for current thread. */
                if (current->rseq != rseq || !current->rseq)
                        return -EINVAL;
@@ -324,43 +331,43 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, 
rseq_len,
                        return ret;
                current->rseq = NULL;
                current->rseq_sig = 0;
-               return 0;
-       }
-
-       if (unlikely(flags))
-               return -EINVAL;
+               break;
+       case RSEQ_FLAG_REGISTER:
+               if (current->rseq) {
+                       /*
+                        * If rseq is already registered, check whether
+                        * the provided address differs from the prior
+                        * one.
+                        */
+                       if (current->rseq != rseq || rseq_len != sizeof(*rseq))
+                               return -EINVAL;
+                       if (current->rseq_sig != sig)
+                               return -EPERM;
+                       /* Already registered. */
+                       return -EBUSY;
+               }
 
-       if (current->rseq) {
                /*
-                * If rseq is already registered, check whether
-                * the provided address differs from the prior
-                * one.
+                * If there was no rseq previously registered,
+                * ensure the provided rseq is properly aligned and valid.
                 */
-               if (current->rseq != rseq || rseq_len != sizeof(*rseq))
+               if (!IS_ALIGNED((unsigned long)rseq, __alignof__(*rseq)) ||
+                   rseq_len != sizeof(*rseq))
                        return -EINVAL;
-               if (current->rseq_sig != sig)
-                       return -EPERM;
-               /* Already registered. */
-               return -EBUSY;
-       }
-
-       /*
-        * If there was no rseq previously registered,
-        * ensure the provided rseq is properly aligned and valid.
-        */
-       if (!IS_ALIGNED((unsigned long)rseq, __alignof__(*rseq)) ||
-           rseq_len != sizeof(*rseq))
+               if (!access_ok(rseq, rseq_len))
+                       return -EFAULT;
+               current->rseq = rseq;
+               current->rseq_sig = sig;
+               /*
+                * If rseq was previously inactive, and has just been
+                * registered, ensure the cpu_id_start and cpu_id fields
+                * are updated before returning to user-space.
+                */
+               rseq_set_notify_resume(current);
+               break;
+       default:
                return -EINVAL;
-       if (!access_ok(rseq, rseq_len))
-               return -EFAULT;
-       current->rseq = rseq;
-       current->rseq_sig = sig;
-       /*
-        * If rseq was previously inactive, and has just been
-        * registered, ensure the cpu_id_start and cpu_id fields
-        * are updated before returning to user-space.
-        */
-       rseq_set_notify_resume(current);
+       }
 
        return 0;
 }
-- 
2.17.1

Reply via email to