1#ifdef TEST_BIO_SCHED
2#include <block/block.h>
3#include <block/sched.h>
4#include <crypto/prng.h>
5#include <fs/ext2.h>
6#include <fs/vfs.h>
7#include <mem/alloc.h>
8#include <stdbool.h>
9#include <stddef.h>
10#include <stdint.h>
11#include <string.h>
12#include <test.h>
13#include <time/spin_sleep.h>
14
15#include "fs/detect.h"
16#define EXT2_INIT \
17 if (global.root_node->fs_type != FS_EXT2) { \
18 test_info("the mounted root is not ext2"); \
19 return TEST_SKIP(TEST_SKIP_NONE); \
20 } \
21 struct vfs_node *root = global.root_node;
22
23static bool done2 = false;
24static atomic_bool cb1d = false, cb2d = false;
25static uint64_t avg_complete_time[BIO_SCHED_LEVELS] = {0};
26static uint64_t total_complete_time[BIO_SCHED_LEVELS] = {0};
27static _Atomic uint32_t runs = 0;
28
29static void bio_sch_callback(struct bio_request *req) {
30 (void) req;
31
32 done2 = true;
33 uint64_t q_ms = (uint64_t) req->user_data >> 12;
34 uint64_t q_lvl = (uint64_t) req->user_data & 7;
35 time_t time = time_get_ms() - q_ms;
36 total_complete_time[q_lvl] += time;
37 req->user_data = NULL;
38 atomic_fetch_add(&runs, 1);
39 TEST_ASSERT_VOID(req->status == BIO_STATUS_OK);
40}
41
42static void bio_sch_callback1(struct bio_request *req) {
43 (void) req;
44
45 atomic_store(&cb1d, true);
46 test_info("cb 1 success");
47}
48
49static void bio_sch_callback2(struct bio_request *req) {
50 (void) req;
51
52 atomic_store(&cb2d, true);
53 test_info("cb 2 success");
54}
55
56TEST_DECLARE(bio_sched_coalesce_test, .tier = TEST_TIER_UNIT) {
57 EXT2_INIT;
58 struct ext2_fs *fs = root->fs_data;
59 struct block_device *d = fs->drive;
60
61 struct bio_request *bio = kmalloc(sizeof(*bio));
62 *bio = (struct bio_request){
63 .lba = 0,
64 .disk = d,
65 .buffer = kmalloc_aligned(512, 4096),
66 .size = 512,
67 .sector_count = 1,
68 .write = false,
69 .done = false,
70 .status = -1,
71 .on_complete = bio_sch_callback1,
72 .priority = BIO_RQ_MEDIUM,
73 .user_data = (void *) BIO_RQ_MEDIUM,
74 };
75
76 struct bio_request *bio2 = kmalloc(sizeof(*bio2));
77 *bio2 = (struct bio_request){
78 .lba = 1,
79 .disk = d,
80 .buffer = kmalloc_aligned(512, 4096),
81 .size = 512,
82 .sector_count = 1,
83 .write = false,
84 .done = false,
85 .status = -1,
86 .on_complete = bio_sch_callback2,
87 .priority = BIO_RQ_MEDIUM,
88 .user_data = (void *) BIO_RQ_MEDIUM,
89 };
90
91 bio->on_complete = bio_sch_callback1;
92 bio2->on_complete = bio_sch_callback2;
93
94 char *name = kmalloc(100);
95 uint64_t t = time_get_us();
96 bio_sched_enqueue(disk: d, req: bio);
97 bio_sched_enqueue(disk: d, req: bio2);
98 snprintf(buffer: name, buffer_len: 100, format: "enqueues took %d us", time_get_us() - t);
99 test_info(name);
100
101 bio_sched_dispatch_all(disk: d);
102
103 for (int i = 0; i < 5000; i++)
104 scheduler_yield();
105 return TEST_SUCCESS;
106}
107
108#define BIO_SCHED_TEST_RUNS 1024
109static uint64_t runs_per_lvl[BIO_SCHED_LEVELS] = {0};
110static struct bio_request *rqs[BIO_SCHED_TEST_RUNS] = {0};
111static uint8_t *buffers[BIO_SCHED_TEST_RUNS] = {0};
112static volatile int send_dispatch = 0;
113
114TEST_DECLARE(bio_sched_delay_enqueue_test, .tier = TEST_TIER_INTEGRATION) {
115 EXT2_INIT;
116 ABORT_IF_RAM_LOW();
117
118 struct ext2_fs *fs = root->fs_data;
119 struct block_device *d = fs->drive;
120 kassert(d);
121
122 prng_seed(seed: time_get_us());
123
124 for (uint64_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) {
125 uint8_t *buf = kmalloc_aligned(PAGE_SIZE, PAGE_SIZE);
126 struct bio_request *rq =
127 kmalloc(sizeof(struct bio_request), ALLOC_FLAGS_ZERO);
128 TEST_ASSERT(rq && buf);
129 TEST_ASSERT(IS_ALIGNED((vaddr_t) buf, PAGE_SIZE));
130
131 rq->disk = d;
132 rq->lba = (i * 2) % 512;
133 rq->sector_count = 1;
134 rq->size = 512;
135 rq->on_complete = bio_sch_callback;
136 rq->buffer = buffers[i];
137 rq->priority = prng_next() % BIO_SCHED_LEVELS;
138 rq->write = false;
139 INIT_LIST_HEAD(list: &rq->list);
140
141 rqs[i] = rq;
142 buffers[i] = buf;
143 }
144
145 for (size_t i = 0; i < BIO_SCHED_TEST_RUNS; i++)
146 for (size_t j = 0; j < BIO_SCHED_TEST_RUNS; j++)
147 if (i != j && rqs[i] == rqs[j])
148 test_err("duplicate at %u and %u\n", i, j);
149
150 for (size_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) {
151 if (!rqs[i]->disk) {
152 test_err("rq %p %u\n", rqs[i], i);
153 return TEST_SUCCESS;
154 }
155
156 kassert(rqs[i]->disk);
157 }
158
159 uint64_t ms = time_get_ms();
160 for (uint64_t i = 0; i < BIO_SCHED_TEST_RUNS; i++) {
161 struct bio_request *rq = rqs[i];
162 runs_per_lvl[rq->priority]++;
163 rq->user_data = (void *) ((time_get_ms() << 12) | rq->priority);
164 bio_sched_enqueue(disk: d, req: rq);
165 }
166 ms = time_get_ms() - ms;
167
168 char *msg = kmalloc(100);
169 TEST_ASSERT(msg);
170 snprintf(buffer: msg, buffer_len: 100, format: "Total time spent enqueuing is %d ms", ms);
171 test_info(msg);
172 send_dispatch = 1;
173 bio_sched_dispatch_all(disk: d);
174 send_dispatch = 2;
175
176 for (uint64_t i = 0; i < 150000; i++)
177 cpu_relax();
178
179 for (uint64_t i = 0; i < BIO_SCHED_LEVELS; i++) {
180 avg_complete_time[i] = total_complete_time[i] / runs_per_lvl[i];
181 char *msg = kmalloc(100, ALLOC_FLAGS_ZERO);
182 TEST_ASSERT(msg);
183 snprintf(buffer: msg, buffer_len: 100, format: "Average completion time of level %d is %d ms", i,
184 avg_complete_time[i]);
185 test_info(msg);
186 }
187
188 char *m2 = kmalloc(100);
189 TEST_ASSERT(m2);
190 snprintf(buffer: m2, buffer_len: 100, format: "Runs is %d, test_runs is %d", atomic_load(&runs),
191 BIO_SCHED_TEST_RUNS);
192 test_info(m2);
193 TEST_ASSERT(atomic_load(&runs) <= BIO_SCHED_TEST_RUNS);
194
195 return TEST_SUCCESS;
196}
197#endif
198