18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * mm/percpu-km.c - kernel memory based chunk allocation
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2010		SUSE Linux Products GmbH
68c2ecf20Sopenharmony_ci * Copyright (C) 2010		Tejun Heo <tj@kernel.org>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * Chunks are allocated as a contiguous kernel memory using gfp
98c2ecf20Sopenharmony_ci * allocation.  This is to be used on nommu architectures.
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * To use percpu-km,
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * - define CONFIG_NEED_PER_CPU_KM from the arch Kconfig.
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * - CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK must not be defined.  It's
168c2ecf20Sopenharmony_ci *   not compatible with PER_CPU_KM.  EMBED_FIRST_CHUNK should work
178c2ecf20Sopenharmony_ci *   fine.
188c2ecf20Sopenharmony_ci *
198c2ecf20Sopenharmony_ci * - NUMA is not supported.  When setting up the first chunk,
208c2ecf20Sopenharmony_ci *   @cpu_distance_fn should be NULL or report all CPUs to be nearer
218c2ecf20Sopenharmony_ci *   than or at LOCAL_DISTANCE.
228c2ecf20Sopenharmony_ci *
238c2ecf20Sopenharmony_ci * - It's best if the chunk size is power of two multiple of
248c2ecf20Sopenharmony_ci *   PAGE_SIZE.  Because each chunk is allocated as a contiguous
258c2ecf20Sopenharmony_ci *   kernel memory block using alloc_pages(), memory will be wasted if
268c2ecf20Sopenharmony_ci *   chunk size is not aligned.  percpu-km code will whine about it.
278c2ecf20Sopenharmony_ci */
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#if defined(CONFIG_SMP) && defined(CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK)
308c2ecf20Sopenharmony_ci#error "contiguous percpu allocation is incompatible with paged first chunk"
318c2ecf20Sopenharmony_ci#endif
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#include <linux/log2.h>
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic int pcpu_populate_chunk(struct pcpu_chunk *chunk,
368c2ecf20Sopenharmony_ci			       int page_start, int page_end, gfp_t gfp)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	return 0;
398c2ecf20Sopenharmony_ci}
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic void pcpu_depopulate_chunk(struct pcpu_chunk *chunk,
428c2ecf20Sopenharmony_ci				  int page_start, int page_end)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	/* nada */
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic struct pcpu_chunk *pcpu_create_chunk(enum pcpu_chunk_type type,
488c2ecf20Sopenharmony_ci					    gfp_t gfp)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	const int nr_pages = pcpu_group_sizes[0] >> PAGE_SHIFT;
518c2ecf20Sopenharmony_ci	struct pcpu_chunk *chunk;
528c2ecf20Sopenharmony_ci	struct page *pages;
538c2ecf20Sopenharmony_ci	unsigned long flags;
548c2ecf20Sopenharmony_ci	int i;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	chunk = pcpu_alloc_chunk(type, gfp);
578c2ecf20Sopenharmony_ci	if (!chunk)
588c2ecf20Sopenharmony_ci		return NULL;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	pages = alloc_pages(gfp, order_base_2(nr_pages));
618c2ecf20Sopenharmony_ci	if (!pages) {
628c2ecf20Sopenharmony_ci		pcpu_free_chunk(chunk);
638c2ecf20Sopenharmony_ci		return NULL;
648c2ecf20Sopenharmony_ci	}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	for (i = 0; i < nr_pages; i++)
678c2ecf20Sopenharmony_ci		pcpu_set_page_chunk(nth_page(pages, i), chunk);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	chunk->data = pages;
708c2ecf20Sopenharmony_ci	chunk->base_addr = page_address(pages);
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	spin_lock_irqsave(&pcpu_lock, flags);
738c2ecf20Sopenharmony_ci	pcpu_chunk_populated(chunk, 0, nr_pages);
748c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&pcpu_lock, flags);
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	pcpu_stats_chunk_alloc();
778c2ecf20Sopenharmony_ci	trace_percpu_create_chunk(chunk->base_addr);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	return chunk;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void pcpu_destroy_chunk(struct pcpu_chunk *chunk)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	const int nr_pages = pcpu_group_sizes[0] >> PAGE_SHIFT;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (!chunk)
878c2ecf20Sopenharmony_ci		return;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	pcpu_stats_chunk_dealloc();
908c2ecf20Sopenharmony_ci	trace_percpu_destroy_chunk(chunk->base_addr);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	if (chunk->data)
938c2ecf20Sopenharmony_ci		__free_pages(chunk->data, order_base_2(nr_pages));
948c2ecf20Sopenharmony_ci	pcpu_free_chunk(chunk);
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic struct page *pcpu_addr_to_page(void *addr)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	return virt_to_page(addr);
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_cistatic int __init pcpu_verify_alloc_info(const struct pcpu_alloc_info *ai)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	size_t nr_pages, alloc_pages;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	/* all units must be in a single group */
1078c2ecf20Sopenharmony_ci	if (ai->nr_groups != 1) {
1088c2ecf20Sopenharmony_ci		pr_crit("can't handle more than one group\n");
1098c2ecf20Sopenharmony_ci		return -EINVAL;
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	nr_pages = (ai->groups[0].nr_units * ai->unit_size) >> PAGE_SHIFT;
1138c2ecf20Sopenharmony_ci	alloc_pages = roundup_pow_of_two(nr_pages);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (alloc_pages > nr_pages)
1168c2ecf20Sopenharmony_ci		pr_warn("wasting %zu pages per chunk\n",
1178c2ecf20Sopenharmony_ci			alloc_pages - nr_pages);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	return 0;
1208c2ecf20Sopenharmony_ci}
121