Merge a562456643 ("Merge branch 'x86-rep-insns': x86 user copy clarifications") into android-mainline
Steps on the way to 6.4-rc1 Change-Id: I35b3b1c7dc6e37e7067fb54d1f358f7863be31f4 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
@@ -107,7 +107,7 @@ ccflags-remove-$(CONFIG_FUNCTION_TRACER) += -pg
|
||||
asflags-y := -DZIMAGE
|
||||
|
||||
# Supply kernel BSS size to the decompressor via a linker symbol.
|
||||
KBSS_SZ = $(shell echo $$(($$($(NM) $(obj)/../../../../vmlinux | \
|
||||
KBSS_SZ = $(shell echo $$(($$($(NM) vmlinux | \
|
||||
sed -n -e 's/^\([^ ]*\) [ABD] __bss_start$$/-0x\1/p' \
|
||||
-e 's/^\([^ ]*\) [ABD] __bss_stop$$/+0x\1/p') )) )
|
||||
LDFLAGS_vmlinux = --defsym _kernel_bss_size=$(KBSS_SZ)
|
||||
|
||||
@@ -244,19 +244,6 @@ THUMB( fpreg .req r7 )
|
||||
.endm
|
||||
#endif
|
||||
|
||||
.macro local_bh_disable, ti, tmp
|
||||
ldr \tmp, [\ti, #TI_PREEMPT]
|
||||
add \tmp, \tmp, #SOFTIRQ_DISABLE_OFFSET
|
||||
str \tmp, [\ti, #TI_PREEMPT]
|
||||
.endm
|
||||
|
||||
.macro local_bh_enable_ti, ti, tmp
|
||||
get_thread_info \ti
|
||||
ldr \tmp, [\ti, #TI_PREEMPT]
|
||||
sub \tmp, \tmp, #SOFTIRQ_DISABLE_OFFSET
|
||||
str \tmp, [\ti, #TI_PREEMPT]
|
||||
.endm
|
||||
|
||||
#define USERL(l, x...) \
|
||||
9999: x; \
|
||||
.pushsection __ex_table,"a"; \
|
||||
|
||||
+3
-14
@@ -22,18 +22,7 @@
|
||||
@ IRQs enabled.
|
||||
@
|
||||
ENTRY(do_vfp)
|
||||
local_bh_disable r10, r4
|
||||
ldr r4, .LCvfp
|
||||
ldr r11, [r10, #TI_CPU] @ CPU number
|
||||
add r10, r10, #TI_VFPSTATE @ r10 = workspace
|
||||
ldr pc, [r4] @ call VFP entry point
|
||||
mov r1, r10
|
||||
mov r3, r9
|
||||
b vfp_entry
|
||||
ENDPROC(do_vfp)
|
||||
|
||||
ENTRY(vfp_null_entry)
|
||||
local_bh_enable_ti r10, r4
|
||||
ret lr
|
||||
ENDPROC(vfp_null_entry)
|
||||
|
||||
.align 2
|
||||
.LCvfp:
|
||||
.word vfp_vector
|
||||
|
||||
+17
-13
@@ -6,9 +6,9 @@
|
||||
* Written by Deep Blue Solutions Limited.
|
||||
*
|
||||
* This code is called from the kernel's undefined instruction trap.
|
||||
* r9 holds the return address for successful handling.
|
||||
* r1 holds the thread_info pointer
|
||||
* r3 holds the return address for successful handling.
|
||||
* lr holds the return address for unrecognised instructions.
|
||||
* r10 points at the start of the private FP workspace in the thread structure
|
||||
* sp points to a struct pt_regs (as defined in include/asm/proc/ptrace.h)
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
@@ -69,13 +69,15 @@
|
||||
@ VFP hardware support entry point.
|
||||
@
|
||||
@ r0 = instruction opcode (32-bit ARM or two 16-bit Thumb)
|
||||
@ r1 = thread_info pointer
|
||||
@ r2 = PC value to resume execution after successful emulation
|
||||
@ r9 = normal "successful" return address
|
||||
@ r10 = vfp_state union
|
||||
@ r11 = CPU number
|
||||
@ r3 = normal "successful" return address
|
||||
@ lr = unrecognised instruction return address
|
||||
@ IRQs enabled.
|
||||
ENTRY(vfp_support_entry)
|
||||
ldr r11, [r1, #TI_CPU] @ CPU number
|
||||
add r10, r1, #TI_VFPSTATE @ r10 = workspace
|
||||
|
||||
DBGSTR3 "instr %08x pc %08x state %p", r0, r2, r10
|
||||
|
||||
.fpu vfpv2
|
||||
@@ -85,9 +87,9 @@ ENTRY(vfp_support_entry)
|
||||
bne look_for_VFP_exceptions @ VFP is already enabled
|
||||
|
||||
DBGSTR1 "enable %x", r10
|
||||
ldr r3, vfp_current_hw_state_address
|
||||
ldr r9, vfp_current_hw_state_address
|
||||
orr r1, r1, #FPEXC_EN @ user FPEXC has the enable bit set
|
||||
ldr r4, [r3, r11, lsl #2] @ vfp_current_hw_state pointer
|
||||
ldr r4, [r9, r11, lsl #2] @ vfp_current_hw_state pointer
|
||||
bic r5, r1, #FPEXC_EX @ make sure exceptions are disabled
|
||||
cmp r4, r10 @ this thread owns the hw context?
|
||||
#ifndef CONFIG_SMP
|
||||
@@ -146,7 +148,7 @@ vfp_reload_hw:
|
||||
#endif
|
||||
|
||||
DBGSTR1 "load state %p", r10
|
||||
str r10, [r3, r11, lsl #2] @ update the vfp_current_hw_state pointer
|
||||
str r10, [r9, r11, lsl #2] @ update the vfp_current_hw_state pointer
|
||||
@ Load the saved state back into the VFP
|
||||
VFPFLDMIA r10, r5 @ reload the working registers while
|
||||
@ FPEXC is in a safe state
|
||||
@@ -175,9 +177,12 @@ vfp_hw_state_valid:
|
||||
@ else it's one 32-bit instruction, so
|
||||
@ always subtract 4 from the following
|
||||
@ instruction address.
|
||||
local_bh_enable_ti r10, r4
|
||||
ret r9 @ we think we have handled things
|
||||
|
||||
mov lr, r3 @ we think we have handled things
|
||||
local_bh_enable_and_ret:
|
||||
adr r0, .
|
||||
mov r1, #SOFTIRQ_DISABLE_OFFSET
|
||||
b __local_bh_enable_ip @ tail call
|
||||
|
||||
look_for_VFP_exceptions:
|
||||
@ Check for synchronous or asynchronous exception
|
||||
@@ -200,13 +205,12 @@ skip:
|
||||
@ not recognised by VFP
|
||||
|
||||
DBGSTR "not VFP"
|
||||
local_bh_enable_ti r10, r4
|
||||
ret lr
|
||||
b local_bh_enable_and_ret
|
||||
|
||||
process_exception:
|
||||
DBGSTR "bounce"
|
||||
mov r2, sp @ nothing stacked - regdump is at TOS
|
||||
mov lr, r9 @ setup for a return to the user code.
|
||||
mov lr, r3 @ setup for a return to the user code.
|
||||
|
||||
@ Now call the C code to package up the bounce to the support code
|
||||
@ r0 holds the trigger instruction
|
||||
|
||||
@@ -32,10 +32,9 @@
|
||||
/*
|
||||
* Our undef handlers (in entry.S)
|
||||
*/
|
||||
asmlinkage void vfp_support_entry(void);
|
||||
asmlinkage void vfp_null_entry(void);
|
||||
asmlinkage void vfp_support_entry(u32, void *, u32, u32);
|
||||
|
||||
asmlinkage void (*vfp_vector)(void) = vfp_null_entry;
|
||||
static bool have_vfp __ro_after_init;
|
||||
|
||||
/*
|
||||
* Dual-use variable.
|
||||
@@ -645,6 +644,25 @@ static int vfp_starting_cpu(unsigned int unused)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Entered with:
|
||||
*
|
||||
* r0 = instruction opcode (32-bit ARM or two 16-bit Thumb)
|
||||
* r1 = thread_info pointer
|
||||
* r2 = PC value to resume execution after successful emulation
|
||||
* r3 = normal "successful" return address
|
||||
* lr = unrecognised instruction return address
|
||||
*/
|
||||
asmlinkage void vfp_entry(u32 trigger, struct thread_info *ti, u32 resume_pc,
|
||||
u32 resume_return_address)
|
||||
{
|
||||
if (unlikely(!have_vfp))
|
||||
return;
|
||||
|
||||
local_bh_disable();
|
||||
vfp_support_entry(trigger, ti, resume_pc, resume_return_address);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MODE_NEON
|
||||
|
||||
static int vfp_kmode_exception(struct pt_regs *regs, unsigned int instr)
|
||||
@@ -798,7 +816,6 @@ static int __init vfp_init(void)
|
||||
vfpsid = fmrx(FPSID);
|
||||
barrier();
|
||||
unregister_undef_hook(&vfp_detect_hook);
|
||||
vfp_vector = vfp_null_entry;
|
||||
|
||||
pr_info("VFP support v0.3: ");
|
||||
if (VFP_arch) {
|
||||
@@ -883,7 +900,7 @@ static int __init vfp_init(void)
|
||||
"arm/vfp:starting", vfp_starting_cpu,
|
||||
vfp_dying_cpu);
|
||||
|
||||
vfp_vector = vfp_support_entry;
|
||||
have_vfp = true;
|
||||
|
||||
thread_register_notifier(&vfp_notifier_block);
|
||||
vfp_pm_init();
|
||||
|
||||
@@ -18,32 +18,26 @@
|
||||
|
||||
/* Handles exceptions in both to and from, but doesn't do access_ok */
|
||||
__must_check unsigned long
|
||||
copy_user_enhanced_fast_string(void *to, const void *from, unsigned len);
|
||||
__must_check unsigned long
|
||||
copy_user_generic_string(void *to, const void *from, unsigned len);
|
||||
__must_check unsigned long
|
||||
copy_user_generic_unrolled(void *to, const void *from, unsigned len);
|
||||
rep_movs_alternative(void *to, const void *from, unsigned len);
|
||||
|
||||
static __always_inline __must_check unsigned long
|
||||
copy_user_generic(void *to, const void *from, unsigned len)
|
||||
copy_user_generic(void *to, const void *from, unsigned long len)
|
||||
{
|
||||
unsigned ret;
|
||||
|
||||
stac();
|
||||
/*
|
||||
* If CPU has ERMS feature, use copy_user_enhanced_fast_string.
|
||||
* Otherwise, if CPU has rep_good feature, use copy_user_generic_string.
|
||||
* Otherwise, use copy_user_generic_unrolled.
|
||||
* If CPU has FSRM feature, use 'rep movs'.
|
||||
* Otherwise, use rep_movs_alternative.
|
||||
*/
|
||||
alternative_call_2(copy_user_generic_unrolled,
|
||||
copy_user_generic_string,
|
||||
X86_FEATURE_REP_GOOD,
|
||||
copy_user_enhanced_fast_string,
|
||||
X86_FEATURE_ERMS,
|
||||
ASM_OUTPUT2("=a" (ret), "=D" (to), "=S" (from),
|
||||
"=d" (len)),
|
||||
"1" (to), "2" (from), "3" (len)
|
||||
: "memory", "rcx", "r8", "r9", "r10", "r11");
|
||||
return ret;
|
||||
asm volatile(
|
||||
"1:\n\t"
|
||||
ALTERNATIVE("rep movsb",
|
||||
"call rep_movs_alternative", ALT_NOT(X86_FEATURE_FSRM))
|
||||
"2:\n"
|
||||
_ASM_EXTABLE_UA(1b, 2b)
|
||||
:"+c" (len), "+D" (to), "+S" (from), ASM_CALL_CONSTRAINT
|
||||
: : "memory", "rax", "r8", "r9", "r10", "r11");
|
||||
clac();
|
||||
return len;
|
||||
}
|
||||
|
||||
static __always_inline __must_check unsigned long
|
||||
@@ -58,9 +52,7 @@ raw_copy_to_user(void __user *dst, const void *src, unsigned long size)
|
||||
return copy_user_generic((__force void *)dst, src, size);
|
||||
}
|
||||
|
||||
extern long __copy_user_nocache(void *dst, const void __user *src,
|
||||
unsigned size, int zerorest);
|
||||
|
||||
extern long __copy_user_nocache(void *dst, const void __user *src, unsigned size);
|
||||
extern long __copy_user_flushcache(void *dst, const void __user *src, unsigned size);
|
||||
extern void memcpy_page_flushcache(char *to, struct page *page, size_t offset,
|
||||
size_t len);
|
||||
@@ -69,8 +61,12 @@ static inline int
|
||||
__copy_from_user_inatomic_nocache(void *dst, const void __user *src,
|
||||
unsigned size)
|
||||
{
|
||||
long ret;
|
||||
kasan_check_write(dst, size);
|
||||
return __copy_user_nocache(dst, src, size, 0);
|
||||
stac();
|
||||
ret = __copy_user_nocache(dst, src, size);
|
||||
clac();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int
|
||||
@@ -85,11 +81,7 @@ __copy_from_user_flushcache(void *dst, const void __user *src, unsigned size)
|
||||
*/
|
||||
|
||||
__must_check unsigned long
|
||||
clear_user_original(void __user *addr, unsigned long len);
|
||||
__must_check unsigned long
|
||||
clear_user_rep_good(void __user *addr, unsigned long len);
|
||||
__must_check unsigned long
|
||||
clear_user_erms(void __user *addr, unsigned long len);
|
||||
rep_stos_alternative(void __user *addr, unsigned long len);
|
||||
|
||||
static __always_inline __must_check unsigned long __clear_user(void __user *addr, unsigned long size)
|
||||
{
|
||||
@@ -102,16 +94,12 @@ static __always_inline __must_check unsigned long __clear_user(void __user *addr
|
||||
*/
|
||||
asm volatile(
|
||||
"1:\n\t"
|
||||
ALTERNATIVE_3("rep stosb",
|
||||
"call clear_user_erms", ALT_NOT(X86_FEATURE_FSRM),
|
||||
"call clear_user_rep_good", ALT_NOT(X86_FEATURE_ERMS),
|
||||
"call clear_user_original", ALT_NOT(X86_FEATURE_REP_GOOD))
|
||||
ALTERNATIVE("rep stosb",
|
||||
"call rep_stos_alternative", ALT_NOT(X86_FEATURE_FSRS))
|
||||
"2:\n"
|
||||
_ASM_EXTABLE_UA(1b, 2b)
|
||||
: "+c" (size), "+D" (addr), ASM_CALL_CONSTRAINT
|
||||
: "a" (0)
|
||||
/* rep_good clobbers %rdx */
|
||||
: "rdx");
|
||||
: "a" (0));
|
||||
|
||||
clac();
|
||||
|
||||
|
||||
@@ -929,6 +929,10 @@ static void init_amd(struct cpuinfo_x86 *c)
|
||||
if (c->x86 >= 0x10)
|
||||
set_cpu_cap(c, X86_FEATURE_REP_GOOD);
|
||||
|
||||
/* AMD FSRM also implies FSRS */
|
||||
if (cpu_has(c, X86_FEATURE_FSRM))
|
||||
set_cpu_cap(c, X86_FEATURE_FSRS);
|
||||
|
||||
/* get apicid instead of initial apic id from cpuid */
|
||||
c->apicid = hard_smp_processor_id();
|
||||
|
||||
|
||||
@@ -71,6 +71,6 @@ ifneq ($(CONFIG_GENERIC_CSUM),y)
|
||||
endif
|
||||
lib-y += clear_page_64.o copy_page_64.o
|
||||
lib-y += memmove_64.o memset_64.o
|
||||
lib-y += copy_user_64.o
|
||||
lib-y += copy_user_64.o copy_user_uncached_64.o
|
||||
lib-y += cmpxchg16b_emu.o
|
||||
endif
|
||||
|
||||
+71
-120
@@ -57,134 +57,85 @@ EXPORT_SYMBOL_GPL(clear_page_erms)
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rcx count
|
||||
* rax is zero
|
||||
*
|
||||
* Output:
|
||||
* rcx: uncleared bytes or 0 if successful.
|
||||
*/
|
||||
SYM_FUNC_START(clear_user_original)
|
||||
/*
|
||||
* Copy only the lower 32 bits of size as that is enough to handle the rest bytes,
|
||||
* i.e., no need for a 'q' suffix and thus a REX prefix.
|
||||
*/
|
||||
mov %ecx,%eax
|
||||
shr $3,%rcx
|
||||
jz .Lrest_bytes
|
||||
SYM_FUNC_START(rep_stos_alternative)
|
||||
cmpq $64,%rcx
|
||||
jae .Lunrolled
|
||||
|
||||
# do the qwords first
|
||||
.p2align 4
|
||||
.Lqwords:
|
||||
movq $0,(%rdi)
|
||||
lea 8(%rdi),%rdi
|
||||
dec %rcx
|
||||
jnz .Lqwords
|
||||
cmp $8,%ecx
|
||||
jae .Lword
|
||||
|
||||
.Lrest_bytes:
|
||||
and $7, %eax
|
||||
jz .Lexit
|
||||
testl %ecx,%ecx
|
||||
je .Lexit
|
||||
|
||||
# now do the rest bytes
|
||||
.Lbytes:
|
||||
movb $0,(%rdi)
|
||||
.Lclear_user_tail:
|
||||
0: movb %al,(%rdi)
|
||||
inc %rdi
|
||||
dec %eax
|
||||
jnz .Lbytes
|
||||
|
||||
dec %rcx
|
||||
jnz .Lclear_user_tail
|
||||
.Lexit:
|
||||
RET
|
||||
|
||||
_ASM_EXTABLE_UA( 0b, .Lexit)
|
||||
|
||||
.Lword:
|
||||
1: movq %rax,(%rdi)
|
||||
addq $8,%rdi
|
||||
sub $8,%ecx
|
||||
je .Lexit
|
||||
cmp $8,%ecx
|
||||
jae .Lword
|
||||
jmp .Lclear_user_tail
|
||||
|
||||
.p2align 4
|
||||
.Lunrolled:
|
||||
10: movq %rax,(%rdi)
|
||||
11: movq %rax,8(%rdi)
|
||||
12: movq %rax,16(%rdi)
|
||||
13: movq %rax,24(%rdi)
|
||||
14: movq %rax,32(%rdi)
|
||||
15: movq %rax,40(%rdi)
|
||||
16: movq %rax,48(%rdi)
|
||||
17: movq %rax,56(%rdi)
|
||||
addq $64,%rdi
|
||||
subq $64,%rcx
|
||||
cmpq $64,%rcx
|
||||
jae .Lunrolled
|
||||
cmpl $8,%ecx
|
||||
jae .Lword
|
||||
testl %ecx,%ecx
|
||||
jne .Lclear_user_tail
|
||||
RET
|
||||
|
||||
/*
|
||||
* %rax still needs to be cleared in the exception case because this function is called
|
||||
* from inline asm and the compiler expects %rax to be zero when exiting the inline asm,
|
||||
* in case it might reuse it somewhere.
|
||||
* If we take an exception on any of the
|
||||
* word stores, we know that %rcx isn't zero,
|
||||
* so we can just go to the tail clearing to
|
||||
* get the exact count.
|
||||
*
|
||||
* The unrolled case might end up clearing
|
||||
* some bytes twice. Don't care.
|
||||
*
|
||||
* We could use the value in %rdi to avoid
|
||||
* a second fault on the exact count case,
|
||||
* but do we really care? No.
|
||||
*
|
||||
* Finally, we could try to align %rdi at the
|
||||
* top of the unrolling. But unaligned stores
|
||||
* just aren't that common or expensive.
|
||||
*/
|
||||
xor %eax,%eax
|
||||
RET
|
||||
|
||||
.Lqwords_exception:
|
||||
# convert remaining qwords back into bytes to return to caller
|
||||
shl $3, %rcx
|
||||
and $7, %eax
|
||||
add %rax,%rcx
|
||||
jmp .Lexit
|
||||
|
||||
.Lbytes_exception:
|
||||
mov %eax,%ecx
|
||||
jmp .Lexit
|
||||
|
||||
_ASM_EXTABLE_UA(.Lqwords, .Lqwords_exception)
|
||||
_ASM_EXTABLE_UA(.Lbytes, .Lbytes_exception)
|
||||
SYM_FUNC_END(clear_user_original)
|
||||
EXPORT_SYMBOL(clear_user_original)
|
||||
|
||||
/*
|
||||
* Alternative clear user-space when CPU feature X86_FEATURE_REP_GOOD is
|
||||
* present.
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rcx count
|
||||
*
|
||||
* Output:
|
||||
* rcx: uncleared bytes or 0 if successful.
|
||||
*/
|
||||
SYM_FUNC_START(clear_user_rep_good)
|
||||
# call the original thing for less than a cacheline
|
||||
cmp $64, %rcx
|
||||
jb clear_user_original
|
||||
|
||||
.Lprep:
|
||||
# copy lower 32-bits for rest bytes
|
||||
mov %ecx, %edx
|
||||
shr $3, %rcx
|
||||
jz .Lrep_good_rest_bytes
|
||||
|
||||
.Lrep_good_qwords:
|
||||
rep stosq
|
||||
|
||||
.Lrep_good_rest_bytes:
|
||||
and $7, %edx
|
||||
jz .Lrep_good_exit
|
||||
|
||||
.Lrep_good_bytes:
|
||||
mov %edx, %ecx
|
||||
rep stosb
|
||||
|
||||
.Lrep_good_exit:
|
||||
# see .Lexit comment above
|
||||
xor %eax, %eax
|
||||
RET
|
||||
|
||||
.Lrep_good_qwords_exception:
|
||||
# convert remaining qwords back into bytes to return to caller
|
||||
shl $3, %rcx
|
||||
and $7, %edx
|
||||
add %rdx, %rcx
|
||||
jmp .Lrep_good_exit
|
||||
|
||||
_ASM_EXTABLE_UA(.Lrep_good_qwords, .Lrep_good_qwords_exception)
|
||||
_ASM_EXTABLE_UA(.Lrep_good_bytes, .Lrep_good_exit)
|
||||
SYM_FUNC_END(clear_user_rep_good)
|
||||
EXPORT_SYMBOL(clear_user_rep_good)
|
||||
|
||||
/*
|
||||
* Alternative clear user-space when CPU feature X86_FEATURE_ERMS is present.
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rcx count
|
||||
*
|
||||
* Output:
|
||||
* rcx: uncleared bytes or 0 if successful.
|
||||
*
|
||||
*/
|
||||
SYM_FUNC_START(clear_user_erms)
|
||||
# call the original thing for less than a cacheline
|
||||
cmp $64, %rcx
|
||||
jb clear_user_original
|
||||
|
||||
.Lerms_bytes:
|
||||
rep stosb
|
||||
|
||||
.Lerms_exit:
|
||||
xorl %eax,%eax
|
||||
RET
|
||||
|
||||
_ASM_EXTABLE_UA(.Lerms_bytes, .Lerms_exit)
|
||||
SYM_FUNC_END(clear_user_erms)
|
||||
EXPORT_SYMBOL(clear_user_erms)
|
||||
_ASM_EXTABLE_UA( 1b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(10b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(11b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(12b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(13b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(14b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(15b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(16b, .Lclear_user_tail)
|
||||
_ASM_EXTABLE_UA(17b, .Lclear_user_tail)
|
||||
SYM_FUNC_END(rep_stos_alternative)
|
||||
EXPORT_SYMBOL(rep_stos_alternative)
|
||||
|
||||
+85
-381
@@ -7,404 +7,108 @@
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/current.h>
|
||||
#include <asm/asm-offsets.h>
|
||||
#include <asm/thread_info.h>
|
||||
#include <asm/cpufeatures.h>
|
||||
#include <asm/alternative.h>
|
||||
#include <asm/asm.h>
|
||||
#include <asm/smap.h>
|
||||
#include <asm/export.h>
|
||||
#include <asm/trapnr.h>
|
||||
|
||||
.macro ALIGN_DESTINATION
|
||||
/* check for bad alignment of destination */
|
||||
movl %edi,%ecx
|
||||
andl $7,%ecx
|
||||
jz 102f /* already aligned */
|
||||
subl $8,%ecx
|
||||
negl %ecx
|
||||
subl %ecx,%edx
|
||||
100: movb (%rsi),%al
|
||||
101: movb %al,(%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decl %ecx
|
||||
jnz 100b
|
||||
102:
|
||||
|
||||
_ASM_EXTABLE_CPY(100b, .Lcopy_user_handle_align)
|
||||
_ASM_EXTABLE_CPY(101b, .Lcopy_user_handle_align)
|
||||
.endm
|
||||
|
||||
/*
|
||||
* copy_user_generic_unrolled - memory copy with exception handling.
|
||||
* This version is for CPUs like P4 that don't have efficient micro
|
||||
* code for rep movsq
|
||||
* rep_movs_alternative - memory copy with exception handling.
|
||||
* This version is for CPUs that don't have FSRM (Fast Short Rep Movs)
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count
|
||||
* rcx count
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
* rcx uncopied bytes or 0 if successful.
|
||||
*
|
||||
* NOTE! The calling convention is very intentionally the same as
|
||||
* for 'rep movs', so that we can rewrite the function call with
|
||||
* just a plain 'rep movs' on machines that have FSRM. But to make
|
||||
* it simpler for us, we can clobber rsi/rdi and rax/r8-r11 freely.
|
||||
*/
|
||||
SYM_FUNC_START(copy_user_generic_unrolled)
|
||||
ASM_STAC
|
||||
cmpl $8,%edx
|
||||
jb .Lcopy_user_short_string_bytes
|
||||
ALIGN_DESTINATION
|
||||
movl %edx,%ecx
|
||||
andl $63,%edx
|
||||
shrl $6,%ecx
|
||||
jz copy_user_short_string
|
||||
1: movq (%rsi),%r8
|
||||
2: movq 1*8(%rsi),%r9
|
||||
3: movq 2*8(%rsi),%r10
|
||||
4: movq 3*8(%rsi),%r11
|
||||
5: movq %r8,(%rdi)
|
||||
6: movq %r9,1*8(%rdi)
|
||||
7: movq %r10,2*8(%rdi)
|
||||
8: movq %r11,3*8(%rdi)
|
||||
9: movq 4*8(%rsi),%r8
|
||||
10: movq 5*8(%rsi),%r9
|
||||
11: movq 6*8(%rsi),%r10
|
||||
12: movq 7*8(%rsi),%r11
|
||||
13: movq %r8,4*8(%rdi)
|
||||
14: movq %r9,5*8(%rdi)
|
||||
15: movq %r10,6*8(%rdi)
|
||||
16: movq %r11,7*8(%rdi)
|
||||
leaq 64(%rsi),%rsi
|
||||
leaq 64(%rdi),%rdi
|
||||
decl %ecx
|
||||
jnz 1b
|
||||
jmp copy_user_short_string
|
||||
SYM_FUNC_START(rep_movs_alternative)
|
||||
cmpq $64,%rcx
|
||||
jae .Lunrolled
|
||||
|
||||
30: shll $6,%ecx
|
||||
addl %ecx,%edx
|
||||
jmp .Lcopy_user_handle_tail
|
||||
cmp $8,%ecx
|
||||
jae .Lword
|
||||
|
||||
_ASM_EXTABLE_CPY(1b, 30b)
|
||||
_ASM_EXTABLE_CPY(2b, 30b)
|
||||
_ASM_EXTABLE_CPY(3b, 30b)
|
||||
_ASM_EXTABLE_CPY(4b, 30b)
|
||||
_ASM_EXTABLE_CPY(5b, 30b)
|
||||
_ASM_EXTABLE_CPY(6b, 30b)
|
||||
_ASM_EXTABLE_CPY(7b, 30b)
|
||||
_ASM_EXTABLE_CPY(8b, 30b)
|
||||
_ASM_EXTABLE_CPY(9b, 30b)
|
||||
_ASM_EXTABLE_CPY(10b, 30b)
|
||||
_ASM_EXTABLE_CPY(11b, 30b)
|
||||
_ASM_EXTABLE_CPY(12b, 30b)
|
||||
_ASM_EXTABLE_CPY(13b, 30b)
|
||||
_ASM_EXTABLE_CPY(14b, 30b)
|
||||
_ASM_EXTABLE_CPY(15b, 30b)
|
||||
_ASM_EXTABLE_CPY(16b, 30b)
|
||||
SYM_FUNC_END(copy_user_generic_unrolled)
|
||||
EXPORT_SYMBOL(copy_user_generic_unrolled)
|
||||
testl %ecx,%ecx
|
||||
je .Lexit
|
||||
|
||||
/* Some CPUs run faster using the string copy instructions.
|
||||
* This is also a lot simpler. Use them when possible.
|
||||
*
|
||||
* Only 4GB of copy is supported. This shouldn't be a problem
|
||||
* because the kernel normally only writes from/to page sized chunks
|
||||
* even if user space passed a longer buffer.
|
||||
* And more would be dangerous because both Intel and AMD have
|
||||
* errata with rep movsq > 4GB. If someone feels the need to fix
|
||||
* this please consider this.
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
SYM_FUNC_START(copy_user_generic_string)
|
||||
ASM_STAC
|
||||
cmpl $8,%edx
|
||||
jb 2f /* less than 8 bytes, go to byte copy loop */
|
||||
ALIGN_DESTINATION
|
||||
movl %edx,%ecx
|
||||
shrl $3,%ecx
|
||||
andl $7,%edx
|
||||
1: rep movsq
|
||||
2: movl %edx,%ecx
|
||||
3: rep movsb
|
||||
xorl %eax,%eax
|
||||
ASM_CLAC
|
||||
.Lcopy_user_tail:
|
||||
0: movb (%rsi),%al
|
||||
1: movb %al,(%rdi)
|
||||
inc %rdi
|
||||
inc %rsi
|
||||
dec %rcx
|
||||
jne .Lcopy_user_tail
|
||||
.Lexit:
|
||||
RET
|
||||
|
||||
11: leal (%rdx,%rcx,8),%ecx
|
||||
12: movl %ecx,%edx /* ecx is zerorest also */
|
||||
jmp .Lcopy_user_handle_tail
|
||||
_ASM_EXTABLE_UA( 0b, .Lexit)
|
||||
_ASM_EXTABLE_UA( 1b, .Lexit)
|
||||
|
||||
_ASM_EXTABLE_CPY(1b, 11b)
|
||||
_ASM_EXTABLE_CPY(3b, 12b)
|
||||
SYM_FUNC_END(copy_user_generic_string)
|
||||
EXPORT_SYMBOL(copy_user_generic_string)
|
||||
.p2align 4
|
||||
.Lword:
|
||||
2: movq (%rsi),%rax
|
||||
3: movq %rax,(%rdi)
|
||||
addq $8,%rsi
|
||||
addq $8,%rdi
|
||||
sub $8,%ecx
|
||||
je .Lexit
|
||||
cmp $8,%ecx
|
||||
jae .Lword
|
||||
jmp .Lcopy_user_tail
|
||||
|
||||
/*
|
||||
* Some CPUs are adding enhanced REP MOVSB/STOSB instructions.
|
||||
* It's recommended to use enhanced REP MOVSB/STOSB if it's enabled.
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
SYM_FUNC_START(copy_user_enhanced_fast_string)
|
||||
ASM_STAC
|
||||
/* CPUs without FSRM should avoid rep movsb for short copies */
|
||||
ALTERNATIVE "cmpl $64, %edx; jb copy_user_short_string", "", X86_FEATURE_FSRM
|
||||
movl %edx,%ecx
|
||||
1: rep movsb
|
||||
xorl %eax,%eax
|
||||
ASM_CLAC
|
||||
_ASM_EXTABLE_UA( 2b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA( 3b, .Lcopy_user_tail)
|
||||
|
||||
.p2align 4
|
||||
.Lunrolled:
|
||||
10: movq (%rsi),%r8
|
||||
11: movq 8(%rsi),%r9
|
||||
12: movq 16(%rsi),%r10
|
||||
13: movq 24(%rsi),%r11
|
||||
14: movq %r8,(%rdi)
|
||||
15: movq %r9,8(%rdi)
|
||||
16: movq %r10,16(%rdi)
|
||||
17: movq %r11,24(%rdi)
|
||||
20: movq 32(%rsi),%r8
|
||||
21: movq 40(%rsi),%r9
|
||||
22: movq 48(%rsi),%r10
|
||||
23: movq 56(%rsi),%r11
|
||||
24: movq %r8,32(%rdi)
|
||||
25: movq %r9,40(%rdi)
|
||||
26: movq %r10,48(%rdi)
|
||||
27: movq %r11,56(%rdi)
|
||||
addq $64,%rsi
|
||||
addq $64,%rdi
|
||||
subq $64,%rcx
|
||||
cmpq $64,%rcx
|
||||
jae .Lunrolled
|
||||
cmpl $8,%ecx
|
||||
jae .Lword
|
||||
testl %ecx,%ecx
|
||||
jne .Lcopy_user_tail
|
||||
RET
|
||||
|
||||
12: movl %ecx,%edx /* ecx is zerorest also */
|
||||
jmp .Lcopy_user_handle_tail
|
||||
|
||||
_ASM_EXTABLE_CPY(1b, 12b)
|
||||
SYM_FUNC_END(copy_user_enhanced_fast_string)
|
||||
EXPORT_SYMBOL(copy_user_enhanced_fast_string)
|
||||
|
||||
/*
|
||||
* Try to copy last bytes and clear the rest if needed.
|
||||
* Since protection fault in copy_from/to_user is not a normal situation,
|
||||
* it is not necessary to optimize tail handling.
|
||||
* Don't try to copy the tail if machine check happened
|
||||
*
|
||||
* Input:
|
||||
* eax trap number written by ex_handler_copy()
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
SYM_CODE_START_LOCAL(.Lcopy_user_handle_tail)
|
||||
cmp $X86_TRAP_MC,%eax
|
||||
je 3f
|
||||
|
||||
movl %edx,%ecx
|
||||
1: rep movsb
|
||||
2: mov %ecx,%eax
|
||||
ASM_CLAC
|
||||
RET
|
||||
|
||||
3:
|
||||
movl %edx,%eax
|
||||
ASM_CLAC
|
||||
RET
|
||||
|
||||
_ASM_EXTABLE_CPY(1b, 2b)
|
||||
|
||||
.Lcopy_user_handle_align:
|
||||
addl %ecx,%edx /* ecx is zerorest also */
|
||||
jmp .Lcopy_user_handle_tail
|
||||
|
||||
SYM_CODE_END(.Lcopy_user_handle_tail)
|
||||
|
||||
/*
|
||||
* Finish memcpy of less than 64 bytes. #AC should already be set.
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count (< 64)
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
SYM_CODE_START_LOCAL(copy_user_short_string)
|
||||
movl %edx,%ecx
|
||||
andl $7,%edx
|
||||
shrl $3,%ecx
|
||||
jz .Lcopy_user_short_string_bytes
|
||||
18: movq (%rsi),%r8
|
||||
19: movq %r8,(%rdi)
|
||||
leaq 8(%rsi),%rsi
|
||||
leaq 8(%rdi),%rdi
|
||||
decl %ecx
|
||||
jnz 18b
|
||||
.Lcopy_user_short_string_bytes:
|
||||
andl %edx,%edx
|
||||
jz 23f
|
||||
movl %edx,%ecx
|
||||
21: movb (%rsi),%al
|
||||
22: movb %al,(%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decl %ecx
|
||||
jnz 21b
|
||||
23: xor %eax,%eax
|
||||
ASM_CLAC
|
||||
RET
|
||||
|
||||
40: leal (%rdx,%rcx,8),%edx
|
||||
jmp 60f
|
||||
50: movl %ecx,%edx /* ecx is zerorest also */
|
||||
60: jmp .Lcopy_user_handle_tail
|
||||
|
||||
_ASM_EXTABLE_CPY(18b, 40b)
|
||||
_ASM_EXTABLE_CPY(19b, 40b)
|
||||
_ASM_EXTABLE_CPY(21b, 50b)
|
||||
_ASM_EXTABLE_CPY(22b, 50b)
|
||||
SYM_CODE_END(copy_user_short_string)
|
||||
|
||||
/*
|
||||
* copy_user_nocache - Uncached memory copy with exception handling
|
||||
* This will force destination out of cache for more performance.
|
||||
*
|
||||
* Note: Cached memory copy is used when destination or size is not
|
||||
* naturally aligned. That is:
|
||||
* - Require 8-byte alignment when size is 8 bytes or larger.
|
||||
* - Require 4-byte alignment when size is 4 bytes.
|
||||
*/
|
||||
SYM_FUNC_START(__copy_user_nocache)
|
||||
ASM_STAC
|
||||
|
||||
/* If size is less than 8 bytes, go to 4-byte copy */
|
||||
cmpl $8,%edx
|
||||
jb .L_4b_nocache_copy_entry
|
||||
|
||||
/* If destination is not 8-byte aligned, "cache" copy to align it */
|
||||
ALIGN_DESTINATION
|
||||
|
||||
/* Set 4x8-byte copy count and remainder */
|
||||
movl %edx,%ecx
|
||||
andl $63,%edx
|
||||
shrl $6,%ecx
|
||||
jz .L_8b_nocache_copy_entry /* jump if count is 0 */
|
||||
|
||||
/* Perform 4x8-byte nocache loop-copy */
|
||||
.L_4x8b_nocache_copy_loop:
|
||||
1: movq (%rsi),%r8
|
||||
2: movq 1*8(%rsi),%r9
|
||||
3: movq 2*8(%rsi),%r10
|
||||
4: movq 3*8(%rsi),%r11
|
||||
5: movnti %r8,(%rdi)
|
||||
6: movnti %r9,1*8(%rdi)
|
||||
7: movnti %r10,2*8(%rdi)
|
||||
8: movnti %r11,3*8(%rdi)
|
||||
9: movq 4*8(%rsi),%r8
|
||||
10: movq 5*8(%rsi),%r9
|
||||
11: movq 6*8(%rsi),%r10
|
||||
12: movq 7*8(%rsi),%r11
|
||||
13: movnti %r8,4*8(%rdi)
|
||||
14: movnti %r9,5*8(%rdi)
|
||||
15: movnti %r10,6*8(%rdi)
|
||||
16: movnti %r11,7*8(%rdi)
|
||||
leaq 64(%rsi),%rsi
|
||||
leaq 64(%rdi),%rdi
|
||||
decl %ecx
|
||||
jnz .L_4x8b_nocache_copy_loop
|
||||
|
||||
/* Set 8-byte copy count and remainder */
|
||||
.L_8b_nocache_copy_entry:
|
||||
movl %edx,%ecx
|
||||
andl $7,%edx
|
||||
shrl $3,%ecx
|
||||
jz .L_4b_nocache_copy_entry /* jump if count is 0 */
|
||||
|
||||
/* Perform 8-byte nocache loop-copy */
|
||||
.L_8b_nocache_copy_loop:
|
||||
20: movq (%rsi),%r8
|
||||
21: movnti %r8,(%rdi)
|
||||
leaq 8(%rsi),%rsi
|
||||
leaq 8(%rdi),%rdi
|
||||
decl %ecx
|
||||
jnz .L_8b_nocache_copy_loop
|
||||
|
||||
/* If no byte left, we're done */
|
||||
.L_4b_nocache_copy_entry:
|
||||
andl %edx,%edx
|
||||
jz .L_finish_copy
|
||||
|
||||
/* If destination is not 4-byte aligned, go to byte copy: */
|
||||
movl %edi,%ecx
|
||||
andl $3,%ecx
|
||||
jnz .L_1b_cache_copy_entry
|
||||
|
||||
/* Set 4-byte copy count (1 or 0) and remainder */
|
||||
movl %edx,%ecx
|
||||
andl $3,%edx
|
||||
shrl $2,%ecx
|
||||
jz .L_1b_cache_copy_entry /* jump if count is 0 */
|
||||
|
||||
/* Perform 4-byte nocache copy: */
|
||||
30: movl (%rsi),%r8d
|
||||
31: movnti %r8d,(%rdi)
|
||||
leaq 4(%rsi),%rsi
|
||||
leaq 4(%rdi),%rdi
|
||||
|
||||
/* If no bytes left, we're done: */
|
||||
andl %edx,%edx
|
||||
jz .L_finish_copy
|
||||
|
||||
/* Perform byte "cache" loop-copy for the remainder */
|
||||
.L_1b_cache_copy_entry:
|
||||
movl %edx,%ecx
|
||||
.L_1b_cache_copy_loop:
|
||||
40: movb (%rsi),%al
|
||||
41: movb %al,(%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decl %ecx
|
||||
jnz .L_1b_cache_copy_loop
|
||||
|
||||
/* Finished copying; fence the prior stores */
|
||||
.L_finish_copy:
|
||||
xorl %eax,%eax
|
||||
ASM_CLAC
|
||||
sfence
|
||||
RET
|
||||
|
||||
.L_fixup_4x8b_copy:
|
||||
shll $6,%ecx
|
||||
addl %ecx,%edx
|
||||
jmp .L_fixup_handle_tail
|
||||
.L_fixup_8b_copy:
|
||||
lea (%rdx,%rcx,8),%rdx
|
||||
jmp .L_fixup_handle_tail
|
||||
.L_fixup_4b_copy:
|
||||
lea (%rdx,%rcx,4),%rdx
|
||||
jmp .L_fixup_handle_tail
|
||||
.L_fixup_1b_copy:
|
||||
movl %ecx,%edx
|
||||
.L_fixup_handle_tail:
|
||||
sfence
|
||||
jmp .Lcopy_user_handle_tail
|
||||
|
||||
_ASM_EXTABLE_CPY(1b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(2b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(3b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(4b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(5b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(6b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(7b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(8b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(9b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(10b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(11b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(12b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(13b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(14b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(15b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(16b, .L_fixup_4x8b_copy)
|
||||
_ASM_EXTABLE_CPY(20b, .L_fixup_8b_copy)
|
||||
_ASM_EXTABLE_CPY(21b, .L_fixup_8b_copy)
|
||||
_ASM_EXTABLE_CPY(30b, .L_fixup_4b_copy)
|
||||
_ASM_EXTABLE_CPY(31b, .L_fixup_4b_copy)
|
||||
_ASM_EXTABLE_CPY(40b, .L_fixup_1b_copy)
|
||||
_ASM_EXTABLE_CPY(41b, .L_fixup_1b_copy)
|
||||
SYM_FUNC_END(__copy_user_nocache)
|
||||
EXPORT_SYMBOL(__copy_user_nocache)
|
||||
_ASM_EXTABLE_UA(10b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(11b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(12b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(13b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(14b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(15b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(16b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(17b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(20b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(21b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(22b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(23b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(24b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(25b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(26b, .Lcopy_user_tail)
|
||||
_ASM_EXTABLE_UA(27b, .Lcopy_user_tail)
|
||||
SYM_FUNC_END(rep_movs_alternative)
|
||||
EXPORT_SYMBOL(rep_movs_alternative)
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright 2023 Linus Torvalds <torvalds@linux-foundation.org>
|
||||
*/
|
||||
|
||||
#include <linux/linkage.h>
|
||||
#include <asm/asm.h>
|
||||
#include <asm/export.h>
|
||||
|
||||
/*
|
||||
* copy_user_nocache - Uncached memory copy with exception handling
|
||||
*
|
||||
* This copies from user space into kernel space, but the kernel
|
||||
* space accesses can take a machine check exception, so they too
|
||||
* need exception handling.
|
||||
*
|
||||
* Note: only 32-bit and 64-bit stores have non-temporal versions,
|
||||
* and we only use aligned versions. Any unaligned parts at the
|
||||
* start or end of the copy will be done using normal cached stores.
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* edx count
|
||||
*
|
||||
* Output:
|
||||
* rax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
SYM_FUNC_START(__copy_user_nocache)
|
||||
/* If destination is not 7-byte aligned, we'll have to align it */
|
||||
testb $7,%dil
|
||||
jne .Lalign
|
||||
|
||||
.Lis_aligned:
|
||||
cmp $64,%edx
|
||||
jb .Lquadwords
|
||||
|
||||
.p2align 4,0x90
|
||||
.Lunrolled:
|
||||
10: movq (%rsi),%r8
|
||||
11: movq 8(%rsi),%r9
|
||||
12: movq 16(%rsi),%r10
|
||||
13: movq 24(%rsi),%r11
|
||||
20: movnti %r8,(%rdi)
|
||||
21: movnti %r9,8(%rdi)
|
||||
22: movnti %r10,16(%rdi)
|
||||
23: movnti %r11,24(%rdi)
|
||||
30: movq 32(%rsi),%r8
|
||||
31: movq 40(%rsi),%r9
|
||||
32: movq 48(%rsi),%r10
|
||||
33: movq 56(%rsi),%r11
|
||||
40: movnti %r8,32(%rdi)
|
||||
41: movnti %r9,40(%rdi)
|
||||
42: movnti %r10,48(%rdi)
|
||||
43: movnti %r11,56(%rdi)
|
||||
|
||||
addq $64,%rsi
|
||||
addq $64,%rdi
|
||||
sub $64,%edx
|
||||
cmp $64,%edx
|
||||
jae .Lunrolled
|
||||
|
||||
/*
|
||||
* First set of user mode loads have been done
|
||||
* without any stores, so if they fail, we can
|
||||
* just try the non-unrolled loop.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(10b, .Lquadwords)
|
||||
_ASM_EXTABLE_UA(11b, .Lquadwords)
|
||||
_ASM_EXTABLE_UA(12b, .Lquadwords)
|
||||
_ASM_EXTABLE_UA(13b, .Lquadwords)
|
||||
|
||||
/*
|
||||
* The second set of user mode loads have been
|
||||
* done with 32 bytes stored to the destination,
|
||||
* so we need to take that into account before
|
||||
* falling back to the unrolled loop.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(30b, .Lfixup32)
|
||||
_ASM_EXTABLE_UA(31b, .Lfixup32)
|
||||
_ASM_EXTABLE_UA(32b, .Lfixup32)
|
||||
_ASM_EXTABLE_UA(33b, .Lfixup32)
|
||||
|
||||
/*
|
||||
* An exception on a write means that we're
|
||||
* done, but we need to update the count
|
||||
* depending on where in the unrolled loop
|
||||
* we were.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(20b, .Ldone0)
|
||||
_ASM_EXTABLE_UA(21b, .Ldone8)
|
||||
_ASM_EXTABLE_UA(22b, .Ldone16)
|
||||
_ASM_EXTABLE_UA(23b, .Ldone24)
|
||||
_ASM_EXTABLE_UA(40b, .Ldone32)
|
||||
_ASM_EXTABLE_UA(41b, .Ldone40)
|
||||
_ASM_EXTABLE_UA(42b, .Ldone48)
|
||||
_ASM_EXTABLE_UA(43b, .Ldone56)
|
||||
|
||||
.Lquadwords:
|
||||
cmp $8,%edx
|
||||
jb .Llong
|
||||
50: movq (%rsi),%rax
|
||||
51: movnti %rax,(%rdi)
|
||||
addq $8,%rsi
|
||||
addq $8,%rdi
|
||||
sub $8,%edx
|
||||
jmp .Lquadwords
|
||||
|
||||
/*
|
||||
* If we fail on the last full quadword, we will
|
||||
* not try to do any byte-wise cached accesses.
|
||||
* We will try to do one more 4-byte uncached
|
||||
* one, though.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(50b, .Llast4)
|
||||
_ASM_EXTABLE_UA(51b, .Ldone0)
|
||||
|
||||
.Llong:
|
||||
test $4,%dl
|
||||
je .Lword
|
||||
60: movl (%rsi),%eax
|
||||
61: movnti %eax,(%rdi)
|
||||
addq $4,%rsi
|
||||
addq $4,%rdi
|
||||
sub $4,%edx
|
||||
.Lword:
|
||||
sfence
|
||||
test $2,%dl
|
||||
je .Lbyte
|
||||
70: movw (%rsi),%ax
|
||||
71: movw %ax,(%rdi)
|
||||
addq $2,%rsi
|
||||
addq $2,%rdi
|
||||
sub $2,%edx
|
||||
.Lbyte:
|
||||
test $1,%dl
|
||||
je .Ldone
|
||||
80: movb (%rsi),%al
|
||||
81: movb %al,(%rdi)
|
||||
dec %edx
|
||||
.Ldone:
|
||||
mov %edx,%eax
|
||||
RET
|
||||
|
||||
/*
|
||||
* If we fail on the last four bytes, we won't
|
||||
* bother with any fixups. It's dead, Jim. Note
|
||||
* that there's no need for 'sfence' for any
|
||||
* of this, since the exception will have been
|
||||
* serializing.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(60b, .Ldone)
|
||||
_ASM_EXTABLE_UA(61b, .Ldone)
|
||||
_ASM_EXTABLE_UA(70b, .Ldone)
|
||||
_ASM_EXTABLE_UA(71b, .Ldone)
|
||||
_ASM_EXTABLE_UA(80b, .Ldone)
|
||||
_ASM_EXTABLE_UA(81b, .Ldone)
|
||||
|
||||
/*
|
||||
* This is the "head needs aliging" case when
|
||||
* the destination isn't 8-byte aligned. The
|
||||
* 4-byte case can be done uncached, but any
|
||||
* smaller alignment is done with regular stores.
|
||||
*/
|
||||
.Lalign:
|
||||
test $1,%dil
|
||||
je .Lalign_word
|
||||
test %edx,%edx
|
||||
je .Ldone
|
||||
90: movb (%rsi),%al
|
||||
91: movb %al,(%rdi)
|
||||
inc %rsi
|
||||
inc %rdi
|
||||
dec %edx
|
||||
.Lalign_word:
|
||||
test $2,%dil
|
||||
je .Lalign_long
|
||||
cmp $2,%edx
|
||||
jb .Lbyte
|
||||
92: movw (%rsi),%ax
|
||||
93: movw %ax,(%rdi)
|
||||
addq $2,%rsi
|
||||
addq $2,%rdi
|
||||
sub $2,%edx
|
||||
.Lalign_long:
|
||||
test $4,%dil
|
||||
je .Lis_aligned
|
||||
cmp $4,%edx
|
||||
jb .Lword
|
||||
94: movl (%rsi),%eax
|
||||
95: movnti %eax,(%rdi)
|
||||
addq $4,%rsi
|
||||
addq $4,%rdi
|
||||
sub $4,%edx
|
||||
jmp .Lis_aligned
|
||||
|
||||
/*
|
||||
* If we fail on the initial alignment accesses,
|
||||
* we're all done. Again, no point in trying to
|
||||
* do byte-by-byte probing if the 4-byte load
|
||||
* fails - we're not doing any uncached accesses
|
||||
* any more.
|
||||
*/
|
||||
_ASM_EXTABLE_UA(90b, .Ldone)
|
||||
_ASM_EXTABLE_UA(91b, .Ldone)
|
||||
_ASM_EXTABLE_UA(92b, .Ldone)
|
||||
_ASM_EXTABLE_UA(93b, .Ldone)
|
||||
_ASM_EXTABLE_UA(94b, .Ldone)
|
||||
_ASM_EXTABLE_UA(95b, .Ldone)
|
||||
|
||||
/*
|
||||
* Exception table fixups for faults in the middle
|
||||
*/
|
||||
.Ldone56: sub $8,%edx
|
||||
.Ldone48: sub $8,%edx
|
||||
.Ldone40: sub $8,%edx
|
||||
.Ldone32: sub $8,%edx
|
||||
.Ldone24: sub $8,%edx
|
||||
.Ldone16: sub $8,%edx
|
||||
.Ldone8: sub $8,%edx
|
||||
.Ldone0:
|
||||
mov %edx,%eax
|
||||
RET
|
||||
|
||||
.Lfixup32:
|
||||
addq $32,%rsi
|
||||
addq $32,%rdi
|
||||
sub $32,%edx
|
||||
jmp .Lquadwords
|
||||
|
||||
.Llast4:
|
||||
52: movl (%rsi),%eax
|
||||
53: movnti %eax,(%rdi)
|
||||
sfence
|
||||
sub $4,%edx
|
||||
mov %edx,%eax
|
||||
RET
|
||||
_ASM_EXTABLE_UA(52b, .Ldone0)
|
||||
_ASM_EXTABLE_UA(53b, .Ldone0)
|
||||
|
||||
SYM_FUNC_END(__copy_user_nocache)
|
||||
EXPORT_SYMBOL(__copy_user_nocache)
|
||||
+10
-24
@@ -10,13 +10,6 @@
|
||||
|
||||
.section .noinstr.text, "ax"
|
||||
|
||||
/*
|
||||
* We build a jump to memcpy_orig by default which gets NOPped out on
|
||||
* the majority of x86 CPUs which set REP_GOOD. In addition, CPUs which
|
||||
* have the enhanced REP MOVSB/STOSB feature (ERMS), change those NOPs
|
||||
* to a jmp to memcpy_erms which does the REP; MOVSB mem copy.
|
||||
*/
|
||||
|
||||
/*
|
||||
* memcpy - Copy a memory block.
|
||||
*
|
||||
@@ -27,17 +20,21 @@
|
||||
*
|
||||
* Output:
|
||||
* rax original destination
|
||||
*
|
||||
* The FSRM alternative should be done inline (avoiding the call and
|
||||
* the disgusting return handling), but that would require some help
|
||||
* from the compiler for better calling conventions.
|
||||
*
|
||||
* The 'rep movsb' itself is small enough to replace the call, but the
|
||||
* two register moves blow up the code. And one of them is "needed"
|
||||
* only for the return value that is the same as the source input,
|
||||
* which the compiler could/should do much better anyway.
|
||||
*/
|
||||
SYM_TYPED_FUNC_START(__memcpy)
|
||||
ALTERNATIVE_2 "jmp memcpy_orig", "", X86_FEATURE_REP_GOOD, \
|
||||
"jmp memcpy_erms", X86_FEATURE_ERMS
|
||||
ALTERNATIVE "jmp memcpy_orig", "", X86_FEATURE_FSRM
|
||||
|
||||
movq %rdi, %rax
|
||||
movq %rdx, %rcx
|
||||
shrq $3, %rcx
|
||||
andl $7, %edx
|
||||
rep movsq
|
||||
movl %edx, %ecx
|
||||
rep movsb
|
||||
RET
|
||||
SYM_FUNC_END(__memcpy)
|
||||
@@ -46,17 +43,6 @@ EXPORT_SYMBOL(__memcpy)
|
||||
SYM_FUNC_ALIAS(memcpy, __memcpy)
|
||||
EXPORT_SYMBOL(memcpy)
|
||||
|
||||
/*
|
||||
* memcpy_erms() - enhanced fast string memcpy. This is faster and
|
||||
* simpler than memcpy. Use memcpy_erms when possible.
|
||||
*/
|
||||
SYM_FUNC_START_LOCAL(memcpy_erms)
|
||||
movq %rdi, %rax
|
||||
movq %rdx, %rcx
|
||||
rep movsb
|
||||
RET
|
||||
SYM_FUNC_END(memcpy_erms)
|
||||
|
||||
SYM_FUNC_START_LOCAL(memcpy_orig)
|
||||
movq %rdi, %rax
|
||||
|
||||
|
||||
+11
-36
@@ -18,27 +18,22 @@
|
||||
* rdx count (bytes)
|
||||
*
|
||||
* rax original destination
|
||||
*
|
||||
* The FSRS alternative should be done inline (avoiding the call and
|
||||
* the disgusting return handling), but that would require some help
|
||||
* from the compiler for better calling conventions.
|
||||
*
|
||||
* The 'rep stosb' itself is small enough to replace the call, but all
|
||||
* the register moves blow up the code. And two of them are "needed"
|
||||
* only for the return value that is the same as the source input,
|
||||
* which the compiler could/should do much better anyway.
|
||||
*/
|
||||
SYM_FUNC_START(__memset)
|
||||
/*
|
||||
* Some CPUs support enhanced REP MOVSB/STOSB feature. It is recommended
|
||||
* to use it when possible. If not available, use fast string instructions.
|
||||
*
|
||||
* Otherwise, use original memset function.
|
||||
*/
|
||||
ALTERNATIVE_2 "jmp memset_orig", "", X86_FEATURE_REP_GOOD, \
|
||||
"jmp memset_erms", X86_FEATURE_ERMS
|
||||
ALTERNATIVE "jmp memset_orig", "", X86_FEATURE_FSRS
|
||||
|
||||
movq %rdi,%r9
|
||||
movb %sil,%al
|
||||
movq %rdx,%rcx
|
||||
andl $7,%edx
|
||||
shrq $3,%rcx
|
||||
/* expand byte value */
|
||||
movzbl %sil,%esi
|
||||
movabs $0x0101010101010101,%rax
|
||||
imulq %rsi,%rax
|
||||
rep stosq
|
||||
movl %edx,%ecx
|
||||
rep stosb
|
||||
movq %r9,%rax
|
||||
RET
|
||||
@@ -48,26 +43,6 @@ EXPORT_SYMBOL(__memset)
|
||||
SYM_FUNC_ALIAS(memset, __memset)
|
||||
EXPORT_SYMBOL(memset)
|
||||
|
||||
/*
|
||||
* ISO C memset - set a memory block to a byte value. This function uses
|
||||
* enhanced rep stosb to override the fast string function.
|
||||
* The code is simpler and shorter than the fast string function as well.
|
||||
*
|
||||
* rdi destination
|
||||
* rsi value (char)
|
||||
* rdx count (bytes)
|
||||
*
|
||||
* rax original destination
|
||||
*/
|
||||
SYM_FUNC_START_LOCAL(memset_erms)
|
||||
movq %rdi,%r9
|
||||
movb %sil,%al
|
||||
movq %rdx,%rcx
|
||||
rep stosb
|
||||
movq %r9,%rax
|
||||
RET
|
||||
SYM_FUNC_END(memset_erms)
|
||||
|
||||
SYM_FUNC_START_LOCAL(memset_orig)
|
||||
movq %rdi,%r10
|
||||
|
||||
|
||||
@@ -45,7 +45,11 @@ EXPORT_SYMBOL_GPL(arch_wb_cache_pmem);
|
||||
long __copy_user_flushcache(void *dst, const void __user *src, unsigned size)
|
||||
{
|
||||
unsigned long flushed, dest = (unsigned long) dst;
|
||||
long rc = __copy_user_nocache(dst, src, size, 0);
|
||||
long rc;
|
||||
|
||||
stac();
|
||||
rc = __copy_user_nocache(dst, src, size);
|
||||
clac();
|
||||
|
||||
/*
|
||||
* __copy_user_nocache() uses non-temporal stores for the bulk
|
||||
|
||||
+4
-3
@@ -29,10 +29,11 @@ static struct bio_map_data *bio_alloc_map_data(struct iov_iter *data,
|
||||
bmd = kmalloc(struct_size(bmd, iov, data->nr_segs), gfp_mask);
|
||||
if (!bmd)
|
||||
return NULL;
|
||||
memcpy(bmd->iov, data->iov, sizeof(struct iovec) * data->nr_segs);
|
||||
bmd->iter = *data;
|
||||
if (iter_is_iovec(data))
|
||||
bmd->iter.iov = bmd->iov;
|
||||
if (iter_is_iovec(data)) {
|
||||
memcpy(bmd->iov, iter_iov(data), sizeof(struct iovec) * data->nr_segs);
|
||||
bmd->iter.__iov = bmd->iov;
|
||||
}
|
||||
return bmd;
|
||||
}
|
||||
|
||||
|
||||
@@ -267,6 +267,8 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
||||
|
||||
if (!HFI1_CAP_IS_KSET(SDMA))
|
||||
return -EINVAL;
|
||||
if (!from->user_backed)
|
||||
return -EINVAL;
|
||||
idx = srcu_read_lock(&fd->pq_srcu);
|
||||
pq = srcu_dereference(fd->pq, &fd->pq_srcu);
|
||||
if (!cq || !pq) {
|
||||
@@ -274,11 +276,6 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!iter_is_iovec(from) || !dim) {
|
||||
srcu_read_unlock(&fd->pq_srcu, idx);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
trace_hfi1_sdma_request(fd->dd, fd->uctxt->ctxt, fd->subctxt, dim);
|
||||
|
||||
if (atomic_read(&pq->n_reqs) == pq->n_max_reqs) {
|
||||
@@ -287,11 +284,12 @@ static ssize_t hfi1_write_iter(struct kiocb *kiocb, struct iov_iter *from)
|
||||
}
|
||||
|
||||
while (dim) {
|
||||
const struct iovec *iov = iter_iov(from);
|
||||
int ret;
|
||||
unsigned long count = 0;
|
||||
|
||||
ret = hfi1_user_sdma_process_request(
|
||||
fd, (struct iovec *)(from->iov + done),
|
||||
fd, (struct iovec *)(iov + done),
|
||||
dim, &count);
|
||||
if (ret) {
|
||||
reqs = ret;
|
||||
|
||||
@@ -2245,10 +2245,10 @@ static ssize_t qib_write_iter(struct kiocb *iocb, struct iov_iter *from)
|
||||
struct qib_ctxtdata *rcd = ctxt_fp(iocb->ki_filp);
|
||||
struct qib_user_sdma_queue *pq = fp->pq;
|
||||
|
||||
if (!iter_is_iovec(from) || !from->nr_segs || !pq)
|
||||
if (!from->user_backed || !from->nr_segs || !pq)
|
||||
return -EINVAL;
|
||||
|
||||
return qib_user_sdma_writev(rcd, pq, from->iov, from->nr_segs);
|
||||
return qib_user_sdma_writev(rcd, pq, iter_iov(from), from->nr_segs);
|
||||
}
|
||||
|
||||
static struct class *qib_class;
|
||||
|
||||
@@ -97,7 +97,7 @@ static void cacheless_memcpy(void *dst, void *src, size_t n)
|
||||
* there are no security issues. The extra fault recovery machinery
|
||||
* is not invoked.
|
||||
*/
|
||||
__copy_user_nocache(dst, (void __user *)src, n, 0);
|
||||
__copy_user_nocache(dst, (void __user *)src, n);
|
||||
}
|
||||
|
||||
void rvt_wss_exit(struct rvt_dev_info *rdi)
|
||||
|
||||
+2
-1
@@ -1486,7 +1486,8 @@ static struct sk_buff *tun_napi_alloc_frags(struct tun_file *tfile,
|
||||
skb->truesize += skb->data_len;
|
||||
|
||||
for (i = 1; i < it->nr_segs; i++) {
|
||||
size_t fragsz = it->iov[i].iov_len;
|
||||
const struct iovec *iov = iter_iov(it);
|
||||
size_t fragsz = iov->iov_len;
|
||||
struct page *page;
|
||||
void *frag;
|
||||
|
||||
|
||||
@@ -665,7 +665,7 @@ vhost_scsi_calc_sgls(struct iov_iter *iter, size_t bytes, int max_sgls)
|
||||
{
|
||||
int sgl_count = 0;
|
||||
|
||||
if (!iter || !iter->iov) {
|
||||
if (!iter || !iter_iov(iter)) {
|
||||
pr_err("%s: iter->iov is NULL, but expected bytes: %zu"
|
||||
" present\n", __func__, bytes);
|
||||
return -EINVAL;
|
||||
|
||||
+8
-3
@@ -3730,10 +3730,15 @@ static int check_direct_read(struct btrfs_fs_info *fs_info,
|
||||
if (!iter_is_iovec(iter))
|
||||
return 0;
|
||||
|
||||
for (seg = 0; seg < iter->nr_segs; seg++)
|
||||
for (i = seg + 1; i < iter->nr_segs; i++)
|
||||
if (iter->iov[seg].iov_base == iter->iov[i].iov_base)
|
||||
for (seg = 0; seg < iter->nr_segs; seg++) {
|
||||
for (i = seg + 1; i < iter->nr_segs; i++) {
|
||||
const struct iovec *iov1 = iter_iov(iter) + seg;
|
||||
const struct iovec *iov2 = iter_iov(iter) + i;
|
||||
|
||||
if (iov1->iov_base == iov2->iov_base)
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1421,7 +1421,7 @@ out:
|
||||
|
||||
static inline unsigned long fuse_get_user_addr(const struct iov_iter *ii)
|
||||
{
|
||||
return (unsigned long)ii->iov->iov_base + ii->iov_offset;
|
||||
return (unsigned long)iter_iov(ii)->iov_base + ii->iov_offset;
|
||||
}
|
||||
|
||||
static inline size_t fuse_get_frag_size(const struct iov_iter *ii,
|
||||
|
||||
+5
-6
@@ -749,15 +749,14 @@ static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter,
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
while (iov_iter_count(iter)) {
|
||||
struct iovec iovec = iov_iter_iovec(iter);
|
||||
ssize_t nr;
|
||||
|
||||
if (type == READ) {
|
||||
nr = filp->f_op->read(filp, iovec.iov_base,
|
||||
iovec.iov_len, ppos);
|
||||
nr = filp->f_op->read(filp, iter_iov_addr(iter),
|
||||
iter_iov_len(iter), ppos);
|
||||
} else {
|
||||
nr = filp->f_op->write(filp, iovec.iov_base,
|
||||
iovec.iov_len, ppos);
|
||||
nr = filp->f_op->write(filp, iter_iov_addr(iter),
|
||||
iter_iov_len(iter), ppos);
|
||||
}
|
||||
|
||||
if (nr < 0) {
|
||||
@@ -766,7 +765,7 @@ static ssize_t do_loop_readv_writev(struct file *filp, struct iov_iter *iter,
|
||||
break;
|
||||
}
|
||||
ret += nr;
|
||||
if (nr != iovec.iov_len)
|
||||
if (nr != iter_iov_len(iter))
|
||||
break;
|
||||
iov_iter_advance(iter, nr);
|
||||
}
|
||||
|
||||
+40
-17
@@ -49,14 +49,35 @@ struct iov_iter {
|
||||
size_t iov_offset;
|
||||
int last_offset;
|
||||
};
|
||||
size_t count;
|
||||
/*
|
||||
* Hack alert: overlay ubuf_iovec with iovec + count, so
|
||||
* that the members resolve correctly regardless of the type
|
||||
* of iterator used. This means that you can use:
|
||||
*
|
||||
* &iter->__ubuf_iovec or iter->__iov
|
||||
*
|
||||
* interchangably for the user_backed cases, hence simplifying
|
||||
* some of the cases that need to deal with both.
|
||||
*/
|
||||
union {
|
||||
const struct iovec *iov;
|
||||
const struct kvec *kvec;
|
||||
const struct bio_vec *bvec;
|
||||
struct xarray *xarray;
|
||||
struct pipe_inode_info *pipe;
|
||||
void __user *ubuf;
|
||||
/*
|
||||
* This really should be a const, but we cannot do that without
|
||||
* also modifying any of the zero-filling iter init functions.
|
||||
* Leave it non-const for now, but it should be treated as such.
|
||||
*/
|
||||
struct iovec __ubuf_iovec;
|
||||
struct {
|
||||
union {
|
||||
/* use iter_iov() to get the current vec */
|
||||
const struct iovec *__iov;
|
||||
const struct kvec *kvec;
|
||||
const struct bio_vec *bvec;
|
||||
struct xarray *xarray;
|
||||
struct pipe_inode_info *pipe;
|
||||
void __user *ubuf;
|
||||
};
|
||||
size_t count;
|
||||
};
|
||||
};
|
||||
union {
|
||||
unsigned long nr_segs;
|
||||
@@ -68,6 +89,16 @@ struct iov_iter {
|
||||
};
|
||||
};
|
||||
|
||||
static inline const struct iovec *iter_iov(const struct iov_iter *iter)
|
||||
{
|
||||
if (iter->iter_type == ITER_UBUF)
|
||||
return (const struct iovec *) &iter->__ubuf_iovec;
|
||||
return iter->__iov;
|
||||
}
|
||||
|
||||
#define iter_iov_addr(iter) (iter_iov(iter)->iov_base + (iter)->iov_offset)
|
||||
#define iter_iov_len(iter) (iter_iov(iter)->iov_len - (iter)->iov_offset)
|
||||
|
||||
static inline enum iter_type iov_iter_type(const struct iov_iter *i)
|
||||
{
|
||||
return i->iter_type;
|
||||
@@ -143,15 +174,6 @@ static inline size_t iov_length(const struct iovec *iov, unsigned long nr_segs)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline struct iovec iov_iter_iovec(const struct iov_iter *iter)
|
||||
{
|
||||
return (struct iovec) {
|
||||
.iov_base = iter->iov->iov_base + iter->iov_offset,
|
||||
.iov_len = min(iter->count,
|
||||
iter->iov->iov_len - iter->iov_offset),
|
||||
};
|
||||
}
|
||||
|
||||
size_t copy_page_from_iter_atomic(struct page *page, unsigned offset,
|
||||
size_t bytes, struct iov_iter *i);
|
||||
void iov_iter_advance(struct iov_iter *i, size_t bytes);
|
||||
@@ -359,7 +381,8 @@ static inline void iov_iter_ubuf(struct iov_iter *i, unsigned int direction,
|
||||
.user_backed = true,
|
||||
.data_source = direction,
|
||||
.ubuf = buf,
|
||||
.count = count
|
||||
.count = count,
|
||||
.nr_segs = 1
|
||||
};
|
||||
}
|
||||
/* Flags for iov_iter_get/extract_pages*() */
|
||||
|
||||
+2
-2
@@ -184,8 +184,8 @@ static int io_setup_async_msg(struct io_kiocb *req,
|
||||
async_msg->msg.msg_name = &async_msg->addr;
|
||||
/* if were using fast_iov, set it to the new one */
|
||||
if (iter_is_iovec(&kmsg->msg.msg_iter) && !kmsg->free_iov) {
|
||||
size_t fast_idx = kmsg->msg.msg_iter.iov - kmsg->fast_iov;
|
||||
async_msg->msg.msg_iter.iov = &async_msg->fast_iov[fast_idx];
|
||||
size_t fast_idx = iter_iov(&kmsg->msg.msg_iter) - kmsg->fast_iov;
|
||||
async_msg->msg.msg_iter.__iov = &async_msg->fast_iov[fast_idx];
|
||||
}
|
||||
|
||||
return -EAGAIN;
|
||||
|
||||
+17
-18
@@ -447,26 +447,25 @@ static ssize_t loop_rw_iter(int ddir, struct io_rw *rw, struct iov_iter *iter)
|
||||
ppos = io_kiocb_ppos(kiocb);
|
||||
|
||||
while (iov_iter_count(iter)) {
|
||||
struct iovec iovec;
|
||||
void __user *addr;
|
||||
size_t len;
|
||||
ssize_t nr;
|
||||
|
||||
if (iter_is_ubuf(iter)) {
|
||||
iovec.iov_base = iter->ubuf + iter->iov_offset;
|
||||
iovec.iov_len = iov_iter_count(iter);
|
||||
addr = iter->ubuf + iter->iov_offset;
|
||||
len = iov_iter_count(iter);
|
||||
} else if (!iov_iter_is_bvec(iter)) {
|
||||
iovec = iov_iter_iovec(iter);
|
||||
addr = iter_iov_addr(iter);
|
||||
len = iter_iov_len(iter);
|
||||
} else {
|
||||
iovec.iov_base = u64_to_user_ptr(rw->addr);
|
||||
iovec.iov_len = rw->len;
|
||||
addr = u64_to_user_ptr(rw->addr);
|
||||
len = rw->len;
|
||||
}
|
||||
|
||||
if (ddir == READ) {
|
||||
nr = file->f_op->read(file, iovec.iov_base,
|
||||
iovec.iov_len, ppos);
|
||||
} else {
|
||||
nr = file->f_op->write(file, iovec.iov_base,
|
||||
iovec.iov_len, ppos);
|
||||
}
|
||||
if (ddir == READ)
|
||||
nr = file->f_op->read(file, addr, len, ppos);
|
||||
else
|
||||
nr = file->f_op->write(file, addr, len, ppos);
|
||||
|
||||
if (nr < 0) {
|
||||
if (!ret)
|
||||
@@ -482,7 +481,7 @@ static ssize_t loop_rw_iter(int ddir, struct io_rw *rw, struct iov_iter *iter)
|
||||
if (!rw->len)
|
||||
break;
|
||||
}
|
||||
if (nr != iovec.iov_len)
|
||||
if (nr != len)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -503,10 +502,10 @@ static void io_req_map_rw(struct io_kiocb *req, const struct iovec *iovec,
|
||||
if (!iovec) {
|
||||
unsigned iov_off = 0;
|
||||
|
||||
io->s.iter.iov = io->s.fast_iov;
|
||||
if (iter->iov != fast_iov) {
|
||||
iov_off = iter->iov - fast_iov;
|
||||
io->s.iter.iov += iov_off;
|
||||
io->s.iter.__iov = io->s.fast_iov;
|
||||
if (iter->__iov != fast_iov) {
|
||||
iov_off = iter_iov(iter) - fast_iov;
|
||||
io->s.iter.__iov += iov_off;
|
||||
}
|
||||
if (io->s.fast_iov != fast_iov)
|
||||
memcpy(io->s.fast_iov + iov_off, fast_iov + iov_off,
|
||||
|
||||
+85
-39
@@ -126,13 +126,13 @@ __out: \
|
||||
iterate_buf(i, n, base, len, off, \
|
||||
i->ubuf, (I)) \
|
||||
} else if (likely(iter_is_iovec(i))) { \
|
||||
const struct iovec *iov = i->iov; \
|
||||
const struct iovec *iov = iter_iov(i); \
|
||||
void __user *base; \
|
||||
size_t len; \
|
||||
iterate_iovec(i, n, base, len, off, \
|
||||
iov, (I)) \
|
||||
i->nr_segs -= iov - i->iov; \
|
||||
i->iov = iov; \
|
||||
i->nr_segs -= iov - iter_iov(i); \
|
||||
i->__iov = iov; \
|
||||
} else if (iov_iter_is_bvec(i)) { \
|
||||
const struct bio_vec *bvec = i->bvec; \
|
||||
void *base; \
|
||||
@@ -355,7 +355,7 @@ size_t fault_in_iov_iter_readable(const struct iov_iter *i, size_t size)
|
||||
size_t skip;
|
||||
|
||||
size -= count;
|
||||
for (p = i->iov, skip = i->iov_offset; count; p++, skip = 0) {
|
||||
for (p = iter_iov(i), skip = i->iov_offset; count; p++, skip = 0) {
|
||||
size_t len = min(count, p->iov_len - skip);
|
||||
size_t ret;
|
||||
|
||||
@@ -398,7 +398,7 @@ size_t fault_in_iov_iter_writeable(const struct iov_iter *i, size_t size)
|
||||
size_t skip;
|
||||
|
||||
size -= count;
|
||||
for (p = i->iov, skip = i->iov_offset; count; p++, skip = 0) {
|
||||
for (p = iter_iov(i), skip = i->iov_offset; count; p++, skip = 0) {
|
||||
size_t len = min(count, p->iov_len - skip);
|
||||
size_t ret;
|
||||
|
||||
@@ -425,7 +425,7 @@ void iov_iter_init(struct iov_iter *i, unsigned int direction,
|
||||
.nofault = false,
|
||||
.user_backed = true,
|
||||
.data_source = direction,
|
||||
.iov = iov,
|
||||
.__iov = iov,
|
||||
.nr_segs = nr_segs,
|
||||
.iov_offset = 0,
|
||||
.count = count
|
||||
@@ -876,14 +876,14 @@ static void iov_iter_iovec_advance(struct iov_iter *i, size_t size)
|
||||
i->count -= size;
|
||||
|
||||
size += i->iov_offset; // from beginning of current segment
|
||||
for (iov = i->iov, end = iov + i->nr_segs; iov < end; iov++) {
|
||||
for (iov = iter_iov(i), end = iov + i->nr_segs; iov < end; iov++) {
|
||||
if (likely(size < iov->iov_len))
|
||||
break;
|
||||
size -= iov->iov_len;
|
||||
}
|
||||
i->iov_offset = size;
|
||||
i->nr_segs -= iov - i->iov;
|
||||
i->iov = iov;
|
||||
i->nr_segs -= iov - iter_iov(i);
|
||||
i->__iov = iov;
|
||||
}
|
||||
|
||||
void iov_iter_advance(struct iov_iter *i, size_t size)
|
||||
@@ -958,12 +958,12 @@ void iov_iter_revert(struct iov_iter *i, size_t unroll)
|
||||
unroll -= n;
|
||||
}
|
||||
} else { /* same logics for iovec and kvec */
|
||||
const struct iovec *iov = i->iov;
|
||||
const struct iovec *iov = iter_iov(i);
|
||||
while (1) {
|
||||
size_t n = (--iov)->iov_len;
|
||||
i->nr_segs++;
|
||||
if (unroll <= n) {
|
||||
i->iov = iov;
|
||||
i->__iov = iov;
|
||||
i->iov_offset = n - unroll;
|
||||
return;
|
||||
}
|
||||
@@ -980,7 +980,7 @@ size_t iov_iter_single_seg_count(const struct iov_iter *i)
|
||||
{
|
||||
if (i->nr_segs > 1) {
|
||||
if (likely(iter_is_iovec(i) || iov_iter_is_kvec(i)))
|
||||
return min(i->count, i->iov->iov_len - i->iov_offset);
|
||||
return min(i->count, iter_iov(i)->iov_len - i->iov_offset);
|
||||
if (iov_iter_is_bvec(i))
|
||||
return min(i->count, i->bvec->bv_len - i->iov_offset);
|
||||
}
|
||||
@@ -1095,13 +1095,14 @@ static bool iov_iter_aligned_iovec(const struct iov_iter *i, unsigned addr_mask,
|
||||
unsigned k;
|
||||
|
||||
for (k = 0; k < i->nr_segs; k++, skip = 0) {
|
||||
size_t len = i->iov[k].iov_len - skip;
|
||||
const struct iovec *iov = iter_iov(i) + k;
|
||||
size_t len = iov->iov_len - skip;
|
||||
|
||||
if (len > size)
|
||||
len = size;
|
||||
if (len & len_mask)
|
||||
return false;
|
||||
if ((unsigned long)(i->iov[k].iov_base + skip) & addr_mask)
|
||||
if ((unsigned long)(iov->iov_base + skip) & addr_mask)
|
||||
return false;
|
||||
|
||||
size -= len;
|
||||
@@ -1194,9 +1195,10 @@ static unsigned long iov_iter_alignment_iovec(const struct iov_iter *i)
|
||||
unsigned k;
|
||||
|
||||
for (k = 0; k < i->nr_segs; k++, skip = 0) {
|
||||
size_t len = i->iov[k].iov_len - skip;
|
||||
const struct iovec *iov = iter_iov(i) + k;
|
||||
size_t len = iov->iov_len - skip;
|
||||
if (len) {
|
||||
res |= (unsigned long)i->iov[k].iov_base + skip;
|
||||
res |= (unsigned long)iov->iov_base + skip;
|
||||
if (len > size)
|
||||
len = size;
|
||||
res |= len;
|
||||
@@ -1273,14 +1275,15 @@ unsigned long iov_iter_gap_alignment(const struct iov_iter *i)
|
||||
return ~0U;
|
||||
|
||||
for (k = 0; k < i->nr_segs; k++) {
|
||||
if (i->iov[k].iov_len) {
|
||||
unsigned long base = (unsigned long)i->iov[k].iov_base;
|
||||
const struct iovec *iov = iter_iov(i) + k;
|
||||
if (iov->iov_len) {
|
||||
unsigned long base = (unsigned long)iov->iov_base;
|
||||
if (v) // if not the first one
|
||||
res |= base | v; // this start | previous end
|
||||
v = base + i->iov[k].iov_len;
|
||||
if (size <= i->iov[k].iov_len)
|
||||
v = base + iov->iov_len;
|
||||
if (size <= iov->iov_len)
|
||||
break;
|
||||
size -= i->iov[k].iov_len;
|
||||
size -= iov->iov_len;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
@@ -1396,13 +1399,14 @@ static unsigned long first_iovec_segment(const struct iov_iter *i, size_t *size)
|
||||
return (unsigned long)i->ubuf + i->iov_offset;
|
||||
|
||||
for (k = 0, skip = i->iov_offset; k < i->nr_segs; k++, skip = 0) {
|
||||
size_t len = i->iov[k].iov_len - skip;
|
||||
const struct iovec *iov = iter_iov(i) + k;
|
||||
size_t len = iov->iov_len - skip;
|
||||
|
||||
if (unlikely(!len))
|
||||
continue;
|
||||
if (*size > len)
|
||||
*size = len;
|
||||
return (unsigned long)i->iov[k].iov_base + skip;
|
||||
return (unsigned long)iov->iov_base + skip;
|
||||
}
|
||||
BUG(); // if it had been empty, we wouldn't get called
|
||||
}
|
||||
@@ -1614,7 +1618,7 @@ static int iov_npages(const struct iov_iter *i, int maxpages)
|
||||
const struct iovec *p;
|
||||
int npages = 0;
|
||||
|
||||
for (p = i->iov; size; skip = 0, p++) {
|
||||
for (p = iter_iov(i); size; skip = 0, p++) {
|
||||
unsigned offs = offset_in_page(p->iov_base + skip);
|
||||
size_t len = min(p->iov_len - skip, size);
|
||||
|
||||
@@ -1691,14 +1695,14 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags)
|
||||
flags);
|
||||
else if (iov_iter_is_kvec(new) || iter_is_iovec(new))
|
||||
/* iovec and kvec have identical layout */
|
||||
return new->iov = kmemdup(new->iov,
|
||||
return new->__iov = kmemdup(new->__iov,
|
||||
new->nr_segs * sizeof(struct iovec),
|
||||
flags);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(dup_iter);
|
||||
|
||||
static int copy_compat_iovec_from_user(struct iovec *iov,
|
||||
static __noclone int copy_compat_iovec_from_user(struct iovec *iov,
|
||||
const struct iovec __user *uvec, unsigned long nr_segs)
|
||||
{
|
||||
const struct compat_iovec __user *uiov =
|
||||
@@ -1731,18 +1735,35 @@ uaccess_end:
|
||||
}
|
||||
|
||||
static int copy_iovec_from_user(struct iovec *iov,
|
||||
const struct iovec __user *uvec, unsigned long nr_segs)
|
||||
const struct iovec __user *uiov, unsigned long nr_segs)
|
||||
{
|
||||
unsigned long seg;
|
||||
int ret = -EFAULT;
|
||||
|
||||
if (copy_from_user(iov, uvec, nr_segs * sizeof(*uvec)))
|
||||
if (!user_access_begin(uiov, nr_segs * sizeof(*uiov)))
|
||||
return -EFAULT;
|
||||
for (seg = 0; seg < nr_segs; seg++) {
|
||||
if ((ssize_t)iov[seg].iov_len < 0)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
do {
|
||||
void __user *buf;
|
||||
ssize_t len;
|
||||
|
||||
unsafe_get_user(len, &uiov->iov_len, uaccess_end);
|
||||
unsafe_get_user(buf, &uiov->iov_base, uaccess_end);
|
||||
|
||||
/* check for size_t not fitting in ssize_t .. */
|
||||
if (unlikely(len < 0)) {
|
||||
ret = -EINVAL;
|
||||
goto uaccess_end;
|
||||
}
|
||||
iov->iov_base = buf;
|
||||
iov->iov_len = len;
|
||||
|
||||
uiov++; iov++;
|
||||
} while (--nr_segs);
|
||||
|
||||
ret = 0;
|
||||
uaccess_end:
|
||||
user_access_end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct iovec *iovec_from_user(const struct iovec __user *uvec,
|
||||
@@ -1767,7 +1788,7 @@ struct iovec *iovec_from_user(const struct iovec __user *uvec,
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
if (compat)
|
||||
if (unlikely(compat))
|
||||
ret = copy_compat_iovec_from_user(iov, uvec, nr_segs);
|
||||
else
|
||||
ret = copy_iovec_from_user(iov, uvec, nr_segs);
|
||||
@@ -1780,6 +1801,30 @@ struct iovec *iovec_from_user(const struct iovec __user *uvec,
|
||||
return iov;
|
||||
}
|
||||
|
||||
/*
|
||||
* Single segment iovec supplied by the user, import it as ITER_UBUF.
|
||||
*/
|
||||
static ssize_t __import_iovec_ubuf(int type, const struct iovec __user *uvec,
|
||||
struct iovec **iovp, struct iov_iter *i,
|
||||
bool compat)
|
||||
{
|
||||
struct iovec *iov = *iovp;
|
||||
ssize_t ret;
|
||||
|
||||
if (compat)
|
||||
ret = copy_compat_iovec_from_user(iov, uvec, 1);
|
||||
else
|
||||
ret = copy_iovec_from_user(iov, uvec, 1);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
|
||||
ret = import_ubuf(type, iov->iov_base, iov->iov_len, i);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
*iovp = NULL;
|
||||
return i->count;
|
||||
}
|
||||
|
||||
ssize_t __import_iovec(int type, const struct iovec __user *uvec,
|
||||
unsigned nr_segs, unsigned fast_segs, struct iovec **iovp,
|
||||
struct iov_iter *i, bool compat)
|
||||
@@ -1788,6 +1833,9 @@ ssize_t __import_iovec(int type, const struct iovec __user *uvec,
|
||||
unsigned long seg;
|
||||
struct iovec *iov;
|
||||
|
||||
if (nr_segs == 1)
|
||||
return __import_iovec_ubuf(type, uvec, iovp, i, compat);
|
||||
|
||||
iov = iovec_from_user(uvec, nr_segs, fast_segs, *iovp, compat);
|
||||
if (IS_ERR(iov)) {
|
||||
*iovp = NULL;
|
||||
@@ -1866,9 +1914,7 @@ int import_single_range(int rw, void __user *buf, size_t len,
|
||||
if (unlikely(!access_ok(buf, len)))
|
||||
return -EFAULT;
|
||||
|
||||
iov->iov_base = buf;
|
||||
iov->iov_len = len;
|
||||
iov_iter_init(i, rw, iov, 1, len);
|
||||
iov_iter_ubuf(i, rw, buf, len);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(import_single_range);
|
||||
@@ -1918,7 +1964,7 @@ void iov_iter_restore(struct iov_iter *i, struct iov_iter_state *state)
|
||||
if (iov_iter_is_bvec(i))
|
||||
i->bvec -= state->nr_segs - i->nr_segs;
|
||||
else
|
||||
i->iov -= state->nr_segs - i->nr_segs;
|
||||
i->__iov -= state->nr_segs - i->nr_segs;
|
||||
i->nr_segs = state->nr_segs;
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -1456,7 +1456,7 @@ SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec,
|
||||
size_t, vlen, int, behavior, unsigned int, flags)
|
||||
{
|
||||
ssize_t ret;
|
||||
struct iovec iovstack[UIO_FASTIOV], iovec;
|
||||
struct iovec iovstack[UIO_FASTIOV];
|
||||
struct iovec *iov = iovstack;
|
||||
struct iov_iter iter;
|
||||
struct task_struct *task;
|
||||
@@ -1503,12 +1503,11 @@ SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec,
|
||||
total_len = iov_iter_count(&iter);
|
||||
|
||||
while (iov_iter_count(&iter)) {
|
||||
iovec = iov_iter_iovec(&iter);
|
||||
ret = do_madvise(mm, (unsigned long)iovec.iov_base,
|
||||
iovec.iov_len, behavior);
|
||||
ret = do_madvise(mm, (unsigned long)iter_iov_addr(&iter),
|
||||
iter_iov_len(&iter), behavior);
|
||||
if (ret < 0)
|
||||
break;
|
||||
iov_iter_advance(&iter, iovec.iov_len);
|
||||
iov_iter_advance(&iter, iter_iov_len(&iter));
|
||||
}
|
||||
|
||||
ret = (total_len - iov_iter_count(&iter)) ? : ret;
|
||||
|
||||
@@ -45,10 +45,6 @@ Clang)
|
||||
version=$2.$3.$4
|
||||
min_version=$($min_tool_version llvm)
|
||||
;;
|
||||
ICC)
|
||||
version=$(($2 / 100)).$(($2 % 100)).$3
|
||||
min_version=$($min_tool_version icc)
|
||||
;;
|
||||
*)
|
||||
echo "$orig_args: unknown C compiler" >&2
|
||||
exit 1
|
||||
|
||||
+16
-10
@@ -3521,6 +3521,7 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to)
|
||||
unsigned long i;
|
||||
void __user **bufs;
|
||||
snd_pcm_uframes_t frames;
|
||||
const struct iovec *iov = iter_iov(to);
|
||||
|
||||
pcm_file = iocb->ki_filp->private_data;
|
||||
substream = pcm_file->substream;
|
||||
@@ -3530,18 +3531,20 @@ static ssize_t snd_pcm_readv(struct kiocb *iocb, struct iov_iter *to)
|
||||
if (runtime->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!iter_is_iovec(to))
|
||||
if (!to->user_backed)
|
||||
return -EINVAL;
|
||||
if (to->nr_segs > 1024 || to->nr_segs != runtime->channels)
|
||||
return -EINVAL;
|
||||
if (!frame_aligned(runtime, to->iov->iov_len))
|
||||
if (!frame_aligned(runtime, iov->iov_len))
|
||||
return -EINVAL;
|
||||
frames = bytes_to_samples(runtime, to->iov->iov_len);
|
||||
frames = bytes_to_samples(runtime, iov->iov_len);
|
||||
bufs = kmalloc_array(to->nr_segs, sizeof(void *), GFP_KERNEL);
|
||||
if (bufs == NULL)
|
||||
return -ENOMEM;
|
||||
for (i = 0; i < to->nr_segs; ++i)
|
||||
bufs[i] = to->iov[i].iov_base;
|
||||
for (i = 0; i < to->nr_segs; ++i) {
|
||||
bufs[i] = iov->iov_base;
|
||||
iov++;
|
||||
}
|
||||
result = snd_pcm_lib_readv(substream, bufs, frames);
|
||||
if (result > 0)
|
||||
result = frames_to_bytes(runtime, result);
|
||||
@@ -3558,6 +3561,7 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from)
|
||||
unsigned long i;
|
||||
void __user **bufs;
|
||||
snd_pcm_uframes_t frames;
|
||||
const struct iovec *iov = iter_iov(from);
|
||||
|
||||
pcm_file = iocb->ki_filp->private_data;
|
||||
substream = pcm_file->substream;
|
||||
@@ -3567,17 +3571,19 @@ static ssize_t snd_pcm_writev(struct kiocb *iocb, struct iov_iter *from)
|
||||
if (runtime->state == SNDRV_PCM_STATE_OPEN ||
|
||||
runtime->state == SNDRV_PCM_STATE_DISCONNECTED)
|
||||
return -EBADFD;
|
||||
if (!iter_is_iovec(from))
|
||||
if (!from->user_backed)
|
||||
return -EINVAL;
|
||||
if (from->nr_segs > 128 || from->nr_segs != runtime->channels ||
|
||||
!frame_aligned(runtime, from->iov->iov_len))
|
||||
!frame_aligned(runtime, iov->iov_len))
|
||||
return -EINVAL;
|
||||
frames = bytes_to_samples(runtime, from->iov->iov_len);
|
||||
frames = bytes_to_samples(runtime, iov->iov_len);
|
||||
bufs = kmalloc_array(from->nr_segs, sizeof(void *), GFP_KERNEL);
|
||||
if (bufs == NULL)
|
||||
return -ENOMEM;
|
||||
for (i = 0; i < from->nr_segs; ++i)
|
||||
bufs[i] = from->iov[i].iov_base;
|
||||
for (i = 0; i < from->nr_segs; ++i) {
|
||||
bufs[i] = iov->iov_base;
|
||||
iov++;
|
||||
}
|
||||
result = snd_pcm_lib_writev(substream, bufs, frames);
|
||||
if (result > 0)
|
||||
result = frames_to_bytes(runtime, result);
|
||||
|
||||
@@ -1284,9 +1284,9 @@ static const char *uaccess_safe_builtin[] = {
|
||||
"copy_mc_fragile_handle_tail",
|
||||
"copy_mc_enhanced_fast_string",
|
||||
"ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */
|
||||
"clear_user_erms",
|
||||
"clear_user_rep_good",
|
||||
"clear_user_original",
|
||||
"rep_stos_alternative",
|
||||
"rep_movs_alternative",
|
||||
"__copy_user_nocache",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user