1/*
2 * Copyright © 2017 Broadcom
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#include <assert.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <limits.h>
28#include <xcb/sync.h>
29
30#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
31
32static const int64_t some_values[] = {
33        0,
34        1,
35        -1,
36        LLONG_MAX,
37        LLONG_MIN,
38};
39
40static int64_t
41pack_sync_value(xcb_sync_int64_t val)
42{
43    return ((int64_t)val.hi << 32) | val.lo;
44}
45
46static int64_t
47counter_value(struct xcb_connection_t *c,
48              xcb_sync_query_counter_cookie_t cookie)
49{
50    xcb_sync_query_counter_reply_t *reply =
51        xcb_sync_query_counter_reply(c, cookie, NULL);
52    int64_t value = pack_sync_value(reply->counter_value);
53
54    free(reply);
55    return value;
56}
57
58static xcb_sync_int64_t
59sync_value(int64_t value)
60{
61    xcb_sync_int64_t v = {
62        .hi = value >> 32,
63        .lo = value,
64    };
65
66    return v;
67}
68
69/* Initializes counters with a bunch of interesting values and makes
70 * sure it comes back the same.
71 */
72static void
73test_create_counter(xcb_connection_t *c)
74{
75    xcb_sync_query_counter_cookie_t queries[ARRAY_SIZE(some_values)];
76
77    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
78        xcb_sync_counter_t counter = xcb_generate_id(c);
79        xcb_sync_create_counter(c, counter, sync_value(some_values[i]));
80        queries[i] = xcb_sync_query_counter_unchecked(c, counter);
81    }
82
83    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
84        int64_t value = counter_value(c, queries[i]);
85
86        if (value != some_values[i]) {
87            fprintf(stderr, "Creating counter with %lld returned %lld\n",
88                    (long long)some_values[i],
89                    (long long)value);
90            exit(1);
91        }
92    }
93}
94
95/* Set a single counter to a bunch of interesting values and make sure
96 * it comes the same.
97 */
98static void
99test_set_counter(xcb_connection_t *c)
100{
101    xcb_sync_counter_t counter = xcb_generate_id(c);
102    xcb_sync_query_counter_cookie_t queries[ARRAY_SIZE(some_values)];
103
104    xcb_sync_create_counter(c, counter, sync_value(0));
105
106    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
107        xcb_sync_set_counter(c, counter, sync_value(some_values[i]));
108        queries[i] = xcb_sync_query_counter_unchecked(c, counter);
109    }
110
111    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
112        int64_t value = counter_value(c, queries[i]);
113
114        if (value != some_values[i]) {
115            fprintf(stderr, "Setting counter to %lld returned %lld\n",
116                    (long long)some_values[i],
117                    (long long)value);
118            exit(1);
119        }
120    }
121}
122
123/* Add [0, 1, 2, 3] to a counter and check that the values stick. */
124static void
125test_change_counter_basic(xcb_connection_t *c)
126{
127    int iterations = 4;
128    xcb_sync_query_counter_cookie_t queries[iterations];
129
130    xcb_sync_counter_t counter = xcb_generate_id(c);
131    xcb_sync_create_counter(c, counter, sync_value(0));
132
133    for (int i = 0; i < iterations; i++) {
134        xcb_sync_change_counter(c, counter, sync_value(i));
135        queries[i] = xcb_sync_query_counter_unchecked(c, counter);
136    }
137
138    int64_t expected_value = 0;
139    for (int i = 0; i < iterations; i++) {
140        expected_value += i;
141        int64_t value = counter_value(c, queries[i]);
142
143        if (value != expected_value) {
144            fprintf(stderr, "Adding %d to counter expected %lld returned %lld\n",
145                    i,
146                    (long long)expected_value,
147                    (long long)value);
148            exit(1);
149        }
150    }
151}
152
153/* Test change_counter where we trigger an integer overflow. */
154static void
155test_change_counter_overflow(xcb_connection_t *c)
156{
157    int iterations = 4;
158    xcb_sync_query_counter_cookie_t queries[iterations];
159    xcb_void_cookie_t changes[iterations];
160    static const struct {
161        int64_t a, b;
162    } overflow_args[] = {
163        { LLONG_MAX, 1 },
164        { LLONG_MAX, LLONG_MAX },
165        { LLONG_MIN, -1 },
166        { LLONG_MIN, LLONG_MIN },
167    };
168
169    xcb_sync_counter_t counter = xcb_generate_id(c);
170    xcb_sync_create_counter(c, counter, sync_value(0));
171
172    for (int i = 0; i < ARRAY_SIZE(overflow_args); i++) {
173        int64_t a = overflow_args[i].a;
174        int64_t b = overflow_args[i].b;
175        xcb_sync_set_counter(c, counter, sync_value(a));
176        changes[i] = xcb_sync_change_counter_checked(c, counter,
177                                                     sync_value(b));
178        queries[i] = xcb_sync_query_counter(c, counter);
179    }
180
181    for (int i = 0; i < ARRAY_SIZE(overflow_args); i++) {
182        int64_t a = overflow_args[i].a;
183        int64_t b = overflow_args[i].b;
184        xcb_sync_query_counter_reply_t *reply =
185            xcb_sync_query_counter_reply(c, queries[i], NULL);
186        int64_t value = (((int64_t)reply->counter_value.hi << 32) |
187                         reply->counter_value.lo);
188        int64_t expected_value = a;
189
190        /* The change_counter should have thrown BadValue */
191        xcb_generic_error_t *e = xcb_request_check(c, changes[i]);
192        if (!e) {
193            fprintf(stderr, "(%lld + %lld) failed to return an error\n",
194                    (long long)a,
195                    (long long)b);
196            exit(1);
197        }
198
199        if (e->error_code != XCB_VALUE) {
200            fprintf(stderr, "(%lld + %lld) returned %d, not BadValue\n",
201                    (long long)a,
202                    (long long)b,
203                    e->error_code);
204            exit(1);
205        }
206
207        /* The change_counter should have had no other effect if it
208         * errored out.
209         */
210        if (value != expected_value) {
211            fprintf(stderr, "(%lld + %lld) expected %lld returned %lld\n",
212                    (long long)a,
213                    (long long)b,
214                    (long long)expected_value,
215                    (long long)value);
216            exit(1);
217        }
218
219        free(e);
220        free(reply);
221    }
222}
223
224static void
225test_change_alarm_value(xcb_connection_t *c)
226{
227    xcb_sync_alarm_t alarm = xcb_generate_id(c);
228    xcb_sync_query_alarm_cookie_t queries[ARRAY_SIZE(some_values)];
229
230    xcb_sync_create_alarm(c, alarm, 0, NULL);
231
232    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
233        uint32_t values[] = { some_values[i] >> 32, some_values[i] };
234
235        xcb_sync_change_alarm(c, alarm, XCB_SYNC_CA_VALUE, values);
236        queries[i] = xcb_sync_query_alarm_unchecked(c, alarm);
237    }
238
239    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
240        xcb_sync_query_alarm_reply_t *reply =
241            xcb_sync_query_alarm_reply(c, queries[i], NULL);
242        int64_t value = pack_sync_value(reply->trigger.wait_value);
243
244        if (value != some_values[i]) {
245            fprintf(stderr, "Setting alarm value to %lld returned %lld\n",
246                    (long long)some_values[i],
247                    (long long)value);
248            exit(1);
249        }
250        free(reply);
251    }
252}
253
254static void
255test_change_alarm_delta(xcb_connection_t *c)
256{
257    xcb_sync_alarm_t alarm = xcb_generate_id(c);
258    xcb_sync_query_alarm_cookie_t queries[ARRAY_SIZE(some_values)];
259
260    xcb_sync_create_alarm(c, alarm, 0, NULL);
261
262    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
263        uint32_t mask = XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA;
264        uint32_t test_type = (some_values[i] >= 0 ?
265                               XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON :
266                               XCB_SYNC_TESTTYPE_NEGATIVE_COMPARISON);
267        uint32_t values[] = { test_type, some_values[i] >> 32, some_values[i] };
268
269        xcb_sync_change_alarm(c, alarm, mask, values);
270        queries[i] = xcb_sync_query_alarm_unchecked(c, alarm);
271    }
272
273    for (int i = 0; i < ARRAY_SIZE(some_values); i++) {
274        xcb_sync_query_alarm_reply_t *reply =
275            xcb_sync_query_alarm_reply(c, queries[i], NULL);
276        int64_t value = pack_sync_value(reply->delta);
277
278        if (value != some_values[i]) {
279            fprintf(stderr, "Setting alarm delta to %lld returned %lld\n",
280                    (long long)some_values[i],
281                    (long long)value);
282            exit(1);
283        }
284        free(reply);
285    }
286}
287
288int main(int argc, char **argv)
289{
290    int screen;
291    xcb_connection_t *c = xcb_connect(NULL, &screen);
292    const xcb_query_extension_reply_t *ext = xcb_get_extension_data(c, &xcb_sync_id);
293
294    if (!ext->present) {
295        printf("No XSync present\n");
296        exit(77);
297    }
298
299    test_create_counter(c);
300    test_set_counter(c);
301    test_change_counter_basic(c);
302    test_change_counter_overflow(c);
303    test_change_alarm_value(c);
304    test_change_alarm_delta(c);
305
306    xcb_disconnect(c);
307    exit(0);
308}
309