| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* Copyright (c) 2016 Facebook |
|---|
| 2 | | - * |
|---|
| 3 | | - * This program is free software; you can redistribute it and/or |
|---|
| 4 | | - * modify it under the terms of version 2 of the GNU General Public |
|---|
| 5 | | - * License as published by the Free Software Foundation. |
|---|
| 6 | 3 | */ |
|---|
| 7 | 4 | #include "percpu_freelist.h" |
|---|
| 8 | 5 | |
|---|
| .. | .. |
|---|
| 20 | 17 | raw_spin_lock_init(&head->lock); |
|---|
| 21 | 18 | head->first = NULL; |
|---|
| 22 | 19 | } |
|---|
| 20 | + raw_spin_lock_init(&s->extralist.lock); |
|---|
| 21 | + s->extralist.first = NULL; |
|---|
| 23 | 22 | return 0; |
|---|
| 24 | 23 | } |
|---|
| 25 | 24 | |
|---|
| .. | .. |
|---|
| 28 | 27 | free_percpu(s->freelist); |
|---|
| 29 | 28 | } |
|---|
| 30 | 29 | |
|---|
| 30 | +static inline void pcpu_freelist_push_node(struct pcpu_freelist_head *head, |
|---|
| 31 | + struct pcpu_freelist_node *node) |
|---|
| 32 | +{ |
|---|
| 33 | + node->next = head->first; |
|---|
| 34 | + head->first = node; |
|---|
| 35 | +} |
|---|
| 36 | + |
|---|
| 31 | 37 | static inline void ___pcpu_freelist_push(struct pcpu_freelist_head *head, |
|---|
| 32 | 38 | struct pcpu_freelist_node *node) |
|---|
| 33 | 39 | { |
|---|
| 34 | 40 | raw_spin_lock(&head->lock); |
|---|
| 35 | | - node->next = head->first; |
|---|
| 36 | | - head->first = node; |
|---|
| 41 | + pcpu_freelist_push_node(head, node); |
|---|
| 37 | 42 | raw_spin_unlock(&head->lock); |
|---|
| 43 | +} |
|---|
| 44 | + |
|---|
| 45 | +static inline bool pcpu_freelist_try_push_extra(struct pcpu_freelist *s, |
|---|
| 46 | + struct pcpu_freelist_node *node) |
|---|
| 47 | +{ |
|---|
| 48 | + if (!raw_spin_trylock(&s->extralist.lock)) |
|---|
| 49 | + return false; |
|---|
| 50 | + |
|---|
| 51 | + pcpu_freelist_push_node(&s->extralist, node); |
|---|
| 52 | + raw_spin_unlock(&s->extralist.lock); |
|---|
| 53 | + return true; |
|---|
| 54 | +} |
|---|
| 55 | + |
|---|
| 56 | +static inline void ___pcpu_freelist_push_nmi(struct pcpu_freelist *s, |
|---|
| 57 | + struct pcpu_freelist_node *node) |
|---|
| 58 | +{ |
|---|
| 59 | + int cpu, orig_cpu; |
|---|
| 60 | + |
|---|
| 61 | + orig_cpu = cpu = raw_smp_processor_id(); |
|---|
| 62 | + while (1) { |
|---|
| 63 | + struct pcpu_freelist_head *head; |
|---|
| 64 | + |
|---|
| 65 | + head = per_cpu_ptr(s->freelist, cpu); |
|---|
| 66 | + if (raw_spin_trylock(&head->lock)) { |
|---|
| 67 | + pcpu_freelist_push_node(head, node); |
|---|
| 68 | + raw_spin_unlock(&head->lock); |
|---|
| 69 | + return; |
|---|
| 70 | + } |
|---|
| 71 | + cpu = cpumask_next(cpu, cpu_possible_mask); |
|---|
| 72 | + if (cpu >= nr_cpu_ids) |
|---|
| 73 | + cpu = 0; |
|---|
| 74 | + |
|---|
| 75 | + /* cannot lock any per cpu lock, try extralist */ |
|---|
| 76 | + if (cpu == orig_cpu && |
|---|
| 77 | + pcpu_freelist_try_push_extra(s, node)) |
|---|
| 78 | + return; |
|---|
| 79 | + } |
|---|
| 38 | 80 | } |
|---|
| 39 | 81 | |
|---|
| 40 | 82 | void __pcpu_freelist_push(struct pcpu_freelist *s, |
|---|
| 41 | 83 | struct pcpu_freelist_node *node) |
|---|
| 42 | 84 | { |
|---|
| 43 | | - struct pcpu_freelist_head *head = this_cpu_ptr(s->freelist); |
|---|
| 44 | | - |
|---|
| 45 | | - ___pcpu_freelist_push(head, node); |
|---|
| 85 | + if (in_nmi()) |
|---|
| 86 | + ___pcpu_freelist_push_nmi(s, node); |
|---|
| 87 | + else |
|---|
| 88 | + ___pcpu_freelist_push(this_cpu_ptr(s->freelist), node); |
|---|
| 46 | 89 | } |
|---|
| 47 | 90 | |
|---|
| 48 | 91 | void pcpu_freelist_push(struct pcpu_freelist *s, |
|---|
| .. | .. |
|---|
| 59 | 102 | u32 nr_elems) |
|---|
| 60 | 103 | { |
|---|
| 61 | 104 | struct pcpu_freelist_head *head; |
|---|
| 62 | | - unsigned long flags; |
|---|
| 63 | | - int i, cpu, pcpu_entries; |
|---|
| 105 | + unsigned int cpu, cpu_idx, i, j, n, m; |
|---|
| 64 | 106 | |
|---|
| 65 | | - pcpu_entries = nr_elems / num_possible_cpus() + 1; |
|---|
| 66 | | - i = 0; |
|---|
| 107 | + n = nr_elems / num_possible_cpus(); |
|---|
| 108 | + m = nr_elems % num_possible_cpus(); |
|---|
| 67 | 109 | |
|---|
| 68 | | - /* disable irq to workaround lockdep false positive |
|---|
| 69 | | - * in bpf usage pcpu_freelist_populate() will never race |
|---|
| 70 | | - * with pcpu_freelist_push() |
|---|
| 71 | | - */ |
|---|
| 72 | | - local_irq_save(flags); |
|---|
| 110 | + cpu_idx = 0; |
|---|
| 73 | 111 | for_each_possible_cpu(cpu) { |
|---|
| 74 | | -again: |
|---|
| 75 | 112 | head = per_cpu_ptr(s->freelist, cpu); |
|---|
| 76 | | - ___pcpu_freelist_push(head, buf); |
|---|
| 77 | | - i++; |
|---|
| 78 | | - buf += elem_size; |
|---|
| 79 | | - if (i == nr_elems) |
|---|
| 80 | | - break; |
|---|
| 81 | | - if (i % pcpu_entries) |
|---|
| 82 | | - goto again; |
|---|
| 113 | + j = n + (cpu_idx < m ? 1 : 0); |
|---|
| 114 | + for (i = 0; i < j; i++) { |
|---|
| 115 | + /* No locking required as this is not visible yet. */ |
|---|
| 116 | + pcpu_freelist_push_node(head, buf); |
|---|
| 117 | + buf += elem_size; |
|---|
| 118 | + } |
|---|
| 119 | + cpu_idx++; |
|---|
| 83 | 120 | } |
|---|
| 84 | | - local_irq_restore(flags); |
|---|
| 85 | 121 | } |
|---|
| 86 | 122 | |
|---|
| 87 | | -struct pcpu_freelist_node *__pcpu_freelist_pop(struct pcpu_freelist *s) |
|---|
| 123 | +static struct pcpu_freelist_node *___pcpu_freelist_pop(struct pcpu_freelist *s) |
|---|
| 88 | 124 | { |
|---|
| 89 | 125 | struct pcpu_freelist_head *head; |
|---|
| 90 | 126 | struct pcpu_freelist_node *node; |
|---|
| .. | .. |
|---|
| 105 | 141 | if (cpu >= nr_cpu_ids) |
|---|
| 106 | 142 | cpu = 0; |
|---|
| 107 | 143 | if (cpu == orig_cpu) |
|---|
| 108 | | - return NULL; |
|---|
| 144 | + break; |
|---|
| 109 | 145 | } |
|---|
| 146 | + |
|---|
| 147 | + /* per cpu lists are all empty, try extralist */ |
|---|
| 148 | + raw_spin_lock(&s->extralist.lock); |
|---|
| 149 | + node = s->extralist.first; |
|---|
| 150 | + if (node) |
|---|
| 151 | + s->extralist.first = node->next; |
|---|
| 152 | + raw_spin_unlock(&s->extralist.lock); |
|---|
| 153 | + return node; |
|---|
| 154 | +} |
|---|
| 155 | + |
|---|
| 156 | +static struct pcpu_freelist_node * |
|---|
| 157 | +___pcpu_freelist_pop_nmi(struct pcpu_freelist *s) |
|---|
| 158 | +{ |
|---|
| 159 | + struct pcpu_freelist_head *head; |
|---|
| 160 | + struct pcpu_freelist_node *node; |
|---|
| 161 | + int orig_cpu, cpu; |
|---|
| 162 | + |
|---|
| 163 | + orig_cpu = cpu = raw_smp_processor_id(); |
|---|
| 164 | + while (1) { |
|---|
| 165 | + head = per_cpu_ptr(s->freelist, cpu); |
|---|
| 166 | + if (raw_spin_trylock(&head->lock)) { |
|---|
| 167 | + node = head->first; |
|---|
| 168 | + if (node) { |
|---|
| 169 | + head->first = node->next; |
|---|
| 170 | + raw_spin_unlock(&head->lock); |
|---|
| 171 | + return node; |
|---|
| 172 | + } |
|---|
| 173 | + raw_spin_unlock(&head->lock); |
|---|
| 174 | + } |
|---|
| 175 | + cpu = cpumask_next(cpu, cpu_possible_mask); |
|---|
| 176 | + if (cpu >= nr_cpu_ids) |
|---|
| 177 | + cpu = 0; |
|---|
| 178 | + if (cpu == orig_cpu) |
|---|
| 179 | + break; |
|---|
| 180 | + } |
|---|
| 181 | + |
|---|
| 182 | + /* cannot pop from per cpu lists, try extralist */ |
|---|
| 183 | + if (!raw_spin_trylock(&s->extralist.lock)) |
|---|
| 184 | + return NULL; |
|---|
| 185 | + node = s->extralist.first; |
|---|
| 186 | + if (node) |
|---|
| 187 | + s->extralist.first = node->next; |
|---|
| 188 | + raw_spin_unlock(&s->extralist.lock); |
|---|
| 189 | + return node; |
|---|
| 190 | +} |
|---|
| 191 | + |
|---|
| 192 | +struct pcpu_freelist_node *__pcpu_freelist_pop(struct pcpu_freelist *s) |
|---|
| 193 | +{ |
|---|
| 194 | + if (in_nmi()) |
|---|
| 195 | + return ___pcpu_freelist_pop_nmi(s); |
|---|
| 196 | + return ___pcpu_freelist_pop(s); |
|---|
| 110 | 197 | } |
|---|
| 111 | 198 | |
|---|
| 112 | 199 | struct pcpu_freelist_node *pcpu_freelist_pop(struct pcpu_freelist *s) |
|---|