Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions arch/arm64/include/asm/barrier.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,19 @@ do { \
(typeof(*ptr))VAL; \
})

#define SMP_TIMEOUT_POLL_COUNT 1

/* Re-declared here to avoid include dependency. */
extern bool arch_timer_evtstrm_available(void);

#define cpu_poll_relax(ptr, val) \
do { \
if (arch_timer_evtstrm_available()) \
__cmpwait_relaxed(ptr, val); \
else \
cpu_relax(); \
} while (0)

#include <asm-generic/barrier.h>

#endif /* __ASSEMBLY__ */
Expand Down
85 changes: 0 additions & 85 deletions arch/arm64/include/asm/rqspinlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,91 +3,6 @@
#define _ASM_RQSPINLOCK_H

#include <asm/barrier.h>

/*
* Hardcode res_smp_cond_load_acquire implementations for arm64 to a custom
* version based on [0]. In rqspinlock code, our conditional expression involves
* checking the value _and_ additionally a timeout. However, on arm64, the
* WFE-based implementation may never spin again if no stores occur to the
* locked byte in the lock word. As such, we may be stuck forever if
* event-stream based unblocking is not available on the platform for WFE spin
* loops (arch_timer_evtstrm_available).
*
* Once support for smp_cond_load_acquire_timewait [0] lands, we can drop this
* copy-paste.
*
* While we rely on the implementation to amortize the cost of sampling
* cond_expr for us, it will not happen when event stream support is
* unavailable, time_expr check is amortized. This is not the common case, and
* it would be difficult to fit our logic in the time_expr_ns >= time_limit_ns
* comparison, hence just let it be. In case of event-stream, the loop is woken
* up at microsecond granularity.
*
* [0]: https://lore.kernel.org/lkml/[email protected]
*/

#ifndef smp_cond_load_acquire_timewait

#define smp_cond_time_check_count 200

#define __smp_cond_load_relaxed_spinwait(ptr, cond_expr, time_expr_ns, \
time_limit_ns) ({ \
typeof(ptr) __PTR = (ptr); \
__unqual_scalar_typeof(*ptr) VAL; \
unsigned int __count = 0; \
for (;;) { \
VAL = READ_ONCE(*__PTR); \
if (cond_expr) \
break; \
cpu_relax(); \
if (__count++ < smp_cond_time_check_count) \
continue; \
if ((time_expr_ns) >= (time_limit_ns)) \
break; \
__count = 0; \
} \
(typeof(*ptr))VAL; \
})

#define __smp_cond_load_acquire_timewait(ptr, cond_expr, \
time_expr_ns, time_limit_ns) \
({ \
typeof(ptr) __PTR = (ptr); \
__unqual_scalar_typeof(*ptr) VAL; \
for (;;) { \
VAL = smp_load_acquire(__PTR); \
if (cond_expr) \
break; \
__cmpwait_relaxed(__PTR, VAL); \
if ((time_expr_ns) >= (time_limit_ns)) \
break; \
} \
(typeof(*ptr))VAL; \
})

#define smp_cond_load_acquire_timewait(ptr, cond_expr, \
time_expr_ns, time_limit_ns) \
({ \
__unqual_scalar_typeof(*ptr) _val; \
int __wfe = arch_timer_evtstrm_available(); \
\
if (likely(__wfe)) { \
_val = __smp_cond_load_acquire_timewait(ptr, cond_expr, \
time_expr_ns, \
time_limit_ns); \
} else { \
_val = __smp_cond_load_relaxed_spinwait(ptr, cond_expr, \
time_expr_ns, \
time_limit_ns); \
smp_acquire__after_ctrl_dep(); \
} \
(typeof(*ptr))_val; \
})

#endif

#define res_smp_cond_load_acquire(v, c) smp_cond_load_acquire_timewait(v, c, 0, 1)

#include <asm-generic/rqspinlock.h>

#endif /* _ASM_RQSPINLOCK_H */
29 changes: 8 additions & 21 deletions drivers/cpuidle/poll_state.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,22 @@
#include <linux/sched/clock.h>
#include <linux/sched/idle.h>

