]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
s390/uaccess: Shorten raw_copy_from_user() / raw_copy_to_user() inline assemblies
authorHeiko Carstens <hca@linux.ibm.com>
Tue, 11 Feb 2025 19:19:26 +0000 (20:19 +0100)
committerVasily Gorbik <gor@linux.ibm.com>
Tue, 4 Mar 2025 16:18:03 +0000 (17:18 +0100)
Add specific exception handler for copy_to_user() / copy_from_user()
mvcos fault handling, which allows to shorten the inline assemblies to
three instructions.

On fault the exception handler adjusts the length used by the mvcos
instruction in a way that the instruction completes with condition code
zero, indicating the number of bytes copied with the input/output operand
'size'. This allows to calculate and return the number of bytes not copied,
if any, like required.

Loop and return value handling is changed to C so that the compiler may
optimize the code.

Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
arch/s390/include/asm/asm-extable.h
arch/s390/include/asm/uaccess.h
arch/s390/mm/extable.c

index 2e829c16fd8ad2404fb49f12f21b1da56d88d495..d23ea0c94e4ea643be85575ba653856bf2d0a733 100644 (file)
@@ -14,6 +14,8 @@
 #define EX_TYPE_UA_LOAD_REGPAIR        6
 #define EX_TYPE_ZEROPAD                7
 #define EX_TYPE_FPC            8
+#define EX_TYPE_UA_MVCOS_TO    9
+#define EX_TYPE_UA_MVCOS_FROM  10
 
 #define EX_DATA_REG_ERR_SHIFT  0
 #define EX_DATA_REG_ERR                GENMASK(3, 0)
 #define EX_TABLE_FPC(_fault, _target)                                  \
        __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_FPC, __stringify(%%r0), __stringify(%%r0), 0)
 
+#define EX_TABLE_UA_MVCOS_TO(_fault, _target)                          \
+       __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_MVCOS_TO, __stringify(%%r0), __stringify(%%r0), 0)
+
+#define EX_TABLE_UA_MVCOS_FROM(_fault, _target)                                \
+       __EX_TABLE(__ex_table, _fault, _target, EX_TYPE_UA_MVCOS_FROM, __stringify(%%r0), __stringify(%%r0), 0)
+
 #endif /* __ASM_EXTABLE_H */
index f5920163ee974b577549eccde585dc2dd2fe4c4f..f9ace938743ca37ff0408337cd0b6285cdea8a46 100644 (file)
@@ -44,43 +44,42 @@ union oac {
        };
 };
 
