s390/uaccess: Shorten raw_copy_from_user() / raw_copy_to_user() inline assemblies

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>
This commit is contained in:
Heiko Carstens
2025-03-04 17:18:03 +01:00
committed by Vasily Gorbik
parent fb5bbcdcc3
commit c488f5187a
3 changed files with 103 additions and 62 deletions
+8
View 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)
@@ -84,4 +86,10 @@
#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 */
+48 -62
View 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,
};
int cc;
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;
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,
};
int cc;
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;
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) \
+47
View 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");
}