blob: 1626e38b737e8f26d477d11babeb405ec86adb30 [file] [log] [blame]
/* Copyright (C) 1991-2017, the Linux Kernel authors */
/*
* Fast batching percpu counters.
*/
#include <percpu_counter.h>
#include <linux_compat.h>
void percpu_counter_set(struct percpu_counter *fbc, int64_t amount)
{
int cpu;
unsigned long flags;
spin_lock_irqsave(&fbc->lock);
for_each_possible_cpu(cpu) {
int32_t *pcount = _PERCPU_VARPTR(*fbc->counters, cpu);
*pcount = 0;
}
fbc->count = amount;
spin_unlock_irqsave(&fbc->lock);
}
/**
* This function is both preempt and irq safe. The former is due to explicit
* preemption disable. The latter is guaranteed by the fact that the slow path
* is explicitly protected by an irq-safe spinlock whereas the fast patch uses
* this_cpu_add which is irq-safe by definition. Hence there is no need muck
* with irq state before calling this one
*/
void percpu_counter_add_batch(struct percpu_counter *fbc, int64_t amount,
int32_t batch)
{
int64_t count;
preempt_disable();
count = __this_cpu_read(*fbc->counters) + amount;
if (count >= batch || count <= -batch) {
unsigned long flags;
spin_lock_irqsave(&fbc->lock);
fbc->count += count;
__this_cpu_sub(*fbc->counters, count - amount);
spin_unlock_irqsave(&fbc->lock);
} else {
this_cpu_add(*fbc->counters, amount);
}
preempt_enable();
}
/*
* Add up all the per-cpu counts, return the result. This is a more accurate
* but much slower version of percpu_counter_read_positive()
*/
int64_t __percpu_counter_sum(struct percpu_counter *fbc)
{
int64_t ret;
int cpu;
unsigned long flags;
spin_lock_irqsave(&fbc->lock);
ret = fbc->count;
for_each_online_cpu(cpu) {
int32_t *pcount = _PERCPU_VARPTR(*fbc->counters, cpu);
ret += *pcount;
}
spin_unlock_irqsave(&fbc->lock);
return ret;
}
int percpu_counter_init(struct percpu_counter *fbc, int64_t amount, gfp_t gfp)
{
unsigned long flags __maybe_unused;
spinlock_init_irqsave(&fbc->lock);
fbc->count = amount;
fbc->counters = alloc_percpu_gfp(int32_t, gfp);
if (!fbc->counters)
return -ENOMEM;
return 0;
}
void percpu_counter_destroy(struct percpu_counter *fbc)
{
unsigned long flags __maybe_unused;
if (!fbc->counters)
return;
free_percpu(fbc->counters);
fbc->counters = NULL;
}
int percpu_counter_batch __read_mostly = 32;
/*
* Compare counter against given value.
* Return 1 if greater, 0 if equal and -1 if less
*/
int __percpu_counter_compare(struct percpu_counter *fbc, int64_t rhs,
int32_t batch)
{
int64_t count;
count = percpu_counter_read(fbc);
/* Check to see if rough count will be sufficient for comparison */
if (abs(count - rhs) > (batch * num_online_cpus())) {
if (count > rhs)
return 1;
else
return -1;
}
/* Need to use precise count */
count = percpu_counter_sum(fbc);
if (count > rhs)
return 1;
else if (count < rhs)
return -1;
else
return 0;
}