Просмотр исходного кода

m68k: use the kernel cmpxchg helper for atomics on ColdFire

Commit 5c0333f6b gave m68k its own bits/atomic.h built on the CAS
instruction, which broke every ColdFire target:

  Error: invalid instruction for this architecture; needs 68020 or higher
  -- statement `cas.l %d0,%d3,(%a0)' ignored
  [Makerules:373: libc/stdlib/__cxa_finalize.os] Error 1

cas.b/cas.w/cas.l are 68020 and up; ColdFire dropped them and has no
atomic read-modify-write instruction at all. The m68k kernel exports a
compare-and-exchange helper for exactly this case -- sys_atomic_cmpxchg_32
(and sys_atomic_barrier, a no-op on uniprocessors), available on both the
MMU and the noMMU side. glibc's ColdFire port uses it as well.

Keep the CAS path for 68020+ and use the helper on ColdFire: d0 holds the
syscall number, d1 the new value, d2 the old one and a0 the address; the
previous value comes back in d0. Since the swap happens inside the
syscall, neither a signal handler nor another thread can interpose
between the load and the store, so ColdFire gets real atomicity rather
than the read-compare-write of the generic fallback -- which is what made
tst-cond16 lose wakeups before 5c0333f6b.

The helper is 32-bit only. The 8- and 16-bit primitives therefore stay
non-atomic on ColdFire instead of widening the access over neighbouring
bytes; uClibc's atomic users are int- and pointer-sized, so the bysize
dispatch does not reach them. The 64-bit case is unchanged.

Verified with the ColdFire (cf51, no FPU) toolchain: before the change
the tree stops at libc/stdlib/__cxa_finalize.os with the error above,
after it the whole libc compiles with no cas diagnostics and
__cxa_finalize.os contains the trap that calls the helper.

Reported-by: Waldemar Brodkorb <wbx@openadk.org>
Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
ramin 2 недель назад
Родитель
Сommit
e6d49b5b4a
1 измененных файлов с 42 добавлено и 2 удалено
  1. 42 2
      libc/sysdeps/linux/m68k/bits/atomic.h

+ 42 - 2
libc/sysdeps/linux/m68k/bits/atomic.h

@@ -8,6 +8,9 @@
    with cas.b/cas.w/cas.l; include/atomic.h derives every higher-level
    operation from it.
 
+   ColdFire has no CAS at all, so there we use the kernel's
+   atomic_cmpxchg_32 helper, which exists for exactly this reason.
+
    Without this file m68k fell back to the generic non-atomic
    bits/atomic.h, whose "atomic" compare-and-exchange is a plain
    read-compare-write.  Under sustained contention a preemption between
@@ -19,6 +22,9 @@
 #define _M68K_BITS_ATOMIC_H	1
 
 #include <stdint.h>
+#ifdef __mcoldfire__
+# include <sys/syscall.h>
+#endif
 
 typedef int8_t atomic8_t;
 typedef uint8_t uatomic8_t;
@@ -45,12 +51,15 @@ typedef uintptr_t uatomicptr_t;
 typedef intmax_t atomic_max_t;
 typedef uintmax_t uatomic_max_t;
 
-/* UP m68k; CAS is itself the read-modify-write atomic, so a plain
-   compiler barrier around it is enough.  */
+/* UP m68k; the compare-and-exchange below is itself the read-modify-write
+   atomic, so a plain compiler barrier around it is enough.  (The kernel's
+   sys_atomic_barrier is a no-op on uniprocessors.)  */
 #define atomic_full_barrier()	__asm__ __volatile__ ("" ::: "memory")
 #define atomic_read_barrier()	atomic_full_barrier ()
 #define atomic_write_barrier()	atomic_full_barrier ()
 
+#ifndef __mcoldfire__
+
 /* cas Dc,Du,<ea>: compare <ea> with Dc; if equal store Du, else load <ea>
    into Dc.  Either way Dc ends up holding the original *MEM, which is the
    value compare-and-exchange must return.  */
@@ -78,6 +87,37 @@ typedef uintmax_t uatomic_max_t;
 		       : "memory");					\
      __ret; })
 
+#else /* __mcoldfire__ */
+
+/* d0 = syscall number, d1 = newval, d2 = oldval, a0 = mem; the helper
+   returns the previous value.  The swap happens inside the syscall, so
+   nothing in userspace can interpose between the load and the store.  */
+#define __arch_compare_and_exchange_val_32_acq(mem, newval, oldval) \
+  ({ register uint32_t __d0 __asm__ ("d0") = __NR_atomic_cmpxchg_32;	\
+     register uint32_t __d1 __asm__ ("d1") = (uint32_t) (newval);	\
+     register uint32_t __d2 __asm__ ("d2") = (uint32_t) (oldval);	\
+     register uint32_t *__a0 __asm__ ("a0") = (uint32_t *) (mem);	\
+     __asm__ __volatile__ ("trap #0"					\
+		       : "+d" (__d0), "+m" (*__a0)			\
+		       : "a" (__a0), "d" (__d2), "d" (__d1)		\
+		       : "memory");					\
+     (__typeof (*(mem))) __d0; })
+
+/* The kernel helper is 32-bit only.  uClibc's atomic users are int- and
+   pointer-sized, so these stay non-atomic rather than have a 32-bit access
+   clobber the neighbouring bytes.  */
+#define __arch_compare_and_exchange_val_8_acq(mem, newval, oldval) \
+  ({ __typeof (mem) __gmem = (mem);					\
+     __typeof (*(mem)) __gret = *__gmem;				\
+     if (__gret == (oldval))						\
+       *__gmem = (newval);						\
+     __gret; })
+
+#define __arch_compare_and_exchange_val_16_acq(mem, newval, oldval) \
+  __arch_compare_and_exchange_val_8_acq (mem, newval, oldval)
+
+#endif /* __mcoldfire__ */
+
 /* m68k has no 64-bit CAS; NPTL and include/atomic.h only use int- and
    pointer-sized objects, so the bysize dispatch never reaches this.  */
 #define __arch_compare_and_exchange_val_64_acq(mem, newval, oldval) \