1#include "mem/tests/test_internal.h"
2
3#define DP_PAGES 16
4#define DP_STRIDE (PAGE_SIZE / sizeof(uint64_t))
5#define DP_MAX_BUFS 8
6#define DP_MAX_THREADS 64
7
8struct dp_worker {
9 _Atomic uint64_t **bufs; /* nbuf demand buffers, counter at page head */
10 size_t nbuf;
11 size_t pages;
12 atomic_uint *done;
13};
14
15static void dp_hammer(void *arg) {
16 struct dp_worker *w = arg;
17
18 /* touch every page of every buffer */
19 for (size_t b = 0; b < w->nbuf; b++)
20 for (size_t p = 0; p < w->pages; p++)
21 atomic_fetch_add_explicit(&w->bufs[b][p * DP_STRIDE], 1,
22 memory_order_relaxed);
23
24 atomic_fetch_add(w->done, 1);
25}
26
27static bool dp_alloc_bufs(_Atomic uint64_t **bufs, size_t nbuf, size_t pages) {
28 for (size_t b = 0; b < nbuf; b++) {
29 bufs[b] = page_alloc_demand(pages, ALLOC_FLAGS_ZERO);
30 if (!bufs[b]) {
31 for (size_t j = 0; j < b; j++)
32 page_free((void *) bufs[j], pages);
33 return false;
34 }
35 }
36 return true;
37}
38
39/* every page was faulted in by workers, so all frames are present */
40static void dp_free_bufs(_Atomic uint64_t **bufs, size_t nbuf, size_t pages) {
41 for (size_t b = 0; b < nbuf; b++)
42 page_free((void *) bufs[b], pages);
43}
44
45static bool dp_verify(_Atomic uint64_t **bufs, size_t nbuf, size_t pages,
46 uint64_t expect) {
47 for (size_t b = 0; b < nbuf; b++)
48 for (size_t p = 0; p < pages; p++)
49 if (atomic_load(&bufs[b][p * DP_STRIDE]) != expect)
50 return false;
51
52 return true;
53}
54
55/* Spawn nthreads workers over shared buffer set. single_core pins them,
56 * and we can test all on one CPU vs spread out */
57static void dp_spawn(struct thread **t, size_t nthreads, struct dp_worker *w,
58 bool single_core) {
59 for (size_t i = 0; i < nthreads; i++) {
60 uint64_t core = single_core ? 0 : (i % global.core_count);
61 /* Join reference is what makes thread_pin safe, worker may
62 * have already exited by then */
63 t[i] = kassert(
64 thread_spawn_joinable_on_core("dp_hammer", dp_hammer, w, core));
65 if (single_core)
66 thread_pin(t: t[i]);
67 }
68}
69
70static void dp_join(struct thread **t, size_t nthreads) {
71 for (size_t i = 0; i < nthreads; i++)
72 thread_join(t: t[i]);
73}
74
75/* 1 buffer, N threads, 1 CPU = serialized faults + preemption mid-handler */
76TEST_DECLARE_UNIT(mem, demand_single_buf_up, TEST_INTENSITY(2, 8, 32)) {
77 ABORT_IF_RAM_LOW();
78
79 size_t nthreads = ctx->intensity_val ? ctx->intensity_val : 8;
80 if (nthreads > DP_MAX_THREADS)
81 nthreads = DP_MAX_THREADS;
82 const size_t pages = DP_PAGES, nbuf = 1;
83 _Atomic uint64_t *bufs[1];
84 TEST_ASSERT(dp_alloc_bufs(bufs, nbuf, pages));
85
86 atomic_uint done = 0;
87 struct dp_worker w = {bufs, nbuf, pages, &done};
88 struct thread *t[DP_MAX_THREADS];
89 dp_spawn(t, nthreads, w: &w, /*single_core=*/true);
90
91 dp_join(t, nthreads);
92
93 TEST_ASSERT_EQ(atomic_load(&done), nthreads);
94 TEST_ASSERT(dp_verify(bufs, nbuf, pages, nthreads));
95 dp_free_bufs(bufs, nbuf, pages);
96 return TEST_SUCCESS;
97}
98