arc_usr_cmpxchg syscall is supposed to be used on platforms...
that lack support of Load-Locked/Store-Conditional instructions
in hardware. And in that case we mimic missing hardware features
with help of kernel's sycall that "atomically" checks current
value in memory and then if it matches caller expectation new
value is written to that same location.
2. What's worse if we're dealing with data from not yet allocated
page (think of pre-copy-on-write state) we'll successfully
read data but on write we'll silently return to user-space
with correct result
to very strange problems in user-space app further down the line
because new value was never written to the destination.
3. Regardless of what went wrong we'll return from syscall
and user-space application will continue to execute.
Even if user's pointer was completely bogus.
In case of hardware LL/SC that app would have been killed
by the kernel.
With that change we attempt to imrove on all 3 items above:
1. We still disable preemption around read-and-write of
user's data but if we happen to fail with either of them
we're enabling preemption and try to force page fault so
that we have a correct mapping in the TLB. Then re-try
again in "atomic" context.
2. If real page fault fails or even access_ok() returns false
we send SIGSEGV to the user-space process so if something goes
seriously wrong we'll know about it much earlier.
/*
* This is only for old cores lacking LLOCK/SCOND, which by defintion
@@ -60,23 +62,48 @@ SYSCALL_DEFINE3(arc_usr_cmpxchg, int *, uaddr, int, expected, int, new)
/* Z indicates to userspace if operation succeded */
regs->status32 &= ~STATUS_Z_MASK;
- if (!access_ok(VERIFY_WRITE, uaddr, sizeof(int)))
- return -EFAULT;
+ ret = access_ok(VERIFY_WRITE, uaddr, sizeof(*uaddr));
+ if (!ret)
+ goto fail;
+again:
preempt_disable();
- if (__get_user(uval, uaddr))
- goto done;
-
- if (uval == expected) {
- if (!__put_user(new, uaddr))
+ ret = __get_user(val, uaddr);
+ if (ret == -EFAULT) {
+ preempt_enable();
+ ret = get_user_pages_fast((unsigned long)uaddr, 1, 1, &page);
+ if (ret < 0)
+ goto fail;
+
+ put_page(page);
+ goto again;
+ } else if (ret)
+ goto fail;
+
+ if (val == expected) {
+ ret = __put_user(new, uaddr);
+ if (!ret)
regs->status32 |= STATUS_Z_MASK;
}
-done:
preempt_enable();
- return uval;
+ if (ret == -EFAULT) {
+ ret = get_user_pages_fast((unsigned long)uaddr, 1, 1, &page);
+ if (ret < 0)
+ goto fail;
+
+ put_page(page);
+ goto again;
+ } else if (ret)
+ goto fail;
+
+ return val;
+
+fail:
+ force_sig(SIGSEGV, current);
+ return ret;
}
#ifdef CONFIG_ISA_ARCV2