1 /*
2  * Copyright (c) 2019 Intel Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 #include <sys/sys_heap.h>
7 #include <kernel.h>
8 #include "heap.h"
9 
10 /* White-box sys_heap validation code.  Uses internal data structures.
11  * Not expected to be useful in production apps.  This checks every
12  * header field of every chunk and returns true if the totality of the
13  * data structure is a valid heap.  It doesn't necessarily tell you
14  * that it is the CORRECT heap given the history of alloc/free calls
15  * that it can't inspect.  In a pathological case, you can imagine
16  * something scribbling a copy of a previously-valid heap on top of a
17  * running one and corrupting it. YMMV.
18  */
19 
20 #define VALIDATE(cond) do { if (!(cond)) { return false; } } while (0)
21 
in_bounds(struct z_heap * h,chunkid_t c)22 static bool in_bounds(struct z_heap *h, chunkid_t c)
23 {
24 	VALIDATE(c >= right_chunk(h, 0));
25 	VALIDATE(c < h->end_chunk);
26 	VALIDATE(chunk_size(h, c) < h->end_chunk);
27 	return true;
28 }
29 
valid_chunk(struct z_heap * h,chunkid_t c)30 static bool valid_chunk(struct z_heap *h, chunkid_t c)
31 {
32 	VALIDATE(chunk_size(h, c) > 0);
33 	VALIDATE(c + chunk_size(h, c) <= h->end_chunk);
34 	VALIDATE(in_bounds(h, c));
35 	VALIDATE(right_chunk(h, left_chunk(h, c)) == c);
36 	VALIDATE(left_chunk(h, right_chunk(h, c)) == c);
37 	if (chunk_used(h, c)) {
38 		VALIDATE(!solo_free_header(h, c));
39 	} else {
40 		VALIDATE(chunk_used(h, left_chunk(h, c)));
41 		VALIDATE(chunk_used(h, right_chunk(h, c)));
42 		if (!solo_free_header(h, c)) {
43 			VALIDATE(in_bounds(h, prev_free_chunk(h, c)));
44 			VALIDATE(in_bounds(h, next_free_chunk(h, c)));
45 		}
46 	}
47 	return true;
48 }
49 
50 /* Validate multiple state dimensions for the bucket "next" pointer
51  * and see that they match.  Probably should unify the design a
52  * bit...
53  */
check_nexts(struct z_heap * h,int bidx)54 static inline void check_nexts(struct z_heap *h, int bidx)
55 {
56 	struct z_heap_bucket *b = &h->buckets[bidx];
57 
58 	bool emptybit = (h->avail_buckets & (1 << bidx)) == 0;
59 	bool emptylist = b->next == 0;
60 	bool empties_match = emptybit == emptylist;
61 
62 	(void)empties_match;
63 	CHECK(empties_match);
64 
65 	if (b->next != 0) {
66 		CHECK(valid_chunk(h, b->next));
67 	}
68 }
69 
sys_heap_validate(struct sys_heap * heap)70 bool sys_heap_validate(struct sys_heap *heap)
71 {
72 	struct z_heap *h = heap->heap;
73 	chunkid_t c;
74 
75 	/*
76 	 * Walk through the chunks linearly, verifying sizes and end pointer.
77 	 */
78 	for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
79 		if (!valid_chunk(h, c)) {
80 			return false;
81 		}
82 	}
83 	if (c != h->end_chunk) {
84 		return false;  /* Should have exactly consumed the buffer */
85 	}
86 
87 	/* Check the free lists: entry count should match, empty bit
88 	 * should be correct, and all chunk entries should point into
89 	 * valid unused chunks.  Mark those chunks USED, temporarily.
90 	 */
91 	for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) {
92 		chunkid_t c0 = h->buckets[b].next;
93 		uint32_t n = 0;
94 
95 		check_nexts(h, b);
96 
97 		for (c = c0; c != 0 && (n == 0 || c != c0);
98 		     n++, c = next_free_chunk(h, c)) {
99 			if (!valid_chunk(h, c)) {
100 				return false;
101 			}
102 			set_chunk_used(h, c, true);
103 		}
104 
105 		bool empty = (h->avail_buckets & (1 << b)) == 0;
106 		bool zero = n == 0;
107 
108 		if (empty != zero) {
109 			return false;
110 		}
111 
112 		if (empty && h->buckets[b].next != 0) {
113 			return false;
114 		}
115 	}
116 
117 	/*
118 	 * Walk through the chunks linearly again, verifying that all chunks
119 	 * but solo headers are now USED (i.e. all free blocks were found
120 	 * during enumeration).  Mark all such blocks UNUSED and solo headers
121 	 * USED.
122 	 */
123 	chunkid_t prev_chunk = 0;
124 	for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
125 		if (!chunk_used(h, c) && !solo_free_header(h, c)) {
126 			return false;
127 		}
128 		if (left_chunk(h, c) != prev_chunk) {
129 			return false;
130 		}
131 		prev_chunk = c;
132 
133 		set_chunk_used(h, c, solo_free_header(h, c));
134 	}
135 	if (c != h->end_chunk) {
136 		return false;  /* Should have exactly consumed the buffer */
137 	}
138 
139 	/* Go through the free lists again checking that the linear
140 	 * pass caught all the blocks and that they now show UNUSED.
141 	 * Mark them USED.
142 	 */
143 	for (int b = 0; b <= bucket_idx(h, h->end_chunk); b++) {
144 		chunkid_t c0 = h->buckets[b].next;
145 		int n = 0;
146 
147 		if (c0 == 0) {
148 			continue;
149 		}
150 
151 		for (c = c0; n == 0 || c != c0; n++, c = next_free_chunk(h, c)) {
152 			if (chunk_used(h, c)) {
153 				return false;
154 			}
155 			set_chunk_used(h, c, true);
156 		}
157 	}
158 
159 	/* Now we are valid, but have managed to invert all the in-use
160 	 * fields.  One more linear pass to fix them up
161 	 */
162 	for (c = right_chunk(h, 0); c < h->end_chunk; c = right_chunk(h, c)) {
163 		set_chunk_used(h, c, !chunk_used(h, c));
164 	}
165 	return true;
166 }
167 
168 struct z_heap_stress_rec {
169 	void *(*alloc_fn)(void *arg, size_t bytes);
170 	void (*free_fn)(void *arg, void *p);
171 	void *arg;
172 	size_t total_bytes;
173 	struct z_heap_stress_block *blocks;
174 	size_t nblocks;
175 	size_t blocks_alloced;
176 	size_t bytes_alloced;
177 	uint32_t target_percent;
178 };
179 
180 struct z_heap_stress_block {
181 	void *ptr;
182 	size_t sz;
183 };
184 
185 /* Very simple LCRNG (from https://nuclear.llnl.gov/CNP/rng/rngman/node4.html)
186  *
187  * Here to guarantee cross-platform test repeatability.
188  */
rand32(void)189 static uint32_t rand32(void)
190 {
191 	static uint64_t state = 123456789; /* seed */
192 
193 	state = state * 2862933555777941757UL + 3037000493UL;
194 
195 	return (uint32_t)(state >> 32);
196 }
197 
rand_alloc_choice(struct z_heap_stress_rec * sr)198 static bool rand_alloc_choice(struct z_heap_stress_rec *sr)
199 {
200 	/* Edge cases: no blocks allocated, and no space for a new one */
201 	if (sr->blocks_alloced == 0) {
202 		return true;
203 	} else if (sr->blocks_alloced >= sr->nblocks) {
204 		return false;
205 	} else {
206 
207 		/* The way this works is to scale the chance of choosing to
208 		 * allocate vs. free such that it's even odds when the heap is
209 		 * at the target percent, with linear tapering on the low
210 		 * slope (i.e. we choose to always allocate with an empty
211 		 * heap, allocate 50% of the time when the heap is exactly at
212 		 * the target, and always free when above the target).  In
213 		 * practice, the operations aren't quite symmetric (you can
214 		 * always free, but your allocation might fail), and the units
215 		 * aren't matched (we're doing math based on bytes allocated
216 		 * and ignoring the overhead) but this is close enough.  And
217 		 * yes, the math here is coarse (in units of percent), but
218 		 * that's good enough and fits well inside 32 bit quantities.
219 		 * (Note precision issue when heap size is above 40MB
220 		 * though!).
221 		 */
222 		__ASSERT(sr->total_bytes < 0xffffffffU / 100, "too big for u32!");
223 		uint32_t full_pct = (100 * sr->bytes_alloced) / sr->total_bytes;
224 		uint32_t target = sr->target_percent ? sr->target_percent : 1;
225 		uint32_t free_chance = 0xffffffffU;
226 
227 		if (full_pct < sr->target_percent) {
228 			free_chance = full_pct * (0x80000000U / target);
229 		}
230 
231 		return rand32() > free_chance;
232 	}
233 }
234 
235 /* Chooses a size of block to allocate, logarithmically favoring
236  * smaller blocks (i.e. blocks twice as large are half as frequent
237  */
rand_alloc_size(struct z_heap_stress_rec * sr)238 static size_t rand_alloc_size(struct z_heap_stress_rec *sr)
239 {
240 	ARG_UNUSED(sr);
241 
242 	/* Min scale of 4 means that the half of the requests in the
243 	 * smallest size have an average size of 8
244 	 */
245 	int scale = 4 + __builtin_clz(rand32());
246 
247 	return rand32() & ((1 << scale) - 1);
248 }
249 
250 /* Returns the index of a randomly chosen block to free */
rand_free_choice(struct z_heap_stress_rec * sr)251 static size_t rand_free_choice(struct z_heap_stress_rec *sr)
252 {
253 	return rand32() % sr->blocks_alloced;
254 }
255 
256 /* General purpose heap stress test.  Takes function pointers to allow
257  * for testing multiple heap APIs with the same rig.  The alloc and
258  * free functions are passed back the argument as a context pointer.
259  * The "log" function is for readable user output.  The total_bytes
260  * argument should reflect the size of the heap being tested.  The
261  * scratch array is used to store temporary state and should be sized
262  * about half as large as the heap itself. Returns true on success.
263  */
sys_heap_stress(void * (* alloc_fn)(void * arg,size_t bytes),void (* free_fn)(void * arg,void * p),void * arg,size_t total_bytes,uint32_t op_count,void * scratch_mem,size_t scratch_bytes,int target_percent,struct z_heap_stress_result * result)264 void sys_heap_stress(void *(*alloc_fn)(void *arg, size_t bytes),
265 		     void (*free_fn)(void *arg, void *p),
266 		     void *arg, size_t total_bytes,
267 		     uint32_t op_count,
268 		     void *scratch_mem, size_t scratch_bytes,
269 		     int target_percent,
270 		     struct z_heap_stress_result *result)
271 {
272 	struct z_heap_stress_rec sr = {
273 	       .alloc_fn = alloc_fn,
274 	       .free_fn = free_fn,
275 	       .arg = arg,
276 	       .total_bytes = total_bytes,
277 	       .blocks = scratch_mem,
278 	       .nblocks = scratch_bytes / sizeof(struct z_heap_stress_block),
279 	       .target_percent = target_percent,
280 	};
281 
282 	*result = (struct z_heap_stress_result) {0};
283 
284 	for (uint32_t i = 0; i < op_count; i++) {
285 		if (rand_alloc_choice(&sr)) {
286 			size_t sz = rand_alloc_size(&sr);
287 			void *p = sr.alloc_fn(sr.arg, sz);
288 
289 			result->total_allocs++;
290 			if (p != NULL) {
291 				result->successful_allocs++;
292 				sr.blocks[sr.blocks_alloced].ptr = p;
293 				sr.blocks[sr.blocks_alloced].sz = sz;
294 				sr.blocks_alloced++;
295 				sr.bytes_alloced += sz;
296 			}
297 		} else {
298 			int b = rand_free_choice(&sr);
299 			void *p = sr.blocks[b].ptr;
300 			size_t sz = sr.blocks[b].sz;
301 
302 			result->total_frees++;
303 			sr.blocks[b] = sr.blocks[sr.blocks_alloced - 1];
304 			sr.blocks_alloced--;
305 			sr.bytes_alloced -= sz;
306 			sr.free_fn(sr.arg, p);
307 		}
308 		result->accumulated_in_use_bytes += sr.bytes_alloced;
309 	}
310 }
311 
312 /*
313  * Print heap info for debugging / analysis purpose
314  */
heap_print_info(struct z_heap * h,bool dump_chunks)315 void heap_print_info(struct z_heap *h, bool dump_chunks)
316 {
317 	int i, nb_buckets = bucket_idx(h, h->end_chunk) + 1;
318 	size_t free_bytes, allocated_bytes, total, overhead;
319 
320 	printk("Heap at %p contains %d units in %d buckets\n\n",
321 	       chunk_buf(h), h->end_chunk, nb_buckets);
322 
323 	printk("  bucket#    min units        total      largest      largest\n"
324 	       "             threshold       chunks      (units)      (bytes)\n"
325 	       "  -----------------------------------------------------------\n");
326 	for (i = 0; i < nb_buckets; i++) {
327 		chunkid_t first = h->buckets[i].next;
328 		chunksz_t largest = 0;
329 		int count = 0;
330 
331 		if (first) {
332 			chunkid_t curr = first;
333 			do {
334 				count++;
335 				largest = MAX(largest, chunk_size(h, curr));
336 				curr = next_free_chunk(h, curr);
337 			} while (curr != first);
338 		}
339 		if (count) {
340 			printk("%9d %12d %12d %12d %12zd\n",
341 			       i, (1 << i) - 1 + min_chunk_size(h), count,
342 			       largest, chunksz_to_bytes(h, largest));
343 		}
344 	}
345 
346 	if (dump_chunks) {
347 		printk("\nChunk dump:\n");
348 	}
349 	free_bytes = allocated_bytes = 0;
350 	for (chunkid_t c = 0; ; c = right_chunk(h, c)) {
351 		if (chunk_used(h, c)) {
352 			if ((c != 0) && (c != h->end_chunk)) {
353 				/* 1st and last are always allocated for internal purposes */
354 				allocated_bytes += chunksz_to_bytes(h, chunk_size(h, c));
355 			}
356 		} else {
357 			if (!solo_free_header(h, c)) {
358 				free_bytes += chunksz_to_bytes(h, chunk_size(h, c));
359 			}
360 		}
361 		if (dump_chunks) {
362 			printk("chunk %4d: [%c] size=%-4d left=%-4d right=%d\n",
363 			       c,
364 			       chunk_used(h, c) ? '*'
365 			       : solo_free_header(h, c) ? '.'
366 			       : '-',
367 			       chunk_size(h, c),
368 			       left_chunk(h, c),
369 			       right_chunk(h, c));
370 		}
371 		if (c == h->end_chunk) {
372 			break;
373 		}
374 	}
375 
376 	/* The end marker chunk has a header. It is part of the overhead. */
377 	total = h->end_chunk * CHUNK_UNIT + chunk_header_bytes(h);
378 	overhead = total - free_bytes - allocated_bytes;
379 	printk("\n%zd free bytes, %zd allocated bytes, overhead = %zd bytes (%zd.%zd%%)\n",
380 	       free_bytes, allocated_bytes, overhead,
381 	       (1000 * overhead + total/2) / total / 10,
382 	       (1000 * overhead + total/2) / total % 10);
383 }
384 
sys_heap_print_info(struct sys_heap * heap,bool dump_chunks)385 void sys_heap_print_info(struct sys_heap *heap, bool dump_chunks)
386 {
387 	heap_print_info(heap->heap, dump_chunks);
388 }
389