]> git.karo-electronics.de Git - karo-tx-linux.git/blob - lib/percpu-refcount.c
generic-dynamic-per-cpu-refcounting-fix
[karo-tx-linux.git] / lib / percpu-refcount.c
1 #define pr_fmt(fmt) "%s: " fmt "\n", __func__
2
3 #include <linux/kernel.h>
4 #include <linux/jiffies.h>
5 #include <linux/percpu-refcount.h>
6 #include <linux/rcupdate.h>
7
8 #define PCPU_COUNT_BITS         50
9 #define PCPU_COUNT_MASK         ((1LL << PCPU_COUNT_BITS) - 1)
10
11 #define PCPU_STATUS_BITS        2
12 #define PCPU_STATUS_MASK        ((1 << PCPU_STATUS_BITS) - 1)
13
14 #define PCPU_REF_PTR            0
15 #define PCPU_REF_NONE           1
16 #define PCPU_REF_DYING          2
17 #define PCPU_REF_DEAD           3
18
19 #define REF_STATUS(count)       ((unsigned long) count & PCPU_STATUS_MASK)
20
21 void percpu_ref_init(struct percpu_ref *ref)
22 {
23         unsigned long now = jiffies;
24
25         atomic64_set(&ref->count, 1);
26
27         now <<= PCPU_STATUS_BITS;
28         now |= PCPU_REF_NONE;
29
30         ref->pcpu_count = (void *) now;
31 }
32
33 static void percpu_ref_alloc(struct percpu_ref *ref, unsigned __user *pcpu_count)
34 {
35         unsigned __percpu *new;
36         unsigned long last = (unsigned long) pcpu_count;
37         unsigned long now = jiffies;
38
39         now <<= PCPU_STATUS_BITS;
40         now |= PCPU_REF_NONE;
41
42         if (now - last <= HZ << PCPU_STATUS_BITS) {
43                 rcu_read_unlock();
44                 new = alloc_percpu(unsigned);
45                 rcu_read_lock();
46
47                 if (!new)
48                         goto update_time;
49
50                 BUG_ON(((unsigned long) new) & PCPU_STATUS_MASK);
51
52                 if (cmpxchg(&ref->pcpu_count, pcpu_count, new) != pcpu_count)
53                         free_percpu(new);
54                 else
55                         pr_debug("created");
56         } else {
57 update_time:    new = (void *) now;
58                 cmpxchg(&ref->pcpu_count, pcpu_count, new);
59         }
60 }
61
62 void __percpu_ref_get(struct percpu_ref *ref, bool alloc)
63 {
64         unsigned __percpu *pcpu_count;
65         uint64_t v;
66
67         pcpu_count = rcu_dereference(ref->pcpu_count);
68
69         if (REF_STATUS(pcpu_count) == PCPU_REF_PTR) {
70                 __this_cpu_inc(*pcpu_count);
71         } else {
72                 v = atomic64_add_return(1 + (1ULL << PCPU_COUNT_BITS),
73                                         &ref->count);
74
75                 if (!(v >> PCPU_COUNT_BITS) &&
76                     REF_STATUS(pcpu_count) == PCPU_REF_NONE && alloc)
77                         percpu_ref_alloc(ref, pcpu_count);
78         }
79 }
80
81 int percpu_ref_put(struct percpu_ref *ref)
82 {
83         unsigned __percpu *pcpu_count;
84         uint64_t v;
85         int ret = 0;
86
87         rcu_read_lock();
88
89         pcpu_count = rcu_dereference(ref->pcpu_count);
90
91         switch (REF_STATUS(pcpu_count)) {
92         case PCPU_REF_PTR:
93                 __this_cpu_dec(*pcpu_count);
94                 break;
95         case PCPU_REF_NONE:
96         case PCPU_REF_DYING:
97                 atomic64_dec(&ref->count);
98                 break;
99         case PCPU_REF_DEAD:
100                 v = atomic64_dec_return(&ref->count);
101                 v &= PCPU_COUNT_MASK;
102
103                 ret = v == 0;
104                 break;
105         }
106
107         rcu_read_unlock();
108
109         return ret;
110 }
111
112 int percpu_ref_kill(struct percpu_ref *ref)
113 {
114         unsigned __percpu *old, *new, *pcpu_count = ref->pcpu_count;
115         unsigned long status;
116
117         do {
118                 status = REF_STATUS(pcpu_count);
119
120                 switch (status) {
121                 case PCPU_REF_PTR:
122                         new = (void *) PCPU_REF_DYING;
123                         break;
124                 case PCPU_REF_NONE:
125                         new = (void *) PCPU_REF_DEAD;
126                         break;
127                 case PCPU_REF_DYING:
128                 case PCPU_REF_DEAD:
129                         return 0;
130                 }
131
132                 old = pcpu_count;
133                 pcpu_count = cmpxchg(&ref->pcpu_count, old, new);
134         } while (pcpu_count != old);
135
136         if (status == PCPU_REF_PTR) {
137                 unsigned count = 0, cpu;
138
139                 synchronize_rcu();
140
141                 for_each_possible_cpu(cpu)
142                         count += *per_cpu_ptr(pcpu_count, cpu);
143
144                 pr_debug("global %lli pcpu %i",
145                          atomic64_read(&ref->count) & PCPU_COUNT_MASK,
146                          (int) count);
147
148                 atomic64_add((int) count, &ref->count);
149                 smp_wmb();
150                 /* Between setting global count and setting PCPU_REF_DEAD */
151                 ref->pcpu_count = (void *) PCPU_REF_DEAD;
152
153                 free_percpu(pcpu_count);
154         }
155
156         return 1;
157 }
158
159 int percpu_ref_dead(struct percpu_ref *ref)
160 {
161         unsigned status = REF_STATUS(ref->pcpu_count);
162
163         return status == PCPU_REF_DYING ||
164                 status == PCPU_REF_DEAD;
165 }