-static __always_inline __must_check unsigned long
+#ifdef CONFIG_KMSAN
+#define uaccess_kmsan_or_inline noinline __maybe_unused __no_sanitize_memory
+#else
+#define uaccess_kmsan_or_inline __always_inline
+#endif
+
+static uaccess_kmsan_or_inline __must_check unsigned long
 raw_copy_from_user_key(void *to, const void __user *from, unsigned long size, unsigned long key)
 {
-       unsigned long rem;
+       unsigned long osize;
        union oac spec = {
                .oac2.key = key,
                .oac2.as = PSW_BITS_AS_SECONDARY,
                .oac2.k = 1,
                .oac2.a = 1,
        };
-
-       asm_inline volatile(
-               "       lr      %%r0,%[spec]\n"
-               "0:     mvcos   0(%[to]),0(%[from]),%[size]\n"
-               "1:     jz      5f\n"
-               "       algr    %[size],%[val]\n"
-               "       slgr    %[from],%[val]\n"
-               "       slgr    %[to],%[val]\n"
-               "       j       0b\n"
-               "2:     la      %[rem],4095(%[from])\n" /* rem = from + 4095 */
-               "       nr      %[rem],%[val]\n"        /* rem = (from + 4095) & -4096 */
-               "       slgr    %[rem],%[from]\n"
-               "       clgr    %[size],%[rem]\n"       /* copy crosses next page boundary? */
-               "       jnh     6f\n"
-               "3:     mvcos   0(%[to]),0(%[from]),%[rem]\n"
-               "4:     slgr    %[size],%[rem]\n"
-               "       j       6f\n"
-               "5:     lghi    %[size],0\n"
-               "6:\n"
-               EX_TABLE(0b, 2b)
-               EX_TABLE(1b, 2b)
-               EX_TABLE(3b, 6b)
-               EX_TABLE(4b, 6b)
-               : [size] "+&a" (size), [from] "+&a" (from), [to] "+&a" (to), [rem] "=&a" (rem)
-               : [val] "a" (-4096UL), [spec] "d" (spec.val)
-               : "cc", "memory", "0");
-       return size;
+       int cc;
+
+       while (1) {
+               osize = size;
+               asm_inline volatile(
+                       "       lr      %%r0,%[spec]\n"
+                       "0:     mvcos   %[to],%[from],%[size]\n"
+                       "1:     nopr    %%r7\n"
+                       CC_IPM(cc)
+                       EX_TABLE_UA_MVCOS_FROM(0b, 0b)
+                       EX_TABLE_UA_MVCOS_FROM(1b, 0b)
+                       : CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char *)to)
+                       : [spec] "d" (spec.val), [from] "Q" (*(const char __user *)from)
+                       : CC_CLOBBER_LIST("memory", "0"));
+               if (likely(CC_TRANSFORM(cc) == 0))
+                       return osize - size;
+               size -= 4096;
+               to += 4096;
+               from += 4096;
+       }
 }
 
 static __always_inline __must_check unsigned long
@@ -89,43 +88,36 @@ raw_copy_from_user(void *to, const void __user *from, unsigned long n)
        return raw_copy_from_user_key(to, from, n, 0);
 }
 
-static __always_inline __must_check unsigned long
+static uaccess_kmsan_or_inline __must_check unsigned long
 raw_copy_to_user_key(void __user *to, const void *from, unsigned long size, unsigned long key)
 {
-       unsigned long rem;
+       unsigned long osize;
        union oac spec = {
                .oac1.key = key,
                .oac1.as = PSW_BITS_AS_SECONDARY,
                .oac1.k = 1,
                .oac1.a = 1,
        };
-
-       asm_inline volatile(
-               "       lr      %%r0,%[spec]\n"
-               "0:     mvcos   0(%[to]),0(%[from]),%[size]\n"
-               "1:     jz      5f\n"
-               "       algr    %[size],%[val]\n"
-               "       slgr    %[to],%[val]\n"
-               "       slgr    %[from],%[val]\n"
-               "       j       0b\n"
-               "2:     la      %[rem],4095(%[to])\n"   /* rem = to + 4095 */
-               "       nr      %[rem],%[val]\n"        /* rem = (to + 4095) & -4096 */
-               "       slgr    %[rem],%[to]\n"
-               "       clgr    %[size],%[rem]\n"       /* copy crosses next page boundary? */
-               "       jnh     6f\n"
-               "3:     mvcos   0(%[to]),0(%[from]),%[rem]\n"
-               "4:     slgr    %[size],%[rem]\n"
-               "       j       6f\n"
-               "5:     lghi    %[size],0\n"
-               "6:\n"
-               EX_TABLE(0b, 2b)
-               EX_TABLE(1b, 2b)
-               EX_TABLE(3b, 6b)
-               EX_TABLE(4b, 6b)
-               : [size] "+&a" (size), [to] "+&a" (to), [from] "+&a" (from), [rem] "=&a" (rem)
-               : [val] "a" (-4096UL), [spec] "d" (spec.val)
-               : "cc", "memory", "0");
-       return size;
+       int cc;
+
+       while (1) {
+               osize = size;
+               asm_inline volatile(
+                       "       lr      %%r0,%[spec]\n"
+                       "0:     mvcos   %[to],%[from],%[size]\n"
+                       "1:     nopr    %%r7\n"
+                       CC_IPM(cc)
+                       EX_TABLE_UA_MVCOS_TO(0b, 0b)
+                       EX_TABLE_UA_MVCOS_TO(1b, 0b)
+                       : CC_OUT(cc, cc), [size] "+d" (size), [to] "=Q" (*(char __user *)to)
+                       : [spec] "d" (spec.val), [from] "Q" (*(const char *)from)
+                       : CC_CLOBBER_LIST("memory", "0"));
+               if (likely(CC_TRANSFORM(cc) == 0))
+                       return osize - size;
+               size -= 4096;
+               to += 4096;
+               from += 4096;
+       }
 }
 
 static __always_inline __must_check unsigned long