#define POLL_IDLE_RELAX_COUNT 200

static int __cpuidle poll_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv, int index)
{
u64 time_start;

time_start = local_clock_noinstr();
u64 time_end;
u32 flags = 0;

dev->poll_time_limit = false;

time_end = local_clock_noinstr() + cpuidle_poll_time(drv, dev);

raw_local_irq_enable();
if (!current_set_polling_and_test()) {
unsigned int loop_count = 0;
u64 limit;

limit = cpuidle_poll_time(drv, dev);

while (!need_resched()) {
cpu_relax();
if (loop_count++ < POLL_IDLE_RELAX_COUNT)
continue;

loop_count = 0;
if (local_clock_noinstr() - time_start > limit) {
dev->poll_time_limit = true;
break;
}
}
flags = smp_cond_load_relaxed_timeout(&current_thread_info()->flags,
(VAL & _TIF_NEED_RESCHED),
(local_clock_noinstr() >= time_end));
dev->poll_time_limit = !(flags & _TIF_NEED_RESCHED);
}
raw_local_irq_disable();

Expand Down
63 changes: 63 additions & 0 deletions include/asm-generic/barrier.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,69 @@ do { \
})
#endif

#ifndef SMP_TIMEOUT_POLL_COUNT
#define SMP_TIMEOUT_POLL_COUNT 200
#endif

#ifndef cpu_poll_relax
#define cpu_poll_relax(ptr, val) cpu_relax()
#endif

/**
* smp_cond_load_relaxed_timeout() - (Spin) wait for cond with no ordering
* guarantees until a timeout expires.
* @ptr: pointer to the variable to wait on
* @cond: boolean expression to wait for
* @time_check_expr: expression to decide when to bail out
*
* Equivalent to using READ_ONCE() on the condition variable.
*/
#ifndef smp_cond_load_relaxed_timeout
#define smp_cond_load_relaxed_timeout(ptr, cond_expr, time_check_expr) \
({ \
typeof(ptr) __PTR = (ptr); \
__unqual_scalar_typeof(*ptr) VAL; \
u32 __n = 0, __spin = SMP_TIMEOUT_POLL_COUNT; \
\
for (;;) { \
VAL = READ_ONCE(*__PTR); \
if (cond_expr) \
break; \
cpu_poll_relax(__PTR, VAL); \
if (++__n < __spin) \
continue; \
if (time_check_expr) { \
VAL = READ_ONCE(*__PTR); \
break; \
} \
__n = 0; \
} \
(typeof(*ptr))VAL; \
})
#endif

/**
* smp_cond_load_acquire_timeout() - (Spin) wait for cond with ACQUIRE ordering
* until a timeout expires.
*
* Arguments: same as smp_cond_load_relaxed_timeout().
*
* Equivalent to using smp_cond_load_acquire() on the condition variable with
* a timeout.
*/
#ifndef smp_cond_load_acquire_timeout
#define smp_cond_load_acquire_timeout(ptr, cond_expr, time_check_expr) \
({ \
__unqual_scalar_typeof(*ptr) _val; \
_val = smp_cond_load_relaxed_timeout(ptr, cond_expr, \
time_check_expr); \
\
/* Depends on the control dependency of the wait above. */ \
smp_acquire__after_ctrl_dep(); \
(typeof(*ptr))_val; \
})
#endif

