#include <sys/types.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/spinlock.h>
#include <sys/spinlock2.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/rcupdate.h>
#include <machine/atomic.h>
typedef struct rcu_elm {
enum { RCU_NULL, RCU_CALL, RCU_FREE } type;
int ticks;
void (*func)(struct rcu_head *arg);
void *ptr;
} rcu_elm_t;
typedef struct rcu_pcpu {
rcu_elm_t *elms;
int size;
int mask;
int s;
int e;
int running;
struct callout timer_callout;
} rcu_pcpu_t;
static rcu_pcpu_t *rcupcpu;
static void
rcu_timer(void *arg)
{
rcu_pcpu_t *rcu = arg;
rcu_elm_t *elm;
int delta;
crit_enter();
while (rcu->s != rcu->e) {
elm = &rcu->elms[rcu->s & rcu->mask];
delta = ticks - elm->ticks;
if (delta < hz)
break;
switch(elm->type) {
case RCU_NULL:
break;
case RCU_CALL:
elm->func(elm->ptr);
break;
case RCU_FREE:
kfree(elm->ptr);
break;
}
elm->type = RCU_NULL;
++rcu->s;
}
if (rcu->s == rcu->e) {
rcu->running = 0;
} else {
callout_reset_bycpu(&rcu->timer_callout, hz / 10, rcu_timer,
rcu, mycpuid);
}
crit_exit();
}
static void
rcu_ping(rcu_pcpu_t *rcu)
{
if (rcu->running == 0) {
rcu->running = 1;
callout_reset_bycpu(&rcu->timer_callout, hz / 10, rcu_timer,
rcu, mycpuid);
}
}
static void
rcu_expand(rcu_pcpu_t *rcu)
{
rcu_elm_t *oelms;
rcu_elm_t *nelms;
int count;
int nsize;
int nmask;
int n;
count = rcu->e - rcu->s;
while (unlikely(count == rcu->size)) {
nsize = count ? count * 2 : 16;
nelms = kzalloc(nsize * sizeof(*nelms), GFP_KERNEL);
kprintf("drm: expand RCU cpu %d to %d\n", mycpuid, nsize);
if (likely(count == rcu->size)) {
nmask = nsize - 1;
oelms = rcu->elms;
n = rcu->s;
while (n != rcu->e) {
nelms[n & nmask] = oelms[n & rcu->mask];
++n;
}
rcu->elms = nelms;
rcu->size = nsize;
rcu->mask = nmask;
nelms = oelms;
}
if (likely(nelms != NULL))
kfree(nelms);
count = rcu->e - rcu->s;
}
KKASSERT(count >= 0 && count < rcu->size);
}
void
__kfree_rcu(void *ptr)
{
rcu_pcpu_t *rcu;
rcu_elm_t *elm;
if (unlikely(rcupcpu == NULL)) {
kfree(ptr);
return;
}
rcu = &rcupcpu[mycpuid];
crit_enter();
rcu_expand(rcu);
elm = &rcu->elms[rcu->e & rcu->mask];
++rcu->e;
elm->type = RCU_FREE;
elm->ticks = ticks;
elm->ptr = ptr;
rcu_ping(rcu);
crit_exit();
}
void
call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *))
{
rcu_pcpu_t *rcu;
rcu_elm_t *elm;
if (unlikely(rcupcpu == NULL)) {
func(head);
return;
}
rcu = &rcupcpu[mycpuid];
crit_enter();
rcu_expand(rcu);
elm = &rcu->elms[rcu->e & rcu->mask];
++rcu->e;
elm->type = RCU_CALL;
elm->ticks = ticks;
elm->func = func;
elm->ptr = head;
rcu_ping(rcu);
crit_exit();
}
static int
init_rcu(void *dummy __unused)
{
int i;
rcupcpu = kzalloc(ncpus * sizeof(*rcupcpu), GFP_KERNEL);
for (i = 0; i < ncpus; ++i) {
callout_init_mp(&rcupcpu[i].timer_callout);
}
return 0;
}
SYSINIT(linux_rcu_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, init_rcu, NULL);