1 // SPDX-License-Identifier: GPL-2.0
2 #include <stdlib.h>
3 #include <string.h>
4 #include <unistd.h>
5 #include <sys/ioctl.h>
6 #include <linux/hw_breakpoint.h>
7 #include <linux/kernel.h>
8 #include "tests.h"
9 #include "debug.h"
10 #include "event.h"
11 #include "cloexec.h"
12 #include "../perf-sys.h"
13
14 #define WP_TEST_ASSERT_VAL(fd, text, val) \
15 do { \
16 long long count; \
17 wp_read(fd, &count, sizeof(long long)); \
18 TEST_ASSERT_VAL(text, count == val); \
19 } while (0)
20
21 volatile u64 data1;
22 volatile u8 data2[3];
23
24 #ifndef __s390x__
wp_read(int fd,long long * count,int size)25 static int wp_read(int fd, long long *count, int size)
26 {
27 int ret = read(fd, count, size);
28
29 if (ret != size) {
30 pr_debug("failed to read: %d\n", ret);
31 return -1;
32 }
33 return 0;
34 }
35
get__perf_event_attr(struct perf_event_attr * attr,int wp_type,void * wp_addr,unsigned long wp_len)36 static void get__perf_event_attr(struct perf_event_attr *attr, int wp_type,
37 void *wp_addr, unsigned long wp_len)
38 {
39 memset(attr, 0, sizeof(struct perf_event_attr));
40 attr->type = PERF_TYPE_BREAKPOINT;
41 attr->size = sizeof(struct perf_event_attr);
42 attr->config = 0;
43 attr->bp_type = wp_type;
44 attr->bp_addr = (unsigned long)wp_addr;
45 attr->bp_len = wp_len;
46 attr->sample_period = 1;
47 attr->sample_type = PERF_SAMPLE_IP;
48 attr->exclude_kernel = 1;
49 attr->exclude_hv = 1;
50 }
51
__event(int wp_type,void * wp_addr,unsigned long wp_len)52 static int __event(int wp_type, void *wp_addr, unsigned long wp_len)
53 {
54 int fd;
55 struct perf_event_attr attr;
56
57 get__perf_event_attr(&attr, wp_type, wp_addr, wp_len);
58 fd = sys_perf_event_open(&attr, 0, -1, -1,
59 perf_event_open_cloexec_flag());
60 if (fd < 0)
61 pr_debug("failed opening event %x\n", attr.bp_type);
62
63 return fd;
64 }
65 #endif
66
test__wp_ro(struct test_suite * test __maybe_unused,int subtest __maybe_unused)67 static int test__wp_ro(struct test_suite *test __maybe_unused,
68 int subtest __maybe_unused)
69 {
70 #if defined(__s390x__) || defined(__x86_64__) || defined(__i386__)
71 return TEST_SKIP;
72 #else
73 int fd;
74 unsigned long tmp, tmp1 = rand();
75
76 fd = __event(HW_BREAKPOINT_R, (void *)&data1, sizeof(data1));
77 if (fd < 0)
78 return -1;
79
80 tmp = data1;
81 WP_TEST_ASSERT_VAL(fd, "RO watchpoint", 1);
82
83 data1 = tmp1 + tmp;
84 WP_TEST_ASSERT_VAL(fd, "RO watchpoint", 1);
85
86 close(fd);
87 return 0;
88 #endif
89 }
90
test__wp_wo(struct test_suite * test __maybe_unused,int subtest __maybe_unused)91 static int test__wp_wo(struct test_suite *test __maybe_unused,
92 int subtest __maybe_unused)
93 {
94 #if defined(__s390x__)
95 return TEST_SKIP;
96 #else
97 int fd;
98 unsigned long tmp, tmp1 = rand();
99
100 fd = __event(HW_BREAKPOINT_W, (void *)&data1, sizeof(data1));
101 if (fd < 0)
102 return -1;
103
104 tmp = data1;
105 WP_TEST_ASSERT_VAL(fd, "WO watchpoint", 0);
106
107 data1 = tmp1 + tmp;
108 WP_TEST_ASSERT_VAL(fd, "WO watchpoint", 1);
109
110 close(fd);
111 return 0;
112 #endif
113 }
114
test__wp_rw(struct test_suite * test __maybe_unused,int subtest __maybe_unused)115 static int test__wp_rw(struct test_suite *test __maybe_unused,
116 int subtest __maybe_unused)
117 {
118 #if defined(__s390x__)
119 return TEST_SKIP;
120 #else
121 int fd;
122 unsigned long tmp, tmp1 = rand();
123
124 fd = __event(HW_BREAKPOINT_R | HW_BREAKPOINT_W, (void *)&data1,
125 sizeof(data1));
126 if (fd < 0)
127 return -1;
128
129 tmp = data1;
130 WP_TEST_ASSERT_VAL(fd, "RW watchpoint", 1);
131
132 data1 = tmp1 + tmp;
133 WP_TEST_ASSERT_VAL(fd, "RW watchpoint", 2);
134
135 close(fd);
136 return 0;
137 #endif
138 }
139
test__wp_modify(struct test_suite * test __maybe_unused,int subtest __maybe_unused)140 static int test__wp_modify(struct test_suite *test __maybe_unused,
141 int subtest __maybe_unused)
142 {
143 #if defined(__s390x__)
144 return TEST_SKIP;
145 #else
146 int fd, ret;
147 unsigned long tmp = rand();
148 struct perf_event_attr new_attr;
149
150 fd = __event(HW_BREAKPOINT_W, (void *)&data1, sizeof(data1));
151 if (fd < 0)
152 return -1;
153
154 data1 = tmp;
155 WP_TEST_ASSERT_VAL(fd, "Modify watchpoint", 1);
156
157 /* Modify watchpoint with disabled = 1 */
158 get__perf_event_attr(&new_attr, HW_BREAKPOINT_W, (void *)&data2[0],
159 sizeof(u8) * 2);
160 new_attr.disabled = 1;
161 ret = ioctl(fd, PERF_EVENT_IOC_MODIFY_ATTRIBUTES, &new_attr);
162 if (ret < 0) {
163 pr_debug("ioctl(PERF_EVENT_IOC_MODIFY_ATTRIBUTES) failed\n");
164 close(fd);
165 return ret;
166 }
167
168 data2[1] = tmp; /* Not Counted */
169 WP_TEST_ASSERT_VAL(fd, "Modify watchpoint", 1);
170
171 /* Enable the event */
172 ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
173 if (ret < 0) {
174 pr_debug("Failed to enable event\n");
175 close(fd);
176 return ret;
177 }
178
179 data2[1] = tmp; /* Counted */
180 WP_TEST_ASSERT_VAL(fd, "Modify watchpoint", 2);
181
182 data2[2] = tmp; /* Not Counted */
183 WP_TEST_ASSERT_VAL(fd, "Modify watchpoint", 2);
184
185 close(fd);
186 return 0;
187 #endif
188 }
189
190 static struct test_case wp_tests[] = {
191 TEST_CASE_REASON("Read Only Watchpoint", wp_ro, "missing hardware support"),
192 TEST_CASE_REASON("Write Only Watchpoint", wp_wo, "missing hardware support"),
193 TEST_CASE_REASON("Read / Write Watchpoint", wp_rw, "missing hardware support"),
194 TEST_CASE_REASON("Modify Watchpoint", wp_modify, "missing hardware support"),
195 { .name = NULL, }
196 };
197
198 struct test_suite suite__wp = {
199 .desc = "Watchpoint",
200 .test_cases = wp_tests,
201 };
202