| 1 | |
| 2 | |
| 3 | * KUnit test for hw_breakpoint constraints accounting logic.
|
| 4 | *
|
| 5 | * Copyright (C) 2022, Google LLC.
|
| 6 |
|
| 7 |
|
| 8 | #include <kunit/test.h>
|
| 9 | #include <linux/cpumask.h>
|
| 10 | #include <linux/hw_breakpoint.h>
|
| 11 | #include <linux/kthread.h>
|
| 12 | #include <linux/perf_event.h>
|
| 13 | #include <asm/hw_breakpoint.h>
|
| 14 |
|
| 15 | #define TEST_REQUIRES_BP_SLOTS(test, slots) \
|
| 16 | do { \
|
| 17 | if ((slots) > get_test_bp_slots()) { \
|
| 18 | kunit_skip((test), "Requires breakpoint slots: %d > %d", slots, \
|
| 19 | get_test_bp_slots()); \
|
| 20 | } \
|
| 21 | } while (0)
|
| 22 |
|
| 23 | #define TEST_EXPECT_NOSPC(expr) KUNIT_EXPECT_EQ(test, -ENOSPC, PTR_ERR(expr))
|
| 24 |
|
| 25 | #define MAX_TEST_BREAKPOINTS 512
|
| 26 |
|
| 27 | static char break_vars[MAX_TEST_BREAKPOINTS];
|
| 28 | static struct perf_event *test_bps[MAX_TEST_BREAKPOINTS];
|
| 29 | static struct task_struct *__other_task;
|
| 30 |
|
| 31 | static struct perf_event *register_test_bp(int cpu, struct task_struct *tsk, int idx)
|
| 32 | {
|
| 33 | struct perf_event_attr attr = {};
|
| 34 |
|
| 35 | if (WARN_ON(idx < 0 || idx >= MAX_TEST_BREAKPOINTS))
|
| 36 | return NULL;
|
| 37 |
|
| 38 | hw_breakpoint_init(&attr);
|
| 39 | attr.bp_addr = (unsigned long)&break_vars[idx];
|
| 40 | attr.bp_len = HW_BREAKPOINT_LEN_1;
|
| 41 | attr.bp_type = HW_BREAKPOINT_RW;
|
| 42 | return perf_event_create_kernel_counter(&attr, cpu, tsk, NULL, NULL);
|
| 43 | }
|
| 44 |
|
| 45 | static void unregister_test_bp(struct perf_event **bp)
|
| 46 | {
|
| 47 | if (WARN_ON(IS_ERR(*bp)))
|
| 48 | return;
|
| 49 | if (WARN_ON(!*bp))
|
| 50 | return;
|
| 51 | unregister_hw_breakpoint(*bp);
|
| 52 | *bp = NULL;
|
| 53 | }
|
| 54 |
|
| 55 | static int get_test_bp_slots(void)
|
| 56 | {
|
| 57 | static int slots;
|
| 58 |
|
| 59 | if (!slots)
|
| 60 | slots = hw_breakpoint_slots(TYPE_DATA);
|
| 61 |
|
| 62 | return slots;
|
| 63 | }
|
| 64 |
|
| 65 | static void fill_one_bp_slot(struct kunit *test, int *id, int cpu, struct task_struct *tsk)
|
| 66 | {
|
| 67 | struct perf_event *bp = register_test_bp(cpu, tsk, *id);
|
| 68 |
|
| 69 | KUNIT_ASSERT_NOT_NULL(test, bp);
|
| 70 | KUNIT_ASSERT_FALSE(test, IS_ERR(bp));
|
| 71 | KUNIT_ASSERT_NULL(test, test_bps[*id]);
|
| 72 | test_bps[(*id)++] = bp;
|
| 73 | }
|
| 74 |
|
| 75 | |
| 76 | * Fills up the given @cpu/@tsk with breakpoints, only leaving @skip slots free.
|
| 77 | *
|
| 78 | * Returns true if this can be called again, continuing at @id.
|
| 79 |
|
| 80 | static bool fill_bp_slots(struct kunit *test, int *id, int cpu, struct task_struct *tsk, int skip)
|
| 81 | {
|
| 82 | for (int i = 0; i < get_test_bp_slots() - skip; ++i)
|
| 83 | fill_one_bp_slot(test, id, cpu, tsk);
|
| 84 |
|
| 85 | return *id + get_test_bp_slots() <= MAX_TEST_BREAKPOINTS;
|
| 86 | }
|
| 87 |
|
| 88 | static int dummy_kthread(void *arg)
|
| 89 | {
|
| 90 | return 0;
|
| 91 | }
|
| 92 |
|
| 93 | static struct task_struct *get_other_task(struct kunit *test)
|
| 94 | {
|
| 95 | struct task_struct *tsk;
|
| 96 |
|
| 97 | if (__other_task)
|
| 98 | return __other_task;
|
| 99 |
|
| 100 | tsk = kthread_create(dummy_kthread, NULL, "hw_breakpoint_dummy_task");
|
| 101 | KUNIT_ASSERT_FALSE(test, IS_ERR(tsk));
|
| 102 | __other_task = tsk;
|
| 103 | return __other_task;
|
| 104 | }
|
| 105 |
|
| 106 | static int get_test_cpu(int num)
|
| 107 | {
|
| 108 | int cpu;
|
| 109 |
|
| 110 | WARN_ON(num < 0);
|
| 111 |
|
| 112 | for_each_online_cpu(cpu) {
|
| 113 | if (num-- <= 0)
|
| 114 | break;
|
| 115 | }
|
| 116 |
|
| 117 | return cpu;
|
| 118 | }
|
| 119 |
|
| 120 | |
| 121 |
|
| 122 | static void test_one_cpu(struct kunit *test)
|
| 123 | {
|
| 124 | int idx = 0;
|
| 125 |
|
| 126 | fill_bp_slots(test, &idx, get_test_cpu(0), NULL, 0);
|
| 127 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 128 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 129 | }
|
| 130 |
|
| 131 | static void test_many_cpus(struct kunit *test)
|
| 132 | {
|
| 133 | int idx = 0;
|
| 134 | int cpu;
|
| 135 |
|
| 136 |
|
| 137 | for_each_online_cpu(cpu) {
|
| 138 | bool do_continue = fill_bp_slots(test, &idx, cpu, NULL, 0);
|
| 139 |
|
| 140 | TEST_EXPECT_NOSPC(register_test_bp(cpu, NULL, idx));
|
| 141 | if (!do_continue)
|
| 142 | break;
|
| 143 | }
|
| 144 | }
|
| 145 |
|
| 146 | static void test_one_task_on_all_cpus(struct kunit *test)
|
| 147 | {
|
| 148 | int idx = 0;
|
| 149 |
|
| 150 | fill_bp_slots(test, &idx, -1, current, 0);
|
| 151 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 152 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 153 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 154 |
|
| 155 | unregister_test_bp(&test_bps[0]);
|
| 156 | fill_one_bp_slot(test, &idx, get_test_cpu(0), NULL);
|
| 157 | }
|
| 158 |
|
| 159 | static void test_two_tasks_on_all_cpus(struct kunit *test)
|
| 160 | {
|
| 161 | int idx = 0;
|
| 162 |
|
| 163 |
|
| 164 | fill_bp_slots(test, &idx, -1, current, 0);
|
| 165 | fill_bp_slots(test, &idx, -1, get_other_task(test), 0);
|
| 166 |
|
| 167 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 168 | TEST_EXPECT_NOSPC(register_test_bp(-1, get_other_task(test), idx));
|
| 169 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 170 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), get_other_task(test), idx));
|
| 171 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 172 |
|
| 173 | unregister_test_bp(&test_bps[0]);
|
| 174 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 175 | }
|
| 176 |
|
| 177 | static void test_one_task_on_one_cpu(struct kunit *test)
|
| 178 | {
|
| 179 | int idx = 0;
|
| 180 |
|
| 181 | fill_bp_slots(test, &idx, get_test_cpu(0), current, 0);
|
| 182 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 183 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 184 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 185 | |
| 186 | * Remove one and adding back CPU-target should work; this case is
|
| 187 | * special vs. above because the task's constraints are CPU-dependent.
|
| 188 |
|
| 189 | unregister_test_bp(&test_bps[0]);
|
| 190 | fill_one_bp_slot(test, &idx, get_test_cpu(0), NULL);
|
| 191 | }
|
| 192 |
|
| 193 | static void test_one_task_mixed(struct kunit *test)
|
| 194 | {
|
| 195 | int idx = 0;
|
| 196 |
|
| 197 | TEST_REQUIRES_BP_SLOTS(test, 3);
|
| 198 |
|
| 199 | fill_one_bp_slot(test, &idx, get_test_cpu(0), current);
|
| 200 | fill_bp_slots(test, &idx, -1, current, 1);
|
| 201 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 202 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 203 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 204 |
|
| 205 |
|
| 206 | unregister_test_bp(&test_bps[0]);
|
| 207 | unregister_test_bp(&test_bps[1]);
|
| 208 | fill_one_bp_slot(test, &idx, get_test_cpu(0), NULL);
|
| 209 | fill_one_bp_slot(test, &idx, get_test_cpu(0), NULL);
|
| 210 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 211 | }
|
| 212 |
|
| 213 | static void test_two_tasks_on_one_cpu(struct kunit *test)
|
| 214 | {
|
| 215 | int idx = 0;
|
| 216 |
|
| 217 | fill_bp_slots(test, &idx, get_test_cpu(0), current, 0);
|
| 218 | fill_bp_slots(test, &idx, get_test_cpu(0), get_other_task(test), 0);
|
| 219 |
|
| 220 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 221 | TEST_EXPECT_NOSPC(register_test_bp(-1, get_other_task(test), idx));
|
| 222 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 223 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), get_other_task(test), idx));
|
| 224 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 225 |
|
| 226 | fill_bp_slots(test, &idx, get_test_cpu(1), NULL, 0);
|
| 227 | }
|
| 228 |
|
| 229 | static void test_two_tasks_on_one_all_cpus(struct kunit *test)
|
| 230 | {
|
| 231 | int idx = 0;
|
| 232 |
|
| 233 | fill_bp_slots(test, &idx, get_test_cpu(0), current, 0);
|
| 234 | fill_bp_slots(test, &idx, -1, get_other_task(test), 0);
|
| 235 |
|
| 236 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 237 | TEST_EXPECT_NOSPC(register_test_bp(-1, get_other_task(test), idx));
|
| 238 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 239 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), get_other_task(test), idx));
|
| 240 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 241 |
|
| 242 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(1), NULL, idx));
|
| 243 | }
|
| 244 |
|
| 245 | static void test_task_on_all_and_one_cpu(struct kunit *test)
|
| 246 | {
|
| 247 | int tsk_on_cpu_idx, cpu_idx;
|
| 248 | int idx = 0;
|
| 249 |
|
| 250 | TEST_REQUIRES_BP_SLOTS(test, 3);
|
| 251 |
|
| 252 | fill_bp_slots(test, &idx, -1, current, 2);
|
| 253 |
|
| 254 | tsk_on_cpu_idx = idx;
|
| 255 | fill_one_bp_slot(test, &idx, get_test_cpu(0), current);
|
| 256 | fill_one_bp_slot(test, &idx, -1, current);
|
| 257 |
|
| 258 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 259 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 260 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 261 |
|
| 262 |
|
| 263 | cpu_idx = idx;
|
| 264 | fill_one_bp_slot(test, &idx, get_test_cpu(1), NULL);
|
| 265 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(1), NULL, idx));
|
| 266 |
|
| 267 |
|
| 268 | unregister_test_bp(&test_bps[tsk_on_cpu_idx]);
|
| 269 |
|
| 270 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 271 |
|
| 272 | unregister_test_bp(&test_bps[cpu_idx]);
|
| 273 | fill_one_bp_slot(test, &idx, -1, current);
|
| 274 |
|
| 275 | TEST_EXPECT_NOSPC(register_test_bp(-1, current, idx));
|
| 276 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), current, idx));
|
| 277 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(0), NULL, idx));
|
| 278 | TEST_EXPECT_NOSPC(register_test_bp(get_test_cpu(1), NULL, idx));
|
| 279 | }
|
| 280 |
|
| 281 | static struct kunit_case hw_breakpoint_test_cases[] = {
|
| 282 | KUNIT_CASE(test_one_cpu),
|
| 283 | KUNIT_CASE(test_many_cpus),
|
| 284 | KUNIT_CASE(test_one_task_on_all_cpus),
|
| 285 | KUNIT_CASE(test_two_tasks_on_all_cpus),
|
| 286 | KUNIT_CASE(test_one_task_on_one_cpu),
|
| 287 | KUNIT_CASE(test_one_task_mixed),
|
| 288 | KUNIT_CASE(test_two_tasks_on_one_cpu),
|
| 289 | KUNIT_CASE(test_two_tasks_on_one_all_cpus),
|
| 290 | KUNIT_CASE(test_task_on_all_and_one_cpu),
|
| 291 | {},
|
| 292 | };
|
| 293 |
|
| 294 | static int test_init(struct kunit *test)
|
| 295 | {
|
| 296 |
|
| 297 | if (num_online_cpus() < 2)
|
| 298 | kunit_skip(test, "not enough cpus");
|
| 299 |
|
| 300 |
|
| 301 | if (hw_breakpoint_is_used())
|
| 302 | kunit_skip(test, "hw breakpoint already in use");
|
| 303 |
|
| 304 | return 0;
|
| 305 | }
|
| 306 |
|
| 307 | static void test_exit(struct kunit *test)
|
| 308 | {
|
| 309 | for (int i = 0; i < MAX_TEST_BREAKPOINTS; ++i) {
|
| 310 | if (test_bps[i])
|
| 311 | unregister_test_bp(&test_bps[i]);
|
| 312 | }
|
| 313 |
|
| 314 | if (__other_task) {
|
| 315 | kthread_stop(__other_task);
|
| 316 | __other_task = NULL;
|
| 317 | }
|
| 318 |
|
| 319 |
|
| 320 | KUNIT_EXPECT_FALSE(test, hw_breakpoint_is_used());
|
| 321 | }
|
| 322 |
|
| 323 | static struct kunit_suite hw_breakpoint_test_suite = {
|
| 324 | .name = "hw_breakpoint",
|
| 325 | .test_cases = hw_breakpoint_test_cases,
|
| 326 | .init = test_init,
|
| 327 | .exit = test_exit,
|
| 328 | };
|
| 329 |
|
| 330 | kunit_test_suites(&hw_breakpoint_test_suite);
|
| 331 |
|
| 332 | MODULE_AUTHOR("Marco Elver <[email protected]>");
|