mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Convert the percpu_freelist.c code to use rqspinlock, and remove the extralist fallback and trylock-based acquisitions to avoid deadlocks. Key thing to note is the retained while (true) loop to search through other CPUs when failing to push a node due to locking errors. This retains the behavior of the old code, where it would keep trying until it would be able to successfully push the node back into the freelist of a CPU. Technically, we should start iteration for this loop from raw_smp_processor_id() + 1, but to avoid hitting the edge of nr_cpus, we skip execution in the loop body instead. Closes: https://lore.kernel.org/bpf/CAPPBnEa1_pZ6W24+WwtcNFvTUHTHO7KUmzEbOcMqxp+m2o15qQ@mail.gmail.com Closes: https://lore.kernel.org/bpf/CAPPBnEYm+9zduStsZaDnq93q1jPLqO-PiKX9jy0MuL8LCXmCrQ@mail.gmail.com Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> Link: https://lore.kernel.org/r/20250316040541.108729-21-memxor@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
137 lines
3.1 KiB
C
137 lines
3.1 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2016 Facebook
|
|
*/
|
|
#include "percpu_freelist.h"
|
|
|
|
int pcpu_freelist_init(struct pcpu_freelist *s)
|
|
{
|
|
int cpu;
|
|
|
|
s->freelist = alloc_percpu(struct pcpu_freelist_head);
|
|
if (!s->freelist)
|
|
return -ENOMEM;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
struct pcpu_freelist_head *head = per_cpu_ptr(s->freelist, cpu);
|
|
|
|
raw_res_spin_lock_init(&head->lock);
|
|
head->first = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void pcpu_freelist_destroy(struct pcpu_freelist *s)
|
|
{
|
|
free_percpu(s->freelist);
|
|
}
|
|
|
|
static inline void pcpu_freelist_push_node(struct pcpu_freelist_head *head,
|
|
struct pcpu_freelist_node *node)
|
|
{
|
|
node->next = head->first;
|
|
WRITE_ONCE(head->first, node);
|
|
}
|
|
|
|
static inline bool ___pcpu_freelist_push(struct pcpu_freelist_head *head,
|
|
struct pcpu_freelist_node *node)
|
|
{
|
|
if (raw_res_spin_lock(&head->lock))
|
|
return false;
|
|
pcpu_freelist_push_node(head, node);
|
|
raw_res_spin_unlock(&head->lock);
|
|
return true;
|
|
}
|
|
|
|
void __pcpu_freelist_push(struct pcpu_freelist *s,
|
|
struct pcpu_freelist_node *node)
|
|
{
|
|
struct pcpu_freelist_head *head;
|
|
int cpu;
|
|
|
|
if (___pcpu_freelist_push(this_cpu_ptr(s->freelist), node))
|
|
return;
|
|
|
|
while (true) {
|
|
for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
|
|
if (cpu == raw_smp_processor_id())
|
|
continue;
|
|
head = per_cpu_ptr(s->freelist, cpu);
|
|
if (raw_res_spin_lock(&head->lock))
|
|
continue;
|
|
pcpu_freelist_push_node(head, node);
|
|
raw_res_spin_unlock(&head->lock);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pcpu_freelist_push(struct pcpu_freelist *s,
|
|
struct pcpu_freelist_node *node)
|
|
{
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
__pcpu_freelist_push(s, node);
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
void pcpu_freelist_populate(struct pcpu_freelist *s, void *buf, u32 elem_size,
|
|
u32 nr_elems)
|
|
{
|
|
struct pcpu_freelist_head *head;
|
|
unsigned int cpu, cpu_idx, i, j, n, m;
|
|
|
|
n = nr_elems / num_possible_cpus();
|
|
m = nr_elems % num_possible_cpus();
|
|
|
|
cpu_idx = 0;
|
|
for_each_possible_cpu(cpu) {
|
|
head = per_cpu_ptr(s->freelist, cpu);
|
|
j = n + (cpu_idx < m ? 1 : 0);
|
|
for (i = 0; i < j; i++) {
|
|
/* No locking required as this is not visible yet. */
|
|
pcpu_freelist_push_node(head, buf);
|
|
buf += elem_size;
|
|
}
|
|
cpu_idx++;
|
|
}
|
|
}
|
|
|
|
static struct pcpu_freelist_node *___pcpu_freelist_pop(struct pcpu_freelist *s)
|
|
{
|
|
struct pcpu_freelist_node *node = NULL;
|
|
struct pcpu_freelist_head *head;
|
|
int cpu;
|
|
|
|
for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
|
|
head = per_cpu_ptr(s->freelist, cpu);
|
|
if (!READ_ONCE(head->first))
|
|
continue;
|
|
if (raw_res_spin_lock(&head->lock))
|
|
continue;
|
|
node = head->first;
|
|
if (node) {
|
|
WRITE_ONCE(head->first, node->next);
|
|
raw_res_spin_unlock(&head->lock);
|
|
return node;
|
|
}
|
|
raw_res_spin_unlock(&head->lock);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
struct pcpu_freelist_node *__pcpu_freelist_pop(struct pcpu_freelist *s)
|
|
{
|
|
return ___pcpu_freelist_pop(s);
|
|
}
|
|
|
|
struct pcpu_freelist_node *pcpu_freelist_pop(struct pcpu_freelist *s)
|
|
{
|
|
struct pcpu_freelist_node *ret;
|
|
unsigned long flags;
|
|
|
|
local_irq_save(flags);
|
|
ret = __pcpu_freelist_pop(s);
|
|
local_irq_restore(flags);
|
|
return ret;
|
|
}
|