/*
* pmem_wmb() ensures that all stores for which the modification
* are written to persistent storage by preceding instructions have
Expand Down
10 changes: 10 additions & 0 deletions include/linux/atomic.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
#define atomic64_cond_read_acquire(v, c) smp_cond_load_acquire(&(v)->counter, (c))
#define atomic64_cond_read_relaxed(v, c) smp_cond_load_relaxed(&(v)->counter, (c))

#define atomic_cond_read_acquire_timeout(v, c, t) \
smp_cond_load_acquire_timeout(&(v)->counter, (c), (t))
#define atomic_cond_read_relaxed_timeout(v, c, t) \
smp_cond_load_relaxed_timeout(&(v)->counter, (c), (t))

#define atomic64_cond_read_acquire_timeout(v, c, t) \
smp_cond_load_acquire_timeout(&(v)->counter, (c), (t))
#define atomic64_cond_read_relaxed_timeout(v, c, t) \
smp_cond_load_relaxed_timeout(&(v)->counter, (c), (t))

/*
* The idea here is to build acquire/release variants by adding explicit
* barriers on top of the relaxed variant. In the case where the relaxed
Expand Down
32 changes: 15 additions & 17 deletions kernel/bpf/rqspinlock.c
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,14 @@ static noinline int check_timeout(rqspinlock_t *lock, u32 mask,
}

/*
* Do not amortize with spins when res_smp_cond_load_acquire is defined,
* as the macro does internal amortization for us.
* Amortize timeout check for busy-wait loops.
*/
#ifndef res_smp_cond_load_acquire
#define RES_CHECK_TIMEOUT(ts, ret, mask) \
({ \
if (!(ts).spin++) \
(ret) = check_timeout((lock), (mask), &(ts)); \
(ret); \
})
#else
#define RES_CHECK_TIMEOUT(ts, ret, mask) \
({ (ret) = check_timeout((lock), (mask), &(ts)); })
#endif

/*
* Initialize the 'spin' member.
Expand All @@ -265,6 +259,16 @@ static noinline int check_timeout(rqspinlock_t *lock, u32 mask,
*/
#define RES_RESET_TIMEOUT(ts, _duration) ({ (ts).timeout_end = 0; (ts).duration = _duration; })

/*
* Limit how often check_timeout() is invoked while spin-waiting in
* smp_cond_load_acquire_timeout() or atomic_cond_read_acquire_timeout().
* (ARM64, typically uses a waited implementation so we exclude that.)
*/
#ifndef CONFIG_ARM64
#undef SMP_TIMEOUT_POLL_COUNT
#define SMP_TIMEOUT_POLL_COUNT (16*1024)
#endif

/*
* Provide a test-and-set fallback for cases when queued spin lock support is
* absent from the architecture.
Expand Down Expand Up @@ -310,12 +314,6 @@ EXPORT_SYMBOL_GPL(resilient_tas_spin_lock);
*/
static DEFINE_PER_CPU_ALIGNED(struct qnode, rqnodes[_Q_MAX_NODES]);

#ifndef res_smp_cond_load_acquire
#define res_smp_cond_load_acquire(v, c) smp_cond_load_acquire(v, c)
#endif

#define res_atomic_cond_read_acquire(v, c) res_smp_cond_load_acquire(&(v)->counter, (c))

/**
* resilient_queued_spin_lock_slowpath - acquire the queued spinlock
* @lock: Pointer to queued spinlock structure
Expand Down Expand Up @@ -415,7 +413,8 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val)
*/
if (val & _Q_LOCKED_MASK) {
RES_RESET_TIMEOUT(ts, RES_DEF_TIMEOUT);
res_smp_cond_load_acquire(&lock->locked, !VAL || RES_CHECK_TIMEOUT(ts, ret, _Q_LOCKED_MASK));
smp_cond_load_acquire_timeout(&lock->locked, !VAL,
(ret = check_timeout(lock, _Q_LOCKED_MASK, &ts)));
}

if (ret) {
Expand Down Expand Up @@ -569,9 +568,8 @@ int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val)
* us.
*/
RES_RESET_TIMEOUT(ts, RES_DEF_TIMEOUT * 2);
val = res_atomic_cond_read_acquire(&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK) ||
RES_CHECK_TIMEOUT(ts, ret, _Q_LOCKED_PENDING_MASK));

val = atomic_cond_read_acquire_timeout(&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK),
(ret = check_timeout(lock, _Q_LOCKED_PENDING_MASK, &ts)));
waitq_timeout:
if (ret) {
/*
Expand Down
Loading