@@ -158,12 +150,6 @@ copy_to_user_key(void __user *to, const void *from, unsigned long n, unsigned lo
 
 int __noreturn __put_user_bad(void);
 
-#ifdef CONFIG_KMSAN
-#define uaccess_kmsan_or_inline noinline __maybe_unused __no_sanitize_memory
-#else
-#define uaccess_kmsan_or_inline __always_inline
-#endif
-
 #ifdef CONFIG_CC_HAS_ASM_GOTO_OUTPUT
 
 #define DEFINE_PUT_USER_NOINSTR(type)                                  \
index a046be1715cf0019e1f8cbcd71572413ac4b2930..7498e858c401950714d5fa02adb12e8b28a2fd98 100644 (file)
@@ -73,6 +73,49 @@ static bool ex_handler_fpc(const struct exception_table_entry *ex, struct pt_reg
        return true;
 }
 
+struct insn_ssf {
+       u64     opc1 : 8;
+       u64     r3   : 4;
+       u64     opc2 : 4;
+       u64     b1   : 4;
+       u64     d1   : 12;
+       u64     b2   : 4;
+       u64     d2   : 12;
+} __packed;
+
+static bool ex_handler_ua_mvcos(const struct exception_table_entry *ex,
+                               bool from, struct pt_regs *regs)
+{
+       unsigned long uaddr, remainder;
+       struct insn_ssf *insn;
+
+       /*
+        * If the faulting user space access crossed a page boundary retry by
+        * limiting the access to the first page (adjust length accordingly).
+        * Then the mvcos instruction will either complete with condition code
+        * zero, or generate another fault where the user space access did not
+        * cross a page boundary.
+        * If the faulting user space access did not cross a page boundary set
+        * length to zero and retry. In this case no user space access will
+        * happen, and the mvcos instruction will complete with condition code
+        * zero.
+        * In both cases the instruction will complete with condition code
+        * zero (copying finished), and the register which contains the
+        * length, indicates the number of bytes copied.
+        */
+       regs->psw.addr = extable_fixup(ex);
+       insn = (struct insn_ssf *)regs->psw.addr;
+       if (from)
+               uaddr = regs->gprs[insn->b2] + insn->d2;
+       else
+               uaddr = regs->gprs[insn->b1] + insn->d1;
+       remainder = PAGE_SIZE - (uaddr & (PAGE_SIZE - 1));
+       if (regs->gprs[insn->r3] <= remainder)
+               remainder = 0;
+       regs->gprs[insn->r3] = remainder;
+       return true;
+}
+
 bool fixup_exception(struct pt_regs *regs)
 {
        const struct exception_table_entry *ex;
@@ -95,6 +138,10 @@ bool fixup_exception(struct pt_regs *regs)
                return ex_handler_zeropad(ex, regs);
        case EX_TYPE_FPC:
                return ex_handler_fpc(ex, regs);
+       case EX_TYPE_UA_MVCOS_TO:
+               return ex_handler_ua_mvcos(ex, false, regs);
+       case EX_TYPE_UA_MVCOS_FROM:
+               return ex_handler_ua_mvcos(ex, true, regs);
        }
        panic("invalid exception table entry");
 }