/* * Atomic operations on 64-bit quantities. * * Copyright (C) 2017 Red Hat, Inc. * * Author: Paolo Bonzini * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ #include "qemu/osdep.h" #include "qemu/atomic.h" #include "qemu/stats64.h" #include "qemu/processor.h" #ifndef CONFIG_ATOMIC64 static inline void stat64_rdlock(Stat64 *s) { /* Keep out incoming writers to avoid them starving us. */ atomic_add(&s->lock, 2); /* If there is a concurrent writer, wait for it. */ while (atomic_read(&s->lock) & 1) { cpu_relax(); } } static inline void stat64_rdunlock(Stat64 *s) { atomic_sub(&s->lock, 2); } static inline bool stat64_wrtrylock(Stat64 *s) { return atomic_cmpxchg(&s->lock, 0, 1) == 0; } static inline void stat64_wrunlock(Stat64 *s) { atomic_dec(&s->lock); } uint64_t stat64_get(const Stat64 *s) { uint32_t high, low; stat64_rdlock((Stat64 *)s); /* 64-bit writes always take the lock, so we can read in * any order. */ high = atomic_read(&s->high); low = atomic_read(&s->low); stat64_rdunlock((Stat64 *)s); return ((uint64_t)high << 32) | low; } bool stat64_add32_carry(Stat64 *s, uint32_t low, uint32_t high) { uint32_t old; if (!stat64_wrtrylock(s)) { cpu_relax(); return false; } /* 64-bit reads always take the lock, so they don't care about the * order of our update. By updating s->low first, we can check * whether we have to carry into s->high. */ old = atomic_fetch_add(&s->low, low); high += (old + low) < old; atomic_add(&s->high, high); stat64_wrunlock(s); return true; } bool stat64_min_slow(Stat64 *s, uint64_t value) { uint32_t high, low; uint64_t orig; if (!stat64_wrtrylock(s)) { cpu_relax(); return false; } high = atomic_read(&s->high); low = atomic_read(&s->low); orig = ((uint64_t)high << 32) | low; if (value < orig) { /* We have to set low before high, just like stat64_min reads * high before low. The value may become higher temporarily, but * stat64_get does not notice (it takes the lock) and the only ill * effect on stat64_min is that the slow path may be triggered * unnecessarily. */ atomic_set(&s->low, (uint32_t)value); smp_wmb(); atomic_set(&s->high, value >> 32); } stat64_wrunlock(s); return true; } bool stat64_max_slow(Stat64 *s, uint64_t value) { uint32_t high, low; uint64_t orig; if (!stat64_wrtrylock(s)) { cpu_relax(); return false; } high = atomic_read(&s->high); low = atomic_read(&s->low); orig = ((uint64_t)high << 32) | low; if (value > orig) { /* We have to set low before high, just like stat64_max reads * high before low. The value may become lower temporarily, but * stat64_get does not notice (it takes the lock) and the only ill * effect on stat64_max is that the slow path may be triggered * unnecessarily. */ atomic_set(&s->low, (uint32_t)value); smp_wmb(); atomic_set(&s->high, value >> 32); } stat64_wrunlock(s); return true; } #endif