xref: /openbsd/sys/kern/subr_percpu.c (revision 3bef86f7)
1 /*	$OpenBSD: subr_percpu.c,v 1.11 2023/09/16 09:33:27 mpi Exp $ */
2 
3 /*
4  * Copyright (c) 2016 David Gwynne <dlg@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/pool.h>
22 #include <sys/malloc.h>
23 
24 #include <sys/percpu.h>
25 
26 #ifdef MULTIPROCESSOR
27 struct pool cpumem_pl;
28 
29 void
30 percpu_init(void)
31 {
32 	pool_init(&cpumem_pl, sizeof(struct cpumem) * ncpusfound, 0,
33 	    IPL_NONE, PR_WAITOK, "percpumem", &pool_allocator_single);
34 }
35 
36 struct cpumem *
37 cpumem_get(struct pool *pp)
38 {
39 	struct cpumem *cm;
40 	unsigned int cpu;
41 
42 	cm = pool_get(&cpumem_pl, PR_WAITOK);
43 
44 	for (cpu = 0; cpu < ncpusfound; cpu++)
45 		cm[cpu].mem = pool_get(pp, PR_WAITOK | PR_ZERO);
46 
47 	return (cm);
48 }
49 
50 void
51 cpumem_put(struct pool *pp, struct cpumem *cm)
52 {
53 	unsigned int cpu;
54 
55 	for (cpu = 0; cpu < ncpusfound; cpu++)
56 		pool_put(pp, cm[cpu].mem);
57 
58 	pool_put(&cpumem_pl, cm);
59 }
60 
61 struct cpumem *
62 cpumem_malloc(size_t sz, int type)
63 {
64 	struct cpumem *cm;
65 	unsigned int cpu;
66 
67 	sz = roundup(sz, CACHELINESIZE);
68 
69 	cm = pool_get(&cpumem_pl, PR_WAITOK);
70 
71 	for (cpu = 0; cpu < ncpusfound; cpu++)
72 		cm[cpu].mem = malloc(sz, type, M_WAITOK | M_ZERO);
73 
74 	return (cm);
75 }
76 
77 struct cpumem *
78 cpumem_malloc_ncpus(struct cpumem *bootcm, size_t sz, int type)
79 {
80 	struct cpumem *cm;
81 	unsigned int cpu;
82 
83 	sz = roundup(sz, CACHELINESIZE);
84 
85 	cm = pool_get(&cpumem_pl, PR_WAITOK);
86 
87 	cm[0].mem = bootcm[0].mem;
88 	for (cpu = 1; cpu < ncpusfound; cpu++)
89 		cm[cpu].mem = malloc(sz, type, M_WAITOK | M_ZERO);
90 
91 	return (cm);
92 }
93 
94 void
95 cpumem_free(struct cpumem *cm, int type, size_t sz)
96 {
97 	unsigned int cpu;
98 
99 	sz = roundup(sz, CACHELINESIZE);
100 
101 	for (cpu = 0; cpu < ncpusfound; cpu++)
102 		free(cm[cpu].mem, type, sz);
103 
104 	pool_put(&cpumem_pl, cm);
105 }
106 
107 void *
108 cpumem_first(struct cpumem_iter *i, struct cpumem *cm)
109 {
110 	i->cpu = 0;
111 
112 	return (cm[0].mem);
113 }
114 
115 void *
116 cpumem_next(struct cpumem_iter *i, struct cpumem *cm)
117 {
118 	unsigned int cpu = ++i->cpu;
119 
120 	if (cpu >= ncpusfound)
121 		return (NULL);
122 
123 	return (cm[cpu].mem);
124 }
125 
126 struct cpumem *
127 counters_alloc(unsigned int n)
128 {
129 	struct cpumem *cm;
130 	struct cpumem_iter cmi;
131 	uint64_t *counters;
132 	unsigned int i;
133 
134 	KASSERT(n > 0);
135 
136 	n++; /* add space for a generation number */
137 	cm = cpumem_malloc(n * sizeof(uint64_t), M_COUNTERS);
138 
139 	CPUMEM_FOREACH(counters, &cmi, cm) {
140 		for (i = 0; i < n; i++)
141 			counters[i] = 0;
142 	}
143 
144 	return (cm);
145 }
146 
147 struct cpumem *
148 counters_alloc_ncpus(struct cpumem *cm, unsigned int n)
149 {
150 	n++; /* the generation number */
151 	return (cpumem_malloc_ncpus(cm, n * sizeof(uint64_t), M_COUNTERS));
152 }
153 
154 void
155 counters_free(struct cpumem *cm, unsigned int n)
156 {
157 	n++; /* generation number */
158 	cpumem_free(cm, M_COUNTERS, n * sizeof(uint64_t));
159 }
160 
161 void
162 counters_read(struct cpumem *cm, uint64_t *output, unsigned int n,
163     uint64_t *scratch)
164 {
165 	struct cpumem_iter cmi;
166 	uint64_t *gen, *counters, *temp = scratch;
167 	uint64_t enter, leave;
168 	unsigned int i;
169 
170 	for (i = 0; i < n; i++)
171 		output[i] = 0;
172 
173 	if (scratch == NULL)
174 		temp = mallocarray(n, sizeof(uint64_t), M_TEMP, M_WAITOK);
175 
176 	gen = cpumem_first(&cmi, cm);
177 	do {
178 		counters = gen + 1;
179 
180 		enter = *gen;
181 		for (;;) {
182 			/* the generation number is odd during an update */
183 			while (enter & 1) {
184 				yield();
185 				enter = *gen;
186 			}
187 
188 			membar_consumer();
189 			for (i = 0; i < n; i++)
190 				temp[i] = counters[i];
191 
192 			membar_consumer();
193 			leave = *gen;
194 
195 			if (enter == leave)
196 				break;
197 
198 			enter = leave;
199 		}
200 
201 		for (i = 0; i < n; i++)
202 			output[i] += temp[i];
203 
204 		gen = cpumem_next(&cmi, cm);
205 	} while (gen != NULL);
206 
207 	if (scratch == NULL)
208 		free(temp, M_TEMP, n * sizeof(uint64_t));
209 }
210 
211 void
212 counters_zero(struct cpumem *cm, unsigned int n)
213 {
214 	struct cpumem_iter cmi;
215 	uint64_t *counters;
216 	unsigned int i;
217 
218 	counters = cpumem_first(&cmi, cm);
219 	membar_producer();
220 	do {
221 		for (i = 0; i < n; i++)
222 			counters[i] = 0;
223 		/* zero the generation numbers too */
224 		membar_producer();
225 		counters[i] = 0;
226 
227 		counters = cpumem_next(&cmi, cm);
228 	} while (counters != NULL);
229 }
230 
231 #else /* MULTIPROCESSOR */
232 
233 /*
234  * Uniprocessor implementation of per-CPU data structures.
235  *
236  * UP percpu memory is a single memory allocation cast to/from the
237  * cpumem struct. It is not scaled up to the size of cacheline because
238  * there's no other cache to contend with.
239  */
240 
241 void
242 percpu_init(void)
243 {
244 	/* nop */
245 }
246 
247 struct cpumem *
248 cpumem_get(struct pool *pp)
249 {
250 	return (pool_get(pp, PR_WAITOK | PR_ZERO));
251 }
252 
253 void
254 cpumem_put(struct pool *pp, struct cpumem *cm)
255 {
256 	pool_put(pp, cm);
257 }
258 
259 struct cpumem *
260 cpumem_malloc(size_t sz, int type)
261 {
262 	return (malloc(sz, type, M_WAITOK | M_ZERO));
263 }
264 
265 struct cpumem *
266 cpumem_malloc_ncpus(struct cpumem *cm, size_t sz, int type)
267 {
268 	return (cm);
269 }
270 
271 void
272 cpumem_free(struct cpumem *cm, int type, size_t sz)
273 {
274 	free(cm, type, sz);
275 }
276 
277 void *
278 cpumem_first(struct cpumem_iter *i, struct cpumem *cm)
279 {
280 	return (cm);
281 }
282 
283 void *
284 cpumem_next(struct cpumem_iter *i, struct cpumem *cm)
285 {
286 	return (NULL);
287 }
288 
289 struct cpumem *
290 counters_alloc(unsigned int n)
291 {
292 	KASSERT(n > 0);
293 
294 	return (cpumem_malloc(n * sizeof(uint64_t), M_COUNTERS));
295 }
296 
297 struct cpumem *
298 counters_alloc_ncpus(struct cpumem *cm, unsigned int n)
299 {
300 	/* this is unnecessary, but symmetrical */
301 	return (cpumem_malloc_ncpus(cm, n * sizeof(uint64_t), M_COUNTERS));
302 }
303 
304 void
305 counters_free(struct cpumem *cm, unsigned int n)
306 {
307 	cpumem_free(cm, M_COUNTERS, n * sizeof(uint64_t));
308 }
309 
310 void
311 counters_read(struct cpumem *cm, uint64_t *output, unsigned int n,
312     uint64_t *scratch)
313 {
314 	uint64_t *counters;
315 	unsigned int i;
316 	int s;
317 
318 	counters = (uint64_t *)cm;
319 
320 	s = splhigh();
321 	for (i = 0; i < n; i++)
322 		output[i] = counters[i];
323 	splx(s);
324 }
325 
326 void
327 counters_zero(struct cpumem *cm, unsigned int n)
328 {
329 	uint64_t *counters;
330 	unsigned int i;
331 	int s;
332 
333 	counters = (uint64_t *)cm;
334 
335 	s = splhigh();
336 	for (i = 0; i < n; i++)
337 		counters[i] = 0;
338 	splx(s);
339 }
340 
341 #endif /* MULTIPROCESSOR */
342