xref: /kernel/linux/linux-6.6/fs/erofs/pcpubuf.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) Gao Xiang <xiang@kernel.org>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * For low-latency decompression algorithms (e.g. lz4), reserve consecutive
662306a36Sopenharmony_ci * per-CPU virtual memory (in pages) in advance to store such inplace I/O
762306a36Sopenharmony_ci * data if inplace decompression is failed (due to unmet inplace margin for
862306a36Sopenharmony_ci * example).
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci#include "internal.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct erofs_pcpubuf {
1362306a36Sopenharmony_ci	raw_spinlock_t lock;
1462306a36Sopenharmony_ci	void *ptr;
1562306a36Sopenharmony_ci	struct page **pages;
1662306a36Sopenharmony_ci	unsigned int nrpages;
1762306a36Sopenharmony_ci};
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct erofs_pcpubuf, erofs_pcb);
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_civoid *erofs_get_pcpubuf(unsigned int requiredpages)
2262306a36Sopenharmony_ci	__acquires(pcb->lock)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct erofs_pcpubuf *pcb = &get_cpu_var(erofs_pcb);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	raw_spin_lock(&pcb->lock);
2762306a36Sopenharmony_ci	/* check if the per-CPU buffer is too small */
2862306a36Sopenharmony_ci	if (requiredpages > pcb->nrpages) {
2962306a36Sopenharmony_ci		raw_spin_unlock(&pcb->lock);
3062306a36Sopenharmony_ci		put_cpu_var(erofs_pcb);
3162306a36Sopenharmony_ci		/* (for sparse checker) pretend pcb->lock is still taken */
3262306a36Sopenharmony_ci		__acquire(pcb->lock);
3362306a36Sopenharmony_ci		return NULL;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci	return pcb->ptr;
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_civoid erofs_put_pcpubuf(void *ptr) __releases(pcb->lock)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, smp_processor_id());
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	DBG_BUGON(pcb->ptr != ptr);
4362306a36Sopenharmony_ci	raw_spin_unlock(&pcb->lock);
4462306a36Sopenharmony_ci	put_cpu_var(erofs_pcb);
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/* the next step: support per-CPU page buffers hotplug */
4862306a36Sopenharmony_ciint erofs_pcpubuf_growsize(unsigned int nrpages)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	static DEFINE_MUTEX(pcb_resize_mutex);
5162306a36Sopenharmony_ci	static unsigned int pcb_nrpages;
5262306a36Sopenharmony_ci	struct page *pagepool = NULL;
5362306a36Sopenharmony_ci	int delta, cpu, ret, i;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	mutex_lock(&pcb_resize_mutex);
5662306a36Sopenharmony_ci	delta = nrpages - pcb_nrpages;
5762306a36Sopenharmony_ci	ret = 0;
5862306a36Sopenharmony_ci	/* avoid shrinking pcpubuf, since no idea how many fses rely on */
5962306a36Sopenharmony_ci	if (delta <= 0)
6062306a36Sopenharmony_ci		goto out;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
6362306a36Sopenharmony_ci		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
6462306a36Sopenharmony_ci		struct page **pages, **oldpages;
6562306a36Sopenharmony_ci		void *ptr, *old_ptr;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci		pages = kmalloc_array(nrpages, sizeof(*pages), GFP_KERNEL);
6862306a36Sopenharmony_ci		if (!pages) {
6962306a36Sopenharmony_ci			ret = -ENOMEM;
7062306a36Sopenharmony_ci			break;
7162306a36Sopenharmony_ci		}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci		for (i = 0; i < nrpages; ++i) {
7462306a36Sopenharmony_ci			pages[i] = erofs_allocpage(&pagepool, GFP_KERNEL);
7562306a36Sopenharmony_ci			if (!pages[i]) {
7662306a36Sopenharmony_ci				ret = -ENOMEM;
7762306a36Sopenharmony_ci				oldpages = pages;
7862306a36Sopenharmony_ci				goto free_pagearray;
7962306a36Sopenharmony_ci			}
8062306a36Sopenharmony_ci		}
8162306a36Sopenharmony_ci		ptr = vmap(pages, nrpages, VM_MAP, PAGE_KERNEL);
8262306a36Sopenharmony_ci		if (!ptr) {
8362306a36Sopenharmony_ci			ret = -ENOMEM;
8462306a36Sopenharmony_ci			oldpages = pages;
8562306a36Sopenharmony_ci			goto free_pagearray;
8662306a36Sopenharmony_ci		}
8762306a36Sopenharmony_ci		raw_spin_lock(&pcb->lock);
8862306a36Sopenharmony_ci		old_ptr = pcb->ptr;
8962306a36Sopenharmony_ci		pcb->ptr = ptr;
9062306a36Sopenharmony_ci		oldpages = pcb->pages;
9162306a36Sopenharmony_ci		pcb->pages = pages;
9262306a36Sopenharmony_ci		i = pcb->nrpages;
9362306a36Sopenharmony_ci		pcb->nrpages = nrpages;
9462306a36Sopenharmony_ci		raw_spin_unlock(&pcb->lock);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		if (!oldpages) {
9762306a36Sopenharmony_ci			DBG_BUGON(old_ptr);
9862306a36Sopenharmony_ci			continue;
9962306a36Sopenharmony_ci		}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		if (old_ptr)
10262306a36Sopenharmony_ci			vunmap(old_ptr);
10362306a36Sopenharmony_cifree_pagearray:
10462306a36Sopenharmony_ci		while (i)
10562306a36Sopenharmony_ci			erofs_pagepool_add(&pagepool, oldpages[--i]);
10662306a36Sopenharmony_ci		kfree(oldpages);
10762306a36Sopenharmony_ci		if (ret)
10862306a36Sopenharmony_ci			break;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci	pcb_nrpages = nrpages;
11162306a36Sopenharmony_ci	erofs_release_pages(&pagepool);
11262306a36Sopenharmony_ciout:
11362306a36Sopenharmony_ci	mutex_unlock(&pcb_resize_mutex);
11462306a36Sopenharmony_ci	return ret;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_civoid __init erofs_pcpubuf_init(void)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	int cpu;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
12262306a36Sopenharmony_ci		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci		raw_spin_lock_init(&pcb->lock);
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_civoid erofs_pcpubuf_exit(void)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	int cpu, i;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
13362306a36Sopenharmony_ci		struct erofs_pcpubuf *pcb = &per_cpu(erofs_pcb, cpu);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci		if (pcb->ptr) {
13662306a36Sopenharmony_ci			vunmap(pcb->ptr);
13762306a36Sopenharmony_ci			pcb->ptr = NULL;
13862306a36Sopenharmony_ci		}
13962306a36Sopenharmony_ci		if (!pcb->pages)
14062306a36Sopenharmony_ci			continue;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		for (i = 0; i < pcb->nrpages; ++i)
14362306a36Sopenharmony_ci			if (pcb->pages[i])
14462306a36Sopenharmony_ci				put_page(pcb->pages[i]);
14562306a36Sopenharmony_ci		kfree(pcb->pages);
14662306a36Sopenharmony_ci		pcb->pages = NULL;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci}
149